From 5f8159e8518ed40bf1bbd48c3efbc0970e8ebd0a Mon Sep 17 00:00:00 2001 From: lanyi Date: Tue, 18 May 2021 14:05:35 +0200 Subject: [PATCH 01/72] Fixed missing write mask in DX9 HlslWriter --- .../DX9Shader/Bytecode/InstructionToken.cs | 43 +++------ .../DX9Shader/Decompiler/HlslWriter.cs | 95 ++++++++++++------- .../DX9Shader/Decompiler/RegisterState.cs | 8 +- src/DXDecompiler/DXDecompiler.csproj | 1 + 4 files changed, 78 insertions(+), 69 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Bytecode/InstructionToken.cs b/src/DXDecompiler/DX9Shader/Bytecode/InstructionToken.cs index a92c5db..13030d4 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/InstructionToken.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/InstructionToken.cs @@ -344,44 +344,29 @@ public byte[] GetSourceSwizzleComponents(int srcIndex) public string GetSourceSwizzleName(int srcIndex, bool hlsl = false) { - int swizzleLength = 4; - if(Opcode == Opcode.Dp4) - { - swizzleLength = 4; - } + int? swizzleLimit = null; //TODO: Probably useful in hlsl mode - else if(hlsl) + if(hlsl) { - if(Opcode == Opcode.Dp3) - { - swizzleLength = 3; - } - else if(HasDestination) + switch(Opcode) { - swizzleLength = GetDestinationMaskLength(); + case Opcode.Dp3: + swizzleLimit = 3; + break; + case Opcode.DP2Add: + // dp2add src0.xy src1.xy src2.x + swizzleLimit = srcIndex < 2 ? 2 : 1; + break; } } string swizzleName = ""; byte[] swizzle = GetSourceSwizzleComponents(srcIndex); - for(int i = 0; i < swizzleLength; i++) + for(int i = 0; i < (swizzleLimit ?? 4); i++) { - switch(swizzle[i]) - { - case 0: - swizzleName += "x"; - break; - case 1: - swizzleName += "y"; - break; - case 2: - swizzleName += "z"; - break; - case 3: - swizzleName += "w"; - break; - } + swizzleName += "xyzw"[swizzle[i]]; } + switch(swizzleName) { case "xxx": @@ -390,7 +375,7 @@ public string GetSourceSwizzleName(int srcIndex, bool hlsl = false) return ".y"; case "zzz": return ".z"; - case "xyz": + case "xyz" when swizzleLimit is null: return ""; case "xyzw": return ""; diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index afad8df..892d727 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -1,4 +1,4 @@ -using DXDecompiler.DX9Shader.Bytecode.Ctab; +using DXDecompiler.DX9Shader.Bytecode.Ctab; using DXDecompiler.Util; using System; using System.Collections.Generic; @@ -45,9 +45,15 @@ public static string Decompile(ShaderModel shaderModel, string entryPoint = null return hlslWriter.Decompile(); } - private string GetDestinationName(InstructionToken instruction) + private string GetDestinationName(InstructionToken instruction, out string writeMaskName) { - return _registers.GetDestinationName(instruction); + return _registers.GetDestinationName(instruction, out writeMaskName); + } + + private string GetDestinationNameWithWriteMask(InstructionToken instruction) + { + var destinationName = GetDestinationName(instruction, out var writeMask); + return destinationName + writeMask; } private string GetSourceName(InstructionToken instruction, int srcIndex) @@ -115,38 +121,55 @@ private void WriteInstruction(InstructionToken instruction) return; } WriteIndent(); + + void WriteAssignment(string sourceFormat, params string[] args) + { + var destination = GetDestinationName(instruction, out var writeMask); + var sourceResult = string.Format(sourceFormat, args); + + if(writeMask.Length > 0) + { + destination += writeMask; + if(sourceResult.Contains(',')) + { + sourceResult = $"({sourceResult}){writeMask}"; + } + else + { + sourceResult += writeMask; + } + } + WriteLine("{0} = {1};", destination, sourceResult); + } + switch(instruction.Opcode) { case Opcode.Abs: - WriteLine("{0} = abs({1});", GetDestinationName(instruction), - GetSourceName(instruction, 1)); + WriteAssignment("abs({0})", GetSourceName(instruction, 1)); break; case Opcode.Add: - WriteLine("{0} = {1} + {2};", GetDestinationName(instruction), - GetSourceName(instruction, 1), GetSourceName(instruction, 2)); + WriteAssignment("{0} + {1}", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Cmp: // TODO: should be per-component - WriteLine("{0} = ({1} >= 0) ? {2} : {3};", GetDestinationName(instruction), + WriteAssignment("({0} >= 0) ? {1} : {2}", GetSourceName(instruction, 1), GetSourceName(instruction, 2), GetSourceName(instruction, 3)); break; case Opcode.DP2Add: - WriteLine("{0} = dot({1}, {2}) + {3};", GetDestinationName(instruction), + WriteAssignment("dot({0}, {1}) + {2}", GetSourceName(instruction, 1), GetSourceName(instruction, 2), GetSourceName(instruction, 3)); break; case Opcode.Dp3: - WriteLine("{0} = dot({1}, {2});", GetDestinationName(instruction), - GetSourceName(instruction, 1), GetSourceName(instruction, 2)); + WriteAssignment("dot({0}, {1})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Dp4: - WriteLine("{0} = dot({1}, {2});", GetDestinationName(instruction), - GetSourceName(instruction, 1), GetSourceName(instruction, 2)); + WriteAssignment("dot({0}, {1})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Exp: - WriteLine("{0} = exp2({1});", GetDestinationName(instruction), GetSourceName(instruction, 1)); + WriteAssignment("exp2({0})", GetSourceName(instruction, 1)); break; case Opcode.Frc: - WriteLine("{0} = frac({1});", GetDestinationName(instruction), GetSourceName(instruction, 1)); + WriteAssignment("frac({0})", GetSourceName(instruction, 1)); break; case Opcode.If: WriteLine("if ({0}) {{", GetSourceName(instruction, 0)); @@ -200,46 +223,46 @@ private void WriteInstruction(InstructionToken instruction) Indent++; break; case Opcode.Log: - WriteLine("{0} = log2({1});", GetDestinationName(instruction), GetSourceName(instruction, 1)); + WriteAssignment("log2({0})", GetSourceName(instruction, 1)); break; case Opcode.Lrp: - WriteLine("{0} = lerp({2}, {3}, {1});", GetDestinationName(instruction), + WriteAssignment("lerp({1}, {2}, {0})", GetSourceName(instruction, 1), GetSourceName(instruction, 2), GetSourceName(instruction, 3)); break; case Opcode.Mad: - WriteLine("{0} = {1} * {2} + {3};", GetDestinationName(instruction), + WriteAssignment("{0} * {1} + {2}", GetSourceName(instruction, 1), GetSourceName(instruction, 2), GetSourceName(instruction, 3)); break; case Opcode.Max: - WriteLine("{0} = max({1}, {2});", GetDestinationName(instruction), + WriteAssignment("max({0}, {1})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Min: - WriteLine("{0} = min({1}, {2});", GetDestinationName(instruction), + WriteAssignment("min({0}, {1})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Mov: - WriteLine("{0} = {1};", GetDestinationName(instruction), GetSourceName(instruction, 1)); + WriteAssignment("{0}", GetSourceName(instruction, 1)); break; case Opcode.MovA: - WriteLine("{0} = {1};", GetDestinationName(instruction), GetSourceName(instruction, 1)); + WriteAssignment("{0}", GetSourceName(instruction, 1)); break; case Opcode.Mul: - WriteLine("{0} = {1} * {2};", GetDestinationName(instruction), + WriteAssignment("{0} * {1}", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Nrm: - WriteLine("{0} = normalize({1});", GetDestinationName(instruction), GetSourceName(instruction, 1)); + WriteAssignment("normalize({0})", GetSourceName(instruction, 1)); break; case Opcode.Pow: - WriteLine("{0} = pow({1}, {2});", GetDestinationName(instruction), + WriteAssignment("pow({0}, {1})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Rcp: - WriteLine("{0} = 1 / {1};", GetDestinationName(instruction), GetSourceName(instruction, 1)); + WriteAssignment("1 / {0}", GetSourceName(instruction, 1)); break; case Opcode.Rsq: - WriteLine("{0} = 1 / sqrt({1});", GetDestinationName(instruction), GetSourceName(instruction, 1)); + WriteAssignment("1 / sqrt({0})", GetSourceName(instruction, 1)); break; case Opcode.Sge: if(instruction.GetSourceModifier(1) == SourceModifier.AbsAndNegate && @@ -247,39 +270,39 @@ private void WriteInstruction(InstructionToken instruction) instruction.GetParamRegisterName(1) + instruction.GetSourceSwizzleName(1) == instruction.GetParamRegisterName(2) + instruction.GetSourceSwizzleName(2)) { - WriteLine("{0} = ({1} == 0) ? 1 : 0;", GetDestinationName(instruction), + WriteAssignment("({0} == 0) ? 1 : 0", instruction.GetParamRegisterName(1) + instruction.GetSourceSwizzleName(1)); } else { - WriteLine("{0} = ({1} >= {2}) ? 1 : 0;", GetDestinationName(instruction), GetSourceName(instruction, 1), + WriteAssignment("({0} >= {1}) ? 1 : 0", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); } break; case Opcode.Slt: - WriteLine("{0} = ({1} < {2}) ? 1 : 0;", GetDestinationName(instruction), GetSourceName(instruction, 1), + WriteAssignment("({0} < {1}) ? 1 : 0", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.SinCos: - WriteLine("sincos({1}, {0}, {0});", GetDestinationName(instruction), GetSourceName(instruction, 1)); + WriteLine("sincos({1}, {0}, {0});", GetDestinationNameWithWriteMask(instruction), GetSourceName(instruction, 1)); break; case Opcode.Sub: - WriteLine("{0} = {1} - {2};", GetDestinationName(instruction), + WriteAssignment("{0} - {1}", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Tex: if((_shader.MajorVersion == 1 && _shader.MinorVersion >= 4) || (_shader.MajorVersion > 1)) { - WriteLine("{0} = tex2D({2}, {1});", GetDestinationName(instruction), + WriteAssignment("tex2D({1}, {0})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); } else { - WriteLine("{0} = tex2D();", GetDestinationName(instruction)); + WriteAssignment("tex2D()"); } break; case Opcode.TexLDL: - WriteLine("{0} = tex2Dlod({2}, {1});", GetDestinationName(instruction), + WriteAssignment("tex2Dlod({1}, {0})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Comment: @@ -296,7 +319,7 @@ private void WriteInstruction(InstructionToken instruction) Indent++; break; case Opcode.TexKill: - WriteLine("clip({0});", GetDestinationName(instruction)); + WriteLine("clip({0});", GetDestinationNameWithWriteMask(instruction)); break; default: throw new NotImplementedException(instruction.Opcode.ToString()); diff --git a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs index 1267eb7..7654fb5 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs @@ -1,4 +1,4 @@ -using DXDecompiler.DX9Shader.Bytecode.Ctab; +using DXDecompiler.DX9Shader.Bytecode.Ctab; using System; using System.Collections.Generic; using System.Globalization; @@ -137,7 +137,7 @@ private void Load(ShaderModel shader) } } - public string GetDestinationName(InstructionToken instruction) + public string GetDestinationName(InstructionToken instruction, out string writeMaskName) { int destIndex = instruction.GetDestinationParamIndex(); RegisterKey registerKey = instruction.GetParamRegisterKey(destIndex); @@ -145,9 +145,9 @@ public string GetDestinationName(InstructionToken instruction) string registerName = GetRegisterName(registerKey); registerName = registerName ?? instruction.GetParamRegisterName(destIndex); var registerLength = GetRegisterFullLength(registerKey); - string writeMaskName = instruction.GetDestinationWriteMaskName(registerLength, true); + writeMaskName = instruction.GetDestinationWriteMaskName(registerLength, true); - return string.Format("{0}{1}", registerName, writeMaskName); + return registerName; } public string GetSourceName(InstructionToken instruction, int srcIndex) diff --git a/src/DXDecompiler/DXDecompiler.csproj b/src/DXDecompiler/DXDecompiler.csproj index 4d5f6d1..0725884 100644 --- a/src/DXDecompiler/DXDecompiler.csproj +++ b/src/DXDecompiler/DXDecompiler.csproj @@ -2,6 +2,7 @@ netstandard2.0 + 9.0 From d379a071cb2cc6a8a9a0ba0d9583f62dbfb4035c Mon Sep 17 00:00:00 2001 From: lanyi Date: Thu, 20 May 2021 15:55:02 +0200 Subject: [PATCH 02/72] unified constant name handling in DX9 shader --- .../Bytecode/Ctab/ConstantDeclaration.cs | 42 +++++++++++++++++++ .../DX9Shader/Decompiler/RegisterState.cs | 41 +----------------- 2 files changed, 44 insertions(+), 39 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs b/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs index 05f1f52..357b554 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs @@ -93,6 +93,48 @@ public string GetRegisterName() return $"{RegisterSet.GetDescription()}{RegisterIndex}"; } + public string GetConstantNameByRegisterNumber(uint registerNumber) + { + var decl = this; + var totalOffset = registerNumber - decl.RegisterIndex; + var data = decl.GetRegisterTypeByOffset(totalOffset); + var name = decl.GetMemberNameByOffset(totalOffset); + var offsetFromMember = registerNumber - data.RegisterIndex; + + // how many registers should be occupied by the current constant item + var registersOccupied = data.Type.ParameterClass == ParameterClass.MatrixColumns + ? data.Type.Columns + : data.Type.Rows; + // sanity check: if the current constant item only occupies one register + // then its start index must match with the register number + if(registersOccupied == 1 && data.RegisterIndex != registerNumber) + { + throw new InvalidOperationException(); + } + + switch(data.Type.ParameterType) + { + case ParameterType.Float: + // implemented + break; + default: + throw new NotImplementedException(); + } + + // matrix row / columns + if(data.Type.ParameterClass == ParameterClass.MatrixRows) + { + return $"{name}[{offsetFromMember}]"; + } + else if(data.Type.ParameterClass == ParameterClass.MatrixColumns) + { + var accessors = Enumerable.Range(0, (int)data.Type.Rows).Select(i => $"m{i}{offsetFromMember}"); + return $"({name}._{string.Join("_", accessors)})"; + } + + return name; + } + public class ConstantRegisterData { public ConstantType Type { get; } diff --git a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs index 7654fb5..d000fcd 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs @@ -196,18 +196,7 @@ public string GetSourceName(InstructionToken instruction, int srcIndex) return $"Error {registerType}{registerNumber}"; //throw new NotImplementedException(); } - var totalOffset = registerNumber - decl.RegisterIndex; - var data = decl.GetRegisterTypeByOffset(totalOffset); - var offsetFromMember = registerNumber - data.RegisterIndex; - sourceRegisterName = decl.GetMemberNameByOffset(totalOffset); - if(data.Type.ParameterClass == ParameterClass.MatrixRows) - { - sourceRegisterName = string.Format("{0}[{1}]", decl.Name, offsetFromMember); - } - else if(data.Type.ParameterClass == ParameterClass.MatrixColumns) - { - sourceRegisterName = string.Format("transpose({0})[{1}]", decl.Name, offsetFromMember); - } + sourceRegisterName = decl.GetConstantNameByRegisterNumber(registerNumber); break; default: sourceRegisterName = GetRegisterName(registerKey); @@ -268,33 +257,7 @@ public string GetRegisterName(RegisterKey registerKey) return (MethodOutputRegisters.Count == 1) ? decl.Name : ("o." + decl.Name); case RegisterType.Const: var constDecl = FindConstant(registerKey.Number); - var relativeOffset = registerKey.Number - constDecl.RegisterIndex; - var name = constDecl.GetMemberNameByOffset(relativeOffset); - var data = constDecl.GetRegisterTypeByOffset(relativeOffset); - - // sanity check - var registersOccupied = data.Type.ParameterClass == ParameterClass.MatrixColumns - ? data.Type.Columns - : data.Type.Rows; - if(registersOccupied == 1 && data.RegisterIndex != registerKey.Number) - { - throw new InvalidOperationException(); - } - - switch(data.Type.ParameterType) - { - case ParameterType.Float: - if(registersOccupied == 1) - { - return name; - } - var subElement = (registerKey.Number - data.RegisterIndex).ToString(); - return ColumnMajorOrder - ? $"transpose({name})[{subElement}]" // subElement = col - : $"{name}[{subElement}]"; // subElement = row; - default: - throw new NotImplementedException(); - } + return constDecl.GetConstantNameByRegisterNumber(registerKey.Number); case RegisterType.Sampler: ConstantDeclaration samplerDecl = FindConstant(RegisterSet.Sampler, registerKey.Number); if(samplerDecl != null) From b5b3688d6242b8cd34df0d01a762b46b33d1b2ac Mon Sep 17 00:00:00 2001 From: lanyi Date: Thu, 20 May 2021 16:43:43 +0200 Subject: [PATCH 03/72] Fixed failed test cases in DX9's AsmMatchesFxc Now the disassembler can execute the index shader to choose the default shader array element when writing out technique passes --- src/DXDecompiler/DX9Shader/Asm/AsmWriter.cs | 65 +++++-- .../DX9Shader/Asm/EffectAsmWriter.cs | 35 +++- .../DX9Shader/Bytecode/CliToken.cs | 12 +- .../Bytecode/Ctab/ConstantDeclaration.cs | 9 - .../DX9Shader/Bytecode/Fxlvm/Fx9FxlVm.cs | 184 ++++++++++++++++++ .../DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs | 36 ++-- 6 files changed, 303 insertions(+), 38 deletions(-) create mode 100644 src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/Fx9FxlVm.cs diff --git a/src/DXDecompiler/DX9Shader/Asm/AsmWriter.cs b/src/DXDecompiler/DX9Shader/Asm/AsmWriter.cs index 434ef8f..6a8880f 100644 --- a/src/DXDecompiler/DX9Shader/Asm/AsmWriter.cs +++ b/src/DXDecompiler/DX9Shader/Asm/AsmWriter.cs @@ -90,7 +90,7 @@ string GetSourceName(InstructionToken instruction, int srcIndex, bool isLogicalI { // compute the actual data index, which might be different from logical index // because of relative addressing mode. - + // TODO: Handle relative addressing mode in a better way, // by using `InstructionToken.Operands`: // https://github.com/spacehamster/DXDecompiler/pull/6#issuecomment-782958769 @@ -193,16 +193,7 @@ public void WriteConstantTable(ConstantTable constantTable) WriteLine("//"); foreach(var declaration in constantTable.ConstantDeclarations) { - string arraySubscript = ""; - if(declaration.Elements > 1) - { - arraySubscript = $"[{declaration.Elements}]"; - } - WriteLine("// {0} {1}{2};", - declaration.GetTypeName(), - declaration.Name, - arraySubscript - ); + WriteConstantType(declaration.Type, declaration.Name); } WriteLine("//"); WriteLine("//"); @@ -615,7 +606,7 @@ private void WriteInstruction(InstructionToken instruction) WriteLine("ret"); break; case Opcode.Label: - WriteLine("label", GetSourceName(instruction, 0)); + WriteLine("label {0}", GetSourceName(instruction, 0)); break; case Opcode.Comment: case Opcode.End: @@ -626,5 +617,55 @@ private void WriteInstruction(InstructionToken instruction) throw new NotImplementedException($"Instruction not implemented {instruction.Opcode}"); } } + + // copied from HLSLWriter + private static string GetConstantTypeName(ConstantType type) + { + switch(type.ParameterClass) + { + case ParameterClass.Scalar: + return type.ParameterType.GetDescription(); + case ParameterClass.Vector: + return type.ParameterType.GetDescription() + type.Columns; + case ParameterClass.Struct: + return "struct"; + case ParameterClass.MatrixColumns: + case ParameterClass.MatrixRows: + return $"{type.ParameterType.GetDescription()}{type.Rows}x{type.Columns}"; + case ParameterClass.Object: + return type.ParameterType.GetDescription(); + } + throw new NotImplementedException(); + } + + private void WriteConstantType(ConstantType type, string name, bool isStructMember = false) + { + string typeName = GetConstantTypeName(type); + Write("// "); + WriteIndent(); + Write("{0}", typeName); + if(type.ParameterClass == ParameterClass.Struct) + { + WriteLine(""); + Write("// "); + WriteIndent(); + WriteLine("{"); + Indent++; + foreach(var member in type.Members) + { + WriteConstantType(member.Type, member.Name, true); + } + Indent--; + Write("// "); + WriteIndent(); + Write("}"); + } + Write(" {0}", name); + if(type.Elements > 1) + { + Write("[{0}]", type.Elements); + } + WriteLine(";"); + } } } diff --git a/src/DXDecompiler/DX9Shader/Asm/EffectAsmWriter.cs b/src/DXDecompiler/DX9Shader/Asm/EffectAsmWriter.cs index 86c0202..6bac9a2 100644 --- a/src/DXDecompiler/DX9Shader/Asm/EffectAsmWriter.cs +++ b/src/DXDecompiler/DX9Shader/Asm/EffectAsmWriter.cs @@ -1,5 +1,7 @@ -using DXDecompiler.DX9Shader.Bytecode.Ctab; +using DXDecompiler.DX9Shader.Bytecode.Ctab; +using DXDecompiler.DX9Shader.Bytecode.Fxlvm; using DXDecompiler.DX9Shader.FX9; +using System; using System.Linq; namespace DXDecompiler.DX9Shader @@ -178,7 +180,36 @@ public void WriteAssignment(Assignment assignment) } else if(stateBlob.BlobType == StateBlobType.IndexShader) { - WriteLine("{0}[TODO];", stateBlob.VariableName); + var variable = EffectChunk.Variables.Find(x => x.Parameter.Name == stateBlob.VariableName); + + var indexer = stateBlob.Shader; + // TODO: Actually the index should be calculated by executing the index shader + var index = 0; + try + { + index = (int)Fx9FxlVm.Execute(indexer)[0]; + } + catch(Exception e) + { + Write($"/* failed to execute index shader: {e.Message} */"); + } + + WriteLine(); + Indent++; + WriteIndent(); + WriteLine("asm {"); + + var variableBlobIndex = variable.DefaultValue[index].UInt; + var variableBlob = EffectChunk.VariableBlobs.Find(x => x.Index == variableBlobIndex); + + var disasm = string.Join("\n", AsmWriter.Disassemble(variableBlob.Shader) + .Split('\n') + .Select(l => $"{new string(' ', Indent * 4)}{l}")); + WriteLine(disasm); + + WriteIndent(); + WriteLine("};"); + Indent--; } } else if(assignment.Type == StateType.VertexShader) diff --git a/src/DXDecompiler/DX9Shader/Bytecode/CliToken.cs b/src/DXDecompiler/DX9Shader/Bytecode/CliToken.cs index 1dc75fe..4f9d53b 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/CliToken.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/CliToken.cs @@ -1,4 +1,5 @@ using DXDecompiler.Util; +using System; using System.Collections.Generic; namespace DXDecompiler.DX9Shader.Bytecode @@ -22,7 +23,16 @@ public static CliToken Parse(BytecodeReader reader) public string GetLiteral(uint index) { - return Numbers[(int)index].ToString(); + var value = Numbers[(int)index]; + // TODO: Upgrade to .NET Core 3.0 or above so we can avoid this explicit check + // https://stackoverflow.com/a/62768234/4399840 + if(BitConverter.DoubleToInt64Bits(value) == BitConverter.DoubleToInt64Bits(-0.0)) + { + return "-0"; + } + return value.ToString(); } + + public float GetLiteralAsFloat(uint index) => (float)Numbers[(int)index]; } } diff --git a/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs b/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs index 357b554..b4f91ba 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs @@ -112,15 +112,6 @@ public string GetConstantNameByRegisterNumber(uint registerNumber) throw new InvalidOperationException(); } - switch(data.Type.ParameterType) - { - case ParameterType.Float: - // implemented - break; - default: - throw new NotImplementedException(); - } - // matrix row / columns if(data.Type.ParameterClass == ParameterClass.MatrixRows) { diff --git a/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/Fx9FxlVm.cs b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/Fx9FxlVm.cs new file mode 100644 index 0000000..c39c9ae --- /dev/null +++ b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/Fx9FxlVm.cs @@ -0,0 +1,184 @@ +using DXDecompiler.DX9Shader.Bytecode.Ctab; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace DXDecompiler.DX9Shader.Bytecode.Fxlvm +{ + /// + /// Mainly used to execute index shaders and select the default-displayed shader in effect disassembly + /// + class Fx9FxlVm + { + private abstract class VmOperand + { + public int Start { get; } + public int Count { get; } + + public VmOperand(FxlcOperand operand) + { + Start = (int)operand.OpIndex; + Count = (int)operand.ComponentCount; + } + public abstract void Assign(IEnumerable values); + public abstract IEnumerable Retrieve(); + } + + private readonly Dictionary _expressions = new(); + private readonly Dictionary _temporaries = new(); + private readonly List _tokens; + private readonly ConstantTable _ctab; + private readonly CliToken _cli; + + public static List Execute(ShaderModel shader) + { + return new Fx9FxlVm(shader.Fxlc, shader.ConstantTable, shader.Cli).Execute(); + } + + private Fx9FxlVm(FxlcBlock block, ConstantTable ctab, CliToken cli) + { + _tokens = block.Tokens; + _ctab = ctab; + _cli = cli; + } + + private VmOperand MakeOperand(FxlcOperand operand) + { + return operand.OpType switch + { + FxlcOperandType.Expr => new TempOperand(operand, _expressions), + FxlcOperandType.Temp => new TempOperand(operand, _temporaries), + FxlcOperandType.Variable => new CtabOperand(operand, _ctab), + FxlcOperandType.Literal => new LiteralOperand(operand, _cli), + _ => throw new NotImplementedException() + }; + } + + private List Execute() + { + foreach(var token in _tokens) + { + var destination = MakeOperand(token.Operands[0]); + + switch(token.Opcode) + { + case FxlcOpcode.Mov: + destination.Assign(MakeOperand(token.Operands[1]).Retrieve()); + break; + case FxlcOpcode.Add: + var a = MakeOperand(token.Operands[1]).Retrieve(); + var b = MakeOperand(token.Operands[2]).Retrieve(); + destination.Assign(a.Zip(b, (a, b) => a + b)); + break; + default: + throw new NotImplementedException(token.Opcode.ToString()); + } + + } + var result = new List(); + for(var i = 0; i < 4; ++i) + { + _expressions.TryGetValue(i, out var value); + result.Add(value); + } + return result; + } + + private class TempOperand : VmOperand + { + private readonly Dictionary _source; + + public TempOperand(FxlcOperand operand, Dictionary source) : base(operand) + { + _source = source; + } + + public override void Assign(IEnumerable values) + { + var list = values.ToList(); + var end = Start + Count; + for(var i = Start; i < end; ++i) + { + _source[i] = list[i % list.Count]; + } + } + + public override IEnumerable Retrieve() + { + var end = Start + Count; + for(var i = Start; i < end; ++i) + { + _source.TryGetValue(i, out var value); + yield return value; + } + } + } + + private class CtabOperand : VmOperand + { + private readonly ConstantTable _source; + private readonly RegisterSet _registerSet; + + public CtabOperand(FxlcOperand operand, ConstantTable table) : base(operand) + { + _source = table; + // TODO + _registerSet = RegisterSet.Float4; + if(operand.IsArray != 0) + { + throw new NotImplementedException(); + } + } + + public override void Assign(IEnumerable values) => throw new NotSupportedException(); + + public override IEnumerable Retrieve() + { + List current = null; + int offset = 0; + var end = Start + Count; + for(var i = Start; i < end; ++i) + { + if(current == null || (offset + i) < current.Count) + { + foreach(var x in _source.ConstantDeclarations) + { + if(x.RegisterSet != _registerSet) + { + continue; + } + var constantDecarationStart = x.RegisterIndex * 4; + var constantValueCount = x.RegisterCount * 4; + if(i < constantDecarationStart || i >= (constantDecarationStart + constantValueCount)) + { + continue; + } + if(x.DefaultValue.Count != constantValueCount) + { + throw new InvalidOperationException(); + } + offset = -constantDecarationStart; + current = x.DefaultValue; + } + } + + yield return current[offset + i]; + } + } + } + + private class LiteralOperand : VmOperand + { + public IEnumerable Literals { get; } + + public LiteralOperand(FxlcOperand operand, CliToken cli) : base(operand) + { + Literals = Enumerable.Repeat(cli.GetLiteralAsFloat(operand.OpIndex), (int)operand.ComponentCount); + } + + public override void Assign(IEnumerable values) => throw new NotSupportedException(); + + public override IEnumerable Retrieve() => Literals; + } + } +} diff --git a/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs index 139282e..ca8b474 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs @@ -13,8 +13,8 @@ public class FxlcOperand public uint OpIndex { get; private set; } public FxlcOperandType ArrayType { get; private set; } public uint ArrayIndex { get; private set; } + public uint ComponentCount { get; private set; } - private uint ComponentCount; public static FxlcOperand Parse(BytecodeReader reader, uint componentCount, bool isScalarOp) { var result = new FxlcOperand() @@ -104,25 +104,33 @@ private string FormatComponent(uint componentIndex, uint componentCount) return $".UnknownCount{componentCount}"; } } - private string FormatOperand(ConstantTable ctab, CliToken cli, FxlcOperandType type, uint index) + private string FormatOperand(ConstantTable ctab, CliToken cli, FxlcOperandType type, uint index, out string component) { var elementIndex = index / 4; var componentIndex = index % 4; - var component = FormatComponent(componentIndex, ComponentCount); + component = FormatComponent(componentIndex, ComponentCount); switch(type) { case FxlcOperandType.Literal: - var literal = string.Join(", ", - Enumerable.Repeat(cli.GetLiteral(index), (int)ComponentCount)); - return string.Format("({0})", literal); + var literal = cli.GetLiteral(index); + if(literal == "-0") + { + literal = string.Format("{0}, 0, 0, 0", literal); + } + else + { + literal = string.Join(", ", Enumerable.Repeat(cli.GetLiteral(index), (int)ComponentCount)); + } + component = string.Empty; + return $"({literal})"; case FxlcOperandType.Temp: - return string.Format("r{0}{1}", elementIndex, component); + return $"r{elementIndex}"; case FxlcOperandType.Variable: - return string.Format("c{0}{1}", elementIndex, component); + return $"c{elementIndex}"; case FxlcOperandType.Expr: - return string.Format("c{0}{1}", elementIndex, component); + return $"c{elementIndex}"; default: - return string.Format("unknown{0}{1}", elementIndex, component); + return $"unknown{elementIndex}"; ; } } private string FormatOperand(ConstantTable ctab, Chunks.Fxlvm.Cli4Chunk cli, FxlcOperandType type, uint index) @@ -166,13 +174,13 @@ public string FormatOperand(ConstantTable ctab, CliToken cli) { if(IsArray == 0) { - return FormatOperand(ctab, cli, OpType, OpIndex); + return FormatOperand(ctab, cli, OpType, OpIndex, out var component) + component; } else { - return string.Format("{0}[{1}]", - FormatOperand(ctab, cli, ArrayType, ArrayIndex), - FormatOperand(ctab, cli, OpType, OpIndex)); + var arrayOperand = FormatOperand(ctab, cli, ArrayType, ArrayIndex, out var elementComponent); + var indexOperand = FormatOperand(ctab, cli, OpType, OpIndex, out _); + return string.Format("{0}[{1}.x]{2}", arrayOperand, indexOperand, elementComponent); } } /// From b5348d73c3fb6052df0ee00c54dd2b98e2e98d3d Mon Sep 17 00:00:00 2001 From: lanyi Date: Thu, 20 May 2021 18:02:00 +0200 Subject: [PATCH 04/72] let FxlcOperand support both raw / descriptive name --- .../DX9Shader/Asm/PreshaderAsmWriter.cs | 2 +- .../DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs | 19 ++++++++++++------- .../DX9Shader/Bytecode/Fxlvm/FxlcToken.cs | 2 +- .../DX9Shader/Decompiler/ExpressionWriter.cs | 10 +++++----- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Asm/PreshaderAsmWriter.cs b/src/DXDecompiler/DX9Shader/Asm/PreshaderAsmWriter.cs index a8e1068..6050b66 100644 --- a/src/DXDecompiler/DX9Shader/Asm/PreshaderAsmWriter.cs +++ b/src/DXDecompiler/DX9Shader/Asm/PreshaderAsmWriter.cs @@ -42,7 +42,7 @@ private void WriteInstruction(FxlcToken instruction) } string FormatOperand(FxlcOperand operand) { - return operand.FormatOperand(Preshader.ConstantTable, Preshader.Cli); + return operand.FormatOperand(Preshader.Cli, null); } } } diff --git a/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs index ca8b474..95d88c3 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs @@ -122,13 +122,18 @@ private string FormatOperand(ConstantTable ctab, CliToken cli, FxlcOperandType t literal = string.Join(", ", Enumerable.Repeat(cli.GetLiteral(index), (int)ComponentCount)); } component = string.Empty; - return $"({literal})"; + var typeName = ctab is null || ComponentCount == 1 ? string.Empty : $"float{ComponentCount}"; + return $"{typeName}({literal})"; case FxlcOperandType.Temp: - return $"r{elementIndex}"; - case FxlcOperandType.Variable: + return $"{(ctab is null ? "r" : "temp")}{elementIndex}"; + case FxlcOperandType.Variable when ctab is null: return $"c{elementIndex}"; + case FxlcOperandType.Variable when ctab is not null: + return ctab.ConstantDeclarations + .FirstOrDefault(d => d.ContainsIndex(elementIndex)) + .GetConstantNameByRegisterNumber(elementIndex); case FxlcOperandType.Expr: - return $"c{elementIndex}"; + return $"{(ctab is null ? "c" : "expr")}{elementIndex}"; default: return $"unknown{elementIndex}"; ; } @@ -167,10 +172,10 @@ private string FormatOperand(ConstantTable ctab, Chunks.Fxlvm.Cli4Chunk cli, Fxl /// /// Format operand for FX9 preshaders /// - /// - /// + /// CliToken, neccessary for retrieving literal values + /// ConstantTable, optional. If not null, it will be used to resolve constants. /// - public string FormatOperand(ConstantTable ctab, CliToken cli) + public string FormatOperand(CliToken cli, ConstantTable ctab) { if(IsArray == 0) { diff --git a/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcToken.cs b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcToken.cs index def24a5..a6f89d5 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcToken.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcToken.cs @@ -58,7 +58,7 @@ public override string ToString() public string ToString(ConstantTable ctab, CliToken cli) { - var operands = string.Join(", ", Operands.Select(o => o.FormatOperand(ctab, cli))); + var operands = string.Join(", ", Operands.Select(o => o.FormatOperand(cli, null))); return string.Format("{0} {1}", Opcode.ToString().ToLowerInvariant(), operands); diff --git a/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs index 4e0e85b..0013d77 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs @@ -1,4 +1,4 @@ -using DXDecompiler.DX9Shader.Bytecode; +using DXDecompiler.DX9Shader.Bytecode; using DXDecompiler.DX9Shader.Bytecode.Ctab; using DXDecompiler.DX9Shader.Bytecode.Fxlvm; using System.Linq; @@ -54,14 +54,14 @@ void Write(FxlcToken token) case Bytecode.Fxlvm.FxlcOpcode.Mov: WriteIndent(); WriteLine("{0} = {1};", - token.Operands[0].FormatOperand(Ctab, Cli), - token.Operands[1].FormatOperand(Ctab, Cli)); + token.Operands[0].FormatOperand(Cli, Ctab), + token.Operands[1].FormatOperand(Cli, Ctab)); break; case Bytecode.Fxlvm.FxlcOpcode.Neg: WriteIndent(); WriteLine("{0} = -{1};", - token.Operands[0].FormatOperand(Ctab, Cli), - token.Operands[1].FormatOperand(Ctab, Cli)); + token.Operands[0].FormatOperand(Cli, Ctab), + token.Operands[1].FormatOperand(Cli, Ctab)); break; case Bytecode.Fxlvm.FxlcOpcode.Frc: WriteFunction("frac", token); From 607bc007aeb206ffb6c2d473e5d0b8e2607a3f65 Mon Sep 17 00:00:00 2001 From: lanyi Date: Thu, 20 May 2021 18:05:02 +0200 Subject: [PATCH 05/72] Proper support of expression decompilation which contains descriptive names instead of registers --- .../DX9Shader/Decompiler/ExpressionWriter.cs | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs index 0013d77..e3ae0b3 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs @@ -1,6 +1,8 @@ -using DXDecompiler.DX9Shader.Bytecode; +using DXDecompiler.DX9Shader.Bytecode; using DXDecompiler.DX9Shader.Bytecode.Ctab; using DXDecompiler.DX9Shader.Bytecode.Fxlvm; +using System; +using System.Collections.Generic; using System.Linq; namespace DXDecompiler.DX9Shader.Decompiler @@ -28,21 +30,31 @@ protected override void Write() WriteLine($"float4 {ExpressionName}()"); WriteLine("{"); Indent++; + + var temporaryRegisters = new SortedSet(); + foreach(var operands in Shader.Fxlc.Tokens.SelectMany(t => t.Operands)) + { + if(operands.OpType == FxlcOperandType.Temp) + { + temporaryRegisters.Add(operands.OpIndex); + } + } + foreach(var tempIndex in temporaryRegisters) + { + WriteIndent(); + WriteLine($"float4 temp{tempIndex};"); + } + WriteIndent(); - WriteLine("float4 expr;"); + WriteLine("float expr0;"); foreach(var token in Shader.Fxlc.Tokens) { Write(token); } WriteIndent(); - WriteLine("return expr;"); + WriteLine("return expr0;"); Indent--; WriteLine("}"); - - if(Shader.Preshader != null) - { - Write("Have Pres"); - } } void Write(FxlcToken token) @@ -51,6 +63,8 @@ void Write(FxlcToken token) WriteLine($"// {token.ToString(Shader.ConstantTable, Shader.Cli)}"); switch(token.Opcode) { + default: + throw new NotImplementedException(token.Opcode.ToString()); case Bytecode.Fxlvm.FxlcOpcode.Mov: WriteIndent(); WriteLine("{0} = {1};", @@ -149,19 +163,19 @@ void WriteInfix(string op, FxlcToken token) { WriteIndent(); WriteLine("{0} = {1} {2} {3};", - token.Operands[0].FormatOperand(Ctab, Cli), - token.Operands[1].FormatOperand(Ctab, Cli), + token.Operands[0].FormatOperand(Cli, Ctab), + token.Operands[1].FormatOperand(Cli, Ctab), op, - token.Operands[2].FormatOperand(Ctab, Cli)); + token.Operands[2].FormatOperand(Cli, Ctab)); } void WriteFunction(string func, FxlcToken token) { WriteIndent(); var operands = token.Operands .Skip(1) - .Select(o => o.FormatOperand(Ctab, Cli)); + .Select(o => o.FormatOperand(Cli, Ctab)); WriteLine("{0} = {1}({2});", - token.Operands[0].FormatOperand(Ctab, Cli), + token.Operands[0].FormatOperand(Cli, Ctab), func, string.Join(", ", operands)); } From a2d0e1d43933295c9f3e4e860f0bc5d588afa702 Mon Sep 17 00:00:00 2001 From: lanyi Date: Thu, 20 May 2021 18:07:01 +0200 Subject: [PATCH 06/72] Support more instructions (ddx, ddy) --- .../DX9Shader/Decompiler/HlslWriter.cs | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 892d727..19bc29e 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -234,12 +234,10 @@ void WriteAssignment(string sourceFormat, params string[] args) GetSourceName(instruction, 1), GetSourceName(instruction, 2), GetSourceName(instruction, 3)); break; case Opcode.Max: - WriteAssignment("max({0}, {1})", - GetSourceName(instruction, 1), GetSourceName(instruction, 2)); + WriteAssignment("max({0}, {1})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Min: - WriteAssignment("min({0}, {1})", - GetSourceName(instruction, 1), GetSourceName(instruction, 2)); + WriteAssignment("min({0}, {1})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Mov: WriteAssignment("{0}", GetSourceName(instruction, 1)); @@ -248,15 +246,13 @@ void WriteAssignment(string sourceFormat, params string[] args) WriteAssignment("{0}", GetSourceName(instruction, 1)); break; case Opcode.Mul: - WriteAssignment("{0} * {1}", - GetSourceName(instruction, 1), GetSourceName(instruction, 2)); + WriteAssignment("{0} * {1}", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Nrm: WriteAssignment("normalize({0})", GetSourceName(instruction, 1)); break; case Opcode.Pow: - WriteAssignment("pow({0}, {1})", - GetSourceName(instruction, 1), GetSourceName(instruction, 2)); + WriteAssignment("pow({0}, {1})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Rcp: WriteAssignment("1 / {0}", GetSourceName(instruction, 1)); @@ -280,21 +276,18 @@ void WriteAssignment(string sourceFormat, params string[] args) } break; case Opcode.Slt: - WriteAssignment("({0} < {1}) ? 1 : 0", GetSourceName(instruction, 1), - GetSourceName(instruction, 2)); + WriteAssignment("({0} < {1}) ? 1 : 0", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.SinCos: WriteLine("sincos({1}, {0}, {0});", GetDestinationNameWithWriteMask(instruction), GetSourceName(instruction, 1)); break; case Opcode.Sub: - WriteAssignment("{0} - {1}", - GetSourceName(instruction, 1), GetSourceName(instruction, 2)); + WriteAssignment("{0} - {1}", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Tex: if((_shader.MajorVersion == 1 && _shader.MinorVersion >= 4) || (_shader.MajorVersion > 1)) { - WriteAssignment("tex2D({1}, {0})", - GetSourceName(instruction, 1), GetSourceName(instruction, 2)); + WriteAssignment("tex2D({1}, {0})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); } else { @@ -302,8 +295,7 @@ void WriteAssignment(string sourceFormat, params string[] args) } break; case Opcode.TexLDL: - WriteAssignment("tex2Dlod({1}, {0})", - GetSourceName(instruction, 1), GetSourceName(instruction, 2)); + WriteAssignment("tex2Dlod({1}, {0})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Comment: { @@ -321,6 +313,12 @@ void WriteAssignment(string sourceFormat, params string[] args) case Opcode.TexKill: WriteLine("clip({0});", GetDestinationNameWithWriteMask(instruction)); break; + case Opcode.DSX: + WriteAssignment("ddx({0})", GetSourceName(instruction, 1)); + break; + case Opcode.DSY: + WriteAssignment("ddy({0})", GetSourceName(instruction, 1)); + break; default: throw new NotImplementedException(instruction.Opcode.ToString()); } From d5fc02e81f62ef14fe34c280bb9ed0c247b93dab Mon Sep 17 00:00:00 2001 From: lanyi Date: Fri, 21 May 2021 00:49:43 +0200 Subject: [PATCH 07/72] Support preshader decompilation; Refactored DX9 Fxlc --- .../DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs | 28 ++- .../DX9Shader/Bytecode/Fxlvm/FxlcToken.cs | 2 +- .../DX9Shader/Decompiler/ExpressionWriter.cs | 162 +--------------- .../DX9Shader/Decompiler/FxlcHlslWriter.cs | 180 ++++++++++++++++++ .../DX9Shader/Decompiler/HlslWriter.cs | 30 +-- .../DX9Shader/Decompiler/PreshaderWriter.cs | 60 ++++++ 6 files changed, 275 insertions(+), 187 deletions(-) create mode 100644 src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs create mode 100644 src/DXDecompiler/DX9Shader/Decompiler/PreshaderWriter.cs diff --git a/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs index 95d88c3..00d9eb9 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs @@ -1,6 +1,7 @@ using DXDecompiler.DX9Shader.Bytecode.Ctab; using DXDecompiler.Util; using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -104,7 +105,7 @@ private string FormatComponent(uint componentIndex, uint componentCount) return $".UnknownCount{componentCount}"; } } - private string FormatOperand(ConstantTable ctab, CliToken cli, FxlcOperandType type, uint index, out string component) + private string FormatOperand(ConstantTable ctab, CliToken cli, HashSet ctabOverride, FxlcOperandType type, uint index, out string component) { var elementIndex = index / 4; var componentIndex = index % 4; @@ -126,12 +127,18 @@ private string FormatOperand(ConstantTable ctab, CliToken cli, FxlcOperandType t return $"{typeName}({literal})"; case FxlcOperandType.Temp: return $"{(ctab is null ? "r" : "temp")}{elementIndex}"; - case FxlcOperandType.Variable when ctab is null: + case FxlcOperandType.Variable: + if(ctabOverride?.Contains(elementIndex) is true) + { + return $"expr{elementIndex}"; + } + if(ctab is not null) + { + return ctab.ConstantDeclarations + .First(d => d.ContainsIndex(elementIndex)) + .GetConstantNameByRegisterNumber(elementIndex); + } return $"c{elementIndex}"; - case FxlcOperandType.Variable when ctab is not null: - return ctab.ConstantDeclarations - .FirstOrDefault(d => d.ContainsIndex(elementIndex)) - .GetConstantNameByRegisterNumber(elementIndex); case FxlcOperandType.Expr: return $"{(ctab is null ? "c" : "expr")}{elementIndex}"; default: @@ -174,17 +181,18 @@ private string FormatOperand(ConstantTable ctab, Chunks.Fxlvm.Cli4Chunk cli, Fxl /// /// CliToken, neccessary for retrieving literal values /// ConstantTable, optional. If not null, it will be used to resolve constants. + /// Constant registers overwritten by preshader /// - public string FormatOperand(CliToken cli, ConstantTable ctab) + public string FormatOperand(CliToken cli, ConstantTable ctab, HashSet ctabOverride = null) { if(IsArray == 0) { - return FormatOperand(ctab, cli, OpType, OpIndex, out var component) + component; + return FormatOperand(ctab, cli, ctabOverride, OpType, OpIndex, out var component) + component; } else { - var arrayOperand = FormatOperand(ctab, cli, ArrayType, ArrayIndex, out var elementComponent); - var indexOperand = FormatOperand(ctab, cli, OpType, OpIndex, out _); + var arrayOperand = FormatOperand(ctab, cli, ctabOverride, ArrayType, ArrayIndex, out var elementComponent); + var indexOperand = FormatOperand(ctab, cli, ctabOverride, OpType, OpIndex, out _); return string.Format("{0}[{1}.x]{2}", arrayOperand, indexOperand, elementComponent); } } diff --git a/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcToken.cs b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcToken.cs index a6f89d5..21e5a66 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcToken.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcToken.cs @@ -56,7 +56,7 @@ public override string ToString() operands); } - public string ToString(ConstantTable ctab, CliToken cli) + public string ToString(CliToken cli) { var operands = string.Join(", ", Operands.Select(o => o.FormatOperand(cli, null))); return string.Format("{0} {1}", diff --git a/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs index e3ae0b3..a813177 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs @@ -1,24 +1,15 @@ -using DXDecompiler.DX9Shader.Bytecode; -using DXDecompiler.DX9Shader.Bytecode.Ctab; -using DXDecompiler.DX9Shader.Bytecode.Fxlvm; -using System; +using DXDecompiler.DX9Shader.Bytecode.Fxlvm; using System.Collections.Generic; using System.Linq; namespace DXDecompiler.DX9Shader.Decompiler { - class ExpressionHLSLWriter : DecompileWriter + class ExpressionHLSLWriter : FxlcHlslWriter { - ShaderModel Shader; - ConstantTable Ctab; - CliToken Cli; - string ExpressionName; - public ExpressionHLSLWriter(ShaderModel shader, string expressionName) + string ExpressionName { get; } + public ExpressionHLSLWriter(ShaderModel shader, string expressionName) : base(shader) { - Shader = shader; ExpressionName = expressionName; - Ctab = shader.ConstantTable; - Cli = shader.Cli; } public static string Decompile(ShaderModel shader, string expressionName = "Expression") { @@ -31,153 +22,18 @@ protected override void Write() WriteLine("{"); Indent++; - var temporaryRegisters = new SortedSet(); - foreach(var operands in Shader.Fxlc.Tokens.SelectMany(t => t.Operands)) - { - if(operands.OpType == FxlcOperandType.Temp) - { - temporaryRegisters.Add(operands.OpIndex); - } - } - foreach(var tempIndex in temporaryRegisters) - { - WriteIndent(); - WriteLine($"float4 temp{tempIndex};"); - } + WriteTemporaries(); WriteIndent(); WriteLine("float expr0;"); - foreach(var token in Shader.Fxlc.Tokens) - { - Write(token); - } + + WriteInstructions(); + WriteIndent(); WriteLine("return expr0;"); + Indent--; WriteLine("}"); } - - void Write(FxlcToken token) - { - WriteIndent(); - WriteLine($"// {token.ToString(Shader.ConstantTable, Shader.Cli)}"); - switch(token.Opcode) - { - default: - throw new NotImplementedException(token.Opcode.ToString()); - case Bytecode.Fxlvm.FxlcOpcode.Mov: - WriteIndent(); - WriteLine("{0} = {1};", - token.Operands[0].FormatOperand(Cli, Ctab), - token.Operands[1].FormatOperand(Cli, Ctab)); - break; - case Bytecode.Fxlvm.FxlcOpcode.Neg: - WriteIndent(); - WriteLine("{0} = -{1};", - token.Operands[0].FormatOperand(Cli, Ctab), - token.Operands[1].FormatOperand(Cli, Ctab)); - break; - case Bytecode.Fxlvm.FxlcOpcode.Frc: - WriteFunction("frac", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Exp: - WriteFunction("exp", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Log: - WriteFunction("log", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Rsq: - WriteFunction("rsq", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Sin: - WriteFunction("sin", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Cos: - WriteFunction("cos", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Asin: - WriteFunction("asin", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Acos: - WriteFunction("acos", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Atan: - WriteFunction("atam", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Atan2: - WriteFunction("atan2", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Sqrt: - WriteFunction("sqrt", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Ineg: - WriteFunction("~int", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Imax: - WriteFunction("(int)max(", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Not: - WriteFunction("!", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Utof: - WriteFunction("utof", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Ftoi: - WriteFunction("ftoi", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Ftou: - WriteFunction("ftou", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Btoi: - WriteFunction("btoi", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Round: - WriteFunction("round", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Floor: - WriteFunction("floor", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Ceil: - WriteFunction("ceil", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Min: - WriteFunction("min", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Max: - WriteFunction("max", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Add: - WriteInfix("+", token); - break; - case Bytecode.Fxlvm.FxlcOpcode.Mul: - WriteInfix("*", token); - break; - - - case Bytecode.Fxlvm.FxlcOpcode.Lt: - WriteInfix("<", token); - break; - } - } - void WriteInfix(string op, FxlcToken token) - { - WriteIndent(); - WriteLine("{0} = {1} {2} {3};", - token.Operands[0].FormatOperand(Cli, Ctab), - token.Operands[1].FormatOperand(Cli, Ctab), - op, - token.Operands[2].FormatOperand(Cli, Ctab)); - } - void WriteFunction(string func, FxlcToken token) - { - WriteIndent(); - var operands = token.Operands - .Skip(1) - .Select(o => o.FormatOperand(Cli, Ctab)); - WriteLine("{0} = {1}({2});", - token.Operands[0].FormatOperand(Cli, Ctab), - func, - string.Join(", ", operands)); - } } } diff --git a/src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs new file mode 100644 index 0000000..281e77c --- /dev/null +++ b/src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs @@ -0,0 +1,180 @@ +using DXDecompiler.DX9Shader.Bytecode; +using DXDecompiler.DX9Shader.Bytecode.Ctab; +using DXDecompiler.DX9Shader.Bytecode.Fxlvm; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace DXDecompiler.DX9Shader.Decompiler +{ + class FxlcHlslWriter : DecompileWriter + { + protected ShaderModel Shader { get; } + protected ConstantTable Ctab { get; } + protected CliToken Cli { get; } + + public FxlcHlslWriter(ShaderModel shader) + { + Shader = shader; + Ctab = shader.ConstantTable; + Cli = shader.Cli; + } + + protected void WriteTemporaries() + { + var temporaryRegisters = new SortedSet(); + foreach(var operands in Shader.Fxlc.Tokens.SelectMany(t => t.Operands)) + { + if(operands.OpType == FxlcOperandType.Temp) + { + if(operands.IsArray != 0) + { + throw new NotImplementedException(); + } + temporaryRegisters.Add(operands.OpIndex); + } + } + foreach(var tempIndex in temporaryRegisters) + { + WriteIndent(); + WriteLine($"float4 temp{tempIndex};"); + } + } + + protected void WriteInstructions(HashSet ctabOverride = null) + { + foreach(var token in Shader.Fxlc.Tokens) + { + Write(token); + } + } + + void Write(FxlcToken token, HashSet ctabOverride = null) + { + WriteIndent(); + WriteLine($"// {token.ToString(Shader.Cli)}"); + switch(token.Opcode) + { + default: + throw new NotImplementedException(token.Opcode.ToString()); + case FxlcOpcode.Rcp: + WriteFunction("1.0f / ", token, ctabOverride); + break; + case FxlcOpcode.Mov: + WriteAssignment(token, ctabOverride, "{0}", token.Operands[1].FormatOperand(Cli, Ctab)); + break; + case FxlcOpcode.Neg: + WriteAssignment(token, ctabOverride, "-{0}", token.Operands[1].FormatOperand(Cli, Ctab)); + break; + case FxlcOpcode.Frc: + WriteFunction("frac", token, ctabOverride); + break; + case FxlcOpcode.Exp: + WriteFunction("exp", token, ctabOverride); + break; + case FxlcOpcode.Log: + WriteFunction("log", token, ctabOverride); + break; + case FxlcOpcode.Rsq: + WriteFunction("rsq", token, ctabOverride); + break; + case FxlcOpcode.Sin: + WriteFunction("sin", token, ctabOverride); + break; + case FxlcOpcode.Cos: + WriteFunction("cos", token, ctabOverride); + break; + case FxlcOpcode.Asin: + WriteFunction("asin", token, ctabOverride); + break; + case FxlcOpcode.Acos: + WriteFunction("acos", token, ctabOverride); + break; + case FxlcOpcode.Atan: + WriteFunction("atan", token, ctabOverride); + break; + case FxlcOpcode.Atan2: + WriteFunction("atan2", token, ctabOverride); + break; + case FxlcOpcode.Sqrt: + WriteFunction("sqrt", token, ctabOverride); + break; + case FxlcOpcode.Ineg: + WriteFunction("~int", token, ctabOverride); + break; + case FxlcOpcode.Imax: + WriteFunction("(int)max", token, ctabOverride); + break; + case FxlcOpcode.Not: + WriteFunction("!", token, ctabOverride); + break; + case FxlcOpcode.Utof: + WriteFunction("utof", token, ctabOverride); + break; + case FxlcOpcode.Ftoi: + WriteFunction("ftoi", token, ctabOverride); + break; + case FxlcOpcode.Ftou: + WriteFunction("ftou", token, ctabOverride); + break; + case FxlcOpcode.Btoi: + WriteFunction("btoi", token, ctabOverride); + break; + case FxlcOpcode.Round: + WriteFunction("round", token, ctabOverride); + break; + case FxlcOpcode.Floor: + WriteFunction("floor", token, ctabOverride); + break; + case FxlcOpcode.Ceil: + WriteFunction("ceil", token, ctabOverride); + break; + case FxlcOpcode.Min: + WriteFunction("min", token, ctabOverride); + break; + case FxlcOpcode.Max: + WriteFunction("max", token, ctabOverride); + break; + case FxlcOpcode.Add: + WriteInfix("+", token, ctabOverride); + break; + case FxlcOpcode.Mul: + WriteInfix("*", token, ctabOverride); + break; + case FxlcOpcode.Lt: + WriteInfix("<", token, ctabOverride); + break; + } + } + void WriteAssignment(FxlcToken token, HashSet ctabOverride, string format, params string[] args) + { + var destination = token.Operands[0]; + if(destination.ArrayIndex % 4 != 0) + { + // I'm not sure if write masks apply to preshaders as well... + throw new NotImplementedException(); + } + WriteIndent(); + WriteLine("{0} = {1};", destination.FormatOperand(Cli, Ctab, ctabOverride), string.Format(format, args)); + if(destination.IsArray != 0) + { + throw new NotImplementedException(); + } + ctabOverride?.Add(destination.OpIndex / 4); + } + void WriteInfix(string op, FxlcToken token, HashSet ctabOverride) + { + WriteAssignment(token, ctabOverride, "{0} {1} {2}", + token.Operands[1].FormatOperand(Cli, Ctab, ctabOverride), + op, + token.Operands[2].FormatOperand(Cli, Ctab, ctabOverride)); + } + void WriteFunction(string func, FxlcToken token, HashSet ctabOverride) + { + var operands = token.Operands + .Skip(1) + .Select(o => o.FormatOperand(Cli, Ctab, ctabOverride)); + WriteAssignment(token, ctabOverride, "{0}({1})", func, string.Join(", ", operands)); + } + } +} diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 19bc29e..4171d16 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -386,18 +386,12 @@ protected override void Write() { if(_shader.Type == ShaderType.Expression) { - Write($"// Writing expression"); - WriteExpression(_shader); - return; + throw new InvalidOperationException($"Expression should be written using {nameof(ExpressionHLSLWriter)} in {nameof(EffectHLSLWriter)}"); } _registers = new RegisterState(_shader); WriteConstantDeclarations(); - if(_shader.Preshader != null) - { - WriteExpression(_shader.Preshader.Shader); - } if(_registers.MethodInputRegisters.Count > 1) { WriteInputStructureDeclaration(); @@ -419,6 +413,12 @@ protected override void Write() WriteLine("{"); Indent++; + if(_shader.Preshader != null) + { + var preshader = PreshaderWriter.Decompile(_shader.Preshader, Indent, out var ctabOverride); + WriteLine(preshader); + } + if(_registers.MethodOutputRegisters.Count > 1) { var outputStructType = _shader.Type == ShaderType.Pixel ? "PS_OUT" : "VS_OUT"; @@ -628,21 +628,5 @@ private void WriteInstructionList() } WriteLine(); } - private void WriteExpression(ShaderModel shader) - { - WriteLine("void {0}Preshader(){{", _entryPoint); - Indent++; - WriteLine($"// {shader.Type}_{shader.MajorVersion}_{shader.MinorVersion}"); - foreach(var token in shader.Fxlc.Tokens) - { - WriteLine($"// {token.ToString(shader.ConstantTable, shader.Cli)}"); - } - if(shader.Prsi != null) - { - WriteLine(shader.Prsi.Dump()); - } - Indent--; - WriteLine("}"); - } } } diff --git a/src/DXDecompiler/DX9Shader/Decompiler/PreshaderWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/PreshaderWriter.cs new file mode 100644 index 0000000..6dd02a2 --- /dev/null +++ b/src/DXDecompiler/DX9Shader/Decompiler/PreshaderWriter.cs @@ -0,0 +1,60 @@ +using DXDecompiler.DX9Shader.Bytecode; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace DXDecompiler.DX9Shader.Decompiler +{ + class PreshaderWriter : FxlcHlslWriter + { + HashSet CtabOverride { get; } = new(); + PrsiToken Prsi { get; } + + + public PreshaderWriter(Preshader shader) : base(shader.Shader) + { + Prsi = shader.Shader.Prsi; + } + + public static string Decompile(Preshader shader, int indent, out HashSet ctabOverride) + { + var writer = new PreshaderWriter(shader) + { + Indent = indent + }; + ctabOverride = writer.CtabOverride; + return writer.Decompile(); + } + + protected override void Write() + { + WriteIndent(); + WriteLine("/*"); + WriteLine(string.Join("\n", Prsi.Dump().Split('\n').Select(x => new string(' ', Indent * 4) + x))); + WriteIndent(); + WriteLine("*/"); + + for(var i = 0; i < Prsi.OutputRegisterCount; ++i) + { + WriteIndent(); + WriteLine($"float4 expr{i + Prsi.OutputRegisterOffset};"); + } + + WriteIndent(); + WriteLine("{"); + Indent++; + + WriteTemporaries(); + + WriteIndent(); + WriteLine($"// {Shader.Type}_{Shader.MajorVersion}_{Shader.MinorVersion}"); + + WriteInstructions(CtabOverride); + + Indent--; + WriteIndent(); + WriteLine("}"); + } + } +} From 9afca30cc5bc34a504f7db217cbdf446adcbea2b Mon Sep 17 00:00:00 2001 From: lanyi Date: Fri, 21 May 2021 00:50:45 +0200 Subject: [PATCH 08/72] Various misc changes / fixes mainly for correct handling of write masks --- .../DX9Shader/Decompiler/EffectHLSLWriter.cs | 21 +- .../DX9Shader/Decompiler/HlslWriter.cs | 54 ++-- .../DX9Shader/Decompiler/RegisterState.cs | 241 +++++++----------- 3 files changed, 141 insertions(+), 175 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs index 2da480a..ebc2b25 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs @@ -22,12 +22,19 @@ public static string Decompile(EffectContainer effectChunk) } void BuildNameLookup() { - int shaderCount = 0; + string MakeShaderName(ShaderModel shader) => shader.Type switch + { + ShaderType.Pixel => "PixelShader", + ShaderType.Vertex => "VertexShader", + ShaderType.Expression => "Expression", + _ => "Shader" + } + $"{ShaderNames.Count + 1}"; + foreach(var blob in EffectChunk.VariableBlobs) { if(blob.IsShader) { - ShaderNames[blob] = $"Shader{shaderCount++}"; + ShaderNames[blob] = MakeShaderName(blob.Shader); } } foreach(var blob in EffectChunk.StateBlobs) @@ -35,10 +42,10 @@ void BuildNameLookup() if(blob.BlobType == StateBlobType.Shader || blob.BlobType == StateBlobType.IndexShader) { - ShaderNames[blob] = $"Shader{shaderCount++}"; + + ShaderNames[blob] = MakeShaderName(blob.Shader); } } - } protected override void Write() { @@ -65,7 +72,7 @@ void WriteShader(string shaderName, ShaderModel shader) { WriteLine($"// {shaderName} {shader.Type}_{shader.MajorVersion}_{shader.MinorVersion} Has PRES {shader.Preshader != null}"); var funcName = shaderName; - var text = ""; + string text; if(shader.Type == ShaderType.Expression) { text = ExpressionHLSLWriter.Decompile(shader, funcName); @@ -93,7 +100,7 @@ public string StateBlobToString(Assignment key) if(data.Shader.Type == ShaderType.Expression) { var funcName = ShaderNames[data]; - return $"{funcName}"; + return $"{funcName}()"; } else { @@ -249,7 +256,7 @@ public void WriteVariable(Variable variable) { WriteLine(" = {0}; // {1}", data, variable.DefaultValue[0].UInt); } - } + } } else if(variable.DefaultValue.All(d => d.UInt == 0)) { diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 4171d16..ae36cd4 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -1,4 +1,5 @@ using DXDecompiler.DX9Shader.Bytecode.Ctab; +using DXDecompiler.DX9Shader.Decompiler; using DXDecompiler.Util; using System; using System.Collections.Generic; @@ -8,6 +9,14 @@ namespace DXDecompiler.DX9Shader { public class HlslWriter : DecompileWriter { + private class SourceOperand + { + public string Body { get; set; } + public string Swizzle { get; set; } + public string Modifier { get; set; } + } + + private readonly ShaderModel _shader; private readonly bool _doAstAnalysis; private int _iterationDepth = 0; @@ -56,9 +65,15 @@ private string GetDestinationNameWithWriteMask(InstructionToken instruction) return destinationName + writeMask; } - private string GetSourceName(InstructionToken instruction, int srcIndex) + private SourceOperand GetSourceName(InstructionToken instruction, int srcIndex) { - return _registers.GetSourceName(instruction, srcIndex); + var body = _registers.GetSourceName(instruction, srcIndex, out var swizzle, out var modifier); + return new SourceOperand + { + Body = body, + Swizzle = swizzle, + Modifier = modifier + }; } private static string GetConstantTypeName(ConstantType type) @@ -122,25 +137,30 @@ private void WriteInstruction(InstructionToken instruction) } WriteIndent(); - void WriteAssignment(string sourceFormat, params string[] args) + void WriteAssignmentEx(string sourceFormat, bool returnsScalar, params SourceOperand[] args) { var destination = GetDestinationName(instruction, out var writeMask); - var sourceResult = string.Format(sourceFormat, args); - + var strings = args.Select(x => string.Format(x.Modifier, x.Body + x.Swizzle)).ToArray(); + var sourceResult = string.Format(sourceFormat, strings); + + var swizzleSizes = args.Select(x => x.Swizzle.StartsWith(".") ? x.Swizzle.Trim('.').Length : -1); + returnsScalar = returnsScalar || swizzleSizes.All(x => x == 1); + if(writeMask.Length > 0) { destination += writeMask; - if(sourceResult.Contains(',')) + if(writeMask.Trim('.').Length == 1 && returnsScalar) { - sourceResult = $"({sourceResult}){writeMask}"; + // do nothing, don't need to append write mask as swizzle } - else + else if(sourceResult.Contains(',') || char.IsDigit(sourceResult.Last())) { - sourceResult += writeMask; + sourceResult = $"({sourceResult}){writeMask}"; } } WriteLine("{0} = {1};", destination, sourceResult); } + void WriteAssignment(string sourceFormat, params SourceOperand[] args) => WriteAssignmentEx(sourceFormat, false, args); switch(instruction.Opcode) { @@ -156,14 +176,14 @@ void WriteAssignment(string sourceFormat, params string[] args) GetSourceName(instruction, 1), GetSourceName(instruction, 2), GetSourceName(instruction, 3)); break; case Opcode.DP2Add: - WriteAssignment("dot({0}, {1}) + {2}", + WriteAssignmentEx("dot({0}, {1}) + {2}", true, GetSourceName(instruction, 1), GetSourceName(instruction, 2), GetSourceName(instruction, 3)); break; case Opcode.Dp3: - WriteAssignment("dot({0}, {1})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); + WriteAssignmentEx("dot({0}, {1})", true, GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Dp4: - WriteAssignment("dot({0}, {1})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); + WriteAssignmentEx("dot({0}, {1})", true, GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Exp: WriteAssignment("exp2({0})", GetSourceName(instruction, 1)); @@ -255,7 +275,7 @@ void WriteAssignment(string sourceFormat, params string[] args) WriteAssignment("pow({0}, {1})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Rcp: - WriteAssignment("1 / {0}", GetSourceName(instruction, 1)); + WriteAssignment("1.0f / {0}", GetSourceName(instruction, 1)); break; case Opcode.Rsq: WriteAssignment("1 / sqrt({0})", GetSourceName(instruction, 1)); @@ -266,8 +286,12 @@ void WriteAssignment(string sourceFormat, params string[] args) instruction.GetParamRegisterName(1) + instruction.GetSourceSwizzleName(1) == instruction.GetParamRegisterName(2) + instruction.GetSourceSwizzleName(2)) { - WriteAssignment("({0} == 0) ? 1 : 0", - instruction.GetParamRegisterName(1) + instruction.GetSourceSwizzleName(1)); + WriteAssignment("({0} == 0) ? 1 : 0", new SourceOperand + { + Body = instruction.GetParamRegisterName(1), + Swizzle = instruction.GetSourceSwizzleName(1), + Modifier = "{0}" + }); } else { diff --git a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs index d000fcd..2a7d8f7 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs @@ -121,7 +121,7 @@ private void Load(ShaderModel shader) var reg = new RegisterDeclaration(registerKey); _registerDeclarations[registerKey] = reg; switch(registerType) - { + { case RegisterType.AttrOut: case RegisterType.ColorOut: case RegisterType.DepthOut: @@ -150,7 +150,7 @@ public string GetDestinationName(InstructionToken instruction, out string writeM return registerName; } - public string GetSourceName(InstructionToken instruction, int srcIndex) + public string GetSourceName(InstructionToken instruction, int srcIndex, out string swizzle, out string modifier) { string sourceRegisterName; RegisterKey registerKey = instruction.GetParamRegisterKey(srcIndex); @@ -166,33 +166,37 @@ public string GetSourceName(InstructionToken instruction, int srcIndex) sourceRegisterName = GetSourceConstantName(instruction, srcIndex); if(sourceRegisterName != null) { + swizzle = string.Empty; + modifier = "{0}"; return sourceRegisterName; } - ParameterType parameterType; + RegisterSet registerSet; switch(registerType) { case RegisterType.Const: case RegisterType.Const2: case RegisterType.Const3: case RegisterType.Const4: - parameterType = ParameterType.Float; + registerSet = RegisterSet.Float4; break; case RegisterType.ConstBool: - parameterType = ParameterType.Bool; + registerSet = RegisterSet.Bool; break; case RegisterType.ConstInt: - parameterType = ParameterType.Int; + registerSet = RegisterSet.Int4; break; default: throw new NotImplementedException(); } var registerNumber = instruction.GetParamRegisterNumber(srcIndex); - ConstantDeclaration decl = FindConstant(registerNumber); + var decl = FindConstant(registerSet, registerNumber); if(decl == null) { // Constant register not found in def statements nor the constant table //TODO: + swizzle = "Error"; + modifier = "{0}"; return $"Error {registerType}{registerNumber}"; //throw new NotImplementedException(); } @@ -205,15 +209,17 @@ public string GetSourceName(InstructionToken instruction, int srcIndex) sourceRegisterName = sourceRegisterName ?? instruction.GetParamRegisterName(srcIndex); - sourceRegisterName += instruction.GetSourceSwizzleName(srcIndex, true); - return ApplyModifier(instruction.GetSourceModifier(srcIndex), sourceRegisterName); + swizzle = instruction.GetSourceSwizzleName(srcIndex, true); + modifier = GetModifier(instruction.GetSourceModifier(srcIndex)); + return sourceRegisterName; } public uint GetRegisterFullLength(RegisterKey registerKey) { + // TODO: handle other cases as well if(registerKey.Type == RegisterType.Const) { - var constant = FindConstant(registerKey.Number); + var constant = FindConstant(RegisterSet.Float4, registerKey.Number); var data = constant.GetRegisterTypeByOffset(registerKey.Number - constant.RegisterIndex); if(data.Type.ParameterType != ParameterType.Float) { @@ -256,7 +262,7 @@ public string GetRegisterName(RegisterKey registerKey) case RegisterType.ColorOut: return (MethodOutputRegisters.Count == 1) ? decl.Name : ("o." + decl.Name); case RegisterType.Const: - var constDecl = FindConstant(registerKey.Number); + var constDecl = FindConstant(RegisterSet.Float4, registerKey.Number); return constDecl.GetConstantNameByRegisterNumber(registerKey.Number); case RegisterType.Sampler: ConstantDeclaration samplerDecl = FindConstant(RegisterSet.Sampler, registerKey.Number); @@ -292,7 +298,7 @@ public ConstantDeclaration FindConstant(RegisterInputNode register) { return null; } - return FindConstant(ParameterType.Float, register.RegisterComponentKey.Number); + return FindConstant(RegisterSet.Float4, register.RegisterComponentKey.Number); } public ConstantDeclaration FindConstant(RegisterSet set, uint index) @@ -309,196 +315,125 @@ public ConstantDeclaration FindConstant(ParameterType type, uint index) c.ContainsIndex(index)); } - - public ConstantDeclaration FindConstant(uint index) - { - return ConstantDeclarations.FirstOrDefault(c => c.ContainsIndex(index)); - } - private string GetSourceConstantName(InstructionToken instruction, int srcIndex) { var registerType = instruction.GetParamRegisterType(srcIndex); var registerNumber = instruction.GetParamRegisterNumber(srcIndex); + string type; + string[] constant; switch(registerType) { case RegisterType.ConstBool: //throw new NotImplementedException(); return null; case RegisterType.ConstInt: + type = "int"; + var constantInt = _constantIntDefinitions.FirstOrDefault(x => x.RegisterIndex == registerNumber); + if(constantInt == null) { - var constantInt = _constantIntDefinitions.FirstOrDefault(x => x.RegisterIndex == registerNumber); - if(constantInt == null) - { - return null; - } - byte[] swizzle = instruction.GetSourceSwizzleComponents(srcIndex); - uint[] constant = { - constantInt[swizzle[0]], - constantInt[swizzle[1]], - constantInt[swizzle[2]], - constantInt[swizzle[3]] }; - - switch(instruction.GetSourceModifier(srcIndex)) - { - case SourceModifier.None: - break; - case SourceModifier.Negate: - throw new NotImplementedException(); - /* - for (int i = 0; i < 4; i++) - { - constant[i] = -constant[i]; - } - break;*/ - default: - throw new NotImplementedException(); - } + return null; + } + var intValues = instruction.GetSourceSwizzleComponents(srcIndex) + .Select(s => constantInt[s]) + .ToArray(); - int destLength = instruction.GetDestinationMaskLength(); - switch(destLength) + switch(instruction.GetSourceModifier(srcIndex)) + { + case SourceModifier.None: + break; + case SourceModifier.Negate: + throw new NotImplementedException(); + /* + for (int i = 0; i < 4; i++) { - case 1: - return constant[0].ToString(); - case 2: - if(constant[0] == constant[1]) - { - return constant[0].ToString(); - } - return $"int2({constant[0]}, {constant[1]})"; - case 3: - if(constant[0] == constant[1] && constant[0] == constant[2]) - { - return constant[0].ToString(); - } - return $"int3({constant[0]}, {constant[1]}, {constant[2]})"; - case 4: - if(constant[0] == constant[1] && constant[0] == constant[2] && constant[0] == constant[3]) - { - return constant[0].ToString(); - } - return $"int4({constant[0]}, {constant[1]}, {constant[2]}, {constant[3]})"; - default: - throw new InvalidOperationException(); + intValues[i] = -intValues[i]; } + break;*/ + default: + throw new NotImplementedException(); } - + constant = intValues.Select(i => i.ToString(_culture)).ToArray(); + break; case RegisterType.Const: case RegisterType.Const2: case RegisterType.Const3: case RegisterType.Const4: + type = "float"; + var constantRegister = _constantDefinitions.FirstOrDefault(x => x.RegisterIndex == registerNumber); + if(constantRegister == null) { - var constantRegister = _constantDefinitions.FirstOrDefault(x => x.RegisterIndex == registerNumber); - if(constantRegister == null) - { - return null; - } + return null; + } - byte[] swizzle = instruction.GetSourceSwizzleComponents(srcIndex); - float[] constant = { - constantRegister[swizzle[0]], - constantRegister[swizzle[1]], - constantRegister[swizzle[2]], - constantRegister[swizzle[3]] }; + var floatValues = instruction.GetSourceSwizzleComponents(srcIndex) + .Select(s => constantRegister[s]) + .ToArray(); - switch(instruction.GetSourceModifier(srcIndex)) - { - case SourceModifier.None: - break; - case SourceModifier.Negate: - for(int i = 0; i < 4; i++) - { - constant[i] = -constant[i]; - } - break; - default: - throw new NotImplementedException(); - } - - int destLength; - if(instruction.HasDestination) - { - destLength = instruction.GetDestinationMaskLength(); - } - else - { - if(instruction.Opcode == Opcode.If || instruction.Opcode == Opcode.IfC) + switch(instruction.GetSourceModifier(srcIndex)) + { + case SourceModifier.None: + break; + case SourceModifier.Negate: + for(int i = 0; i < floatValues.Length; i++) { - // TODO + floatValues[i] = -floatValues[i]; } - destLength = 4; - } - switch(destLength) - { - case 1: - return constant[0].ToString(_culture); - case 2: - if(constant[0] == constant[1]) - { - return constant[0].ToString(_culture); - } - return string.Format("float2({0}, {1})", - constant[0].ToString(_culture), - constant[1].ToString(_culture)); - case 3: - if(constant[0] == constant[1] && constant[0] == constant[2]) - { - return constant[0].ToString(_culture); - } - return string.Format("float3({0}, {1}, {2})", - constant[0].ToString(_culture), - constant[1].ToString(_culture), - constant[2].ToString(_culture)); - case 4: - if(constant[0] == constant[1] && constant[0] == constant[2] && constant[0] == constant[3]) - { - return constant[0].ToString(_culture); - } - return string.Format("float4({0}, {1}, {2}, {3})", - constant[0].ToString(_culture), - constant[1].ToString(_culture), - constant[2].ToString(_culture), - constant[3].ToString(_culture)); - default: - throw new InvalidOperationException(); - } + break; + case SourceModifier.Abs: + for(int i = 0; i < floatValues.Length; i++) + { + floatValues[i] = Math.Abs(floatValues[i]); + } + break; + default: + throw new NotImplementedException(); } + constant = floatValues.Select(f => f.ToString(_culture)).ToArray(); + break; default: return null; } + + if(instruction.Opcode == Opcode.If || instruction.Opcode == Opcode.IfC) + { + // TODO + } + + return string.Format("{0}{1}({2})", type, constant.Length, string.Join(", ", constant)); } - private static string ApplyModifier(SourceModifier modifier, string value) + private static string GetModifier(SourceModifier modifier) { switch(modifier) { case SourceModifier.None: - return value; + return "{0}"; case SourceModifier.Negate: - return $"-{value}"; + return "-{0}"; case SourceModifier.Bias: - return $"{value}_bias"; + return "{0}_bias"; case SourceModifier.BiasAndNegate: - return $"-{value}_bias"; + return "-{0}_bias"; case SourceModifier.Sign: - return $"{value}_bx2"; + return "{0}_bx2"; case SourceModifier.SignAndNegate: - return $"-{value}_bx2"; + return "-{0}_bx2"; case SourceModifier.Complement: throw new NotImplementedException(); case SourceModifier.X2: - return $"(2 * {value})"; + return "(2 * {0})"; case SourceModifier.X2AndNegate: - return $"(-2 * {value})"; + return "(-2 * {0})"; case SourceModifier.DivideByZ: - return $"{value}_dz"; + return "{0}_dz"; case SourceModifier.DivideByW: - return $"{value}_dw"; + return "{0}_dw"; case SourceModifier.Abs: - return $"abs({value})"; + return "abs({0})"; case SourceModifier.AbsAndNegate: - return $"-abs({value})"; + return "-abs({0})"; case SourceModifier.Not: throw new NotImplementedException(); default: From a768b50cdde7eb360a55fd29b6f1e73032b354c5 Mon Sep 17 00:00:00 2001 From: lanyi Date: Fri, 21 May 2021 01:12:08 +0200 Subject: [PATCH 09/72] Support referencing preshader-processed variables in main shader --- .../DX9Shader/Decompiler/FxlcHlslWriter.cs | 7 ++- .../DX9Shader/Decompiler/HlslWriter.cs | 3 +- .../DX9Shader/Decompiler/RegisterState.cs | 45 +++++++++++-------- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs index 281e77c..20ef28c 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs @@ -45,7 +45,7 @@ protected void WriteInstructions(HashSet ctabOverride = null) { foreach(var token in Shader.Fxlc.Tokens) { - Write(token); + Write(token, ctabOverride); } } @@ -160,7 +160,10 @@ void WriteAssignment(FxlcToken token, HashSet ctabOverride, string format, { throw new NotImplementedException(); } - ctabOverride?.Add(destination.OpIndex / 4); + if(destination.OpType == FxlcOperandType.Expr) + { + ctabOverride?.Add(destination.OpIndex / 4); + } } void WriteInfix(string op, FxlcToken token, HashSet ctabOverride) { diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index ae36cd4..b96fed1 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -363,7 +363,7 @@ void WriteTemps() if(!tempRegisters.ContainsKey(registerKey)) { var reg = new RegisterDeclaration(registerKey); - _registers._registerDeclarations[registerKey] = reg; + _registers.RegisterDeclarations[registerKey] = reg; tempRegisters[registerKey] = (int)inst.GetDestinationWriteMask(); } else @@ -440,6 +440,7 @@ protected override void Write() if(_shader.Preshader != null) { var preshader = PreshaderWriter.Decompile(_shader.Preshader, Indent, out var ctabOverride); + _registers.CtabOverride = ctabOverride; WriteLine(preshader); } diff --git a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs index 2a7d8f7..e5eb5fb 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs @@ -12,19 +12,22 @@ public sealed class RegisterState private readonly CultureInfo _culture = CultureInfo.InvariantCulture; - private ICollection _constantDefinitions { get; } = new List(); - private ICollection _constantIntDefinitions { get; } = new List(); - public IDictionary _registerDeclarations = new Dictionary(); + private ICollection _constantDefinitions = new List(); + private ICollection _constantIntDefinitions = new List(); + + public IDictionary RegisterDeclarations { get; } = new Dictionary(); + public IDictionary MethodInputRegisters { get; } = new Dictionary(); + public IDictionary MethodOutputRegisters { get; } = new Dictionary(); + public ICollection ConstantDeclarations { get; private set; } + + public HashSet CtabOverride { get; set; } public RegisterState(ShaderModel shader) { Load(shader); } - public ICollection ConstantDeclarations { get; private set; } - - public IDictionary MethodInputRegisters { get; } = new Dictionary(); - public IDictionary MethodOutputRegisters { get; } = new Dictionary(); + private void Load(ShaderModel shader) { @@ -58,7 +61,7 @@ private void Load(ShaderModel shader) { var registerKey = new RegisterKey(registerType, constantDeclaration.RegisterIndex + r); var registerDeclaration = new RegisterDeclaration(registerKey); - _registerDeclarations.Add(registerKey, registerDeclaration); + RegisterDeclarations.Add(registerKey, registerDeclaration); } } @@ -69,7 +72,7 @@ private void Load(ShaderModel shader) var registerDeclaration = new RegisterDeclaration(instruction); RegisterKey registerKey = registerDeclaration.RegisterKey; - _registerDeclarations.Add(registerKey, registerDeclaration); + RegisterDeclarations.Add(registerKey, registerDeclaration); switch(registerKey.Type) { @@ -116,10 +119,10 @@ private void Load(ShaderModel shader) RegisterType registerType = instruction.GetParamRegisterType(destIndex); var registerNumber = instruction.GetParamRegisterNumber(destIndex); var registerKey = new RegisterKey(registerType, registerNumber); - if(_registerDeclarations.ContainsKey(registerKey) == false) + if(RegisterDeclarations.ContainsKey(registerKey) == false) { var reg = new RegisterDeclaration(registerKey); - _registerDeclarations[registerKey] = reg; + RegisterDeclarations[registerKey] = reg; switch(registerType) { case RegisterType.AttrOut: @@ -153,14 +156,22 @@ public string GetDestinationName(InstructionToken instruction, out string writeM public string GetSourceName(InstructionToken instruction, int srcIndex, out string swizzle, out string modifier) { string sourceRegisterName; - RegisterKey registerKey = instruction.GetParamRegisterKey(srcIndex); + var registerKey = instruction.GetParamRegisterKey(srcIndex); var registerType = instruction.GetParamRegisterType(srcIndex); + var registerNumber = instruction.GetParamRegisterNumber(srcIndex); switch(registerType) { case RegisterType.Const: case RegisterType.Const2: case RegisterType.Const3: case RegisterType.Const4: + if(CtabOverride?.Contains(registerNumber) is true) + { + swizzle = instruction.GetSourceSwizzleName(srcIndex, true); + modifier = GetModifier(instruction.GetSourceModifier(srcIndex)); + return $"expr{srcIndex}"; + } + goto case RegisterType.ConstInt; case RegisterType.ConstBool: case RegisterType.ConstInt: sourceRegisterName = GetSourceConstantName(instruction, srcIndex); @@ -189,7 +200,6 @@ public string GetSourceName(InstructionToken instruction, int srcIndex, out stri default: throw new NotImplementedException(); } - var registerNumber = instruction.GetParamRegisterNumber(srcIndex); var decl = FindConstant(registerSet, registerNumber); if(decl == null) { @@ -207,7 +217,7 @@ public string GetSourceName(InstructionToken instruction, int srcIndex, out stri break; } - sourceRegisterName = sourceRegisterName ?? instruction.GetParamRegisterName(srcIndex); + sourceRegisterName ??= instruction.GetParamRegisterName(srcIndex); swizzle = instruction.GetSourceSwizzleName(srcIndex, true); modifier = GetModifier(instruction.GetSourceModifier(srcIndex)); @@ -232,7 +242,7 @@ public uint GetRegisterFullLength(RegisterKey registerKey) return data.Type.Columns; } - RegisterDeclaration decl = _registerDeclarations[registerKey]; + RegisterDeclaration decl = RegisterDeclarations[registerKey]; switch(decl.TypeName) { case "float": @@ -250,7 +260,7 @@ public uint GetRegisterFullLength(RegisterKey registerKey) public string GetRegisterName(RegisterKey registerKey) { - var decl = _registerDeclarations[registerKey]; + var decl = RegisterDeclarations[registerKey]; switch(registerKey.Type) { case RegisterType.Texture: @@ -262,8 +272,7 @@ public string GetRegisterName(RegisterKey registerKey) case RegisterType.ColorOut: return (MethodOutputRegisters.Count == 1) ? decl.Name : ("o." + decl.Name); case RegisterType.Const: - var constDecl = FindConstant(RegisterSet.Float4, registerKey.Number); - return constDecl.GetConstantNameByRegisterNumber(registerKey.Number); + throw new NotSupportedException($"Use {nameof(GetSourceName)} instead"); case RegisterType.Sampler: ConstantDeclaration samplerDecl = FindConstant(RegisterSet.Sampler, registerKey.Number); if(samplerDecl != null) From d0dcf2e3ad6954b9e119cb46ae4db1f03fb430d3 Mon Sep 17 00:00:00 2001 From: lanyi Date: Fri, 21 May 2021 17:22:16 +0200 Subject: [PATCH 10/72] force invariant culture --- src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs b/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs index 255b030..9c86964 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs @@ -25,6 +25,11 @@ public enum ShaderType public class ShaderModel { + static ShaderModel() + { + System.Globalization.CultureInfo.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture; + } + private static readonly Dictionary KnownCommentTypes = new Dictionary { From 8d1369436e52cd00f3fadb39c622715a8281f685 Mon Sep 17 00:00:00 2001 From: lanyi Date: Fri, 21 May 2021 17:30:46 +0200 Subject: [PATCH 11/72] Some kind of relative addressing support in FX9 decompilation --- .../Bytecode/Ctab/ConstantDeclaration.cs | 24 ++++++++++++- .../DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs | 23 ++++++++---- .../DX9Shader/Decompiler/HlslWriter.cs | 35 +++++++++++++++++-- .../DX9Shader/Decompiler/RegisterState.cs | 10 ++++-- 4 files changed, 81 insertions(+), 11 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs b/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs index b4f91ba..34d9feb 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs @@ -93,7 +93,7 @@ public string GetRegisterName() return $"{RegisterSet.GetDescription()}{RegisterIndex}"; } - public string GetConstantNameByRegisterNumber(uint registerNumber) + public string GetConstantNameByRegisterNumber(uint registerNumber, string relativeAddressing) { var decl = this; var totalOffset = registerNumber - decl.RegisterIndex; @@ -101,6 +101,28 @@ public string GetConstantNameByRegisterNumber(uint registerNumber) var name = decl.GetMemberNameByOffset(totalOffset); var offsetFromMember = registerNumber - data.RegisterIndex; + if(relativeAddressing != null) + { + // a nasty way to check array subscripts + // TODO: we need something more serious... + if(decl.Elements <= 1 || !name.EndsWith("]") || name.Count(x => x == ']') != 1) + { + // we cannot handle relative addressing if this contant declaration is not an array + // we also cannot handle relative addressing if this constant declaration contains nested arrays + name = $"Error({name}, {relativeAddressing})"; + } + else + { + var declElementSize = decl.RegisterCount / decl.Elements; + if(declElementSize > 1) + { + relativeAddressing = $"({relativeAddressing} / {declElementSize})"; + } + name = name.Substring(0, name.Length - 1) // name without the last ']' + + $" + {relativeAddressing}]"; + } + } + // how many registers should be occupied by the current constant item var registersOccupied = data.Type.ParameterClass == ParameterClass.MatrixColumns ? data.Type.Columns diff --git a/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs index 00d9eb9..35aaa0c 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs @@ -105,7 +105,14 @@ private string FormatComponent(uint componentIndex, uint componentCount) return $".UnknownCount{componentCount}"; } } - private string FormatOperand(ConstantTable ctab, CliToken cli, HashSet ctabOverride, FxlcOperandType type, uint index, out string component) + private string FormatOperand( + ConstantTable ctab, + CliToken cli, + HashSet ctabOverride, + FxlcOperandType type, + uint index, + string indexer, + out string component) { var elementIndex = index / 4; var componentIndex = index % 4; @@ -136,7 +143,11 @@ private string FormatOperand(ConstantTable ctab, CliToken cli, HashSet cta { return ctab.ConstantDeclarations .First(d => d.ContainsIndex(elementIndex)) - .GetConstantNameByRegisterNumber(elementIndex); + .GetConstantNameByRegisterNumber(elementIndex, indexer); + } + else if(!string.IsNullOrEmpty(indexer)) + { + return $"c{elementIndex}[{indexer}]"; } return $"c{elementIndex}"; case FxlcOperandType.Expr: @@ -187,13 +198,13 @@ public string FormatOperand(CliToken cli, ConstantTable ctab, HashSet ctab { if(IsArray == 0) { - return FormatOperand(ctab, cli, ctabOverride, OpType, OpIndex, out var component) + component; + return FormatOperand(ctab, cli, ctabOverride, OpType, OpIndex, null, out var component) + component; } else { - var arrayOperand = FormatOperand(ctab, cli, ctabOverride, ArrayType, ArrayIndex, out var elementComponent); - var indexOperand = FormatOperand(ctab, cli, ctabOverride, OpType, OpIndex, out _); - return string.Format("{0}[{1}.x]{2}", arrayOperand, indexOperand, elementComponent); + var indexOperand = FormatOperand(ctab, cli, ctabOverride, OpType, OpIndex, null, out _) + ".x"; + var arrayOperand = FormatOperand(ctab, cli, ctabOverride, ArrayType, ArrayIndex, indexOperand, out var elementComponent); + return string.Format("{0}{1}", arrayOperand, elementComponent); } } /// diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index b96fed1..f9cda95 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -65,9 +65,40 @@ private string GetDestinationNameWithWriteMask(InstructionToken instruction) return destinationName + writeMask; } - private SourceOperand GetSourceName(InstructionToken instruction, int srcIndex) + private SourceOperand GetSourceName(InstructionToken instruction, int srcIndex, bool isLogicalIndex = true) { - var body = _registers.GetSourceName(instruction, srcIndex, out var swizzle, out var modifier); + int dataIndex; + if(isLogicalIndex) + { + // compute the actual data index, which might be different from logical index + // because of relative addressing mode. + + // TODO: Handle relative addressing mode in a better way, + // by using `InstructionToken.Operands`: + // https://github.com/spacehamster/DXDecompiler/pull/6#issuecomment-782958769 + + // if instruction has destination, then source starts at the index 1 + // here we assume destination won't have relative addressing, + // so we assume destination will only occupy 1 slot, + // that is, the start index for sources will be 1 if instruction.HasDestination is true. + var begin = instruction.HasDestination ? 1 : 0; + dataIndex = begin; + while(srcIndex > begin) + { + if(instruction.IsRelativeAddressMode(dataIndex)) + { + ++dataIndex; + } + ++dataIndex; + --srcIndex; + } + } + else + { + dataIndex = srcIndex; + } + + var body = _registers.GetSourceName(instruction, dataIndex, out var swizzle, out var modifier); return new SourceOperand { Body = body, diff --git a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs index e5eb5fb..434b89d 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs @@ -27,7 +27,7 @@ public RegisterState(ShaderModel shader) Load(shader); } - + private void Load(ShaderModel shader) { @@ -210,7 +210,13 @@ public string GetSourceName(InstructionToken instruction, int srcIndex, out stri return $"Error {registerType}{registerNumber}"; //throw new NotImplementedException(); } - sourceRegisterName = decl.GetConstantNameByRegisterNumber(registerNumber); + string indexer = null; + if(instruction.IsRelativeAddressMode(srcIndex)) + { + indexer = GetSourceName(instruction, srcIndex + 1, out var indexSwizzle, out var indexModifier); + indexer = string.Format(indexModifier, indexer + indexSwizzle); + } + sourceRegisterName = decl.GetConstantNameByRegisterNumber(registerNumber, indexer); break; default: sourceRegisterName = GetRegisterName(registerKey); From 9c4321345fbb20731ce0017c840e24ac603c1e02 Mon Sep 17 00:00:00 2001 From: lanyi Date: Fri, 21 May 2021 17:57:49 +0200 Subject: [PATCH 12/72] Support address register in FX9 decompilation --- .../DX9Shader/Decompiler/HlslWriter.cs | 30 +++++-------------- .../DX9Shader/Decompiler/RegisterState.cs | 2 +- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index f9cda95..4a8bd13 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -387,7 +387,7 @@ void WriteTemps() { if(operand is DestinationOperand dest) { - if(dest.RegisterType == RegisterType.Temp) + if(dest.RegisterType is RegisterType.Temp or RegisterType.Addr) { var registerKey = new RegisterKey(dest.RegisterType, dest.RegisterNumber); @@ -411,31 +411,17 @@ void WriteTemps() kv => kv.Key)) { int writeMask = group.Key; - string writeMaskName; switch(writeMask) + string writeMaskName = writeMask switch { - case 0x1: - writeMaskName = "float"; - break; - case 0x3: - writeMaskName = "float2"; - break; - case 0x7: - writeMaskName = "float3"; - break; - case 0xF: - writeMaskName = "float4"; - break; - default: - // TODO - writeMaskName = "float4"; - break; - //throw new NotImplementedException(); - } + 0x1 => "float", + 0x3 => "float2", + 0x7 => "float3", + 0xF => "float4", + _ => "float4",// TODO + }; WriteIndent(); WriteLine("{0} {1};", writeMaskName, string.Join(", ", group)); } - - } protected override void Write() { diff --git a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs index 434b89d..7ee8bbc 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs @@ -269,7 +269,6 @@ public string GetRegisterName(RegisterKey registerKey) var decl = RegisterDeclarations[registerKey]; switch(registerKey.Type) { - case RegisterType.Texture: case RegisterType.Input: return (MethodInputRegisters.Count == 1) ? decl.Name : ("i." + decl.Name); case RegisterType.RastOut: @@ -300,6 +299,7 @@ public string GetRegisterName(RegisterKey registerKey) default: throw new NotImplementedException(); } + case RegisterType.Addr: case RegisterType.Temp: return registerKey.ToString(); default: From a5f70378becaf7a0a8569064133bd7b9f558462c Mon Sep 17 00:00:00 2001 From: lanyi Date: Sat, 22 May 2021 00:09:04 +0200 Subject: [PATCH 13/72] Fix typo --- src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs | 4 ++-- src/DXDecompiler/DX9Shader/FX9/Parameter.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs index ebc2b25..4b4b9b6 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs @@ -1,4 +1,4 @@ -using DXDecompiler.DX9Shader.Bytecode.Ctab; +using DXDecompiler.DX9Shader.Bytecode.Ctab; using DXDecompiler.DX9Shader.Decompiler; using DXDecompiler.DX9Shader.FX9; using System; @@ -171,7 +171,7 @@ public void WriteVariable(Variable variable) ++index; } } - Write(param.GetDecleration()); + Write(param.GetDeclaration()); if(variable.Annotations.Count > 0) { Write(" "); diff --git a/src/DXDecompiler/DX9Shader/FX9/Parameter.cs b/src/DXDecompiler/DX9Shader/FX9/Parameter.cs index 7be1ffd..82e949c 100644 --- a/src/DXDecompiler/DX9Shader/FX9/Parameter.cs +++ b/src/DXDecompiler/DX9Shader/FX9/Parameter.cs @@ -76,7 +76,7 @@ public uint GetSize() return 0; } } - public string GetDecleration(int indentLevel = 0) + public string GetDeclaration(int indentLevel = 0) { string arrayDecl = ""; string semanticDecl = ""; @@ -119,7 +119,7 @@ public string GetTypeName(int indentLevel = 0) sb.AppendLine("struct {"); foreach(var member in StructMembers) { - sb.AppendLine(string.Format("{0};", member.GetDecleration(indentLevel + 1))); + sb.AppendLine(string.Format("{0};", member.GetDeclaration(indentLevel + 1))); } sb.Append(indent); sb.Append("}"); From 5ca2a352a7a79f5255206ccb89454d3efaa1b27c Mon Sep 17 00:00:00 2001 From: lanyi Date: Sat, 22 May 2021 11:19:44 +0200 Subject: [PATCH 14/72] Fix effect decompilation output --- src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs index 4b4b9b6..c097c96 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs @@ -146,6 +146,10 @@ public string VariableBlobToString(FX9.Parameter key, int index = 0) { return $"\"{data.Value}\""; } + else if(string.IsNullOrEmpty(data.Value)) + { + return string.Empty; + } else { return $"<{data.Value}>"; From f9d031a9ff5bfcaaf7912e5dc39987105d4af7a8 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sat, 22 May 2021 12:06:46 +0200 Subject: [PATCH 15/72] Fixed tests and implemented more instructions in Dx9 Fxlc --- .../DX9Shader/Decompiler/FxlcHlslWriter.cs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs index 20ef28c..59d619b 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs @@ -25,12 +25,16 @@ protected void WriteTemporaries() var temporaryRegisters = new SortedSet(); foreach(var operands in Shader.Fxlc.Tokens.SelectMany(t => t.Operands)) { - if(operands.OpType == FxlcOperandType.Temp) + if(operands.IsArray != 0) { - if(operands.IsArray != 0) + if(operands.ArrayType == FxlcOperandType.Temp) { + // will this ever happen? throw new NotImplementedException(); } + } + if(operands.OpType == FxlcOperandType.Temp) + { temporaryRegisters.Add(operands.OpIndex); } } @@ -61,10 +65,10 @@ void Write(FxlcToken token, HashSet ctabOverride = null) WriteFunction("1.0f / ", token, ctabOverride); break; case FxlcOpcode.Mov: - WriteAssignment(token, ctabOverride, "{0}", token.Operands[1].FormatOperand(Cli, Ctab)); + WriteAssignment(token, ctabOverride, "{0}", token.Operands[1].FormatOperand(Cli, Ctab, ctabOverride)); break; case FxlcOpcode.Neg: - WriteAssignment(token, ctabOverride, "-{0}", token.Operands[1].FormatOperand(Cli, Ctab)); + WriteAssignment(token, ctabOverride, "-{0}", token.Operands[1].FormatOperand(Cli, Ctab, ctabOverride)); break; case FxlcOpcode.Frc: WriteFunction("frac", token, ctabOverride); @@ -135,6 +139,9 @@ void Write(FxlcToken token, HashSet ctabOverride = null) case FxlcOpcode.Max: WriteFunction("max", token, ctabOverride); break; + case FxlcOpcode.Dot: + WriteFunction("dot", token, ctabOverride); + break; case FxlcOpcode.Add: WriteInfix("+", token, ctabOverride); break; @@ -144,6 +151,13 @@ void Write(FxlcToken token, HashSet ctabOverride = null) case FxlcOpcode.Lt: WriteInfix("<", token, ctabOverride); break; + case FxlcOpcode.Ge: + WriteInfix(">=", token, ctabOverride); + break; + case FxlcOpcode.Cmp: + WriteAssignment(token, ctabOverride, "({0} >= 0 ? {1} : {2})", + token.Operands.Skip(1).Select(o => o.FormatOperand(Cli, Ctab, ctabOverride)).ToArray()); + break; } } void WriteAssignment(FxlcToken token, HashSet ctabOverride, string format, params string[] args) From 7b16acefd8282f94b2f117721b3b0fb3c2f252e9 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sat, 22 May 2021 14:20:21 +0200 Subject: [PATCH 16/72] Refactored decompilation of constant declarations --- src/DXDecompiler/DX9Shader/Asm/AsmWriter.cs | 54 +------- .../Decompiler/ConstantTypeWriter.cs | 129 ++++++++++++++++++ .../DX9Shader/Decompiler/HlslWriter.cs | 79 ----------- 3 files changed, 132 insertions(+), 130 deletions(-) create mode 100644 src/DXDecompiler/DX9Shader/Decompiler/ConstantTypeWriter.cs diff --git a/src/DXDecompiler/DX9Shader/Asm/AsmWriter.cs b/src/DXDecompiler/DX9Shader/Asm/AsmWriter.cs index 6a8880f..db94d41 100644 --- a/src/DXDecompiler/DX9Shader/Asm/AsmWriter.cs +++ b/src/DXDecompiler/DX9Shader/Asm/AsmWriter.cs @@ -1,5 +1,6 @@ using DXDecompiler.DX9Shader.Asm; using DXDecompiler.DX9Shader.Bytecode.Ctab; +using DXDecompiler.DX9Shader.Decompiler; using System; using System.Linq; @@ -193,7 +194,8 @@ public void WriteConstantTable(ConstantTable constantTable) WriteLine("//"); foreach(var declaration in constantTable.ConstantDeclarations) { - WriteConstantType(declaration.Type, declaration.Name); + var decompiled = ConstantTypeWriter.Decompile(declaration.Type, declaration.Name, false, Indent); + WriteLine(string.Join("\n", decompiled.Split('\n').Select(l => $"// {l}"))); } WriteLine("//"); WriteLine("//"); @@ -617,55 +619,5 @@ private void WriteInstruction(InstructionToken instruction) throw new NotImplementedException($"Instruction not implemented {instruction.Opcode}"); } } - - // copied from HLSLWriter - private static string GetConstantTypeName(ConstantType type) - { - switch(type.ParameterClass) - { - case ParameterClass.Scalar: - return type.ParameterType.GetDescription(); - case ParameterClass.Vector: - return type.ParameterType.GetDescription() + type.Columns; - case ParameterClass.Struct: - return "struct"; - case ParameterClass.MatrixColumns: - case ParameterClass.MatrixRows: - return $"{type.ParameterType.GetDescription()}{type.Rows}x{type.Columns}"; - case ParameterClass.Object: - return type.ParameterType.GetDescription(); - } - throw new NotImplementedException(); - } - - private void WriteConstantType(ConstantType type, string name, bool isStructMember = false) - { - string typeName = GetConstantTypeName(type); - Write("// "); - WriteIndent(); - Write("{0}", typeName); - if(type.ParameterClass == ParameterClass.Struct) - { - WriteLine(""); - Write("// "); - WriteIndent(); - WriteLine("{"); - Indent++; - foreach(var member in type.Members) - { - WriteConstantType(member.Type, member.Name, true); - } - Indent--; - Write("// "); - WriteIndent(); - Write("}"); - } - Write(" {0}", name); - if(type.Elements > 1) - { - Write("[{0}]", type.Elements); - } - WriteLine(";"); - } } } diff --git a/src/DXDecompiler/DX9Shader/Decompiler/ConstantTypeWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/ConstantTypeWriter.cs new file mode 100644 index 0000000..cef3928 --- /dev/null +++ b/src/DXDecompiler/DX9Shader/Decompiler/ConstantTypeWriter.cs @@ -0,0 +1,129 @@ +using DXDecompiler.DX9Shader.Bytecode.Ctab; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace DXDecompiler.DX9Shader.Decompiler +{ + public class DecompiledConstantDeclaration + { + public string Name { get; set; } + public string Code { get; set; } + public Dictionary RegisterAssignments { get; } = new(); // example: key = "ps_2_0", value = "c0" + public string DefaultValue { get; set; } + + public string RegisterAssignmentString => + string.Concat(RegisterAssignments.Select(t => $" : register({t.Key}, {t.Value})")); + } + + class ConstantTypeWriter : DecompileWriter + { + private readonly ConstantType _initialType; + private readonly string _initialName; + private readonly bool _addTrailingSemicolon; + private readonly bool _printMatrixOrientation; + + public static string Decompile(ConstantType type, string name, bool isHlsl, int indent = 0) + { + var decompiler = new ConstantTypeWriter(type, name, !isHlsl, isHlsl) + { + Indent = indent + }; + return decompiler.Decompile(); + } + public static DecompiledConstantDeclaration Decompile(ConstantDeclaration declaration, ShaderModel shader) + { + + var defaultValue = declaration.DefaultValue.All(v => v == 0) + ? null + : string.Format("{{ {0} }}", string.Join(", ", declaration.DefaultValue)); + var decompiled = new DecompiledConstantDeclaration + { + Name = declaration.Name, + Code = Decompile(declaration.Type, declaration.Name, true), + DefaultValue = defaultValue, + }; + if(shader.Type != ShaderType.Expression) + { + var shaderProfile = $"{shader.Type.GetDescription()}_{shader.MajorVersion}_{shader.MinorVersion}"; + var register = declaration.RegisterSet.GetDescription() + declaration.RegisterIndex; + decompiled.RegisterAssignments[shaderProfile] = register; + } + return decompiled; + } + private ConstantTypeWriter(ConstantType initialType, string initialName, bool addTrailingSemicolon, bool printMatrixOrientation) + { + _initialType = initialType; + _initialName = initialName; + _addTrailingSemicolon = addTrailingSemicolon; + _printMatrixOrientation = printMatrixOrientation; + } + protected override void Write() + { + Write(_initialType, _initialName, _addTrailingSemicolon); + } + private void Write(ConstantType type, string name, bool addSemicolon = false) + { + string typeName = GetConstantTypeName(type); + WriteIndent(); + Write("{0}", typeName); + if(type.ParameterClass == ParameterClass.Struct) + { + WriteLine(string.Empty); + WriteIndent(); + WriteLine("{"); + Indent++; + foreach(var member in type.Members) + { + Write(member.Type, member.Name, true); + } + Indent--; + WriteIndent(); + Write("}"); + } + Write(" {0}", name); + if(type.Elements > 1) + { + Write("[{0}]", type.Elements); + } + if(addSemicolon) + { + WriteLine(";"); + } + } + private string GetConstantTypeName(ConstantType type) + { + switch(type.ParameterClass) + { + case ParameterClass.Scalar: + return type.ParameterType.GetDescription(); + case ParameterClass.Vector: + return type.ParameterType.GetDescription() + type.Columns; + case ParameterClass.Struct: + return "struct"; + case ParameterClass.MatrixColumns: + case ParameterClass.MatrixRows: + var prefix = string.Empty; + if(_printMatrixOrientation) + { + prefix = type.ParameterClass == ParameterClass.MatrixColumns + ? "column_major " + : "row_major "; + } + return $"{prefix}{type.ParameterType.GetDescription()}{type.Rows}x{type.Columns}"; + case ParameterClass.Object: + switch(type.ParameterType) + { + case ParameterType.Sampler1D: + case ParameterType.Sampler2D: + case ParameterType.Sampler3D: + case ParameterType.SamplerCube: + return type.ParameterType.GetDescription(); + default: + throw new NotImplementedException(); + } + } + throw new NotImplementedException(); + } + } +} diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 4a8bd13..e39afad 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -1,4 +1,3 @@ -using DXDecompiler.DX9Shader.Bytecode.Ctab; using DXDecompiler.DX9Shader.Decompiler; using DXDecompiler.Util; using System; @@ -107,35 +106,6 @@ private SourceOperand GetSourceName(InstructionToken instruction, int srcIndex, }; } - private static string GetConstantTypeName(ConstantType type) - { - switch(type.ParameterClass) - { - case ParameterClass.Scalar: - return type.ParameterType.GetDescription(); - case ParameterClass.Vector: - return type.ParameterType.GetDescription() + type.Columns; - case ParameterClass.Struct: - return "struct"; - case ParameterClass.MatrixColumns: - return $"column_major {type.ParameterType.GetDescription()}{type.Rows}x{type.Columns}"; - case ParameterClass.MatrixRows: - return $"row_major {type.ParameterType.GetDescription()}{type.Rows}x{type.Columns}"; - case ParameterClass.Object: - switch(type.ParameterType) - { - case ParameterType.Sampler1D: - case ParameterType.Sampler2D: - case ParameterType.Sampler3D: - case ParameterType.SamplerCube: - return "sampler"; - default: - throw new NotImplementedException(); - } - } - throw new NotImplementedException(); - } - private void WriteInstruction(InstructionToken instruction) { WriteIndent(); @@ -431,7 +401,6 @@ protected override void Write() } _registers = new RegisterState(_shader); - WriteConstantDeclarations(); if(_registers.MethodInputRegisters.Count > 1) { @@ -504,54 +473,6 @@ protected override void Write() WriteLine("}"); } - private void WriteConstantDeclarations() - { - if(_registers.ConstantDeclarations.Count != 0) - { - foreach(ConstantDeclaration declaration in _registers.ConstantDeclarations) - { - Write(declaration); - } - } - } - private void Write(ConstantDeclaration declaration) - { - Write(declaration.Type, declaration.Name); - if(!declaration.DefaultValue.All(v => v == 0)) - { - Write(" = {{ {0} }}", string.Join(", ", declaration.DefaultValue)); - } - WriteLine(";"); - WriteLine(); - } - private void Write(ConstantType type, string name, bool isStructMember = false) - { - string typeName = GetConstantTypeName(type); - WriteIndent(); - Write("{0}", typeName); - if(type.ParameterClass == ParameterClass.Struct) - { - WriteLine(""); - WriteLine("{"); - Indent++; - foreach(var member in type.Members) - { - Write(member.Type, member.Name, true); - } - Indent--; - WriteIndent(); - Write("}"); - } - Write(" {0}", name); - if(type.Elements > 1) - { - Write("[{0}]", type.Elements); - } - if(isStructMember) - { - Write(";\n"); - } - } private void WriteInputStructureDeclaration() { var inputStructType = _shader.Type == ShaderType.Pixel ? "PS_IN" : "VS_IN"; From 0a51204262780657f3465c107b10090fb574a5e2 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sat, 22 May 2021 14:24:20 +0200 Subject: [PATCH 17/72] Merge common constant declarations in DX9 Effect --- .../DX9Shader/Decompiler/EffectHLSLWriter.cs | 130 ++++++++++++++---- .../DX9Shader/Decompiler/HlslWriter.cs | 25 +++- 2 files changed, 124 insertions(+), 31 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs index c097c96..84f4bab 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs @@ -1,4 +1,4 @@ -using DXDecompiler.DX9Shader.Bytecode.Ctab; +using DXDecompiler.DX9Shader.Bytecode.Ctab; using DXDecompiler.DX9Shader.Decompiler; using DXDecompiler.DX9Shader.FX9; using System; @@ -9,11 +9,15 @@ namespace DXDecompiler.DX9Shader { public class EffectHLSLWriter : DecompileWriter { - EffectContainer EffectChunk; - Dictionary ShaderNames = new Dictionary(); + private readonly Dictionary _shaderNames = new(); + private readonly EffectContainer _effectChunk; + + public Dictionary CommonConstantDeclarations { get; } = new(); + + public EffectHLSLWriter(EffectContainer effectChunk) { - EffectChunk = effectChunk; + _effectChunk = effectChunk; } public static string Decompile(EffectContainer effectChunk) { @@ -28,33 +32,93 @@ void BuildNameLookup() ShaderType.Vertex => "VertexShader", ShaderType.Expression => "Expression", _ => "Shader" - } + $"{ShaderNames.Count + 1}"; + } + $"{_shaderNames.Count + 1}"; - foreach(var blob in EffectChunk.VariableBlobs) + foreach(var blob in _effectChunk.VariableBlobs) { if(blob.IsShader) { - ShaderNames[blob] = MakeShaderName(blob.Shader); + _shaderNames[blob] = MakeShaderName(blob.Shader); } } - foreach(var blob in EffectChunk.StateBlobs) + foreach(var blob in _effectChunk.StateBlobs) { if(blob.BlobType == StateBlobType.Shader || blob.BlobType == StateBlobType.IndexShader) { - ShaderNames[blob] = MakeShaderName(blob.Shader); + _shaderNames[blob] = MakeShaderName(blob.Shader); + } + } + } + void FindCommonConstantDeclarations() + { + var variableShaders = _effectChunk.VariableBlobs + .Where(x => x.IsShader) + .Select(x => x.Shader); + var blobShaders = _effectChunk.StateBlobs + .Where(x => x.BlobType is StateBlobType.Shader or StateBlobType.IndexShader) + .Select(x => x.Shader); + foreach(var shader in variableShaders.Concat(blobShaders)) + { + var declarations = shader.ConstantTable?.ConstantDeclarations + ?? Enumerable.Empty(); + var decompiled = declarations.Select(c => ConstantTypeWriter.Decompile(c, shader)); + foreach(var declaration in decompiled) + { + // assuming every shader have com + if(!CommonConstantDeclarations.TryGetValue(declaration.Name, out var existing)) + { + CommonConstantDeclarations[declaration.Name] = declaration; + } + // this means we don't have common constant declaration + else if(existing.Code != declaration.Code) + { + CommonConstantDeclarations.Remove(declaration.Name); + } + // check if two declarations only differs on register... + else + { + if(!declaration.RegisterAssignments.Any()) + { + continue; + } + // sanity check + if(declaration.RegisterAssignments.Count > 1) + { + throw new InvalidOperationException(); + } + + var registerAssignment = declaration.RegisterAssignments.First(); + if(existing.RegisterAssignments.TryGetValue(registerAssignment.Key, out var existingRegister)) + { + // if register number of the same constant is different + // betwwen two shaders of same shader profile + if(registerAssignment.Value != existingRegister) + { + // then probably there weren't a register assignment at all. + // I don't think there is a way to specify two different registers + // for the same variable with the same shader profile in DX9's effect. + existing.RegisterAssignments.Remove(registerAssignment.Key); + } + } + else + { + existing.RegisterAssignments[registerAssignment.Key] = registerAssignment.Value; + } + } } } } protected override void Write() { BuildNameLookup(); - foreach(var variable in EffectChunk.Variables) + FindCommonConstantDeclarations(); + foreach(var variable in _effectChunk.Variables) { WriteVariable(variable); } - foreach(var blob in EffectChunk.StateBlobs) + foreach(var blob in _effectChunk.StateBlobs) { if(blob.BlobType == StateBlobType.Shader || blob.BlobType == StateBlobType.IndexShader) @@ -62,12 +126,12 @@ protected override void Write() WriteShader(blob); } } - foreach(var technique in EffectChunk.Techniques) + foreach(var technique in _effectChunk.Techniques) { WriteTechnique(technique); } } - void WriteShader(StateBlob blob) => WriteShader(ShaderNames[blob], blob.Shader); + void WriteShader(StateBlob blob) => WriteShader(_shaderNames[blob], blob.Shader); void WriteShader(string shaderName, ShaderModel shader) { WriteLine($"// {shaderName} {shader.Type}_{shader.MajorVersion}_{shader.MinorVersion} Has PRES {shader.Preshader != null}"); @@ -79,18 +143,17 @@ void WriteShader(string shaderName, ShaderModel shader) } else { - text = HlslWriter.Decompile(shader, funcName); - // text = text.Replace("main(", $"{funcName}("); + text = HlslWriter.Decompile(shader, funcName, this); } WriteLine(text); } public string StateBlobToString(Assignment key) { - if(!EffectChunk.StateBlobLookup.ContainsKey(key)) + if(!_effectChunk.StateBlobLookup.ContainsKey(key)) { return $"Key not found"; } - var data = EffectChunk.StateBlobLookup[key]; + var data = _effectChunk.StateBlobLookup[key]; if(data == null) { return "Blob is NULL"; @@ -99,12 +162,12 @@ public string StateBlobToString(Assignment key) { if(data.Shader.Type == ShaderType.Expression) { - var funcName = ShaderNames[data]; + var funcName = _shaderNames[data]; return $"{funcName}()"; } else { - var funcName = ShaderNames[data]; + var funcName = _shaderNames[data]; return $"compile {data.Shader.Type.GetDescription()}_{data.Shader.MajorVersion}_{data.Shader.MinorVersion} {funcName}()"; } } @@ -121,25 +184,25 @@ public string StateBlobToString(Assignment key) } if(data.BlobType == StateBlobType.IndexShader) { - var funcName = ShaderNames[data]; + var funcName = _shaderNames[data]; return $"{data.VariableName}[{funcName}()]"; } throw new ArgumentException(); } public string VariableBlobToString(FX9.Parameter key, int index = 0) { - if(!EffectChunk.VariableBlobLookup.ContainsKey(key)) + if(!_effectChunk.VariableBlobLookup.ContainsKey(key)) { return $"Key not found"; } - var data = EffectChunk.VariableBlobLookup[key][index]; + var data = _effectChunk.VariableBlobLookup[key][index]; if(data == null) { return "Blob is NULL"; } if(data.IsShader) { - var funcName = ShaderNames[data]; + var funcName = _shaderNames[data]; return $"compile {data.Shader.Type.GetDescription()}_{data.Shader.MajorVersion}_{data.Shader.MinorVersion} {funcName}()"; } else if(key.ParameterType == ParameterType.String) @@ -165,7 +228,7 @@ public void WriteVariable(Variable variable) if(isShaderArray) { shaderArrayElements = new Dictionary(); - var blobs = EffectChunk.VariableBlobLookup[param]; + var blobs = _effectChunk.VariableBlobLookup[param]; var index = 0; foreach(var blob in blobs) { @@ -175,7 +238,20 @@ public void WriteVariable(Variable variable) ++index; } } + if(CommonConstantDeclarations.TryGetValue(variable.Parameter.Name, out var decompiled)) + { + var semantic = string.IsNullOrEmpty(param.Semantic) + ? string.Empty + : $" : {param.Semantic}"; + WriteLine($"{decompiled.Code}{semantic}{decompiled.RegisterAssignmentString}"); + } + // shader's constant declaration might differ from the effect variable's parameter declaration + // in that case, we should prefer shader's one. + // So we write parameter's declaration only if the variable isn't inside the common constant declaration. + else + { Write(param.GetDeclaration()); + } if(variable.Annotations.Count > 0) { Write(" "); @@ -203,7 +279,7 @@ public void WriteVariable(Variable variable) if(assignment.Type == StateType.Texture) { var data = StateBlobToString(assignment); - WriteLine("{0} = <{1}>; // {2}", assignment.Type, data, assignment.Value[0].UInt); + WriteLine("{0} = {1}; // {2}", assignment.Type, data, assignment.Value[0].UInt); } else { @@ -349,7 +425,7 @@ public void WriteAssignment(Assignment assignment) { value = string.Format("{{ {0} }}", string.Join(", ", assignment.Value)); } - else if(EffectChunk.StateBlobLookup.ContainsKey(assignment)) + else if(_effectChunk.StateBlobLookup.ContainsKey(assignment)) { value = StateBlobToString(assignment); } @@ -358,7 +434,7 @@ public void WriteAssignment(Assignment assignment) value = assignment.Value[0].ToString(); } Write("{0}{1} = {2};", assignment.Type.ToString(), index, value); - if(EffectChunk.StateBlobLookup.ContainsKey(assignment)) + if(_effectChunk.StateBlobLookup.ContainsKey(assignment)) { Write(" // {0}", assignment.Value[0].UInt); } diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index e39afad..2266efa 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -15,12 +15,12 @@ private class SourceOperand public string Modifier { get; set; } } - private readonly ShaderModel _shader; private readonly bool _doAstAnalysis; private int _iterationDepth = 0; - public RegisterState _registers; + private EffectHLSLWriter _effectWriter; + private RegisterState _registers; string _entryPoint; public HlslWriter(ShaderModel shader, bool doAstAnalysis = false, string entryPoint = null) @@ -43,13 +43,16 @@ public static string Decompile(byte[] bytecode, string entryPoint = null) var shaderModel = ShaderReader.ReadShader(bytecode); return Decompile(shaderModel); } - public static string Decompile(ShaderModel shaderModel, string entryPoint = null) + public static string Decompile(ShaderModel shaderModel, string entryPoint = null, EffectHLSLWriter effect = null) { if(shaderModel.Type == ShaderType.Effect) { return EffectHLSLWriter.Decompile(shaderModel.EffectChunk); } - var hlslWriter = new HlslWriter(shaderModel, false, entryPoint); + var hlslWriter = new HlslWriter(shaderModel, false, entryPoint) + { + _effectWriter = effect + }; return hlslWriter.Decompile(); } @@ -401,6 +404,20 @@ protected override void Write() } _registers = new RegisterState(_shader); + foreach(var declaration in _registers.ConstantDeclarations) + { + if(_effectWriter?.CommonConstantDeclarations.ContainsKey(declaration.Name) is true) + { + // skip common constant declarations + continue; + } + // write constant declaration + var decompiled = ConstantTypeWriter.Decompile(declaration, _shader); + var assignment = string.IsNullOrEmpty(decompiled.DefaultValue) + ? string.Empty + : $" = {decompiled.DefaultValue}"; + WriteLine($"{decompiled.Code}{decompiled.RegisterAssignmentString}{assignment}"); + } if(_registers.MethodInputRegisters.Count > 1) { From 4c59abd9b03591a2640966257da864ed45f721d4 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sat, 22 May 2021 17:16:25 +0200 Subject: [PATCH 18/72] Merge effect shaders' input / output declaration when possible --- .../DX9Shader/Decompiler/EffectHLSLWriter.cs | 2 +- .../DX9Shader/Decompiler/HlslWriter.cs | 113 ++++++++---------- 2 files changed, 53 insertions(+), 62 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs index 84f4bab..f9403b5 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs @@ -13,7 +13,7 @@ public class EffectHLSLWriter : DecompileWriter private readonly EffectContainer _effectChunk; public Dictionary CommonConstantDeclarations { get; } = new(); - + public List<(string Name, string[] Declarations)> InputOutputStructures { get; } = new(); public EffectHLSLWriter(EffectContainer effectChunk) { diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 2266efa..05ad399 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -419,19 +419,9 @@ protected override void Write() WriteLine($"{decompiled.Code}{decompiled.RegisterAssignmentString}{assignment}"); } - if(_registers.MethodInputRegisters.Count > 1) - { - WriteInputStructureDeclaration(); - } - if(_registers.MethodOutputRegisters.Count > 1) - { - WriteOutputStructureDeclaration(); - } - - string methodReturnType = GetMethodReturnType(); - string methodParameters = GetMethodParameters(); - string methodSemantic = GetMethodSemantic(); + ProcessMethodInputType(out var methodParameters); + ProcessMethodOutputType(out var methodReturnType, out var methodSemantic); WriteLine("{0} {1}({2}){3}", methodReturnType, _entryPoint, @@ -449,9 +439,8 @@ protected override void Write() if(_registers.MethodOutputRegisters.Count > 1) { - var outputStructType = _shader.Type == ShaderType.Pixel ? "PS_OUT" : "VS_OUT"; WriteIndent(); - WriteLine($"{outputStructType} o;"); + WriteLine($"{methodReturnType} o;"); } else { @@ -490,82 +479,84 @@ protected override void Write() WriteLine("}"); } - private void WriteInputStructureDeclaration() + private void WriteStructureDeclaration(IEnumerable registers, string postFix, out string typeName) { - var inputStructType = _shader.Type == ShaderType.Pixel ? "PS_IN" : "VS_IN"; - WriteLine($"struct {inputStructType}"); - WriteLine("{"); - Indent++; - foreach(var input in _registers.MethodInputRegisters.Values) + var declarations = registers + .Select(x => + { + var registerName = Operand.GetParamRegisterName(x.RegisterKey.Type, (uint)x.RegisterKey.Number); + var comment = $"// {x.RegisterKey} {registerName}"; + var code = $"{x.TypeName} {x.Name} : {x.Semantic};"; + return (Comment: comment, Code: code); + }); + var codes = declarations.Select(t => t.Code).ToArray(); + typeName = $"{_entryPoint}_{postFix}"; + if(_effectWriter is not null) { - WriteIndent(); - WriteLine($"{input.TypeName} {input.Name} : {input.Semantic};"); + // check if a compatible type has alreaady been defined inside this effect + foreach(var (name, existing) in _effectWriter.InputOutputStructures) + { + if(codes.All(x => existing.Contains(x))) + { + typeName = name; + // we don't need to write declaration again in this case + return; + } + } + _effectWriter.InputOutputStructures.Add((typeName, codes)); } - Indent--; - WriteLine("};"); - WriteLine(); - } - private void WriteOutputStructureDeclaration() - { - var outputStructType = _shader.Type == ShaderType.Pixel ? "PS_OUT" : "VS_OUT"; - WriteLine($"struct {outputStructType}"); + WriteLine($"struct {typeName}"); WriteLine("{"); Indent++; - foreach(var output in _registers.MethodOutputRegisters.Values) + foreach(var (comment, code) in declarations) { WriteIndent(); - WriteLine($"// {output.RegisterKey} {Operand.GetParamRegisterName(output.RegisterKey.Type, (uint)output.RegisterKey.Number)}"); + WriteLine(comment); WriteIndent(); - WriteLine($"{output.TypeName} {output.Name} : {output.Semantic};"); + WriteLine(code); } Indent--; WriteLine("};"); WriteLine(); } - private string GetMethodReturnType() + private void ProcessMethodInputType(out string methodParameters) { - switch(_registers.MethodOutputRegisters.Count) + var registers = _registers.MethodInputRegisters.Values; + switch(registers.Count) { case 0: - throw new InvalidOperationException(); + methodParameters = string.Empty; + break; case 1: - return _registers.MethodOutputRegisters.Values.First().TypeName; + var input = registers.First(); + methodParameters = $"{input.TypeName} {input.Name} : {input.Semantic}"; + break; default: - return _shader.Type == ShaderType.Pixel ? "PS_OUT" : "VS_OUT"; + WriteStructureDeclaration(registers, "Input", out var inputTypeName); + methodParameters = $"{inputTypeName} i"; + break; } } - private string GetMethodSemantic() + private void ProcessMethodOutputType(out string methodReturnType, out string methodSemantic) { - switch(_registers.MethodOutputRegisters.Count) + var registers = _registers.MethodOutputRegisters.Values; + switch(registers.Count) { case 0: throw new InvalidOperationException(); case 1: - string semantic = _registers.MethodOutputRegisters.Values.First().Semantic; - return $" : {semantic}"; + methodReturnType = registers.First().TypeName; + string semantic = registers.First().Semantic; + methodSemantic = $" : {semantic}"; + break; default: - return string.Empty; - } - } - - private string GetMethodParameters() - { - if(_registers.MethodInputRegisters.Count == 0) - { - return string.Empty; - } - else if(_registers.MethodInputRegisters.Count == 1) - { - var input = _registers.MethodInputRegisters.Values.First(); - return $"{input.TypeName} {input.Name} : {input.Semantic}"; - } - - return _shader.Type == ShaderType.Pixel - ? "PS_IN i" - : "VS_IN i"; + WriteStructureDeclaration(registers, "Output", out methodReturnType); + methodSemantic = string.Empty; + break; + }; } private void WriteAst(HlslAst ast) From d441de8cf2c191a3f1d3354a6b12c2eafdb22231 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sat, 22 May 2021 17:50:13 +0200 Subject: [PATCH 19/72] Fixed the missing constructors in annotations --- src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs index f9403b5..5bfe660 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs @@ -1,4 +1,4 @@ -using DXDecompiler.DX9Shader.Bytecode.Ctab; +using DXDecompiler.DX9Shader.Bytecode.Ctab; using DXDecompiler.DX9Shader.Decompiler; using DXDecompiler.DX9Shader.FX9; using System; @@ -356,6 +356,10 @@ public void WriteAnnotations(List annotations) { var annotation = annotations[i]; var value = string.Join(", ", annotation.Value); + if(annotation.Value.Count > 1) + { + value = string.Format("{0}({1})", annotation.Parameter.GetTypeName(), value); + } if(annotation.Parameter.ParameterType.HasVariableBlob()) { Write("{0} {1} = {2};", From 3eb9e9fb4771a9a4ed980a4ee76600e696a8dd97 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sat, 22 May 2021 17:51:05 +0200 Subject: [PATCH 20/72] Added a ToString method to SourceOperand So it can be passed directly to string.Format --- src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 05ad399..d2a7ad1 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -13,6 +13,8 @@ private class SourceOperand public string Body { get; set; } public string Swizzle { get; set; } public string Modifier { get; set; } + + public override string ToString() => string.Format(Modifier, Body + Swizzle); } private readonly ShaderModel _shader; @@ -144,7 +146,7 @@ private void WriteInstruction(InstructionToken instruction) void WriteAssignmentEx(string sourceFormat, bool returnsScalar, params SourceOperand[] args) { var destination = GetDestinationName(instruction, out var writeMask); - var strings = args.Select(x => string.Format(x.Modifier, x.Body + x.Swizzle)).ToArray(); + var strings = args.Select(x => x.ToString()).ToArray(); var sourceResult = string.Format(sourceFormat, strings); var swizzleSizes = args.Select(x => x.Swizzle.StartsWith(".") ? x.Swizzle.Trim('.').Length : -1); From 85968ac3574ffa53cc9b64da54ee1417f4078750 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sat, 22 May 2021 17:52:05 +0200 Subject: [PATCH 21/72] Added the missing compile statement in shader array initialization --- .../DX9Shader/Decompiler/EffectHLSLWriter.cs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs index 5bfe660..9d58b93 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs @@ -1,4 +1,4 @@ -using DXDecompiler.DX9Shader.Bytecode.Ctab; +using DXDecompiler.DX9Shader.Bytecode.Ctab; using DXDecompiler.DX9Shader.Decompiler; using DXDecompiler.DX9Shader.FX9; using System; @@ -222,19 +222,24 @@ public void WriteVariable(Variable variable) { var param = variable.Parameter; WriteIndent(); - var isShaderArray = param.ParameterClass == ParameterClass.Object && - (param.ParameterType == ParameterType.PixelShader || param.ParameterType == ParameterType.VertexShader); - Dictionary shaderArrayElements = null; + var isShaderArray = param is + { + ParameterClass: ParameterClass.Object, + ParameterType: ParameterType.PixelShader or ParameterType.VertexShader + }; + Dictionary shaderArrayCompileStatements = null; if(isShaderArray) { - shaderArrayElements = new Dictionary(); + shaderArrayCompileStatements = new Dictionary(); var blobs = _effectChunk.VariableBlobLookup[param]; var index = 0; foreach(var blob in blobs) { var name = $"{variable.Parameter.Name}_Shader_{index}"; - shaderArrayElements.Add(blob.Index, name); - WriteShader(name, blob.Shader); + var shader = blob.Shader; + var compileStatement = $"compile {shader.Type.GetDescription()}_{shader.MajorVersion}_{shader.MinorVersion} {name}()"; + shaderArrayCompileStatements.Add(blob.Index, compileStatement); + WriteShader(name, shader); ++index; } } @@ -243,7 +248,7 @@ public void WriteVariable(Variable variable) var semantic = string.IsNullOrEmpty(param.Semantic) ? string.Empty : $" : {param.Semantic}"; - WriteLine($"{decompiled.Code}{semantic}{decompiled.RegisterAssignmentString}"); + Write($"{decompiled.Code}{semantic}{decompiled.RegisterAssignmentString}"); } // shader's constant declaration might differ from the effect variable's parameter declaration // in that case, we should prefer shader's one. @@ -320,7 +325,7 @@ public void WriteVariable(Variable variable) foreach(var idx in variable.DefaultValue) { WriteIndent(); - WriteLine("{0}, // {1}", shaderArrayElements[idx.UInt], idx.UInt); + WriteLine("{0}, // {1}", shaderArrayCompileStatements[idx.UInt], idx.UInt); } Indent--; WriteLine("};"); From 7301daa80c29d3a7903b135613694e1208a24e46 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sat, 22 May 2021 17:52:45 +0200 Subject: [PATCH 22/72] Fixed incorrect typename when decompiling effect parameters --- src/DXDecompiler/DX9Shader/FX9/Parameter.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/DXDecompiler/DX9Shader/FX9/Parameter.cs b/src/DXDecompiler/DX9Shader/FX9/Parameter.cs index 82e949c..34fcf56 100644 --- a/src/DXDecompiler/DX9Shader/FX9/Parameter.cs +++ b/src/DXDecompiler/DX9Shader/FX9/Parameter.cs @@ -126,7 +126,22 @@ public string GetTypeName(int indentLevel = 0) } break; case ParameterClass.Object: - sb.Append(ParameterType.ToString().ToLower()); + switch(ParameterType) + { + case ParameterType.Sampler1D: + case ParameterType.Sampler2D: + case ParameterType.Sampler3D: + case ParameterType.SamplerCube: + sb.Append(ParameterType.GetDescription()); + break; + case ParameterType.PixelShader: + case ParameterType.VertexShader: + sb.Append(ParameterType.ToString()); + break; + default: + sb.Append(ParameterType.ToString().ToLower()); + break; + } break; default: break; From 16d0ef2cd4560bca4626d6aab0fc54fcafa35747 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sat, 22 May 2021 17:53:22 +0200 Subject: [PATCH 23/72] Fixed the wrong register number in ctab override --- src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs index 7ee8bbc..d8c3c1f 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs @@ -169,7 +169,7 @@ public string GetSourceName(InstructionToken instruction, int srcIndex, out stri { swizzle = instruction.GetSourceSwizzleName(srcIndex, true); modifier = GetModifier(instruction.GetSourceModifier(srcIndex)); - return $"expr{srcIndex}"; + return $"expr{registerNumber}"; } goto case RegisterType.ConstInt; case RegisterType.ConstBool: From 170297f7ce7ffcf810fe133ce438d63c6c452bea Mon Sep 17 00:00:00 2001 From: lanyi Date: Sat, 22 May 2021 22:42:46 +0200 Subject: [PATCH 24/72] Proper handling of write masks in DX9 shader decompilation --- .../DX9Shader/Bytecode/EnumExtensions.cs | 60 +++++++++++++++++++ .../DX9Shader/Decompiler/HlslWriter.cs | 55 +++++++++++++---- 2 files changed, 104 insertions(+), 11 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Bytecode/EnumExtensions.cs b/src/DXDecompiler/DX9Shader/Bytecode/EnumExtensions.cs index 544291e..0da6542 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/EnumExtensions.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/EnumExtensions.cs @@ -163,5 +163,65 @@ public static bool IsTextureOperation(this Opcode opcode) return false; } } + public static bool ReturnsScalar(this Opcode opcode) + { + switch(opcode) + { + // TODO: there could be more parallel instructions + case Opcode.DP2Add: + case Opcode.Dp3: + case Opcode.Dp4: + return true; + default: + return false; + } + } + /// + /// Check if the instruction is "parallel", that is, + /// each component will be processed independently without affecting other components. + ///
+ /// This is useful for clipping source swizzles with destination write masks: + /// Since the instruction is parallel, it's safe to clip source swizzles as they will not have any effect. + ///
+ /// The instruction to be checked + /// The current shader + /// true if instruction is parallel + public static bool IsParallel(this Opcode opcode, ShaderModel shader) + { + switch(opcode) + { + // TODO: there could be more parallel instructions + case Opcode.Abs: + case Opcode.Add: + case Opcode.Cmp: + case Opcode.Cnd when shader is { MajorVersion: 1, MinorVersion: >= 4 } or { MajorVersion: > 1 }: + case Opcode.DSX: + case Opcode.DSY: + case Opcode.Exp: + case Opcode.ExpP: + case Opcode.Frc: + case Opcode.Log: + case Opcode.LogP: + case Opcode.Lrp: + case Opcode.Mad: + case Opcode.Max: + case Opcode.Min: + case Opcode.Mov: + case Opcode.MovA when shader.MajorVersion >= 2: + case Opcode.Mul: + case Opcode.Pow: + case Opcode.Rcp: + case Opcode.Rsq: + case Opcode.SetP: + case Opcode.Sge: + case Opcode.Sgn: + case Opcode.Slt: + case Opcode.Sub: + case Opcode.TexKill: + return true; + default: + return false; + } + } } } diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index d2a7ad1..40b8ca7 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -14,7 +14,15 @@ private class SourceOperand public string Swizzle { get; set; } public string Modifier { get; set; } - public override string ToString() => string.Format(Modifier, Body + Swizzle); + public override string ToString() + { + var body = string.Format(Modifier, Body); + if(char.IsDigit(body.Last())) + { + body = $"({body})"; + } + return body + Swizzle; + } } private readonly ShaderModel _shader; @@ -143,30 +151,55 @@ private void WriteInstruction(InstructionToken instruction) } WriteIndent(); - void WriteAssignmentEx(string sourceFormat, bool returnsScalar, params SourceOperand[] args) + void WriteAssignment(string sourceFormat, params SourceOperand[] args) { var destination = GetDestinationName(instruction, out var writeMask); - var strings = args.Select(x => x.ToString()).ToArray(); - var sourceResult = string.Format(sourceFormat, strings); + var sourceResult = string.Format(sourceFormat, args); var swizzleSizes = args.Select(x => x.Swizzle.StartsWith(".") ? x.Swizzle.Trim('.').Length : -1); - returnsScalar = returnsScalar || swizzleSizes.All(x => x == 1); + var returnsScalar = instruction.Opcode.ReturnsScalar() || swizzleSizes.All(x => x == 1); if(writeMask.Length > 0) { destination += writeMask; - if(writeMask.Trim('.').Length == 1 && returnsScalar) + if(returnsScalar) { // do nothing, don't need to append write mask as swizzle } - else if(sourceResult.Contains(',') || char.IsDigit(sourceResult.Last())) + // if the instruction is parallel then we are safe to "edit" source swizzles + else if(instruction.Opcode.IsParallel(_shader)) + { + foreach(var arg in args) + { + const string xyzw = ".xyzw"; + var trimmedSwizzle = "."; + if(string.IsNullOrEmpty(arg.Swizzle)) + { + arg.Swizzle = xyzw; + } + while(arg.Swizzle.Length <= 4) + { + arg.Swizzle += arg.Swizzle.Last(); + } + for(var i = 1; i <= 4; ++i) + { + if(writeMask.Contains(xyzw[i])) + { + trimmedSwizzle += arg.Swizzle[i]; + } + } + arg.Swizzle = trimmedSwizzle; + } + sourceResult = string.Format(sourceFormat, args); + } + // if we cannot "edit" the swizzles, we need to apply write masks on the source result + else if(sourceResult.Last() != ')') { sourceResult = $"({sourceResult}){writeMask}"; } } WriteLine("{0} = {1};", destination, sourceResult); } - void WriteAssignment(string sourceFormat, params SourceOperand[] args) => WriteAssignmentEx(sourceFormat, false, args); switch(instruction.Opcode) { @@ -182,14 +215,14 @@ void WriteAssignmentEx(string sourceFormat, bool returnsScalar, params SourceOpe GetSourceName(instruction, 1), GetSourceName(instruction, 2), GetSourceName(instruction, 3)); break; case Opcode.DP2Add: - WriteAssignmentEx("dot({0}, {1}) + {2}", true, + WriteAssignment("dot({0}, {1}) + {2}", GetSourceName(instruction, 1), GetSourceName(instruction, 2), GetSourceName(instruction, 3)); break; case Opcode.Dp3: - WriteAssignmentEx("dot({0}, {1})", true, GetSourceName(instruction, 1), GetSourceName(instruction, 2)); + WriteAssignment("dot({0}, {1})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Dp4: - WriteAssignmentEx("dot({0}, {1})", true, GetSourceName(instruction, 1), GetSourceName(instruction, 2)); + WriteAssignment("dot({0}, {1})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Exp: WriteAssignment("exp2({0})", GetSourceName(instruction, 1)); From 0d605d8ca5d8a08d436762ea093bfc19baeeedd2 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sat, 22 May 2021 23:38:16 +0200 Subject: [PATCH 25/72] Added the missing semicolon in DX9 shader constant declarations --- src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 40b8ca7..793596e 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -451,7 +451,7 @@ protected override void Write() var assignment = string.IsNullOrEmpty(decompiled.DefaultValue) ? string.Empty : $" = {decompiled.DefaultValue}"; - WriteLine($"{decompiled.Code}{decompiled.RegisterAssignmentString}{assignment}"); + WriteLine($"{decompiled.Code}{decompiled.RegisterAssignmentString}{assignment};"); } From 13259a84ce906f17b7d224553b55edafdb3ca079 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 00:46:42 +0200 Subject: [PATCH 26/72] Fixed pixal shader declaration semantic in DX9 --- .../DX9Shader/Bytecode/InstructionToken.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Bytecode/InstructionToken.cs b/src/DXDecompiler/DX9Shader/Bytecode/InstructionToken.cs index 13030d4..ea1613d 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/InstructionToken.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/InstructionToken.cs @@ -428,6 +428,23 @@ public string GetDeclSemantic() : GetDeclIndex().ToString(); switch(registerType) { + case RegisterType.Input when _shaderModel is { Type: ShaderType.Pixel, MajorVersion: <= 2 }: + case RegisterType.Texture when _shaderModel is { Type: ShaderType.Pixel, MajorVersion: <= 2 }: + if(_shaderModel.MajorVersion == 1) + { + throw new NotImplementedException("Shader model 1 not supported yet"); + } + declIndexString = GetParamRegisterNumber(1) is 0 + ? string.Empty + : GetParamRegisterNumber(1).ToString(); + + // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dcl---ps + return registerType switch + { + RegisterType.Input => "COLOR", + RegisterType.Texture => "TEXCOORD", + _ => throw new NotSupportedException(registerType.ToString()) + } + declIndexString; case RegisterType.Input: case RegisterType.Output when _shaderModel.Type == ShaderType.Vertex && _shaderModel.MajorVersion >= 3: string name; @@ -476,9 +493,6 @@ public string GetDeclSemantic() return "vPos"; } throw new NotImplementedException(); - case RegisterType.Texture when _shaderModel.Type == ShaderType.Pixel && _shaderModel.MajorVersion <= 2: - var registerNumber = GetParamRegisterNumber(1); - return "TEXCOORD" + (registerNumber == 0 ? string.Empty : registerNumber.ToString()); case RegisterType.TexCoordOut: return "TEXCOORD" + declIndexString; default: From 68bfb015a7094b010baf31a70035fcb0e6428aac Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 00:47:28 +0200 Subject: [PATCH 27/72] Handle rare cases where DX9 constant might start with a dollar sign --- src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs | 1 + src/DXDecompiler/DX9Shader/Decompiler/ConstantTypeWriter.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs b/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs index 34d9feb..ece1c2c 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs @@ -208,6 +208,7 @@ public string GetMemberNameByOffset(uint offset) var lastDepth = 0; TraverseChildTree(ref offset, (type, name, index, depth) => { + name = name.TrimStart('$'); if(depth >= indexes.Count) { indexes.Add((name, index, type.Elements)); diff --git a/src/DXDecompiler/DX9Shader/Decompiler/ConstantTypeWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/ConstantTypeWriter.cs index cef3928..a1d05a2 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/ConstantTypeWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/ConstantTypeWriter.cs @@ -40,7 +40,7 @@ public static DecompiledConstantDeclaration Decompile(ConstantDeclaration declar var decompiled = new DecompiledConstantDeclaration { Name = declaration.Name, - Code = Decompile(declaration.Type, declaration.Name, true), + Code = Decompile(declaration.Type, declaration.Name.TrimStart('$'), true), DefaultValue = defaultValue, }; if(shader.Type != ShaderType.Expression) From 4b738ff7b519a4c2124eb0ad38f2e2ab32b2b1c5 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 00:48:52 +0200 Subject: [PATCH 28/72] Handle ambiguities between vs's addr and ps's texcoord in DX9 --- src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs | 6 +++--- src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 793596e..160f0b1 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -395,9 +395,9 @@ void WriteTemps() { if(operand is DestinationOperand dest) { - if(dest.RegisterType is RegisterType.Temp or RegisterType.Addr) + if(dest.RegisterType == RegisterType.Temp + || (_shader.Type == ShaderType.Vertex && dest.RegisterType == RegisterType.Addr)) { - var registerKey = new RegisterKey(dest.RegisterType, dest.RegisterNumber); if(!tempRegisters.ContainsKey(registerKey)) { @@ -519,7 +519,7 @@ private void WriteStructureDeclaration(IEnumerable register var declarations = registers .Select(x => { - var registerName = Operand.GetParamRegisterName(x.RegisterKey.Type, (uint)x.RegisterKey.Number); + var registerName = Operand.GetParamRegisterName(x.RegisterKey.Type, x.RegisterKey.Number); var comment = $"// {x.RegisterKey} {registerName}"; var code = $"{x.TypeName} {x.Name} : {x.Semantic};"; return (Comment: comment, Code: code); diff --git a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs index d8c3c1f..38bafc8 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs @@ -11,6 +11,7 @@ public sealed class RegisterState public readonly bool ColumnMajorOrder = true; private readonly CultureInfo _culture = CultureInfo.InvariantCulture; + private readonly ShaderType _shaderType; private ICollection _constantDefinitions = new List(); private ICollection _constantIntDefinitions = new List(); @@ -24,6 +25,7 @@ public sealed class RegisterState public RegisterState(ShaderModel shader) { + _shaderType = shader.Type; Load(shader); } @@ -270,6 +272,7 @@ public string GetRegisterName(RegisterKey registerKey) switch(registerKey.Type) { case RegisterType.Input: + case RegisterType.Addr when _shaderType is ShaderType.Pixel: return (MethodInputRegisters.Count == 1) ? decl.Name : ("i." + decl.Name); case RegisterType.RastOut: case RegisterType.Output: @@ -299,7 +302,7 @@ public string GetRegisterName(RegisterKey registerKey) default: throw new NotImplementedException(); } - case RegisterType.Addr: + case RegisterType.Addr when _shaderType is not ShaderType.Pixel: case RegisterType.Temp: return registerKey.ToString(); default: From 336e5144afbe00eef375acf604e5b90cc841b3e5 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 00:49:32 +0200 Subject: [PATCH 29/72] fix conflicting names between input / output register names in DX9 --- src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs index 38bafc8..d867366 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs @@ -278,7 +278,7 @@ public string GetRegisterName(RegisterKey registerKey) case RegisterType.Output: case RegisterType.AttrOut: case RegisterType.ColorOut: - return (MethodOutputRegisters.Count == 1) ? decl.Name : ("o." + decl.Name); + return (MethodOutputRegisters.Count == 1) ? "out_" + decl.Name : ("o." + decl.Name); case RegisterType.Const: throw new NotSupportedException($"Use {nameof(GetSourceName)} instead"); case RegisterType.Sampler: From 3e5b87ac40c8d9cec287049bb540e178e13c1420 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 00:50:06 +0200 Subject: [PATCH 30/72] Added the missing entry point name in DX9 tests --- src/DXDecompiler.Tests/TestFixtures/DX9Tests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/DXDecompiler.Tests/TestFixtures/DX9Tests.cs b/src/DXDecompiler.Tests/TestFixtures/DX9Tests.cs index ddf6b5f..e41512a 100644 --- a/src/DXDecompiler.Tests/TestFixtures/DX9Tests.cs +++ b/src/DXDecompiler.Tests/TestFixtures/DX9Tests.cs @@ -181,7 +181,7 @@ public void RecompileShaders(string relPath) // Act. var binaryFileBytes = File.ReadAllBytes(file + ".o"); var shaderModel = ShaderReader.ReadShader(binaryFileBytes); - var decompiledHLSL = HlslWriter.Decompile(shaderModel); + var decompiledHLSL = HlslWriter.Decompile(shaderModel, "main"); File.WriteAllText($"{OutputDir}/{relPath}.d.hlsl", decompiledHLSL); using(var shaderBytecode = ShaderBytecode.FromStream(new MemoryStream(binaryFileBytes))) @@ -189,6 +189,7 @@ public void RecompileShaders(string relPath) var profile = shaderModel.Type == DX9Shader.ShaderType.Pixel ? $"ps_{shaderModel.MajorVersion}_{shaderModel.MinorVersion}" : $"vs_{shaderModel.MajorVersion}_{shaderModel.MinorVersion}"; + var compiledShader = ShaderBytecode.Compile(decompiledHLSL, "main", profile); var disassembly = shaderBytecode.Disassemble(); var redisassembly = compiledShader.Bytecode.Disassemble(); From 79232e0a6396dee0eeb926a2e01fc53a7b12cc6b Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 01:45:15 +0200 Subject: [PATCH 31/72] better handling of relative addressing in dx9 --- .../DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs b/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs index ece1c2c..bcbd9c0 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs @@ -104,8 +104,11 @@ public string GetConstantNameByRegisterNumber(uint registerNumber, string relati if(relativeAddressing != null) { // a nasty way to check array subscripts - // TODO: we need something more serious... - if(decl.Elements <= 1 || !name.EndsWith("]") || name.Count(x => x == ']') != 1) + // TODO: we need something less ugly... + var firstDotIndex = name.IndexOf('.'); + firstDotIndex = firstDotIndex == -1 ? name.Length : name.Length; + var isChildArray = name.LastIndexOf(']') > firstDotIndex; + if(decl.Elements <= 1 || isChildArray || name.Count(x => x == ']') != 1) { // we cannot handle relative addressing if this contant declaration is not an array // we also cannot handle relative addressing if this constant declaration contains nested arrays @@ -118,8 +121,7 @@ public string GetConstantNameByRegisterNumber(uint registerNumber, string relati { relativeAddressing = $"({relativeAddressing} / {declElementSize})"; } - name = name.Substring(0, name.Length - 1) // name without the last ']' - + $" + {relativeAddressing}]"; + name = name.Replace("]", $" + {relativeAddressing}]"); } } From 88ca7fdafc3ecd3ceb4f5a689df671fae780059f Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 01:48:10 +0200 Subject: [PATCH 32/72] shader profile / version mapping --- .../DX9Shader/Bytecode/ShaderModel.cs | 16 ++++++++++++++++ .../DX9Shader/Decompiler/ConstantTypeWriter.cs | 3 +-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs b/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs index 9c86964..4814275 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs @@ -51,6 +51,22 @@ static ShaderModel() public PrsiToken Prsi { get; set; } public IEnumerable Instructions => Tokens.OfType(); + public string Profile + { + get + { + var version = $"{MajorVersion}_{MinorVersion}"; + version = version switch + { + "2_1" => "2_a", + "2_255" => "2_sw", + "3_255" => "3_sw", + _ => version + }; + return $"{Type.GetDescription()}_{version}"; + } + } + public ShaderModel(int majorVersion, int minorVersion, ShaderType type) { MajorVersion = majorVersion; diff --git a/src/DXDecompiler/DX9Shader/Decompiler/ConstantTypeWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/ConstantTypeWriter.cs index a1d05a2..e19bbd7 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/ConstantTypeWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/ConstantTypeWriter.cs @@ -45,9 +45,8 @@ public static DecompiledConstantDeclaration Decompile(ConstantDeclaration declar }; if(shader.Type != ShaderType.Expression) { - var shaderProfile = $"{shader.Type.GetDescription()}_{shader.MajorVersion}_{shader.MinorVersion}"; var register = declaration.RegisterSet.GetDescription() + declaration.RegisterIndex; - decompiled.RegisterAssignments[shaderProfile] = register; + decompiled.RegisterAssignments[shader.Profile] = register; } return decompiled; } From dec7863bf4dc2dd59d887035f46e07b1381ef59a Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 01:48:32 +0200 Subject: [PATCH 33/72] fixed rsq instruction in DX9's Fxlc --- src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs index 59d619b..65f6e2e 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs @@ -80,7 +80,7 @@ void Write(FxlcToken token, HashSet ctabOverride = null) WriteFunction("log", token, ctabOverride); break; case FxlcOpcode.Rsq: - WriteFunction("rsq", token, ctabOverride); + WriteFunction("1.0f / sqrt", token, ctabOverride); break; case FxlcOpcode.Sin: WriteFunction("sin", token, ctabOverride); From 592866bfb822961bd3c7dda2abe2e875edd70c59 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 01:49:03 +0200 Subject: [PATCH 34/72] Let expression return scalar it seems that it's always a scalar shader? --- src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs index a813177..d071b88 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs @@ -18,7 +18,7 @@ public static string Decompile(ShaderModel shader, string expressionName = "Expr } protected override void Write() { - WriteLine($"float4 {ExpressionName}()"); + WriteLine($"float {ExpressionName}()"); WriteLine("{"); Indent++; From 94fa2d14332aa75838b1542a3e63e42f405c8e70 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 01:49:32 +0200 Subject: [PATCH 35/72] Fix DX9 tests' issues with shader profile / effects --- .../TestFixtures/DX9Tests.cs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/DXDecompiler.Tests/TestFixtures/DX9Tests.cs b/src/DXDecompiler.Tests/TestFixtures/DX9Tests.cs index e41512a..c2049be 100644 --- a/src/DXDecompiler.Tests/TestFixtures/DX9Tests.cs +++ b/src/DXDecompiler.Tests/TestFixtures/DX9Tests.cs @@ -3,7 +3,7 @@ using DXDecompiler.DX9Shader; using DXDecompiler.Tests.Util; using NUnit.Framework; -using SharpDX.D3DCompiler; +using SharpDX.Direct3D9; using System; using System.Collections.Generic; using System.IO; @@ -186,13 +186,20 @@ public void RecompileShaders(string relPath) using(var shaderBytecode = ShaderBytecode.FromStream(new MemoryStream(binaryFileBytes))) { - var profile = shaderModel.Type == DX9Shader.ShaderType.Pixel ? - $"ps_{shaderModel.MajorVersion}_{shaderModel.MinorVersion}" : - $"vs_{shaderModel.MajorVersion}_{shaderModel.MinorVersion}"; - - var compiledShader = ShaderBytecode.Compile(decompiledHLSL, "main", profile); - var disassembly = shaderBytecode.Disassemble(); + string profile; + switch(shaderModel.Type) + { + case ShaderType.Effect: + profile = "fx_2_0"; + break; + default: + profile = shaderModel.Profile; + break; + } + + var compiledShader = ShaderBytecode.Compile(decompiledHLSL, "main", profile, default); var redisassembly = compiledShader.Bytecode.Disassemble(); + var disassembly = shaderBytecode.Disassemble(); File.WriteAllText($"{OutputDir}/{relPath}.d1.asm", disassembly); File.WriteAllText($"{OutputDir}/{relPath}.d2.asm", redisassembly); From 2932e74fb1081c2974771b66426d3434f910db22 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 14:47:18 +0200 Subject: [PATCH 36/72] Guess tex operation name by looking at sampler type --- .../DX9Shader/Decompiler/HlslWriter.cs | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 160f0b1..7ccb156 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -1,3 +1,4 @@ +using DXDecompiler.DX9Shader.Bytecode.Ctab; using DXDecompiler.DX9Shader.Decompiler; using DXDecompiler.Util; using System; @@ -13,6 +14,7 @@ private class SourceOperand public string Body { get; set; } public string Swizzle { get; set; } public string Modifier { get; set; } + public ParameterType? SamplerType { get; set; } public override string ToString() { @@ -110,12 +112,24 @@ private SourceOperand GetSourceName(InstructionToken instruction, int srcIndex, dataIndex = srcIndex; } + ParameterType? samplerType = null; + var registerNumber = instruction.GetParamRegisterNumber(dataIndex); + var registerType = instruction.GetParamRegisterType(dataIndex); + if(registerType == RegisterType.Sampler) + { + var decl = _registers.FindConstant(RegisterSet.Sampler, registerNumber); + var type = decl.GetRegisterTypeByOffset(registerNumber - decl.RegisterIndex); + samplerType = type.Type.ParameterType; + } + + var body = _registers.GetSourceName(instruction, dataIndex, out var swizzle, out var modifier); return new SourceOperand { Body = body, Swizzle = swizzle, - Modifier = modifier + Modifier = modifier, + SamplerType = samplerType }; } @@ -201,6 +215,24 @@ void WriteAssignment(string sourceFormat, params SourceOperand[] args) WriteLine("{0} = {1};", destination, sourceResult); } + void WriteTextureAssignment(string postFix, SourceOperand sampler, params SourceOperand[] others) + { + var operation = sampler.SamplerType switch + { + ParameterType.Sampler1D => "tex1D", + ParameterType.Sampler2D => "tex2D", + ParameterType.Sampler3D => "tex3D", + ParameterType.SamplerCube => "texCUBE", + ParameterType.Sampler => "texUnknown", + _ => throw new InvalidOperationException(sampler.SamplerType.ToString()) + }; + var args = new SourceOperand[others.Length + 1]; + args[0] = sampler; + others.CopyTo(args, 1); + var format = string.Join(", ", args.Select((_, i) => $"{{{i}}}")); + WriteAssignment($"{operation}{postFix}({format})", args); + } + switch(instruction.Opcode) { case Opcode.Abs: @@ -350,7 +382,7 @@ void WriteAssignment(string sourceFormat, params SourceOperand[] args) case Opcode.Tex: if((_shader.MajorVersion == 1 && _shader.MinorVersion >= 4) || (_shader.MajorVersion > 1)) { - WriteAssignment("tex2D({1}, {0})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); + WriteTextureAssignment(string.Empty, GetSourceName(instruction, 2), GetSourceName(instruction, 1)); } else { @@ -358,7 +390,7 @@ void WriteAssignment(string sourceFormat, params SourceOperand[] args) } break; case Opcode.TexLDL: - WriteAssignment("tex2Dlod({1}, {0})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); + WriteTextureAssignment("lod", GetSourceName(instruction, 2), GetSourceName(instruction, 1)); break; case Opcode.Comment: { From 25ed8eb643e6907719146f78d33c80620c5ed53d Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 20:09:20 +0200 Subject: [PATCH 37/72] Better support of input declaration (with write masks) in DX9 shaders --- .../DX9Shader/Decompiler/EffectHLSLWriter.cs | 2 +- .../DX9Shader/Decompiler/HlslWriter.cs | 68 ++++++++++++++----- .../Decompiler/RegisterDeclaration.cs | 11 +-- .../DX9Shader/Decompiler/RegisterState.cs | 12 ++-- 4 files changed, 64 insertions(+), 29 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs index 9d58b93..8041c2b 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs @@ -13,7 +13,7 @@ public class EffectHLSLWriter : DecompileWriter private readonly EffectContainer _effectChunk; public Dictionary CommonConstantDeclarations { get; } = new(); - public List<(string Name, string[] Declarations)> InputOutputStructures { get; } = new(); + public List<(string Name, (int MaskedLength, string Semantic)[] Declarations)> InputOutputStructures { get; } = new(); public EffectHLSLWriter(EffectContainer effectChunk) { diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 7ccb156..9bec3ea 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -546,38 +546,72 @@ protected override void Write() WriteLine("}"); } - private void WriteStructureDeclaration(IEnumerable registers, string postFix, out string typeName) + private void WriteInputDeclarationsStructure(IEnumerable declarations, out string typeName) { - var declarations = registers - .Select(x => - { - var registerName = Operand.GetParamRegisterName(x.RegisterKey.Type, x.RegisterKey.Number); - var comment = $"// {x.RegisterKey} {registerName}"; - var code = $"{x.TypeName} {x.Name} : {x.Semantic};"; - return (Comment: comment, Code: code); - }); - var codes = declarations.Select(t => t.Code).ToArray(); - typeName = $"{_entryPoint}_{postFix}"; + typeName = $"{_entryPoint}_Input"; if(_effectWriter is not null) { - // check if a compatible type has alreaady been defined inside this effect + var current = declarations + .Select(r => (r.MaskedLength, r.Semantic)) + .ToArray(); + // check if a compatible type has already been defined inside this effect foreach(var (name, existing) in _effectWriter.InputOutputStructures) { - if(codes.All(x => existing.Contains(x))) + foreach(var (maskedLength, semantic) in current) { + if(!existing.Any(e => e.MaskedLength >= maskedLength && e.Semantic == semantic)) + { + goto checkNext; + } + // we don't need to write declaration again in this case typeName = name; + return; + } + checkNext: + continue; + } + _effectWriter.InputOutputStructures.Add((typeName, current)); + } + + WriteDeclarationsAsStruct(typeName, declarations); + } + + private void WriteOutputDeclarationsStructure(IEnumerable declarations, out string typeName) + { + typeName = $"{_entryPoint}_Output"; + if(_effectWriter is not null) + { + var current = declarations + .Select(r => (r.MaskedLength, r.Semantic)) + .ToArray(); + // check if a matching type has already been defined inside this effect + foreach(var (name, existing) in _effectWriter.InputOutputStructures) + { + var equals = !existing.Except(current).Any(); + if(equals) + { // we don't need to write declaration again in this case + typeName = name; return; } } - _effectWriter.InputOutputStructures.Add((typeName, codes)); + _effectWriter.InputOutputStructures.Add((typeName, current)); } + WriteDeclarationsAsStruct(typeName, declarations); + } + + private void WriteDeclarationsAsStruct(string typeName, IEnumerable declarations) + { WriteLine($"struct {typeName}"); WriteLine("{"); Indent++; - foreach(var (comment, code) in declarations) + foreach(var register in declarations) { + var registerName = Operand.GetParamRegisterName(register.RegisterKey.Type, register.RegisterKey.Number); + var comment = $"// {register.RegisterKey} {registerName}"; + var code = $"{register.TypeName} {register.Name} : {register.Semantic};"; + WriteIndent(); WriteLine(comment); WriteIndent(); @@ -601,7 +635,7 @@ private void ProcessMethodInputType(out string methodParameters) methodParameters = $"{input.TypeName} {input.Name} : {input.Semantic}"; break; default: - WriteStructureDeclaration(registers, "Input", out var inputTypeName); + WriteInputDeclarationsStructure(registers, out var inputTypeName); methodParameters = $"{inputTypeName} i"; break; } @@ -620,7 +654,7 @@ private void ProcessMethodOutputType(out string methodReturnType, out string met methodSemantic = $" : {semantic}"; break; default: - WriteStructureDeclaration(registers, "Output", out methodReturnType); + WriteOutputDeclarationsStructure(registers, out methodReturnType); methodSemantic = string.Empty; break; }; diff --git a/src/DXDecompiler/DX9Shader/Decompiler/RegisterDeclaration.cs b/src/DXDecompiler/DX9Shader/Decompiler/RegisterDeclaration.cs index 93880b4..4bd8959 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/RegisterDeclaration.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/RegisterDeclaration.cs @@ -5,17 +5,16 @@ namespace DXDecompiler.DX9Shader // https://msdn.microsoft.com/en-us/library/windows/hardware/ff549176(v=vs.85).aspx public class RegisterDeclaration { - private readonly int _maskedLength; private readonly string _semantic; public RegisterDeclaration(InstructionToken declInstruction) { RegisterKey = declInstruction.GetParamRegisterKey(1); _semantic = declInstruction.GetDeclSemantic(); - _maskedLength = declInstruction.GetDestinationMaskedLength(); + MaskedLength = declInstruction.GetDestinationMaskedLength(); } - public RegisterDeclaration(RegisterKey registerKey) + public RegisterDeclaration(RegisterKey registerKey, int maskedLength = 4) { RegisterType type = registerKey.Type; _semantic = GuessSemanticByRegisterType(type); @@ -24,18 +23,20 @@ public RegisterDeclaration(RegisterKey registerKey) { _semantic += registerKey.Number; } - _maskedLength = 4; + MaskedLength = maskedLength; } public RegisterKey RegisterKey { get; } + public int MaskedLength { get; } public string Semantic => _semantic ?? throw new NotSupportedException(); public string Name => _semantic?.ToLower() ?? RegisterKey.ToString(); + public string TypeName { get { - switch(_maskedLength) + switch(MaskedLength) { case 1: return "float"; diff --git a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs index d867366..e791ddf 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs @@ -120,11 +120,13 @@ private void Load(ShaderModel shader) int destIndex = instruction.GetDestinationParamIndex(); RegisterType registerType = instruction.GetParamRegisterType(destIndex); var registerNumber = instruction.GetParamRegisterNumber(destIndex); + var maskedLength = instruction.GetDestinationMaskedLength(); var registerKey = new RegisterKey(registerType, registerNumber); - if(RegisterDeclarations.ContainsKey(registerKey) == false) + if(!RegisterDeclarations.TryGetValue(registerKey, out var declaration) + || declaration.MaskedLength < maskedLength) { - var reg = new RegisterDeclaration(registerKey); - RegisterDeclarations[registerKey] = reg; + declaration = new RegisterDeclaration(registerKey, maskedLength); + RegisterDeclarations[registerKey] = declaration; switch(registerType) { case RegisterType.AttrOut: @@ -132,12 +134,10 @@ private void Load(ShaderModel shader) case RegisterType.DepthOut: case RegisterType.Output: case RegisterType.RastOut: - MethodOutputRegisters[registerKey] = reg; + MethodOutputRegisters[registerKey] = declaration; break; - } } - } } } From 305f3b1d0beb8e9554570447d61657a2efa34d5d Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 20:09:44 +0200 Subject: [PATCH 38/72] Automatically truncate uv swizzles in texture operations --- .../DX9Shader/Decompiler/HlslWriter.cs | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 9bec3ea..ed49ce2 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -215,20 +215,26 @@ void WriteAssignment(string sourceFormat, params SourceOperand[] args) WriteLine("{0} = {1};", destination, sourceResult); } - void WriteTextureAssignment(string postFix, SourceOperand sampler, params SourceOperand[] others) + void WriteTextureAssignment(string postFix, SourceOperand sampler, SourceOperand uv, int extraUvDimensions, params SourceOperand[] others) { - var operation = sampler.SamplerType switch + var (operation, dimension) = sampler.SamplerType switch { - ParameterType.Sampler1D => "tex1D", - ParameterType.Sampler2D => "tex2D", - ParameterType.Sampler3D => "tex3D", - ParameterType.SamplerCube => "texCUBE", - ParameterType.Sampler => "texUnknown", + ParameterType.Sampler1D => ("tex1D", 1), + ParameterType.Sampler2D => ("tex2D", 2), + ParameterType.Sampler3D => ("tex3D", 3), + ParameterType.SamplerCube => ("texCUBE", 3), + ParameterType.Sampler => ("texUnknown", 4), _ => throw new InvalidOperationException(sampler.SamplerType.ToString()) }; - var args = new SourceOperand[others.Length + 1]; + var args = new SourceOperand[others.Length + 2]; + var uvSwizzle = uv.Swizzle.TrimStart('.'); + if(uvSwizzle.Length > dimension + extraUvDimensions) + { + uv.Swizzle = "." + uvSwizzle.Substring(0, dimension + extraUvDimensions); + } args[0] = sampler; - others.CopyTo(args, 1); + args[1] = uv; + others.CopyTo(args, 2); var format = string.Join(", ", args.Select((_, i) => $"{{{i}}}")); WriteAssignment($"{operation}{postFix}({format})", args); } @@ -382,7 +388,7 @@ void WriteTextureAssignment(string postFix, SourceOperand sampler, params Source case Opcode.Tex: if((_shader.MajorVersion == 1 && _shader.MinorVersion >= 4) || (_shader.MajorVersion > 1)) { - WriteTextureAssignment(string.Empty, GetSourceName(instruction, 2), GetSourceName(instruction, 1)); + WriteTextureAssignment(string.Empty, GetSourceName(instruction, 2), GetSourceName(instruction, 1), 0); } else { @@ -390,7 +396,7 @@ void WriteTextureAssignment(string postFix, SourceOperand sampler, params Source } break; case Opcode.TexLDL: - WriteTextureAssignment("lod", GetSourceName(instruction, 2), GetSourceName(instruction, 1)); + WriteTextureAssignment("lod", GetSourceName(instruction, 2), GetSourceName(instruction, 1), 1); break; case Opcode.Comment: { From 9c13fec7ee79ec5f7eed10dd41ece27fc0fde571 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 20:34:15 +0200 Subject: [PATCH 39/72] Fix bug in dx9 shader input structure checking --- src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index ed49ce2..b3b557d 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -569,10 +569,10 @@ private void WriteInputDeclarationsStructure(IEnumerable de { goto checkNext; } - // we don't need to write declaration again in this case - typeName = name; - return; } + // we don't need to write declaration again in this case + typeName = name; + return; checkNext: continue; } From 8025c6352a3f573697422e10471c513129aa9a50 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 20:34:42 +0200 Subject: [PATCH 40/72] Let HLSL writer write disassambly in comments --- src/DXDecompiler/DX9Shader/Asm/AsmWriter.cs | 18 ++++++++++++++++++ src/DXDecompiler/DX9Shader/DecompileWriter.cs | 2 +- .../DX9Shader/Decompiler/HlslWriter.cs | 7 ++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Asm/AsmWriter.cs b/src/DXDecompiler/DX9Shader/Asm/AsmWriter.cs index db94d41..dc26ae2 100644 --- a/src/DXDecompiler/DX9Shader/Asm/AsmWriter.cs +++ b/src/DXDecompiler/DX9Shader/Asm/AsmWriter.cs @@ -2,6 +2,7 @@ using DXDecompiler.DX9Shader.Bytecode.Ctab; using DXDecompiler.DX9Shader.Decompiler; using System; +using System.IO; using System.Linq; namespace DXDecompiler.DX9Shader @@ -30,6 +31,23 @@ public static string Disassemble(ShaderModel shaderModel) var asmWriter = new AsmWriter(shaderModel); return asmWriter.Decompile(); } + + public string Disassemble(InstructionToken instruction) + { + using var writer = new StringWriter(); + var previous = Writer; + try + { + Writer = writer; + WriteInstruction(instruction); + return writer.ToString(); + } + finally + { + Writer = previous; + } + } + static string ApplyModifier(SourceModifier modifier, string value) { switch(modifier) diff --git a/src/DXDecompiler/DX9Shader/DecompileWriter.cs b/src/DXDecompiler/DX9Shader/DecompileWriter.cs index adaa256..7788b90 100644 --- a/src/DXDecompiler/DX9Shader/DecompileWriter.cs +++ b/src/DXDecompiler/DX9Shader/DecompileWriter.cs @@ -6,7 +6,7 @@ namespace DXDecompiler.DX9Shader public class DecompileWriter { public int Indent; - StreamWriter Writer; + protected TextWriter Writer; protected void WriteIndent() { Writer.Write(new string(' ', Indent * 4)); diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index b3b557d..39f77a2 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -28,6 +28,7 @@ public override string ToString() } private readonly ShaderModel _shader; + private readonly AsmWriter _disassember; private readonly bool _doAstAnalysis; private int _iterationDepth = 0; @@ -39,6 +40,10 @@ public HlslWriter(ShaderModel shader, bool doAstAnalysis = false, string entryPo { _shader = shader; _doAstAnalysis = doAstAnalysis; + if(!_doAstAnalysis) + { + _disassember = new(shader); + } if(string.IsNullOrEmpty(entryPoint)) { _entryPoint = $"{_shader.Type}Main"; @@ -136,7 +141,7 @@ private SourceOperand GetSourceName(InstructionToken instruction, int srcIndex, private void WriteInstruction(InstructionToken instruction) { WriteIndent(); - WriteLine($"// {instruction}"); + WriteLine($"// {_disassember?.Disassemble(instruction).Trim()}"); switch(instruction.Opcode) { case Opcode.Def: From 2f7313c5aab0703fe8decd5ecee9a5a61b87ccb1 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 21:50:56 +0200 Subject: [PATCH 41/72] fix operand formatting in DX9 --- src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 39f77a2..3f5d603 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -19,7 +19,7 @@ private class SourceOperand public override string ToString() { var body = string.Format(Modifier, Body); - if(char.IsDigit(body.Last())) + if(body.All(char.IsDigit)) { body = $"({body})"; } @@ -233,6 +233,10 @@ void WriteTextureAssignment(string postFix, SourceOperand sampler, SourceOperand }; var args = new SourceOperand[others.Length + 2]; var uvSwizzle = uv.Swizzle.TrimStart('.'); + if(uvSwizzle.Length == 0) + { + uvSwizzle = "xyzw"; + } if(uvSwizzle.Length > dimension + extraUvDimensions) { uv.Swizzle = "." + uvSwizzle.Substring(0, dimension + extraUvDimensions); From da0bca35c935b9b5d2fc706881174080197bf77b Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 21:51:17 +0200 Subject: [PATCH 42/72] Fix output declaration handling in dx9 --- src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 3f5d603..d2217c6 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -602,7 +602,7 @@ private void WriteOutputDeclarationsStructure(IEnumerable d // check if a matching type has already been defined inside this effect foreach(var (name, existing) in _effectWriter.InputOutputStructures) { - var equals = !existing.Except(current).Any(); + var equals = existing.Length == current.Length && !existing.Except(current).Any(); if(equals) { // we don't need to write declaration again in this case From 5c8d472c615058fc702360873a8a0c9d7a25ef96 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 21:51:41 +0200 Subject: [PATCH 43/72] Correct handling of nrm instruction --- .../DX9Shader/Decompiler/HlslWriter.cs | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index d2217c6..1f6a76d 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -355,7 +355,34 @@ void WriteTextureAssignment(string postFix, SourceOperand sampler, SourceOperand WriteAssignment("{0} * {1}", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Nrm: - WriteAssignment("normalize({0})", GetSourceName(instruction, 1)); + // the nrm opcode actually only works on the 3D vector + var operand = GetSourceName(instruction, 1); + if(instruction.GetDestinationMaskedLength() < 4) + { + var swizzle = operand.Swizzle.TrimStart('.'); + switch(swizzle.Length) + { + case 0: + case 4: + WriteAssignment("normalize({0}.xyz)", operand); + break; + case 1: + // let it reach 3 dimensions + operand.Swizzle += swizzle; + operand.Swizzle += swizzle; + goto case 3; + case 3: + WriteAssignment("normalize({0})", operand); + break; + default: + WriteAssignment("({0} / length(float3({0}))", operand); + break; + } + } + else + { + WriteAssignment("({0} / length(float3({0}))", operand); + } break; case Opcode.Pow: WriteAssignment("pow({0}, {1})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); From 18d27cdcf88bf2eddde601d1176cd3ee472fa6ef Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 22:03:51 +0200 Subject: [PATCH 44/72] Prefer blobs instead of literal values in dx9 effect --- src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs index 8041c2b..7c15c45 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs @@ -430,13 +430,13 @@ public void WriteAssignment(Assignment assignment) index = string.Format("[{0}]", assignment.ArrayIndex); } string value; - if(assignment.Value.Count > 1) + if(_effectChunk.StateBlobLookup.ContainsKey(assignment)) { - value = string.Format("{{ {0} }}", string.Join(", ", assignment.Value)); + value = StateBlobToString(assignment); } - else if(_effectChunk.StateBlobLookup.ContainsKey(assignment)) + else if(assignment.Value.Count > 1) { - value = StateBlobToString(assignment); + value = string.Format("{{ {0} }}", string.Join(", ", assignment.Value)); } else { From 1578bb816e4b9088b581855d00240b5563536896 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 22:44:12 +0200 Subject: [PATCH 45/72] Handle multiple output registers in expression shaders --- .../DX9Shader/Decompiler/ExpressionWriter.cs | 52 +++++++++++++++++-- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs index d071b88..a254446 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/ExpressionWriter.cs @@ -1,5 +1,5 @@ using DXDecompiler.DX9Shader.Bytecode.Fxlvm; -using System.Collections.Generic; +using System; using System.Linq; namespace DXDecompiler.DX9Shader.Decompiler @@ -7,6 +7,7 @@ namespace DXDecompiler.DX9Shader.Decompiler class ExpressionHLSLWriter : FxlcHlslWriter { string ExpressionName { get; } + public ExpressionHLSLWriter(ShaderModel shader, string expressionName) : base(shader) { ExpressionName = expressionName; @@ -18,22 +19,63 @@ public static string Decompile(ShaderModel shader, string expressionName = "Expr } protected override void Write() { - WriteLine($"float {ExpressionName}()"); + var (rows, columns) = GetOutputDimensions(); + string returnType; + if(rows == 1) + { + returnType = columns == 1 ? "float" : $"float{columns}"; + } + else + { + returnType = $"float{rows}x{columns}"; + } + + WriteLine($"{returnType} {ExpressionName}()"); WriteLine("{"); Indent++; WriteTemporaries(); - + var destinationVariables = string.Join(", ", Enumerable.Range(0, rows).Select(i => $"expr{i}")); WriteIndent(); - WriteLine("float expr0;"); + WriteLine($"float{columns} {destinationVariables};"); WriteInstructions(); WriteIndent(); - WriteLine("return expr0;"); + if(rows == 1) + { + WriteLine("return expr0;"); + } + else + { + + WriteLine($"return {returnType}({destinationVariables});"); + } Indent--; WriteLine("}"); } + private (int Rows, int Columns) GetOutputDimensions() + { + int registerNumber = 0; + int components = 0; + foreach(var token in Shader.Fxlc.Tokens) + { + var destination = token.Operands[0]; + if(destination.IsArray != 0 && destination.ArrayType == FxlcOperandType.Expr) + { + throw new NotImplementedException(); + } + if(destination.OpType != FxlcOperandType.Expr) + { + continue; + } + registerNumber = Math.Max(registerNumber, (int)destination.OpIndex / 4); + var extraComponents = Math.Max(0, (int)destination.ComponentCount - 1); + var lastIndex = Math.Min(4, (int)destination.OpIndex % 4 + extraComponents); + components = Math.Max(components, lastIndex); + } + return (registerNumber + 1, components + 1); + } } } From d02e13f7dc82df98bccfacae563400797c973f36 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 22:46:11 +0200 Subject: [PATCH 46/72] fix compile target in dx9 effect --- src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs index 7c15c45..98dc71a 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs @@ -93,7 +93,7 @@ void FindCommonConstantDeclarations() if(existing.RegisterAssignments.TryGetValue(registerAssignment.Key, out var existingRegister)) { // if register number of the same constant is different - // betwwen two shaders of same shader profile + // between two shaders of same shader profile if(registerAssignment.Value != existingRegister) { // then probably there weren't a register assignment at all. @@ -168,7 +168,7 @@ public string StateBlobToString(Assignment key) else { var funcName = _shaderNames[data]; - return $"compile {data.Shader.Type.GetDescription()}_{data.Shader.MajorVersion}_{data.Shader.MinorVersion} {funcName}()"; + return $"compile {data.Shader.Profile} {funcName}()"; } } if(data.BlobType == StateBlobType.Variable) From d3feebde0e09899e92a4844b468622d2a0d0e671 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 22:59:12 +0200 Subject: [PATCH 47/72] fix temp variables in fxlc decompilation --- src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs index 65f6e2e..eaa0efb 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/FxlcHlslWriter.cs @@ -35,7 +35,7 @@ protected void WriteTemporaries() } if(operands.OpType == FxlcOperandType.Temp) { - temporaryRegisters.Add(operands.OpIndex); + temporaryRegisters.Add(operands.OpIndex / 4); } } foreach(var tempIndex in temporaryRegisters) From 14390bdcbc89a855818991d2995ce9c0baaf3060 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 23 May 2021 23:05:51 +0200 Subject: [PATCH 48/72] make sure index register only uses one component --- src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs index e791ddf..78c220b 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs @@ -216,6 +216,9 @@ public string GetSourceName(InstructionToken instruction, int srcIndex, out stri if(instruction.IsRelativeAddressMode(srcIndex)) { indexer = GetSourceName(instruction, srcIndex + 1, out var indexSwizzle, out var indexModifier); + // make sure index swizzle only has one component + indexSwizzle = indexSwizzle.TrimStart('.'); + indexSwizzle = "." + indexSwizzle[0]; indexer = string.Format(indexModifier, indexer + indexSwizzle); } sourceRegisterName = decl.GetConstantNameByRegisterNumber(registerNumber, indexer); From f16ce07f50623a67206ad739e43e97692fec934f Mon Sep 17 00:00:00 2001 From: lanyi Date: Mon, 24 May 2021 00:26:43 +0200 Subject: [PATCH 49/72] Read correct number of dx9 constant default values --- .../Bytecode/Ctab/ConstantDeclaration.cs | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs b/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs index bcbd9c0..81f497b 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs @@ -68,9 +68,47 @@ public static ConstantDeclaration Parse(BytecodeReader reader, BytecodeReader de { //Note: thre are corrisponding def instructions. TODO: check that they are the same var defaultValueReader = reader.CopyAtOffset((int)defaultValueOffset); - for(int i = 0; i < 4; i++) + if(result.Type.ParameterClass == ParameterClass.Object) { - result.DefaultValue.Add(defaultValueReader.ReadSingle()); + for(int i = 0; i < 4; i++) + { + result.DefaultValue.Add(defaultValueReader.ReadSingle()); + } + } + else + { + var forever = uint.MaxValue; + var elementCounter = 0; + result.TraverseChildTree(ref forever, (type, name, index, depth) => + { + float[] ReadFour() + { + var values = new float[4]; + for(int i = 0; i < values.Length; ++i) + { + values[i] = defaultValueReader.ReadSingle(); + } + return values; + } + + switch(type.ParameterClass) + { + case ParameterClass.Struct: + break; + case ParameterClass.MatrixColumns: + result.DefaultValue.AddRange(ReadFour().Take((int)type.Rows)); + break; + case ParameterClass.MatrixRows: + case ParameterClass.Vector: + result.DefaultValue.AddRange(ReadFour().Take((int)type.Columns)); + break; + case ParameterClass.Scalar: + result.DefaultValue.Add(ReadFour()[0]); + break; + default: + throw new NotSupportedException(type.ParameterClass.ToString()); + } + }); } } return result; @@ -248,8 +286,9 @@ public string GetMemberNameByOffset(uint offset) /// The type of current target. /// /// The offset of child element to be searched. - /// It will become 0 if a child element has been found exactly on the specified offset, - /// non-zero if we are accessing "in the middle of an element", + /// If it's , then it will traverse the entire tree.
+ /// Otherwise, it will become 0 if a child element has been found exactly on the specified offset, + /// or non-zero if we are accessing "in the middle of an element", /// something like "accessing the 2nd column of matrix". /// /// @@ -257,8 +296,9 @@ public string GetMemberNameByOffset(uint offset) /// private void TraverseChildTree(ref uint offset, ChildTreeVisitor visitor) { + var fullTraverse = offset == uint.MaxValue; var found = TraverseChildTree(Name, Type, ref offset, visitor, 0); - if(!found) + if(!found && !fullTraverse) { throw new InvalidOperationException(); } From 48fd35c816b134d3edba33adf416013b0bf0d8d8 Mon Sep 17 00:00:00 2001 From: lanyi Date: Mon, 24 May 2021 00:29:42 +0200 Subject: [PATCH 50/72] handle null blobs gracefully --- .../DX9Shader/Decompiler/EffectHLSLWriter.cs | 2 +- src/DXDecompilerCmd/Program.cs | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs index 98dc71a..ca6b426 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs @@ -156,7 +156,7 @@ public string StateBlobToString(Assignment key) var data = _effectChunk.StateBlobLookup[key]; if(data == null) { - return "Blob is NULL"; + return "NULL /* Blob is NULL!!! */"; } if(data.BlobType == StateBlobType.Shader) { diff --git a/src/DXDecompilerCmd/Program.cs b/src/DXDecompilerCmd/Program.cs index dc6c356..c149e5b 100644 --- a/src/DXDecompilerCmd/Program.cs +++ b/src/DXDecompilerCmd/Program.cs @@ -146,8 +146,16 @@ static void Main(string[] args) } else if(options.Mode == DecompileMode.Decompile) { - var hlsl = DXDecompiler.DX9Shader.HlslWriter.Decompile(data); - sw.Write(hlsl); + try + { + var hlsl = DXDecompiler.DX9Shader.HlslWriter.Decompile(data); + sw.Write(hlsl); + } + catch(Exception e) when(!System.Diagnostics.Debugger.IsAttached) + { + Console.Error.WriteLine(e); + Environment.Exit(1); + } } else if(options.Mode == DecompileMode.Debug) { From c57fe5f0c456d06a80b6259e988015abce2f319e Mon Sep 17 00:00:00 2001 From: lanyi Date: Mon, 24 May 2021 00:37:17 +0200 Subject: [PATCH 51/72] fix textureCUBE in Parameter.GetTypeName --- src/DXDecompiler/DX9Shader/Bytecode/EnumExtensions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/DXDecompiler/DX9Shader/Bytecode/EnumExtensions.cs b/src/DXDecompiler/DX9Shader/Bytecode/EnumExtensions.cs index 0da6542..cca90a1 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/EnumExtensions.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/EnumExtensions.cs @@ -23,6 +23,8 @@ public static string GetDescription(this ParameterType value) return "texture2D"; case ParameterType.Texture3D: return "texture3D"; + case ParameterType.TextureCube: + return "textureCUBE"; default: return value.ToString().ToLower(); } From 8e00485201f5a33331bdf353f26d69f1861cfb85 Mon Sep 17 00:00:00 2001 From: lanyi Date: Mon, 24 May 2021 00:48:19 +0200 Subject: [PATCH 52/72] Added a "constants' default values with padding" so fxl VM could work correctly --- .../DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs | 11 ++++++++--- src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/Fx9FxlVm.cs | 4 ++-- src/DXDecompiler/DX9Shader/FX9/Parameter.cs | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs b/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs index 81f497b..847e9e7 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/Ctab/ConstantDeclaration.cs @@ -19,6 +19,7 @@ public class ConstantDeclaration public ushort RegisterCount { get; private set; } public ConstantType Type { get; private set; } public List DefaultValue { get; private set; } + public List DefaultValueWithPadding { get; private set; } public uint Rows => Type.Rows; public uint Columns => Type.Columns; @@ -45,7 +46,8 @@ public ConstantDeclaration(string name, RegisterSet registerSet, short registerI } public ConstantDeclaration() { - DefaultValue = new List(); + DefaultValue = new(); + DefaultValueWithPadding = new(); } public static ConstantDeclaration Parse(BytecodeReader reader, BytecodeReader decReader) { @@ -72,7 +74,9 @@ public static ConstantDeclaration Parse(BytecodeReader reader, BytecodeReader de { for(int i = 0; i < 4; i++) { - result.DefaultValue.Add(defaultValueReader.ReadSingle()); + var value = defaultValueReader.ReadSingle(); + result.DefaultValueWithPadding.Add(value); + result.DefaultValue.Add(value); } } else @@ -84,10 +88,11 @@ public static ConstantDeclaration Parse(BytecodeReader reader, BytecodeReader de float[] ReadFour() { var values = new float[4]; - for(int i = 0; i < values.Length; ++i) + for(int i = 0; i < 4; ++i) { values[i] = defaultValueReader.ReadSingle(); } + result.DefaultValueWithPadding.AddRange(values); return values; } diff --git a/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/Fx9FxlVm.cs b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/Fx9FxlVm.cs index c39c9ae..e88d331 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/Fx9FxlVm.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/Fx9FxlVm.cs @@ -153,12 +153,12 @@ public override IEnumerable Retrieve() { continue; } - if(x.DefaultValue.Count != constantValueCount) + if(x.DefaultValueWithPadding.Count != constantValueCount) { throw new InvalidOperationException(); } offset = -constantDecarationStart; - current = x.DefaultValue; + current = x.DefaultValueWithPadding; } } diff --git a/src/DXDecompiler/DX9Shader/FX9/Parameter.cs b/src/DXDecompiler/DX9Shader/FX9/Parameter.cs index 34fcf56..351b100 100644 --- a/src/DXDecompiler/DX9Shader/FX9/Parameter.cs +++ b/src/DXDecompiler/DX9Shader/FX9/Parameter.cs @@ -139,7 +139,7 @@ public string GetTypeName(int indentLevel = 0) sb.Append(ParameterType.ToString()); break; default: - sb.Append(ParameterType.ToString().ToLower()); + sb.Append(ParameterType.GetDescription()); break; } break; From e8cf5e33eb9395b33c0e478ffba27274ded85205 Mon Sep 17 00:00:00 2001 From: lanyi Date: Mon, 24 May 2021 01:03:01 +0200 Subject: [PATCH 53/72] Removed comments in dx9 structs again as they could be misleading --- src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 1f6a76d..2c8389c 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -650,14 +650,8 @@ private void WriteDeclarationsAsStruct(string typeName, IEnumerable Date: Mon, 24 May 2021 16:53:59 +0200 Subject: [PATCH 54/72] Removed structure declaration unification as they could somtimes be misleading --- .../DX9Shader/Decompiler/EffectHLSLWriter.cs | 1 - .../DX9Shader/Decompiler/HlslWriter.cs | 61 ++----------------- 2 files changed, 4 insertions(+), 58 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs index ca6b426..45ad29f 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs @@ -13,7 +13,6 @@ public class EffectHLSLWriter : DecompileWriter private readonly EffectContainer _effectChunk; public Dictionary CommonConstantDeclarations { get; } = new(); - public List<(string Name, (int MaskedLength, string Semantic)[] Declarations)> InputOutputStructures { get; } = new(); public EffectHLSLWriter(EffectContainer effectChunk) { diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 2c8389c..8325ad1 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -588,61 +588,6 @@ protected override void Write() WriteLine("}"); } - private void WriteInputDeclarationsStructure(IEnumerable declarations, out string typeName) - { - typeName = $"{_entryPoint}_Input"; - if(_effectWriter is not null) - { - var current = declarations - .Select(r => (r.MaskedLength, r.Semantic)) - .ToArray(); - // check if a compatible type has already been defined inside this effect - foreach(var (name, existing) in _effectWriter.InputOutputStructures) - { - foreach(var (maskedLength, semantic) in current) - { - if(!existing.Any(e => e.MaskedLength >= maskedLength && e.Semantic == semantic)) - { - goto checkNext; - } - } - // we don't need to write declaration again in this case - typeName = name; - return; - checkNext: - continue; - } - _effectWriter.InputOutputStructures.Add((typeName, current)); - } - - WriteDeclarationsAsStruct(typeName, declarations); - } - - private void WriteOutputDeclarationsStructure(IEnumerable declarations, out string typeName) - { - typeName = $"{_entryPoint}_Output"; - if(_effectWriter is not null) - { - var current = declarations - .Select(r => (r.MaskedLength, r.Semantic)) - .ToArray(); - // check if a matching type has already been defined inside this effect - foreach(var (name, existing) in _effectWriter.InputOutputStructures) - { - var equals = existing.Length == current.Length && !existing.Except(current).Any(); - if(equals) - { - // we don't need to write declaration again in this case - typeName = name; - return; - } - } - _effectWriter.InputOutputStructures.Add((typeName, current)); - } - - WriteDeclarationsAsStruct(typeName, declarations); - } - private void WriteDeclarationsAsStruct(string typeName, IEnumerable declarations) { WriteLine($"struct {typeName}"); @@ -671,7 +616,8 @@ private void ProcessMethodInputType(out string methodParameters) methodParameters = $"{input.TypeName} {input.Name} : {input.Semantic}"; break; default: - WriteInputDeclarationsStructure(registers, out var inputTypeName); + var inputTypeName = $"{_entryPoint}_Input"; + WriteDeclarationsAsStruct(inputTypeName, registers); methodParameters = $"{inputTypeName} i"; break; } @@ -690,7 +636,8 @@ private void ProcessMethodOutputType(out string methodReturnType, out string met methodSemantic = $" : {semantic}"; break; default: - WriteOutputDeclarationsStructure(registers, out methodReturnType); + methodReturnType = $"{_entryPoint}_Output"; + WriteDeclarationsAsStruct(methodReturnType, registers); methodSemantic = string.Empty; break; }; From 798fc790d9b9d2cd53ab2b3c28a02f4446ddd619 Mon Sep 17 00:00:00 2001 From: lanyi Date: Mon, 24 May 2021 21:06:21 +0200 Subject: [PATCH 55/72] Handle empty pres comment token --- src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs b/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs index 4814275..6088c48 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs @@ -1,4 +1,4 @@ -using DXDecompiler.DX9Shader.Bytecode; +using DXDecompiler.DX9Shader.Bytecode; using DXDecompiler.DX9Shader.Bytecode.Ctab; using DXDecompiler.DX9Shader.Bytecode.Fxlvm; using DXDecompiler.DX9Shader.FX9; @@ -138,7 +138,10 @@ Token ReadInstruction(BytecodeReader reader) Fxlc = FxlcBlock.Parse(commentReader); return null; case CommentType.PRES: - Preshader = Preshader.Parse(commentReader); + if(size > 1) + { + Preshader = Preshader.Parse(commentReader); + } return null; case CommentType.PRSI: Prsi = PrsiToken.Parse(commentReader); From 6a473820f470627d3f476a7dd034e3519963f992 Mon Sep 17 00:00:00 2001 From: lanyi Date: Mon, 24 May 2021 21:07:06 +0200 Subject: [PATCH 56/72] Gracefully handle unsupported sm1 shaders --- src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs | 8 ++++++-- src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs | 6 ++++++ src/DXDecompilerCmd/Program.cs | 5 ++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs b/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs index 6088c48..ac60d2a 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs @@ -1,4 +1,4 @@ -using DXDecompiler.DX9Shader.Bytecode; +using DXDecompiler.DX9Shader.Bytecode; using DXDecompiler.DX9Shader.Bytecode.Ctab; using DXDecompiler.DX9Shader.Bytecode.Fxlvm; using DXDecompiler.DX9Shader.FX9; @@ -95,7 +95,11 @@ public static ShaderModel Parse(BytecodeReader reader) result.Type = (ShaderType)reader.ReadUInt16(); //SM1 shaders do not encode instruction size which rely on for reading operands. //So we won't support SM1 - if(result.MajorVersion == 1) throw new ParseException("Shader Model 1 is not supported"); + if(result.MajorVersion == 1) + { + Trace.WriteLine("Shader Model 1 is not supported"); + return result; + } while(true) { var instruction = result.ReadInstruction(reader); diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 8325ad1..73069a2 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -511,6 +511,12 @@ protected override void Write() { throw new InvalidOperationException($"Expression should be written using {nameof(ExpressionHLSLWriter)} in {nameof(EffectHLSLWriter)}"); } + if(_shader.MajorVersion == 1) + { + WriteLine("#pragma message \"Shader Model 1.0 not supported\""); + WriteLine($"float4 {_entryPoint}(): POSITION;"); + return; + } _registers = new RegisterState(_shader); foreach(var declaration in _registers.ConstantDeclarations) diff --git a/src/DXDecompilerCmd/Program.cs b/src/DXDecompilerCmd/Program.cs index c149e5b..50bcf43 100644 --- a/src/DXDecompilerCmd/Program.cs +++ b/src/DXDecompilerCmd/Program.cs @@ -1,10 +1,11 @@ -using DXDecompiler; +using DXDecompiler; using DXDecompiler.DebugParser; using DXDecompiler.DebugParser.DX9; using DXDecompiler.DebugParser.FX9; using DXDecompiler.Decompiler; using DXDecompiler.Util; using System; +using System.Diagnostics; using System.IO; namespace DXDecompilerCmd @@ -58,6 +59,8 @@ static StreamWriter GetStream(Options options) } static void Main(string[] args) { + Trace.Listeners.Add(new ConsoleTraceListener(useErrorStream: true)); + var options = new Options(); for(int i = 0; i < args.Length; i++) { From 6e56258f6a458bc929aa4de1d3b1b7ba7ea87796 Mon Sep 17 00:00:00 2001 From: lanyi Date: Mon, 24 May 2021 21:07:32 +0200 Subject: [PATCH 57/72] added a simple help message in DXDecompilerCmd --- src/DXDecompilerCmd/Program.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/DXDecompilerCmd/Program.cs b/src/DXDecompilerCmd/Program.cs index 50bcf43..dd4ce91 100644 --- a/src/DXDecompilerCmd/Program.cs +++ b/src/DXDecompilerCmd/Program.cs @@ -92,6 +92,12 @@ static void Main(string[] args) if(string.IsNullOrEmpty(options.SourcePath)) { Console.Error.WriteLine("No source path specified"); + Console.Error.WriteLine("Usage: "); + Console.Error.WriteLine(" DXDecompilerCmd # decompile to stdout"); + Console.Error.WriteLine(" DXDecompilerCmd -O # decompile to file"); + Console.Error.WriteLine(" DXDecompilerCmd -a # disassemble to stdout"); + Console.Error.WriteLine(" DXDecompilerCmd -a -O # disassemble to file"); + Console.Error.WriteLine(" DXDecompilerCmd -h -O # generate debug HTML"); Environment.Exit(1); } From 0d3993424ccdacf62fb4f5bc2e06a9b3b66368ab Mon Sep 17 00:00:00 2001 From: lanyi Date: Mon, 24 May 2021 21:46:43 +0200 Subject: [PATCH 58/72] Implement lit instruction --- src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 73069a2..1bccc11 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -456,6 +456,9 @@ void WriteTextureAssignment(string postFix, SourceOperand sampler, SourceOperand case Opcode.DSY: WriteAssignment("ddy({0})", GetSourceName(instruction, 1)); break; + case Opcode.Lit: + WriteAssignment("lit({0}.x, {0}.y, {0}.w)", GetSourceName(instruction, 1)); + break; default: throw new NotImplementedException(instruction.Opcode.ToString()); } From 30edd6e330f2f45494e1934b0aa6aae9e184f9f7 Mon Sep 17 00:00:00 2001 From: lanyi Date: Tue, 25 May 2021 23:06:32 +0200 Subject: [PATCH 59/72] Added the missing destinatinon modifier in dx9 HlslWriter --- src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 1bccc11..aa31f3f 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -173,6 +173,13 @@ private void WriteInstruction(InstructionToken instruction) void WriteAssignment(string sourceFormat, params SourceOperand[] args) { var destination = GetDestinationName(instruction, out var writeMask); + var destinationModifier = instruction.GetDestinationResultModifier() switch + { + ResultModifier.None => "{0} = {1};", + ResultModifier.Saturate => "{0} = saturate({1});", + ResultModifier.PartialPrecision => $"{{0}} = /* not implemented _pp modifier */ {{1}};", + object unknown => throw new NotImplementedException($"{unknown}") + }; var sourceResult = string.Format(sourceFormat, args); var swizzleSizes = args.Select(x => x.Swizzle.StartsWith(".") ? x.Swizzle.Trim('.').Length : -1); @@ -217,7 +224,7 @@ void WriteAssignment(string sourceFormat, params SourceOperand[] args) sourceResult = $"({sourceResult}){writeMask}"; } } - WriteLine("{0} = {1};", destination, sourceResult); + WriteLine(destinationModifier, destination, sourceResult); } void WriteTextureAssignment(string postFix, SourceOperand sampler, SourceOperand uv, int extraUvDimensions, params SourceOperand[] others) From 8e5205203d6e9e4475100011e4b7615268bec5b0 Mon Sep 17 00:00:00 2001 From: lanyi Date: Tue, 25 May 2021 23:07:48 +0200 Subject: [PATCH 60/72] Fixed wrong decompilation of lrp instruction in dx9 HlslWriter --- src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index aa31f3f..35159e8 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -339,7 +339,7 @@ void WriteTextureAssignment(string postFix, SourceOperand sampler, SourceOperand WriteAssignment("log2({0})", GetSourceName(instruction, 1)); break; case Opcode.Lrp: - WriteAssignment("lerp({1}, {2}, {0})", + WriteAssignment("lerp({2}, {1}, {0})", GetSourceName(instruction, 1), GetSourceName(instruction, 2), GetSourceName(instruction, 3)); break; case Opcode.Mad: From 9a454b17e0a5470708a9fb5b42128b46fa6573be Mon Sep 17 00:00:00 2001 From: lanyi Date: Wed, 26 May 2021 00:08:01 +0200 Subject: [PATCH 61/72] Prevent conflicting register assignments in effect --- .../DX9Shader/Decompiler/EffectHLSLWriter.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs index 45ad29f..74e8856 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs @@ -58,6 +58,7 @@ void FindCommonConstantDeclarations() var blobShaders = _effectChunk.StateBlobs .Where(x => x.BlobType is StateBlobType.Shader or StateBlobType.IndexShader) .Select(x => x.Shader); + foreach(var shader in variableShaders.Concat(blobShaders)) { var declarations = shader.ConstantTable?.ConstantDeclarations @@ -65,7 +66,6 @@ void FindCommonConstantDeclarations() var decompiled = declarations.Select(c => ConstantTypeWriter.Decompile(c, shader)); foreach(var declaration in decompiled) { - // assuming every shader have com if(!CommonConstantDeclarations.TryGetValue(declaration.Name, out var existing)) { CommonConstantDeclarations[declaration.Name] = declaration; @@ -108,6 +108,26 @@ void FindCommonConstantDeclarations() } } } + + // we should also remove register assignments, + // when there are two different declarations with the same register assignments + var occupiedRegisterAssignments = new Dictionary, DecompiledConstantDeclaration>(); + foreach(var decl in CommonConstantDeclarations.Values) + { + foreach(var assignment in decl.RegisterAssignments) + { + if(occupiedRegisterAssignments.TryGetValue(assignment, out var other)) + { + // remove conflicting register assignments + other.RegisterAssignments.Remove(assignment.Key); + decl.RegisterAssignments.Remove(assignment.Key); + } + else + { + occupiedRegisterAssignments[assignment] = decl; + } + } + } } protected override void Write() { From f661774e6c96662921baa2931c0d58ca23ffc082 Mon Sep 17 00:00:00 2001 From: lanyi Date: Wed, 26 May 2021 01:05:37 +0200 Subject: [PATCH 62/72] Better handling of constant literals --- .../DX9Shader/Bytecode/InstructionToken.cs | 30 ++++--- .../DX9Shader/Decompiler/HlslWriter.cs | 80 ++++++++++++------- .../DX9Shader/Decompiler/RegisterState.cs | 46 ++++++++--- 3 files changed, 102 insertions(+), 54 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Bytecode/InstructionToken.cs b/src/DXDecompiler/DX9Shader/Bytecode/InstructionToken.cs index ea1613d..ea0ec1c 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/InstructionToken.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/InstructionToken.cs @@ -342,22 +342,32 @@ public byte[] GetSourceSwizzleComponents(int srcIndex) return swizzleArray; } + public int? GetSourceSwizzleLimit(int operandDataIndex) + { + int logicalIndex = 0; + for(var i = 0; i < operandDataIndex; ++i) + { + if(!IsRelativeAddressMode(i)) + { + ++logicalIndex; + } + } + + return Opcode switch + { + Opcode.Dp3 => 3, + Opcode.DP2Add => logicalIndex < 3 ? 2 : 1,// dp2add dst, src0.xy src1.xy src2.x + _ => null, + }; + } + public string GetSourceSwizzleName(int srcIndex, bool hlsl = false) { int? swizzleLimit = null; //TODO: Probably useful in hlsl mode if(hlsl) { - switch(Opcode) - { - case Opcode.Dp3: - swizzleLimit = 3; - break; - case Opcode.DP2Add: - // dp2add src0.xy src1.xy src2.x - swizzleLimit = srcIndex < 2 ? 2 : 1; - break; - } + swizzleLimit = GetSourceSwizzleLimit(srcIndex); } string swizzleName = ""; diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 35159e8..45fae15 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -11,14 +11,20 @@ public class HlslWriter : DecompileWriter { private class SourceOperand { - public string Body { get; set; } - public string Swizzle { get; set; } - public string Modifier { get; set; } - public ParameterType? SamplerType { get; set; } + public string Body { get; set; } // normally, register / constant name, or type name if Literals are not null + public string[] Literals { get; set; } // either null, or literal values + public string Swizzle { get; set; } // either empty, or a swizzle with leading dot. Empty if Literals are not null. + public string Modifier { get; set; } // should be used with string.Format to format the body. Should be "{0}" if Literals are not null. + public ParameterType? SamplerType { get; set; } // not null if it's a sampler public override string ToString() { - var body = string.Format(Modifier, Body); + var body = Body; + if(Literals is not null) + { + body += string.Format("{0}({1})", Literals.Length, string.Join(", ", Literals)); + } + body = string.Format(Modifier, body); if(body.All(char.IsDigit)) { body = $"({body})"; @@ -128,10 +134,11 @@ private SourceOperand GetSourceName(InstructionToken instruction, int srcIndex, } - var body = _registers.GetSourceName(instruction, dataIndex, out var swizzle, out var modifier); + var body = _registers.GetSourceName(instruction, dataIndex, out var swizzle, out var modifier, out var literals); return new SourceOperand { Body = body, + Literals = literals, Swizzle = swizzle, Modifier = modifier, SamplerType = samplerType @@ -198,6 +205,15 @@ void WriteAssignment(string sourceFormat, params SourceOperand[] args) foreach(var arg in args) { const string xyzw = ".xyzw"; + + if(arg.Literals is not null) + { + arg.Literals = arg.Literals + .Where((v, i) => writeMask.Contains(xyzw[i + 1])) + .ToArray(); + continue; + } + var trimmedSwizzle = "."; if(string.IsNullOrEmpty(arg.Swizzle)) { @@ -362,35 +378,37 @@ void WriteTextureAssignment(string postFix, SourceOperand sampler, SourceOperand WriteAssignment("{0} * {1}", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Nrm: - // the nrm opcode actually only works on the 3D vector - var operand = GetSourceName(instruction, 1); - if(instruction.GetDestinationMaskedLength() < 4) { - var swizzle = operand.Swizzle.TrimStart('.'); - switch(swizzle.Length) + // the nrm opcode actually only works on the 3D vector + var operand = GetSourceName(instruction, 1); + if(instruction.GetDestinationMaskedLength() < 4) { - case 0: - case 4: - WriteAssignment("normalize({0}.xyz)", operand); - break; - case 1: - // let it reach 3 dimensions - operand.Swizzle += swizzle; - operand.Swizzle += swizzle; - goto case 3; - case 3: - WriteAssignment("normalize({0})", operand); - break; - default: - WriteAssignment("({0} / length(float3({0}))", operand); - break; + var swizzle = operand.Swizzle.TrimStart('.'); + switch(swizzle.Length) + { + case 0: + case 4: + WriteAssignment("normalize({0}.xyz)", operand); + break; + case 1: + // let it reach 3 dimensions + operand.Swizzle += swizzle; + operand.Swizzle += swizzle; + goto case 3; + case 3: + WriteAssignment("normalize({0})", operand); + break; + default: + WriteAssignment("({0} / length(float3({0}))", operand); + break; + } } + else + { + WriteAssignment("({0} / length(float3({0}))", operand); + } + break; } - else - { - WriteAssignment("({0} / length(float3({0}))", operand); - } - break; case Opcode.Pow: WriteAssignment("pow({0}, {1})", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; diff --git a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs index 78c220b..b7f662f 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs @@ -155,7 +155,7 @@ public string GetDestinationName(InstructionToken instruction, out string writeM return registerName; } - public string GetSourceName(InstructionToken instruction, int srcIndex, out string swizzle, out string modifier) + public string GetSourceName(InstructionToken instruction, int srcIndex, out string swizzle, out string modifier, out string[] literals) { string sourceRegisterName; var registerKey = instruction.GetParamRegisterKey(srcIndex); @@ -171,17 +171,18 @@ public string GetSourceName(InstructionToken instruction, int srcIndex, out stri { swizzle = instruction.GetSourceSwizzleName(srcIndex, true); modifier = GetModifier(instruction.GetSourceModifier(srcIndex)); + literals = null; return $"expr{registerNumber}"; } goto case RegisterType.ConstInt; case RegisterType.ConstBool: case RegisterType.ConstInt: - sourceRegisterName = GetSourceConstantName(instruction, srcIndex); - if(sourceRegisterName != null) + literals = GetSourceConstantLiterals(instruction, srcIndex, out var literalsType); + if(literals != null) { swizzle = string.Empty; modifier = "{0}"; - return sourceRegisterName; + return literalsType; } RegisterSet registerSet; @@ -215,7 +216,11 @@ public string GetSourceName(InstructionToken instruction, int srcIndex, out stri string indexer = null; if(instruction.IsRelativeAddressMode(srcIndex)) { - indexer = GetSourceName(instruction, srcIndex + 1, out var indexSwizzle, out var indexModifier); + indexer = GetSourceName(instruction, srcIndex + 1, out var indexSwizzle, out var indexModifier, out var indexLiterals); + if(indexLiterals != null) + { + throw new InvalidOperationException(); + } // make sure index swizzle only has one component indexSwizzle = indexSwizzle.TrimStart('.'); indexSwizzle = "." + indexSwizzle[0]; @@ -224,6 +229,7 @@ public string GetSourceName(InstructionToken instruction, int srcIndex, out stri sourceRegisterName = decl.GetConstantNameByRegisterNumber(registerNumber, indexer); break; default: + literals = null; sourceRegisterName = GetRegisterName(registerKey); break; } @@ -336,16 +342,16 @@ public ConstantDeclaration FindConstant(ParameterType type, uint index) c.ContainsIndex(index)); } - private string GetSourceConstantName(InstructionToken instruction, int srcIndex) + private string[] GetSourceConstantLiterals(InstructionToken instruction, int srcIndex, out string type) { var registerType = instruction.GetParamRegisterType(srcIndex); var registerNumber = instruction.GetParamRegisterNumber(srcIndex); - - string type; - string[] constant; + var swizzleLimit = instruction.GetSourceSwizzleLimit(srcIndex) ?? 4; + string[] constants; switch(registerType) { case RegisterType.ConstBool: + type = "bool"; //throw new NotImplementedException(); return null; case RegisterType.ConstInt: @@ -355,7 +361,19 @@ private string GetSourceConstantName(InstructionToken instruction, int srcIndex) { return null; } - var intValues = instruction.GetSourceSwizzleComponents(srcIndex) + var intSwizzles = instruction.GetSourceSwizzleComponents(srcIndex); + if(instruction.Opcode == Opcode.Rep) + { + if(intSwizzles[0] != 0) + { + // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/rep---ps + // MSDN says "i#.yzw" are unused, but it's not clear on whether if swizzles are allowed + throw new NotImplementedException("rep i# with swizzles not yet implemented"); + } + swizzleLimit = 1; + } + var intValues = intSwizzles + .Take(swizzleLimit) .Select(s => constantInt[s]) .ToArray(); @@ -374,7 +392,7 @@ private string GetSourceConstantName(InstructionToken instruction, int srcIndex) default: throw new NotImplementedException(); } - constant = intValues.Select(i => i.ToString(_culture)).ToArray(); + constants = intValues.Select(i => i.ToString(_culture)).ToArray(); break; case RegisterType.Const: case RegisterType.Const2: @@ -388,6 +406,7 @@ private string GetSourceConstantName(InstructionToken instruction, int srcIndex) } var floatValues = instruction.GetSourceSwizzleComponents(srcIndex) + .Take(swizzleLimit) .Select(s => constantRegister[s]) .ToArray(); @@ -410,9 +429,10 @@ private string GetSourceConstantName(InstructionToken instruction, int srcIndex) default: throw new NotImplementedException(); } - constant = floatValues.Select(f => f.ToString(_culture)).ToArray(); + constants = floatValues.Select(f => f.ToString(_culture)).ToArray(); break; default: + type = null; return null; } @@ -421,7 +441,7 @@ private string GetSourceConstantName(InstructionToken instruction, int srcIndex) // TODO } - return string.Format("{0}{1}({2})", type, constant.Length, string.Join(", ", constant)); + return constants; } From 3ff820264f4b7486ac02d707601f326e31da3e4a Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 30 May 2021 08:02:27 +0200 Subject: [PATCH 63/72] Experimental SM1 shaders support, made possible by jonwil's effort --- .../DX9Shader/Bytecode/EnumExtensions.cs | 151 ++++++++++++++++++ .../DX9Shader/Bytecode/InstructionToken.cs | 4 - .../DX9Shader/Bytecode/ShaderModel.cs | 40 +++-- src/DXDecompiler/DX9Shader/Bytecode/Token.cs | 9 ++ .../DX9Shader/Decompiler/HlslWriter.cs | 37 ++++- .../Decompiler/RegisterDeclaration.cs | 4 +- .../DX9Shader/Decompiler/RegisterState.cs | 74 ++++++++- 7 files changed, 297 insertions(+), 22 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Bytecode/EnumExtensions.cs b/src/DXDecompiler/DX9Shader/Bytecode/EnumExtensions.cs index cca90a1..843f124 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/EnumExtensions.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/EnumExtensions.cs @@ -225,5 +225,156 @@ public static bool IsParallel(this Opcode opcode, ShaderModel shader) return false; } } + /// + /// Get the size of instruction in shader model 1. + /// + /// The opcode of the instruction + /// The number of tokens inside this instruction. + public static int GetShaderModel1OpcodeSize(this Opcode opcode, int minorVersion) + { + switch(opcode) + { + case Opcode.Abs: + return 2; + case Opcode.Add: + return 3; + case Opcode.Bem: + return 3; + case Opcode.Breakp: + return 1; + case Opcode.Cmp: + return 4; + case Opcode.Cnd: + return 4; + case Opcode.Crs: + return 3; + case Opcode.Dcl: + return 2; + case Opcode.Def: + return 5; + case Opcode.DefB: + return 2; + case Opcode.DefI: + return 5; + case Opcode.Dp3: + return 3; + case Opcode.Dp4: + return 3; + case Opcode.Dst: + return 3; + case Opcode.Exp: + return 2; + case Opcode.ExpP: + return 2; + case Opcode.Frc: + return 2; + case Opcode.Lit: + return 2; + case Opcode.Log: + return 2; + case Opcode.LogP: + return 2; + case Opcode.Lrp: + return 4; + case Opcode.M3x2: + return 3; + case Opcode.M3x3: + return 3; + case Opcode.M3x4: + return 3; + case Opcode.M4x3: + return 3; + case Opcode.M4x4: + return 3; + case Opcode.Mad: + return 4; + case Opcode.Max: + return 3; + case Opcode.Min: + return 3; + case Opcode.Mov: + return 2; + case Opcode.Mul: + return 3; + case Opcode.Nop: + return 0; + case Opcode.Nrm: + return 2; + case Opcode.Phase: + return 0; + case Opcode.Pow: + return 3; + case Opcode.Rcp: + return 2; + case Opcode.Rsq: + return 2; + case Opcode.SetP: + return 3; + case Opcode.Sge: + return 3; + case Opcode.Slt: + return 3; + case Opcode.Sub: + return 3; + case Opcode.Tex: + if(minorVersion < 4) + { + return 1; + } + else + { + return 2; + } + case Opcode.TexBem: + return 2; + case Opcode.TexBeml: + return 2; + case Opcode.TexCoord: + if(minorVersion < 4) + { + return 1; + } + else + { + return 2; + } + case Opcode.TexDepth: + return 1; + case Opcode.TexDP3: + return 2; + case Opcode.TexDP3Tex: + return 2; + case Opcode.TexKill: + return 1; + case Opcode.TexM3x2Depth: + return 2; + case Opcode.TeXM3x2Pad: + return 2; + case Opcode.TexM3x2Tex: + return 2; + case Opcode.TexM3x3: + return 2; + case Opcode.TexM3x3Diff: + return 2; + case Opcode.TeXM3x3Pad: + return 2; + case Opcode.TexM3x3Spec: + return 3; + case Opcode.TexM3x3Tex: + return 2; + case Opcode.TexM3x3VSpec: + return 2; + case Opcode.TexReg2AR: + return 2; + case Opcode.TexReg2GB: + return 2; + case Opcode.TexReg2RGB: + return 2; + case Opcode.End: + return 0; + default: + throw new NotImplementedException(opcode.ToString()); + } + } } } diff --git a/src/DXDecompiler/DX9Shader/Bytecode/InstructionToken.cs b/src/DXDecompiler/DX9Shader/Bytecode/InstructionToken.cs index ea0ec1c..6595957 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/InstructionToken.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/InstructionToken.cs @@ -440,10 +440,6 @@ public string GetDeclSemantic() { case RegisterType.Input when _shaderModel is { Type: ShaderType.Pixel, MajorVersion: <= 2 }: case RegisterType.Texture when _shaderModel is { Type: ShaderType.Pixel, MajorVersion: <= 2 }: - if(_shaderModel.MajorVersion == 1) - { - throw new NotImplementedException("Shader model 1 not supported yet"); - } declIndexString = GetParamRegisterNumber(1) is 0 ? string.Empty : GetParamRegisterNumber(1).ToString(); diff --git a/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs b/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs index ac60d2a..9df1966 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/ShaderModel.cs @@ -93,13 +93,6 @@ public static ShaderModel Parse(BytecodeReader reader) result.MinorVersion = reader.ReadByte(); result.MajorVersion = reader.ReadByte(); result.Type = (ShaderType)reader.ReadUInt16(); - //SM1 shaders do not encode instruction size which rely on for reading operands. - //So we won't support SM1 - if(result.MajorVersion == 1) - { - Trace.WriteLine("Shader Model 1 is not supported"); - return result; - } while(true) { var instruction = result.ReadInstruction(reader); @@ -120,7 +113,14 @@ Token ReadInstruction(BytecodeReader reader) } else { - size = (int)((instructionToken >> 24) & 0x0f); + if(MajorVersion > 1) + { + size = (int)((instructionToken >> 24) & 0x0f); + } + else + { + size = opcode.GetShaderModel1OpcodeSize(MinorVersion); + } } Token token = null; if(opcode == Opcode.Comment) @@ -190,7 +190,17 @@ Token ReadInstruction(BytecodeReader reader) if((token.Data[i] & (1 << 13)) != 0) { //Relative Address mode - token.Data[i + 1] = reader.ReadUInt32(); + if(MajorVersion < 2) + { + // special handling for SM1 shaders + token.AddData(); + token.Data[i + 1] = 0xB0000000; + size++; + } + else + { + token.Data[i + 1] = reader.ReadUInt32(); + } inst.Operands.Add(new DestinationOperand(token.Data[i], token.Data[i + 1])); i++; } @@ -202,7 +212,17 @@ Token ReadInstruction(BytecodeReader reader) else if((token.Data[i] & (1 << 13)) != 0) { //Relative Address mode - token.Data[i + 1] = reader.ReadUInt32(); + if(MajorVersion < 2) + { + // special handling for SM1 shaders + token.AddData(); + token.Data[i + 1] = 0xB0000000; + size++; + } + else + { + token.Data[i + 1] = reader.ReadUInt32(); + } inst.Operands.Add(new SourceOperand(token.Data[i], token.Data[i + 1])); i++; } diff --git a/src/DXDecompiler/DX9Shader/Bytecode/Token.cs b/src/DXDecompiler/DX9Shader/Bytecode/Token.cs index 7c39577..7f9a893 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/Token.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/Token.cs @@ -35,6 +35,15 @@ public Token(Opcode opcode, int numParams, ShaderModel shaderModel) _shaderModel = shaderModel; } + public void AddData() + { + int len = Data.Length; + len++; + uint[] d = new uint[len]; + System.Array.Copy(Data, d, Data.Length); + Data = d; + } + public override string ToString() { return Opcode.ToString(); diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 45fae15..b2ab625 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -447,13 +447,38 @@ void WriteTextureAssignment(string postFix, SourceOperand sampler, SourceOperand WriteAssignment("{0} - {1}", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.Tex: - if((_shader.MajorVersion == 1 && _shader.MinorVersion >= 4) || (_shader.MajorVersion > 1)) + if(_shader.MajorVersion > 1) { WriteTextureAssignment(string.Empty, GetSourceName(instruction, 2), GetSourceName(instruction, 1), 0); } + // shader model 1 + else if(_shader.MinorVersion >= 4) + { + throw new NotImplementedException("texld in ps_1_4 not implemented yet"); + } else { - WriteAssignment("tex2D()"); + var uvName = GetDestinationName(instruction, out var writeMask); + var uvOperand = new SourceOperand + { + Body = uvName, + Swizzle = string.Empty, + Modifier = "{0}" + }; + + var registerNumber = instruction.GetParamRegisterNumber(0); + var samplerDecl = _registers.FindConstant(RegisterSet.Sampler, instruction.GetParamRegisterNumber(0)); + var samplerName = samplerDecl.GetConstantNameByRegisterNumber(registerNumber, null); + var samplerType = samplerDecl.GetRegisterTypeByOffset(registerNumber - samplerDecl.RegisterIndex).Type.ParameterType; + var samplerOperand = new SourceOperand + { + Body = samplerName, + Swizzle = string.Empty, + Modifier = "{0}", + SamplerType = samplerType + }; + + WriteTextureAssignment(string.Empty, samplerOperand, uvOperand, 0); } break; case Opcode.TexLDL: @@ -539,12 +564,12 @@ protected override void Write() { throw new InvalidOperationException($"Expression should be written using {nameof(ExpressionHLSLWriter)} in {nameof(EffectHLSLWriter)}"); } - if(_shader.MajorVersion == 1) + /*if(_shader.MajorVersion == 1) { WriteLine("#pragma message \"Shader Model 1.0 not supported\""); WriteLine($"float4 {_entryPoint}(): POSITION;"); return; - } + }*/ _registers = new RegisterState(_shader); foreach(var declaration in _registers.ConstantDeclarations) @@ -585,6 +610,10 @@ protected override void Write() WriteIndent(); WriteLine($"{methodReturnType} o;"); } + else if(_shader is { Type: ShaderType.Pixel, MajorVersion: 1 }) + { + // TODO + } else { var output = _registers.MethodOutputRegisters.First().Value; diff --git a/src/DXDecompiler/DX9Shader/Decompiler/RegisterDeclaration.cs b/src/DXDecompiler/DX9Shader/Decompiler/RegisterDeclaration.cs index 4bd8959..dd9e01f 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/RegisterDeclaration.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/RegisterDeclaration.cs @@ -14,10 +14,10 @@ public RegisterDeclaration(InstructionToken declInstruction) MaskedLength = declInstruction.GetDestinationMaskedLength(); } - public RegisterDeclaration(RegisterKey registerKey, int maskedLength = 4) + public RegisterDeclaration(RegisterKey registerKey, int maskedLength = 4, string semantic = null) { RegisterType type = registerKey.Type; - _semantic = GuessSemanticByRegisterType(type); + _semantic = semantic ?? GuessSemanticByRegisterType(type); RegisterKey = registerKey; if(_semantic != null && RegisterKey.Number != 0) { diff --git a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs index b7f662f..5c0f2b8 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/RegisterState.cs @@ -34,6 +34,9 @@ public RegisterState(ShaderModel shader) private void Load(ShaderModel shader) { ConstantDeclarations = shader.ConstantTable.ConstantDeclarations; + + var isSm1PixelShader = shader is { Type: ShaderType.Pixel, MajorVersion: 1 }; + foreach(var constantDeclaration in ConstantDeclarations) { RegisterType registerType; @@ -125,8 +128,8 @@ private void Load(ShaderModel shader) if(!RegisterDeclarations.TryGetValue(registerKey, out var declaration) || declaration.MaskedLength < maskedLength) { - declaration = new RegisterDeclaration(registerKey, maskedLength); - RegisterDeclarations[registerKey] = declaration; + var isTemp0 = registerKey is { Type: RegisterType.Temp, Number: 0 }; + var isSm1PixelShaderOutput = isSm1PixelShader && isTemp0; switch(registerType) { case RegisterType.AttrOut: @@ -134,12 +137,79 @@ private void Load(ShaderModel shader) case RegisterType.DepthOut: case RegisterType.Output: case RegisterType.RastOut: + case RegisterType.Temp when isSm1PixelShaderOutput: + declaration = new RegisterDeclaration(registerKey, maskedLength, isSm1PixelShaderOutput ? "COLOR" : null); + RegisterDeclarations[registerKey] = declaration; MethodOutputRegisters[registerKey] = declaration; break; } } } } + + if(isSm1PixelShader) + { + // SM1 shaders doesn't have dcl instruction + // so we must check through all operands + foreach(var instruction in shader.Tokens.OfType()) + { + var operandIndex = 0; + foreach(var operand in instruction.Operands) + { + var i = operandIndex++; + + var registerType = instruction.GetParamRegisterType(i); + var registerNumber = instruction.GetParamRegisterNumber(i); + var writeMask = instruction.HasDestination + ? instruction.GetDestinationWriteMask() + : ComponentFlags.X | ComponentFlags.Y | ComponentFlags.Z | ComponentFlags.W; + int registerSize; + if(operand is SourceOperand source) + { + registerSize = instruction.GetSourceSwizzleComponents(i) + .Take(instruction.GetSourceSwizzleLimit(i) ?? 4) + .Where((_, i) => i switch + { + 0 => writeMask.HasFlag(ComponentFlags.X), + 1 => writeMask.HasFlag(ComponentFlags.Y), + 2 => writeMask.HasFlag(ComponentFlags.Z), + 3 => writeMask.HasFlag(ComponentFlags.W), + _ => throw new InvalidOperationException() + }) + .Max() + 1; + } + else if(operand is DestinationOperand dest) + { + registerSize = instruction.GetDestinationMaskedLength(); + } + else + { + throw new InvalidOperationException("Operand is neither source nor dest"); + } + + var registerKey = new RegisterKey(registerType, registerNumber); + switch(registerKey.Type) + { + case RegisterType.Input: + case RegisterType.Texture when shader.Type == ShaderType.Pixel: + if(!RegisterDeclarations.TryGetValue(registerKey, out var declaration) + || declaration.MaskedLength < registerSize) + { + var semantic = registerKey.Type switch + { + RegisterType.Input => "COLOR", + RegisterType.Texture => "TEXCOORD", + _ => throw new InvalidOperationException(), + }; + declaration = new RegisterDeclaration(registerKey, registerSize, semantic); + RegisterDeclarations[registerKey] = declaration; + MethodInputRegisters[registerKey] = declaration; + } + break; + } + } + } + } } public string GetDestinationName(InstructionToken instruction, out string writeMaskName) From dfab560d632558cd24ee67c73608b9e1d95651a8 Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 30 May 2021 18:43:33 +0200 Subject: [PATCH 64/72] minor style change --- src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index b2ab625..b3743d4 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -610,11 +610,7 @@ protected override void Write() WriteIndent(); WriteLine($"{methodReturnType} o;"); } - else if(_shader is { Type: ShaderType.Pixel, MajorVersion: 1 }) - { - // TODO - } - else + else if(_shader is not { Type: ShaderType.Pixel, MajorVersion: 1 }) // sm1 pixel shader use temp0 as output { var output = _registers.MethodOutputRegisters.First().Value; WriteIndent(); From 7d30021ce73cabe2cc71dcea062bea8c3071f511 Mon Sep 17 00:00:00 2001 From: lanyi Date: Thu, 22 Jul 2021 15:26:51 +0200 Subject: [PATCH 65/72] fix fxlc operand's scalar/vector handling --- .../DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs | 16 +++++++++------- .../DX9Shader/Bytecode/Fxlvm/FxlcToken.cs | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs index 35aaa0c..78be297 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcOperand.cs @@ -15,6 +15,7 @@ public class FxlcOperand public FxlcOperandType ArrayType { get; private set; } public uint ArrayIndex { get; private set; } public uint ComponentCount { get; private set; } + public bool IsScalar { get; private set; } public static FxlcOperand Parse(BytecodeReader reader, uint componentCount, bool isScalarOp) { @@ -23,6 +24,7 @@ public static FxlcOperand Parse(BytecodeReader reader, uint componentCount, bool IsArray = reader.ReadUInt32(), OpType = (FxlcOperandType)reader.ReadUInt32(), OpIndex = reader.ReadUInt32(), + IsScalar = isScalarOp }; result.ComponentCount = isScalarOp && result.OpType != FxlcOperandType.Literal ? 1 : componentCount; Debug.Assert(Enum.IsDefined(typeof(FxlcOperandType), result.OpType), @@ -121,13 +123,13 @@ private string FormatOperand( { case FxlcOperandType.Literal: var literal = cli.GetLiteral(index); - if(literal == "-0") + var firstLiteral = literal is "-0" ? "0" : literal; + for(var i = 1u; i < ComponentCount; ++i) { - literal = string.Format("{0}, 0, 0, 0", literal); - } - else - { - literal = string.Join(", ", Enumerable.Repeat(cli.GetLiteral(index), (int)ComponentCount)); + var text = IsScalar + ? firstLiteral + : cli.GetLiteral(index + i); + literal += $", {text}"; } component = string.Empty; var typeName = ctab is null || ComponentCount == 1 ? string.Empty : $"float{ComponentCount}"; @@ -193,7 +195,7 @@ private string FormatOperand(ConstantTable ctab, Chunks.Fxlvm.Cli4Chunk cli, Fxl /// CliToken, neccessary for retrieving literal values /// ConstantTable, optional. If not null, it will be used to resolve constants. /// Constant registers overwritten by preshader - /// + /// The formatted operand as string public string FormatOperand(CliToken cli, ConstantTable ctab, HashSet ctabOverride = null) { if(IsArray == 0) diff --git a/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcToken.cs b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcToken.cs index 21e5a66..5ea4bb2 100644 --- a/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcToken.cs +++ b/src/DXDecompiler/DX9Shader/Bytecode/Fxlvm/FxlcToken.cs @@ -36,7 +36,7 @@ public static FxlcToken Parse(BytecodeReader reader) for(int i = 0; i < operandCount; i++) { var isScalarOp = i == 0 && result.IsScalarInstruction; - result.Operands.Add(FxlcOperand.Parse(reader, tokenComponentCount, isScalarOp)); + result.Operands.Add(FxlcOperand.Parse(reader, tokenComponentCount, isScalarOp && i == 0)); } // destination operand result.Operands.Insert(0, FxlcOperand.Parse(reader, tokenComponentCount, false)); From 77bcf1215ba9f57011568afd173c2d24afbf445d Mon Sep 17 00:00:00 2001 From: lanyi Date: Thu, 22 Jul 2021 16:40:43 +0200 Subject: [PATCH 66/72] More meaningful shader names --- .../DX9Shader/Decompiler/EffectHLSLWriter.cs | 54 +++++++++++++++---- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs index 74e8856..1671f72 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/EffectHLSLWriter.cs @@ -25,28 +25,62 @@ public static string Decompile(EffectContainer effectChunk) } void BuildNameLookup() { - string MakeShaderName(ShaderModel shader) => shader.Type switch + var hintNames = new Dictionary(); + foreach(var technique in _effectChunk.Techniques) + { + foreach(var assignment in technique.Passes.SelectMany(p => p.Assignments)) + { + if(!_effectChunk.StateBlobLookup.TryGetValue(assignment, out var stateBlob) || stateBlob is null) + { + continue; + } + + if(stateBlob.BlobType is StateBlobType.IndexShader or StateBlobType.Shader) + { + if(!hintNames.TryGetValue(stateBlob, out var existingName)) + { + hintNames.Add(stateBlob, technique.Name); + } + else if(existingName != technique.Name) + { + hintNames[stateBlob] = null; + } + } + } + } + string MakeShaderName(ShaderModel shader) { - ShaderType.Pixel => "PixelShader", - ShaderType.Vertex => "VertexShader", - ShaderType.Expression => "Expression", - _ => "Shader" - } + $"{_shaderNames.Count + 1}"; + return shader.Type switch + { + ShaderType.Pixel => "PixelShader", + ShaderType.Vertex => "VertexShader", + ShaderType.Expression => "Expression", + _ => "Shader" + } + $"{_shaderNames.Count + 1}"; + } foreach(var blob in _effectChunk.VariableBlobs) { - if(blob.IsShader) + if(!blob.IsShader) { - _shaderNames[blob] = MakeShaderName(blob.Shader); + continue; } + _shaderNames[blob] = MakeShaderName(blob.Shader); } foreach(var blob in _effectChunk.StateBlobs) { if(blob.BlobType == StateBlobType.Shader || blob.BlobType == StateBlobType.IndexShader) { - - _shaderNames[blob] = MakeShaderName(blob.Shader); + if(!hintNames.TryGetValue(blob, out var techniquePrefix) || string.IsNullOrWhiteSpace(techniquePrefix)) + { + techniquePrefix = string.Empty; + } + else + { + techniquePrefix += "_"; + } + _shaderNames[blob] = techniquePrefix + MakeShaderName(blob.Shader); } } } From f368384da290c447dd34c2fb64a1d232141e7659 Mon Sep 17 00:00:00 2001 From: lanyi Date: Thu, 22 Jul 2021 16:52:54 +0200 Subject: [PATCH 67/72] fix texXXlod --- .../DX9Shader/Decompiler/HlslWriter.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index b3743d4..1fc521f 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -243,9 +243,9 @@ void WriteAssignment(string sourceFormat, params SourceOperand[] args) WriteLine(destinationModifier, destination, sourceResult); } - void WriteTextureAssignment(string postFix, SourceOperand sampler, SourceOperand uv, int extraUvDimensions, params SourceOperand[] others) + void WriteTextureAssignment(string postFix, SourceOperand sampler, SourceOperand uv, int? dimension, params SourceOperand[] others) { - var (operation, dimension) = sampler.SamplerType switch + var (operation, defaultDimension) = sampler.SamplerType switch { ParameterType.Sampler1D => ("tex1D", 1), ParameterType.Sampler2D => ("tex2D", 2), @@ -254,15 +254,16 @@ void WriteTextureAssignment(string postFix, SourceOperand sampler, SourceOperand ParameterType.Sampler => ("texUnknown", 4), _ => throw new InvalidOperationException(sampler.SamplerType.ToString()) }; + dimension ??= defaultDimension; var args = new SourceOperand[others.Length + 2]; var uvSwizzle = uv.Swizzle.TrimStart('.'); if(uvSwizzle.Length == 0) { uvSwizzle = "xyzw"; } - if(uvSwizzle.Length > dimension + extraUvDimensions) + if(uvSwizzle.Length > dimension) { - uv.Swizzle = "." + uvSwizzle.Substring(0, dimension + extraUvDimensions); + uv.Swizzle = "." + uvSwizzle.Substring(0, dimension.Value); } args[0] = sampler; args[1] = uv; @@ -449,7 +450,7 @@ void WriteTextureAssignment(string postFix, SourceOperand sampler, SourceOperand case Opcode.Tex: if(_shader.MajorVersion > 1) { - WriteTextureAssignment(string.Empty, GetSourceName(instruction, 2), GetSourceName(instruction, 1), 0); + WriteTextureAssignment(string.Empty, GetSourceName(instruction, 2), GetSourceName(instruction, 1), null); } // shader model 1 else if(_shader.MinorVersion >= 4) @@ -478,11 +479,11 @@ void WriteTextureAssignment(string postFix, SourceOperand sampler, SourceOperand SamplerType = samplerType }; - WriteTextureAssignment(string.Empty, samplerOperand, uvOperand, 0); + WriteTextureAssignment(string.Empty, samplerOperand, uvOperand, null); } break; case Opcode.TexLDL: - WriteTextureAssignment("lod", GetSourceName(instruction, 2), GetSourceName(instruction, 1), 1); + WriteTextureAssignment("lod", GetSourceName(instruction, 2), GetSourceName(instruction, 1), 4); break; case Opcode.Comment: { From f3a6b4f2cbcfc13ad938e6f6e6255669d0704eb6 Mon Sep 17 00:00:00 2001 From: lanyi Date: Thu, 22 Jul 2021 18:00:23 +0200 Subject: [PATCH 68/72] fix sincos --- .../DX9Shader/Decompiler/HlslWriter.cs | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs index 1fc521f..850998d 100644 --- a/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs +++ b/src/DXDecompiler/DX9Shader/Decompiler/HlslWriter.cs @@ -235,9 +235,13 @@ void WriteAssignment(string sourceFormat, params SourceOperand[] args) sourceResult = string.Format(sourceFormat, args); } // if we cannot "edit" the swizzles, we need to apply write masks on the source result - else if(sourceResult.Last() != ')') + else { - sourceResult = $"({sourceResult}){writeMask}"; + if(sourceResult.Last() != ')') + { + sourceResult = $"({sourceResult})"; + } + sourceResult += writeMask; } } WriteLine(destinationModifier, destination, sourceResult); @@ -442,7 +446,21 @@ void WriteTextureAssignment(string postFix, SourceOperand sampler, SourceOperand WriteAssignment("({0} < {1}) ? 1 : 0", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); break; case Opcode.SinCos: - WriteLine("sincos({1}, {0}, {0});", GetDestinationNameWithWriteMask(instruction), GetSourceName(instruction, 1)); + { + var writeMask = instruction.GetDestinationWriteMask(); + var values = new List(2); + if(writeMask.HasFlag(ComponentFlags.X)) + { + values.Add("cos({0})"); + } + if(writeMask.HasFlag(ComponentFlags.Y)) + { + values.Add("sin({0})"); + } + var source = string.Join(", ", values); + source = values.Count > 1 ? $"float{values.Count}({source})" : source; + WriteAssignment(source, GetSourceName(instruction, 1)); + } break; case Opcode.Sub: WriteAssignment("{0} - {1}", GetSourceName(instruction, 1), GetSourceName(instruction, 2)); @@ -499,6 +517,10 @@ void WriteTextureAssignment(string postFix, SourceOperand sampler, SourceOperand Indent++; break; case Opcode.TexKill: + if(instruction.GetDestinationResultModifier() is not ResultModifier.None) + { + throw new NotImplementedException("Result modifier in texkill"); + } WriteLine("clip({0});", GetDestinationNameWithWriteMask(instruction)); break; case Opcode.DSX: From 77d0d36c025b04a5fe61cca659c36706e395dae7 Mon Sep 17 00:00:00 2001 From: lanyi Date: Fri, 23 Jul 2021 13:58:30 +0200 Subject: [PATCH 69/72] upgrade to .NET 5 --- .../DXDecompiler.Tests.csproj | 130 ++-------- .../Properties/AssemblyInfo.cs | 25 -- .../TestFixtures/DX9Tests.cs | 6 +- src/DXDecompiler.Tests/packages.config | 12 - src/DXDecompiler/DXDecompiler.csproj | 12 +- src/DXDecompiler/packages.config | 4 - src/DXDecompilerCmd/DXDecompilerCmd.csproj | 66 +----- .../Properties/AssemblyInfo.cs | 25 -- .../PublishProfiles/FolderProfile.pubxml | 18 ++ src/DebugParser/DebugParser.csproj | 222 +----------------- src/DebugParser/Properties/AssemblyInfo.cs | 25 -- 11 files changed, 66 insertions(+), 479 deletions(-) delete mode 100644 src/DXDecompiler.Tests/packages.config delete mode 100644 src/DXDecompiler/packages.config create mode 100644 src/DXDecompilerCmd/Properties/PublishProfiles/FolderProfile.pubxml diff --git a/src/DXDecompiler.Tests/DXDecompiler.Tests.csproj b/src/DXDecompiler.Tests/DXDecompiler.Tests.csproj index 5e25502..0cf563a 100644 --- a/src/DXDecompiler.Tests/DXDecompiler.Tests.csproj +++ b/src/DXDecompiler.Tests/DXDecompiler.Tests.csproj @@ -1,87 +1,36 @@ - - - - - + - Debug - AnyCPU - {3D19DAC1-E727-43BA-A264-A64408B4D598} - Library - Properties - DXDecompiler.Tests - DXDecompiler.Tests - v4.7.2 - 512 - + net5.0 ..\ - true - - + SlimShader.Tests + SlimShader.Tests + Copyright © 2012 + bin\$(Configuration)\ - true full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 x86 true - false pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false + - - ..\packages\NUnit.3.12.0\lib\net40\nunit.framework.dll - - - ..\packages\SharpDX.4.2.0\lib\net40\SharpDX.dll - - - ..\packages\SharpDX.D3DCompiler.4.2.0\lib\net40\SharpDX.D3DCompiler.dll - - - ..\packages\SharpDX.Direct3D10.4.2.0\lib\net40\SharpDX.Direct3D10.dll - - - ..\packages\SharpDX.Direct3D11.4.2.0\lib\net40\SharpDX.Direct3D11.dll - - - ..\packages\SharpDX.Direct3D11.Effects.4.2.0\lib\net40\SharpDX.Direct3D11.Effects.dll - - - ..\packages\SharpDX.Direct3D9.4.2.0\lib\net40\SharpDX.Direct3D9.dll - - - ..\packages\SharpDX.DXGI.4.2.0\lib\net40\SharpDX.DXGI.dll - - - - - - - - - - + + + + + + + + + + + + - - - - - - PreserveNewest @@ -129,7 +78,6 @@ - PreserveNewest @@ -280,24 +228,11 @@ PreserveNewest - - PreserveNewest - - - - - - - - - - - PreserveNewest @@ -1105,14 +1040,8 @@ - - {43b35c43-bd28-4731-81aa-772ee71c08b2} - DebugParser - - - {D75528F5-54F9-4E31-94F6-03C194F74CF2} - DXDecompiler - + + @@ -1272,21 +1201,4 @@ PreserveNewest - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/src/DXDecompiler.Tests/Properties/AssemblyInfo.cs b/src/DXDecompiler.Tests/Properties/AssemblyInfo.cs index 4166b3b..e6beb04 100644 --- a/src/DXDecompiler.Tests/Properties/AssemblyInfo.cs +++ b/src/DXDecompiler.Tests/Properties/AssemblyInfo.cs @@ -1,18 +1,6 @@ using System.Reflection; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("SlimShader.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("SlimShader.Tests")] -[assembly: AssemblyCopyright("Copyright © 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. @@ -20,16 +8,3 @@ // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("27e58db9-828f-4641-a0a5-50f283be5fda")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/DXDecompiler.Tests/TestFixtures/DX9Tests.cs b/src/DXDecompiler.Tests/TestFixtures/DX9Tests.cs index c2049be..cbee201 100644 --- a/src/DXDecompiler.Tests/TestFixtures/DX9Tests.cs +++ b/src/DXDecompiler.Tests/TestFixtures/DX9Tests.cs @@ -46,7 +46,7 @@ private static IEnumerable TestShaders /// /// Compare ASM output produced by fxc.exe and SlimShader. /// - [TestCaseSource("TestShaders")] + [TestCaseSource(nameof(TestShaders))] public void AsmMatchesFxc(string relPath) { string file = $"{ShaderDirectory}/{relPath}"; @@ -78,7 +78,7 @@ public void AsmMatchesFxc(string relPath) /// /// Compare ASM output produced by fxc.exe and SlimShader. /// - [TestCaseSource("TestShaders")] + [TestCaseSource(nameof(TestShaders))] public void Decompile(string relPath) { string file = $"{ShaderDirectory}/{relPath}"; @@ -93,7 +93,7 @@ public void Decompile(string relPath) // Assert. } - [TestCaseSource("TestShaders")] + [TestCaseSource(nameof(TestShaders))] public void DumpStructure(string relPath) { string file = $"{ShaderDirectory}/{relPath}"; diff --git a/src/DXDecompiler.Tests/packages.config b/src/DXDecompiler.Tests/packages.config deleted file mode 100644 index 44a3a3d..0000000 --- a/src/DXDecompiler.Tests/packages.config +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/src/DXDecompiler/DXDecompiler.csproj b/src/DXDecompiler/DXDecompiler.csproj index 0725884..203e30f 100644 --- a/src/DXDecompiler/DXDecompiler.csproj +++ b/src/DXDecompiler/DXDecompiler.csproj @@ -1,16 +1,6 @@  - - netstandard2.0 + netstandard2.1 9.0 - - - AnyCPU - - - - AnyCPU - - \ No newline at end of file diff --git a/src/DXDecompiler/packages.config b/src/DXDecompiler/packages.config deleted file mode 100644 index 6e62391..0000000 --- a/src/DXDecompiler/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/DXDecompilerCmd/DXDecompilerCmd.csproj b/src/DXDecompilerCmd/DXDecompilerCmd.csproj index bfa9708..3189ee8 100644 --- a/src/DXDecompilerCmd/DXDecompilerCmd.csproj +++ b/src/DXDecompilerCmd/DXDecompilerCmd.csproj @@ -1,71 +1,27 @@ - - - + - Debug - AnyCPU - {FD3DB932-913B-476C-9C11-99DA23BC4B3E} Exe - DXDecompilerCmd - DXDecompilerCmd - v4.7 - 512 true - true - v4.7.2 - 512 + net5.0 true - true - + DXDecompilerCmd + DXDecompilerCmd + Copyright © 2020 + bin\$(Configuration)\ - AnyCPU - true full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - AnyCPU pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - + + + - - - - - + + - - - - - - {43b35c43-bd28-4731-81aa-772ee71c08b2} - DebugParser - - - {d75528f5-54f9-4e31-94f6-03c194f74cf2} - DXDecompiler - - - \ No newline at end of file diff --git a/src/DXDecompilerCmd/Properties/AssemblyInfo.cs b/src/DXDecompilerCmd/Properties/AssemblyInfo.cs index 849b337..366304f 100644 --- a/src/DXDecompilerCmd/Properties/AssemblyInfo.cs +++ b/src/DXDecompilerCmd/Properties/AssemblyInfo.cs @@ -1,18 +1,6 @@ using System.Reflection; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("DXDecompilerCmd")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("DXDecompilerCmd")] -[assembly: AssemblyCopyright("Copyright © 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. @@ -20,16 +8,3 @@ // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("fd3db932-913b-476c-9c11-99da23bc4b3e")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/DXDecompilerCmd/Properties/PublishProfiles/FolderProfile.pubxml b/src/DXDecompilerCmd/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 0000000..bffe74b --- /dev/null +++ b/src/DXDecompilerCmd/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,18 @@ + + + + + Release + Any CPU + bin\Release\net5.0\publish\ + FileSystem + net5.0 + win-x86 + true + True + False + True + + \ No newline at end of file diff --git a/src/DebugParser/DebugParser.csproj b/src/DebugParser/DebugParser.csproj index 2466ef3..0df8172 100644 --- a/src/DebugParser/DebugParser.csproj +++ b/src/DebugParser/DebugParser.csproj @@ -1,211 +1,19 @@ - - - + - Debug - AnyCPU - {43B35C43-BD28-4731-81AA-772EE71C08B2} - Library - Properties - DebugParser - DebugParser - v4.7.2 - 512 - true + net5.0 + DebugParser + DebugParser + Copyright © 2020 + bin\$(Configuration)\ - true full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + True True Resources.resx @@ -213,20 +21,14 @@ - - {d75528f5-54f9-4e31-94f6-03c194f74cf2} - DXDecompiler - + - - ResXFileCodeGenerator - Resources.Designer.cs - + - + + + - - \ No newline at end of file diff --git a/src/DebugParser/Properties/AssemblyInfo.cs b/src/DebugParser/Properties/AssemblyInfo.cs index f59d186..c3239c8 100644 --- a/src/DebugParser/Properties/AssemblyInfo.cs +++ b/src/DebugParser/Properties/AssemblyInfo.cs @@ -1,18 +1,6 @@ using System.Reflection; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("DebugParser")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("DebugParser")] -[assembly: AssemblyCopyright("Copyright © 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. @@ -20,16 +8,3 @@ // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("43b35c43-bd28-4731-81aa-772ee71c08b2")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] From c9511988377630caefc3014705abf30d19e8807f Mon Sep 17 00:00:00 2001 From: Lanyi Date: Thu, 17 Feb 2022 23:16:25 +0100 Subject: [PATCH 70/72] Set up Github Actions --- .github/workflows/build.yaml | 38 ++++++++++++++++++++++++++++++++++++ .github/workflows/test.yaml | 32 ++++++++++++++++++++++++++++++ README.md | 3 +++ 3 files changed, 73 insertions(+) create mode 100644 .github/workflows/build.yaml create mode 100644 .github/workflows/test.yaml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..cfd4abe --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,38 @@ +name: build + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: windows-latest + defaults: + run: + working-directory: ./src + steps: + - uses: actions/checkout@v2 + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 5.0.x + - name: Restore previous dependencies + uses: actions/cache@v2 + with: + path: ~/.nuget/packages + key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} + - name: Restore dependencies + run: dotnet restore DXDecompiler.sln + - name: Build + id: build + run: dotnet build --no-restore + - name: Publish + id: publish + run: dotnet publish DXDecompilerCmd --no-build + - name: Upload published files + uses: actions/upload-artifact@v2.3.1 + with: + path: src/DXDecompilerCmd/bin/**/publish diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..f22cfaf --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,32 @@ +name: test + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + test: + runs-on: windows-latest + defaults: + run: + working-directory: ./src + steps: + - uses: actions/checkout@v2 + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 5.0.x + - name: Restore previous dependencies + uses: actions/cache@v2 + with: + path: ~/.nuget/packages + key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} + - name: Restore dependencies + run: dotnet restore DXDecompiler.sln + - name: Build + id: build + run: dotnet build --no-restore + - name: Test + run: dotnet test DXDecompiler.Tests --no-build --verbosity normal diff --git a/README.md b/README.md index ebfc1d6..d489bdf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ DXDecompiler ========== +[![build](https://github.com/lanyizi/DXDecompiler/actions/workflows/build.yaml/badge.svg)](https://github.com/lanyizi/DXDecompiler/actions/workflows/build.yaml) +[![test](https://github.com/lanyizi/DXDecompiler/actions/workflows/test.yaml/badge.svg)](https://github.com/lanyizi/DXDecompiler/actions/workflows/test.yaml) + DXDecompiler is a Direct3D shader bytecode decompiler for .NET. It is in currently in very early development. Acknowledgements From 5a4deec6722aea4374e4f2e03893a1df96c7010a Mon Sep 17 00:00:00 2001 From: Lanyi Date: Thu, 17 Feb 2022 23:32:52 +0100 Subject: [PATCH 71/72] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index d489bdf..a3453d3 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ DXDecompiler DXDecompiler is a Direct3D shader bytecode decompiler for .NET. It is in currently in very early development. +Latest CI build artifact can be downloaded [here](https://nightly.link/lanyizi/DXDecompiler/workflows/build.yaml/master/artifact.zip). +Requires [.NET 5 Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/5.0/runtime). + Acknowledgements ---------------- DXDecompiler uses code and research from the following projects. From ebeea208436599d1efb175941374ff05f2c26c71 Mon Sep 17 00:00:00 2001 From: Lanyi Date: Tue, 24 May 2022 09:12:08 +0200 Subject: [PATCH 72/72] Allow manual triggering of Github Action --- .github/workflows/build.yaml | 1 + .github/workflows/test.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index cfd4abe..17c6788 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -5,6 +5,7 @@ on: branches: [ master ] pull_request: branches: [ master ] + workflow_dispatch: jobs: build: diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f22cfaf..7f0fb22 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -5,6 +5,7 @@ on: branches: [ master ] pull_request: branches: [ master ] + workflow_dispatch: jobs: test: