From b44ae49ab911eb8e176466a7417f8b5ede4ec93f Mon Sep 17 00:00:00 2001 From: "Jeremiah J." Date: Fri, 31 Jul 2026 13:42:13 -0500 Subject: [PATCH 1/7] bugfixed call args going missing on vararg calls so any call like print("hi", ...) or return fmt(...) got decompiled as var1() with no args, cause the B==0 branch looped `for(i=A; i= 1 means B-1 fixed args, + // but B==0 means "everything from A+1 up to the top of the stack" which + // some earlier VARARG/CALL pushed there, so we dig that top up. + this.Op3 = WriteArgs(Instr.A, Instr.B); break; case LuaOpcode.TAILCALL: // TODO: there is something off here? @@ -300,32 +279,9 @@ public void SetMain(LuaInstruction Instr = null) // Function Name this.Op1 = $"return var{Instr.A}"; // func name only (used lateron) //this.Op2 = $"({Instr.A+1}"; - // Function Args - if (Instr.B == 0) - { - // func parms range from A+1 to B (B = top of stack) - this.Op3 = $"("; - for (int i = Instr.A; i < Instr.B; i++) - //for (int i = Instr.A; i < Instr.A + Instr.B - 1; ++i) - { - this.Op3 += $"var{i + 1}"; - if (i < Instr.A + Instr.B - 2) - this.Op3 += ", "; - } - this.Op3 += ")"; - } - else - { - this.Op3 = $"("; - for (int i = Instr.A; i < Instr.A + Instr.B - 1; i++) - //for (int i = Instr.A; i < Instr.A + Instr.B - 1; ++i) - { - this.Op3 += $"var{i + 1}"; - if (i < Instr.A + Instr.B - 2) - this.Op3 += ", "; - } - this.Op3 += ")"; - } + + // Function Args (same B logic as CALL above) + this.Op3 = WriteArgs(Instr.A, Instr.B); break; case LuaOpcode.RETURN: // this gets overwritten by an 'end' afterwards in case its the last RETURN value of a func @@ -503,6 +459,46 @@ public string WriteIndex(int value, bool useLocalKeyword = true) } } + private string WriteArgs(int funcReg, int b) + { + int argCount; + + if (b == 0) + argCount = FindStackTop(funcReg) - funcReg; // A+1 .. top + else + argCount = b - 1; // exactly B-1 fixed args + + string result = "("; + for (int i = 0; i < argCount; i++) + { + result += $"var{funcReg + 1 + i}"; + if (i < argCount - 1) + result += ", "; + } + result += ")"; + return result; + } + + + // NOTE: when B/C == 0 the args reach up to the 'top' of the stack, which a + // preceding VARARG (B==0) or CALL (C==0) pushed there. Walk back to find it + // and return the register that top sits on + private int FindStackTop(int funcReg) + { + int idx = this.Func.Instructions.IndexOf(this.Instr); + for (int i = idx - 1; i >= 0; i--) + { + LuaInstruction prev = this.Func.Instructions[i]; + if (prev.A <= funcReg) + continue; // not part of this call's args + if (prev.OpCode == LuaOpcode.VARARG && prev.B == 0) + return prev.A; // varargs spill from here to top + if (prev.OpCode == LuaOpcode.CALL && prev.C == 0) + return prev.A; // call results spill from here to top + } + return funcReg; // nothing found, no args + } + public int ToIndex(int value, out bool isConstant) { // this is the logic from lua's source code (lopcodes.h) From 042e8967f1e3743481118d3c243ee773af70f213 Mon Sep 17 00:00:00 2001 From: "Jeremiah J." Date: Fri, 31 Jul 2026 03:10:29 -0500 Subject: [PATCH 2/7] bugfixed vararg names coming out blank / squished local a, b = ... was decompiling as "local var1var2 = ..." (no comma) and print("hi", ...) gave "local = ..." with an empty name. the B==0 loop was `for(i=A; i=1 gives B-1 names, B==0 is a variable amount so we show one var = ... and move on. --- src/Decompiler/LuaScriptLine.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Decompiler/LuaScriptLine.cs b/src/Decompiler/LuaScriptLine.cs index c2f4c47..98523c0 100644 --- a/src/Decompiler/LuaScriptLine.cs +++ b/src/Decompiler/LuaScriptLine.cs @@ -355,12 +355,19 @@ public void SetMain(LuaInstruction Instr = null) case LuaOpcode.VARARG: this.Func.ScriptFunction.HasVarargs = true; this.Op1 = "local "; - for (int i = Instr.A; i < Instr.A+Instr.B-1; i++) + int varargCount = Instr.B == 0 ? 1 : Instr.B - 1; + for (int i = 0; i < varargCount; i++) { - this.Op2 += $"var{i}"; - if (i < Instr.B - 2) + this.Op2 += $"var{Instr.A + i}"; + if (i < varargCount - 1) this.Op2 += ", "; } + // for (int i = Instr.A; i < Instr.A+Instr.B-1; i++) + // { + // this.Op2 += $"var{i}"; + // if (i < Instr.B - 2) // <-- bug #1: should be A+b-2, not B-2 + // this.Op2 += ", "; + // } this.Op2 += " = ..."; break; default: From b410d57cf97643cc1835d310e0eaffecb6a76acf Mon Sep 17 00:00:00 2001 From: "Jeremiah J." Date: Fri, 31 Jul 2026 03:22:28 -0500 Subject: [PATCH 3/7] bugfixed local keyword leaking into math expressions stuff like local x = a + b came out as local x = local a + local b whenever a/b were declared somewhere that doesnt register them as 'used' (varargs, call results, etc). WriteConstant was calling WriteIndex without telling it these are reads, so WriteIndex slapped a 'local' on the first time it saw em. WriteIndex already takes a useLocalKeyword flag, so just pass false from WriteConstant since arithmetic operands are always reads. --- src/Decompiler/LuaScriptLine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Decompiler/LuaScriptLine.cs b/src/Decompiler/LuaScriptLine.cs index 98523c0..ef60214 100644 --- a/src/Decompiler/LuaScriptLine.cs +++ b/src/Decompiler/LuaScriptLine.cs @@ -414,7 +414,7 @@ private string WriteConstant(int index, LuaFunction targetFunc = null) if (index > 255 && targetFunc.Constants[index - 256] != null) return targetFunc.Constants[index - 256].ToString(); else - return WriteIndex(index); + return WriteIndex(index, false); } public void SetFunctionRef(LuaFunction func) From 7eee205a744e160a3ba54c1d8014daaed05df2f3 Mon Sep 17 00:00:00 2001 From: "Jeremiah J." Date: Fri, 31 Jul 2026 03:42:05 -0500 Subject: [PATCH 4/7] bugfixed closures showing IDK_SHIT_WENT_MISSING_BRO closures got their name from the child function's ScriptFunction, but the child didnt exist yet when we built the parent lines (parents get created first, kids after), so UpdateClosures ran too early and everything came out as IDK_SHIT_WENT_MISSING_BRO. also fixed WriteF only naming direct children (grandchildren came out blank) by just recursing WriteF on the child instead of creating it + looping its kids separately. --- src/Decompiler/LuaDecompiler.cs | 35 ++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/Decompiler/LuaDecompiler.cs b/src/Decompiler/LuaDecompiler.cs index 16338a2..80f281c 100644 --- a/src/Decompiler/LuaDecompiler.cs +++ b/src/Decompiler/LuaDecompiler.cs @@ -28,12 +28,28 @@ public LuaDecompiler(LuaDecoder decoder) WriteFile(); } + private void ResClosureNames() + { + // walk every function we built and point each CLOSURE line at its child's name + // GetFunctionRef() got hooked up by the child in HandleUpvalues(), so by now its set + foreach (var f in this.LuaFunctions) + foreach (var line in f.GetLines()) + if (line.Instr != null && line.Instr.OpCode == LuaOpcode.CLOSURE && line.GetFunctionRef() != null) + line.Op3 = line.GetFunctionRef().ScriptFunction.Name; + } + private void WriteFile() { // create Script Functions this.Decoder.File.Function.Name = "CRoot"; // or main? WriteF(this.Decoder.File.Function); + // NOTE: closures point at child functions that didnt exist yet while + // building the parent lines, so UpdateClosures ran too early and left them as + // 'IDK_SHIT_WENT_MISSING_BRO' lol, now that every function is created we loop + // back over the closure lines and finally fill in their names + ResClosureNames(); + // NOTE: this is done on GetText //// allign/format/whatever each function //foreach (var f in this.LuaFunctions) @@ -45,14 +61,23 @@ private void WriteF(LuaFunction func) { CreateScripFunction(func); // root first and then inside ? // TODO: write functions on CLOSURE and not each list? + // for (int i = 0; i < func.Functions.Count; i++) + // { + // func.Functions[i].Name = func.Name + "_" + i; // set default name + // CreateScripFunction(func.Functions[i], func.ScriptFunction.Depth+1); + // foreach (var f in func.Functions[i].Functions) + // { + // WriteF(f); // children NOTE: write children in body of parent? + // } + // } + for (int i = 0; i < func.Functions.Count; i++) { func.Functions[i].Name = func.Name + "_" + i; // set default name - CreateScripFunction(func.Functions[i], func.ScriptFunction.Depth+1); - foreach (var f in func.Functions[i].Functions) - { - WriteF(f); // children NOTE: write children in body of parent? - } + // NOTE: recurse into the child itself so it (and any of ITS children) get + // created and named too. before this only the direct children were getting a name + // but grand children came out as "IDK_SHIT_WENT_MISSING_BRO" + WriteF(func.Functions[i]); } } From d85920952ce5435fbca0d09bed7a59348e89bc75 Mon Sep 17 00:00:00 2001 From: "Jeremiah J." Date: Fri, 31 Jul 2026 12:26:41 -0500 Subject: [PATCH 5/7] bugfixed upvalues showing garbage instead of their name GETUPVAL came out as "unk0" and SETUPVAL had its operands backwards + treated the value as a constant. turns out luac stashes the upvalue names in the debug info and the decoder already reads em into DebugUpvalues, we just never used it lol. --- src/Decompiler/LuaScriptLine.cs | 37 ++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/Decompiler/LuaScriptLine.cs b/src/Decompiler/LuaScriptLine.cs index ef60214..cc84631 100644 --- a/src/Decompiler/LuaScriptLine.cs +++ b/src/Decompiler/LuaScriptLine.cs @@ -92,14 +92,27 @@ public void SetMain(LuaInstruction Instr = null) } break; case LuaOpcode.GETUPVAL: - // NOTE: child get local from parent + // NOTE: R(A) = Upvalue[B], luac keeps the upvalue names in debug info, + // so grab that for real names instead of "unk" this.Op1 = WriteIndex(Instr.A); this.Op2 = " = "; - this.Op3 = WriteIndex(Instr.B); + + if (this.Func.DebugUpvalues.Count > Instr.B) + this.Op3 = this.Func.DebugUpvalues[Instr.B].TrimEnd('\0'); + else if (this.Func.Upvalues.Count > Instr.B) + this.Op3 = this.Func.Upvalues[Instr.B].ToString(); // if no debug info + else + this.Op3 = WriteIndex(Instr.B); + + + // NOTE: child get local from parent + // this.Op1 = WriteIndex(Instr.A); + // this.Op2 = " = "; + // this.Op3 = WriteIndex(Instr.B); // TODO: figure if an upvalue is a function or not? - if(this.Func.Upvalues.Count > Instr.B) - this.Op3 = this.Func.Upvalues[Instr.B].ToString(); + // if(this.Func.Upvalues.Count > Instr.B) + // this.Op3 = this.Func.Upvalues[Instr.B].ToString(); //this.Op3 = this.Func.Upvalues[Instr.B].ToString().Substring(1, this.Func.Upvalues[Instr.B].ToString().Length - 2); // this is legit for prototypes etc break; case LuaOpcode.GETGLOBAL: @@ -118,10 +131,20 @@ public void SetMain(LuaInstruction Instr = null) this.Op3 = $"var{Instr.A}"; break; case LuaOpcode.SETUPVAL: - // NOTE: child writes to parent locals - this.Op1 = $"{WriteIndex(Instr.B)}"; // no? + // NOTE: Upvalue[B] = R(A), the name lives in debug info, and + // the value is just whatever reg is in A (a read, so no local) + if (this.Func.DebugUpvalues.Count > Instr.B) + this.Op1 = this.Func.DebugUpvalues[Instr.B].TrimEnd('\0'); + else + this.Op1 = $"var{Instr.B}"; + this.Op2 = " = "; - this.Op3 = $"{GetConstant(Instr.A)}"; + this.Op3 = WriteIndex(Instr.A, false); + + // NOTE: child writes to parent locals + // this.Op1 = $"{WriteIndex(Instr.B)}"; // no? + // this.Op2 = " = "; + // this.Op3 = $"{GetConstant(Instr.A)}"; break; case LuaOpcode.SETTABLE: this.Op1 = $"{WriteIndex(Instr.A)}[{WriteIndex(Instr.B)}]"; From a3657de85a89e0c2a8ae240fe7450983b70e57f7 Mon Sep 17 00:00:00 2001 From: "Jeremiah J." Date: Fri, 31 Jul 2026 13:52:13 -0500 Subject: [PATCH 6/7] added a proper cli so you can actually run this on linux + fixed .gitignore --- .gitignore | 2 + LuaToolkit.sln | 117 ++++++++++++++++++++++++++++++++++++++ cli/.gitignore | 2 + cli/LuaToolkit.Cli.csproj | 12 ++++ cli/Program.cs | 36 ++++++++++++ 5 files changed, 169 insertions(+) create mode 100644 cli/.gitignore create mode 100644 cli/LuaToolkit.Cli.csproj create mode 100644 cli/Program.cs diff --git a/.gitignore b/.gitignore index a76dad1..f7aa6a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .vs/* packages/* +bin/ +obj/ \ No newline at end of file diff --git a/LuaToolkit.sln b/LuaToolkit.sln index 72e39a2..3b8b742 100644 --- a/LuaToolkit.sln +++ b/LuaToolkit.sln @@ -17,54 +17,170 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Recompiler", "demo\Recompil EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "demo\LuaDecompiler-Web\Web.csproj", "{30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cli", "cli", "{342A349A-D343-8551-4064-2E2800C39E13}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuaToolkit.Cli", "cli\LuaToolkit.Cli.csproj", "{BE269D79-DA30-4DBB-8C84-7D5978E70C0C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 TestDebug|Any CPU = TestDebug|Any CPU + TestDebug|x64 = TestDebug|x64 + TestDebug|x86 = TestDebug|x86 WebRelease|Any CPU = WebRelease|Any CPU + WebRelease|x64 = WebRelease|x64 + WebRelease|x86 = WebRelease|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {3C38C968-E485-4154-B6BD-E8446651307F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3C38C968-E485-4154-B6BD-E8446651307F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C38C968-E485-4154-B6BD-E8446651307F}.Debug|x64.ActiveCfg = Debug|Any CPU + {3C38C968-E485-4154-B6BD-E8446651307F}.Debug|x64.Build.0 = Debug|Any CPU + {3C38C968-E485-4154-B6BD-E8446651307F}.Debug|x86.ActiveCfg = Debug|Any CPU + {3C38C968-E485-4154-B6BD-E8446651307F}.Debug|x86.Build.0 = Debug|Any CPU {3C38C968-E485-4154-B6BD-E8446651307F}.Release|Any CPU.ActiveCfg = Release|Any CPU {3C38C968-E485-4154-B6BD-E8446651307F}.Release|Any CPU.Build.0 = Release|Any CPU + {3C38C968-E485-4154-B6BD-E8446651307F}.Release|x64.ActiveCfg = Release|Any CPU + {3C38C968-E485-4154-B6BD-E8446651307F}.Release|x64.Build.0 = Release|Any CPU + {3C38C968-E485-4154-B6BD-E8446651307F}.Release|x86.ActiveCfg = Release|Any CPU + {3C38C968-E485-4154-B6BD-E8446651307F}.Release|x86.Build.0 = Release|Any CPU {3C38C968-E485-4154-B6BD-E8446651307F}.TestDebug|Any CPU.ActiveCfg = TestDebug|Any CPU {3C38C968-E485-4154-B6BD-E8446651307F}.TestDebug|Any CPU.Build.0 = TestDebug|Any CPU + {3C38C968-E485-4154-B6BD-E8446651307F}.TestDebug|x64.ActiveCfg = TestDebug|Any CPU + {3C38C968-E485-4154-B6BD-E8446651307F}.TestDebug|x64.Build.0 = TestDebug|Any CPU + {3C38C968-E485-4154-B6BD-E8446651307F}.TestDebug|x86.ActiveCfg = TestDebug|Any CPU + {3C38C968-E485-4154-B6BD-E8446651307F}.TestDebug|x86.Build.0 = TestDebug|Any CPU {3C38C968-E485-4154-B6BD-E8446651307F}.WebRelease|Any CPU.ActiveCfg = WebRelease|Any CPU {3C38C968-E485-4154-B6BD-E8446651307F}.WebRelease|Any CPU.Build.0 = WebRelease|Any CPU + {3C38C968-E485-4154-B6BD-E8446651307F}.WebRelease|x64.ActiveCfg = WebRelease|Any CPU + {3C38C968-E485-4154-B6BD-E8446651307F}.WebRelease|x64.Build.0 = WebRelease|Any CPU + {3C38C968-E485-4154-B6BD-E8446651307F}.WebRelease|x86.ActiveCfg = WebRelease|Any CPU + {3C38C968-E485-4154-B6BD-E8446651307F}.WebRelease|x86.Build.0 = WebRelease|Any CPU {9A45374E-5548-4C3A-AF06-C31A962295DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9A45374E-5548-4C3A-AF06-C31A962295DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A45374E-5548-4C3A-AF06-C31A962295DD}.Debug|x64.ActiveCfg = Debug|Any CPU + {9A45374E-5548-4C3A-AF06-C31A962295DD}.Debug|x64.Build.0 = Debug|Any CPU + {9A45374E-5548-4C3A-AF06-C31A962295DD}.Debug|x86.ActiveCfg = Debug|Any CPU + {9A45374E-5548-4C3A-AF06-C31A962295DD}.Debug|x86.Build.0 = Debug|Any CPU {9A45374E-5548-4C3A-AF06-C31A962295DD}.Release|Any CPU.ActiveCfg = Release|Any CPU {9A45374E-5548-4C3A-AF06-C31A962295DD}.Release|Any CPU.Build.0 = Release|Any CPU + {9A45374E-5548-4C3A-AF06-C31A962295DD}.Release|x64.ActiveCfg = Release|Any CPU + {9A45374E-5548-4C3A-AF06-C31A962295DD}.Release|x64.Build.0 = Release|Any CPU + {9A45374E-5548-4C3A-AF06-C31A962295DD}.Release|x86.ActiveCfg = Release|Any CPU + {9A45374E-5548-4C3A-AF06-C31A962295DD}.Release|x86.Build.0 = Release|Any CPU {9A45374E-5548-4C3A-AF06-C31A962295DD}.TestDebug|Any CPU.ActiveCfg = TestDebug|Any CPU {9A45374E-5548-4C3A-AF06-C31A962295DD}.TestDebug|Any CPU.Build.0 = TestDebug|Any CPU + {9A45374E-5548-4C3A-AF06-C31A962295DD}.TestDebug|x64.ActiveCfg = TestDebug|Any CPU + {9A45374E-5548-4C3A-AF06-C31A962295DD}.TestDebug|x64.Build.0 = TestDebug|Any CPU + {9A45374E-5548-4C3A-AF06-C31A962295DD}.TestDebug|x86.ActiveCfg = TestDebug|Any CPU + {9A45374E-5548-4C3A-AF06-C31A962295DD}.TestDebug|x86.Build.0 = TestDebug|Any CPU {9A45374E-5548-4C3A-AF06-C31A962295DD}.WebRelease|Any CPU.ActiveCfg = WebRelease|Any CPU {9A45374E-5548-4C3A-AF06-C31A962295DD}.WebRelease|Any CPU.Build.0 = WebRelease|Any CPU + {9A45374E-5548-4C3A-AF06-C31A962295DD}.WebRelease|x64.ActiveCfg = WebRelease|Any CPU + {9A45374E-5548-4C3A-AF06-C31A962295DD}.WebRelease|x64.Build.0 = WebRelease|Any CPU + {9A45374E-5548-4C3A-AF06-C31A962295DD}.WebRelease|x86.ActiveCfg = WebRelease|Any CPU + {9A45374E-5548-4C3A-AF06-C31A962295DD}.WebRelease|x86.Build.0 = WebRelease|Any CPU {C10726C0-8D2C-4F92-9961-5008D1B6940E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C10726C0-8D2C-4F92-9961-5008D1B6940E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C10726C0-8D2C-4F92-9961-5008D1B6940E}.Debug|x64.ActiveCfg = Debug|Any CPU + {C10726C0-8D2C-4F92-9961-5008D1B6940E}.Debug|x64.Build.0 = Debug|Any CPU + {C10726C0-8D2C-4F92-9961-5008D1B6940E}.Debug|x86.ActiveCfg = Debug|Any CPU + {C10726C0-8D2C-4F92-9961-5008D1B6940E}.Debug|x86.Build.0 = Debug|Any CPU {C10726C0-8D2C-4F92-9961-5008D1B6940E}.Release|Any CPU.ActiveCfg = Release|Any CPU {C10726C0-8D2C-4F92-9961-5008D1B6940E}.Release|Any CPU.Build.0 = Release|Any CPU + {C10726C0-8D2C-4F92-9961-5008D1B6940E}.Release|x64.ActiveCfg = Release|Any CPU + {C10726C0-8D2C-4F92-9961-5008D1B6940E}.Release|x64.Build.0 = Release|Any CPU + {C10726C0-8D2C-4F92-9961-5008D1B6940E}.Release|x86.ActiveCfg = Release|Any CPU + {C10726C0-8D2C-4F92-9961-5008D1B6940E}.Release|x86.Build.0 = Release|Any CPU {C10726C0-8D2C-4F92-9961-5008D1B6940E}.TestDebug|Any CPU.ActiveCfg = TestDebug|Any CPU {C10726C0-8D2C-4F92-9961-5008D1B6940E}.TestDebug|Any CPU.Build.0 = TestDebug|Any CPU + {C10726C0-8D2C-4F92-9961-5008D1B6940E}.TestDebug|x64.ActiveCfg = TestDebug|Any CPU + {C10726C0-8D2C-4F92-9961-5008D1B6940E}.TestDebug|x64.Build.0 = TestDebug|Any CPU + {C10726C0-8D2C-4F92-9961-5008D1B6940E}.TestDebug|x86.ActiveCfg = TestDebug|Any CPU + {C10726C0-8D2C-4F92-9961-5008D1B6940E}.TestDebug|x86.Build.0 = TestDebug|Any CPU {C10726C0-8D2C-4F92-9961-5008D1B6940E}.WebRelease|Any CPU.ActiveCfg = WebRelease|Any CPU {C10726C0-8D2C-4F92-9961-5008D1B6940E}.WebRelease|Any CPU.Build.0 = WebRelease|Any CPU + {C10726C0-8D2C-4F92-9961-5008D1B6940E}.WebRelease|x64.ActiveCfg = WebRelease|Any CPU + {C10726C0-8D2C-4F92-9961-5008D1B6940E}.WebRelease|x64.Build.0 = WebRelease|Any CPU + {C10726C0-8D2C-4F92-9961-5008D1B6940E}.WebRelease|x86.ActiveCfg = WebRelease|Any CPU + {C10726C0-8D2C-4F92-9961-5008D1B6940E}.WebRelease|x86.Build.0 = WebRelease|Any CPU {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.Debug|x64.ActiveCfg = Debug|Any CPU + {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.Debug|x64.Build.0 = Debug|Any CPU + {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.Debug|x86.ActiveCfg = Debug|Any CPU + {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.Debug|x86.Build.0 = Debug|Any CPU {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.Release|Any CPU.ActiveCfg = Release|Any CPU {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.Release|Any CPU.Build.0 = Release|Any CPU + {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.Release|x64.ActiveCfg = Release|Any CPU + {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.Release|x64.Build.0 = Release|Any CPU + {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.Release|x86.ActiveCfg = Release|Any CPU + {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.Release|x86.Build.0 = Release|Any CPU {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.TestDebug|Any CPU.ActiveCfg = TestDebug|Any CPU {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.TestDebug|Any CPU.Build.0 = TestDebug|Any CPU + {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.TestDebug|x64.ActiveCfg = TestDebug|Any CPU + {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.TestDebug|x64.Build.0 = TestDebug|Any CPU + {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.TestDebug|x86.ActiveCfg = TestDebug|Any CPU + {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.TestDebug|x86.Build.0 = TestDebug|Any CPU {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.WebRelease|Any CPU.ActiveCfg = WebRelease|Any CPU {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.WebRelease|Any CPU.Build.0 = WebRelease|Any CPU + {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.WebRelease|x64.ActiveCfg = WebRelease|Any CPU + {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.WebRelease|x64.Build.0 = WebRelease|Any CPU + {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.WebRelease|x86.ActiveCfg = WebRelease|Any CPU + {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB}.WebRelease|x86.Build.0 = WebRelease|Any CPU {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.Debug|Any CPU.Build.0 = Debug|Any CPU + {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.Debug|x64.ActiveCfg = Debug|Any CPU + {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.Debug|x64.Build.0 = Debug|Any CPU + {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.Debug|x86.ActiveCfg = Debug|Any CPU + {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.Debug|x86.Build.0 = Debug|Any CPU {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.Release|Any CPU.ActiveCfg = Release|Any CPU {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.Release|Any CPU.Build.0 = Release|Any CPU + {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.Release|x64.ActiveCfg = Release|Any CPU + {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.Release|x64.Build.0 = Release|Any CPU + {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.Release|x86.ActiveCfg = Release|Any CPU + {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.Release|x86.Build.0 = Release|Any CPU {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.TestDebug|Any CPU.ActiveCfg = Debug|Any CPU {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.TestDebug|Any CPU.Build.0 = Debug|Any CPU + {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.TestDebug|x64.ActiveCfg = TestDebug|Any CPU + {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.TestDebug|x64.Build.0 = TestDebug|Any CPU + {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.TestDebug|x86.ActiveCfg = TestDebug|Any CPU + {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.TestDebug|x86.Build.0 = TestDebug|Any CPU {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.WebRelease|Any CPU.ActiveCfg = Release|Any CPU {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.WebRelease|Any CPU.Build.0 = Release|Any CPU + {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.WebRelease|x64.ActiveCfg = WebRelease|Any CPU + {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.WebRelease|x64.Build.0 = WebRelease|Any CPU + {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.WebRelease|x86.ActiveCfg = WebRelease|Any CPU + {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77}.WebRelease|x86.Build.0 = WebRelease|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.Debug|x64.ActiveCfg = Debug|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.Debug|x64.Build.0 = Debug|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.Debug|x86.ActiveCfg = Debug|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.Debug|x86.Build.0 = Debug|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.Release|Any CPU.Build.0 = Release|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.Release|x64.ActiveCfg = Release|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.Release|x64.Build.0 = Release|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.Release|x86.ActiveCfg = Release|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.Release|x86.Build.0 = Release|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.TestDebug|Any CPU.ActiveCfg = Debug|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.TestDebug|Any CPU.Build.0 = Debug|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.TestDebug|x64.ActiveCfg = Debug|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.TestDebug|x64.Build.0 = Debug|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.TestDebug|x86.ActiveCfg = Debug|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.TestDebug|x86.Build.0 = Debug|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.WebRelease|Any CPU.ActiveCfg = Debug|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.WebRelease|Any CPU.Build.0 = Debug|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.WebRelease|x64.ActiveCfg = Debug|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.WebRelease|x64.Build.0 = Debug|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.WebRelease|x86.ActiveCfg = Debug|Any CPU + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C}.WebRelease|x86.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -75,6 +191,7 @@ Global {C10726C0-8D2C-4F92-9961-5008D1B6940E} = {6645C52B-19A3-4114-81F8-DEBA70779569} {CE7FDAB8-2F8A-4E39-AFC9-900D3458B9CB} = {6645C52B-19A3-4114-81F8-DEBA70779569} {30E34A9B-E065-4E33-BB4F-4F3B23C9DB77} = {6645C52B-19A3-4114-81F8-DEBA70779569} + {BE269D79-DA30-4DBB-8C84-7D5978E70C0C} = {342A349A-D343-8551-4064-2E2800C39E13} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {CA86C95D-BB88-475B-B999-4332121C087F} diff --git a/cli/.gitignore b/cli/.gitignore new file mode 100644 index 0000000..cd42ee3 --- /dev/null +++ b/cli/.gitignore @@ -0,0 +1,2 @@ +bin/ +obj/ diff --git a/cli/LuaToolkit.Cli.csproj b/cli/LuaToolkit.Cli.csproj new file mode 100644 index 0000000..68714e4 --- /dev/null +++ b/cli/LuaToolkit.Cli.csproj @@ -0,0 +1,12 @@ + + + Exe + net8.0 + LatestMajor + disable + luadec + + + + + \ No newline at end of file diff --git a/cli/Program.cs b/cli/Program.cs new file mode 100644 index 0000000..f279757 --- /dev/null +++ b/cli/Program.cs @@ -0,0 +1,36 @@ +using System; +using System.IO; +using LuaToolkit.Core; +using LuaToolkit.Disassembler; +using LuaToolkit.Decompiler; + +namespace LuaToolkit.Cli +{ + class Program + { + static int Main(string[] args) + { + if (args.Length < 1) + { + Console.Error.WriteLine("usage: luadec [more.luac ...]"); + Console.Error.WriteLine(" decompiles compiled Lua 5.1 (.luac) files."); + return 1; + } + + foreach (var path in args) + { + if (!File.Exists(path)) + { + Console.Error.WriteLine($"File not found: {path}"); + continue; + } + + var luac = new LuaCFile(File.ReadAllBytes(path)); + var decoder = new LuaDecoder(luac); + var decompiler = new LuaDecompiler(decoder); + Console.WriteLine(decompiler.LuaScript); + } + return 0; + } + } +} \ No newline at end of file From 5ff0f12b9b48aad1132dcf91847831fcc1c95661 Mon Sep 17 00:00:00 2001 From: "Jeremiah J." Date: Fri, 31 Jul 2026 13:57:08 -0500 Subject: [PATCH 7/7] decompiler output fixes + a cross-platform cli - CALL/TAILCALL args, vararg names, local-in-expressions, closure names, upvalue names - adds cli/ (luadec file.luac) since the demos are all windows-path hardcoded - .gitignore now ignores bin/obj --- .gitignore | 11 +++++++++-- cli/.gitignore | 4 ++-- cli/Program.cs | 8 ++++---- src/Decompiler/LuaScriptLine.cs | 1 + 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index f7aa6a5..a251088 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,11 @@ .vs/* packages/* -bin/ -obj/ \ No newline at end of file + +[Bb]in/ +[Oo]bj/ + +.idea/ +*.user + +packages/ +*.nupkg diff --git a/cli/.gitignore b/cli/.gitignore index cd42ee3..d45db4d 100644 --- a/cli/.gitignore +++ b/cli/.gitignore @@ -1,2 +1,2 @@ -bin/ -obj/ +[Bb]in/ +[Oo]bj/ \ No newline at end of file diff --git a/cli/Program.cs b/cli/Program.cs index f279757..b9aed48 100644 --- a/cli/Program.cs +++ b/cli/Program.cs @@ -12,16 +12,16 @@ static int Main(string[] args) { if (args.Length < 1) { - Console.Error.WriteLine("usage: luadec [more.luac ...]"); - Console.Error.WriteLine(" decompiles compiled Lua 5.1 (.luac) files."); + Console.Error.WriteLine("usuage: luadec [more.luac ...]"); + Console.Error.WriteLine(" decompiles compiled Lua 5.1 (.luac) back to source code."); return 1; } - foreach (var path in args) + foreach(var path in args) { if (!File.Exists(path)) { - Console.Error.WriteLine($"File not found: {path}"); + Console.Error.WriteLine($"[!] File not found: {path}"); continue; } diff --git a/src/Decompiler/LuaScriptLine.cs b/src/Decompiler/LuaScriptLine.cs index cc84631..db733bf 100644 --- a/src/Decompiler/LuaScriptLine.cs +++ b/src/Decompiler/LuaScriptLine.cs @@ -291,6 +291,7 @@ public void SetMain(LuaInstruction Instr = null) this.Op2 = $"var{Instr.A}"; // func name only (used lateron) // Function Args + // NOTE: B tells us how many args there are; B >= 1 means B-1 fixed args, // but B==0 means "everything from A+1 up to the top of the stack" which // some earlier VARARG/CALL pushed there, so we dig that top up.