From f860547f934d52f267bea48a416390bae3dd32ae Mon Sep 17 00:00:00 2001 From: luciacover Date: Thu, 23 Oct 2025 18:33:20 -0400 Subject: [PATCH 1/6] patterns: Added PopCap's proprietary Lua bytecode pattern. --- patterns/popcap_luc.hexpat | 178 +++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 patterns/popcap_luc.hexpat diff --git a/patterns/popcap_luc.hexpat b/patterns/popcap_luc.hexpat new file mode 100644 index 00000000..b87fc668 --- /dev/null +++ b/patterns/popcap_luc.hexpat @@ -0,0 +1,178 @@ +#pragma author gluecia +#pragma description PopCap's proprietary Lua bytecode (.luc) +#pragma endian little +#pragma magic [ 1B 4C 75 61 56 ] @ 0x00; + +// original credit: https://github.com/wxarmstrong/PopLua-Disassembler +// i used this code as a jumping off point for figuring out how the +// format works, though there is quite a bit of my own work here as well. + +import std.io; +import std.sys; + +using PLChunk; +using PLConst; +using PLLocal; +using PLOp; +using PLStr; +using cType; + + +struct PLHeader { + char magic[5]; + padding[18]; + + // *technically* this is a part of the first top level chunk, though + // it is essentially just a part of the header since it only appears there. + u32 filenameSize [[hidden]]; + char16 filename[filenameSize] [[name("Filename")]]; +}; + + +// LOCALS +fn fmtLocal(PLLocal l) { + return l.name; +}; + +struct PLLocal { + u32 nameLength [[hidden]]; + char16 name[nameLength] [[name("Local")]]; + u32 begin [[name("Begin")]]; + u32 end [[name("End")]]; +} [[format("fmtLocal")]]; + + +// CONSTANTS +// constant types +fn fmtCType(cType t) { + match (t) { + (cType::Nil): return "nil"; + (cType::Int): return "int"; + (cType::Float): return "float"; + (cType::Str): return "str"; + (_): return "unknown"; + } +}; + +enum cType : u8 { + Nil, + Float = 3, + Int, + Str +} [[format("fmtCType")]]; + +// lua strings +fn fmtStr(PLStr s) { + return std::format("\"{}\"", s.val); +}; + + +struct PLStr { + u32 len [[hidden]]; + if (len > 0) + char16 val[len]; +} [[format("fmtStr"), sealed]]; + +// constants struct +fn fmtConst(PLConst c) { + if (c.type == cType::Nil) { + return c.type; + } else { + return std::format("{} ({})", c.val, c.type); + } +}; + +struct PLConst { + cType type; + match (type) { + (cType::Float): double val; + (cType::Int): s32 val; + (cType::Str): PLStr val; + (cType::Nil): continue; + (_): std::error("unknown cType given"); + } +} [[format("fmtConst"), sealed]]; + + +// PROTOTYPES +struct PLPrototype { + padding[4]; + PLChunk chunk; +} [[inline]]; + + +// UPVALUES +struct PLUpvalue { + u32 len [[hidden]]; + char name[len] [[name("Upvalue")]]; +} [[inline]]; + + +// OPERANDS +fn fmtPLOp(PLOp o) { + return o.opcode; +}; + +// source: https://github.com/wxarmstrong/PopLua-Disassembler/blob/master/popOp.cpp +enum PLOpType : u8 { + MOVE, LOADK, LOADBOOL, LOADNIL, GETUVPVAL, + GETGLOBAL, GETTABLE, SETGLOBAL, SETUPVAL, + SETTABLE, NEWTABLE, SELF, ADD, SUB, MUL, + DIV, MOD, POW, UNM, NOT, SIZ, CONCAT, JMP, + EQ, LT, LE, TEST, CALL, TAILCALL, RETURN, + FORLOOP, TFORLOOP, TFORPREP, SETLIST, + SETLISTO, CLOSE, ALTSELF, CONSTGLOBAL, + CONSTTABLE, DEFGLOBAL, DEFTABLE, + SETSELFORGLOBAL, GETSELFORGLOBAL, + SELFORGLOBAL, CALLSELFORGLOBAL, + TAILCALLSELFORGLOBAL, INT, BREAK, + CLOSURE +}; + +// this is for a different version of lua and its bytecode, but helped a LOT +// https://poga.github.io/lua53-notes/bytecode.html +struct PLOp { + u32 raw [[hidden]]; + PLOpType opcode = raw & 0x3F [[name("Opcode"), hidden, export]]; + u32 rawOperands = raw >> 6; + u8 opA = rawOperands & 0xFF [[name("Operand A"), export]]; + + match(opcode) { + (PLOpType::LOADK | PLOpType::CLOSURE): u32 Bx = (rawOperands >> 8) [[name("Operand Bx"), export]]; + // 131071 is the bias for sBx to make it signed, formed from (2^18 - 1) >> 1 + // where 2^18 - 1 is the maximum value for an 18 bit number + (PLOpType::JMP | PLOpType::FORLOOP | PLOpType::TFORLOOP): s32 opSBx = (rawOperands >> 8) - 131071 [[name("Operand sBx"), comment("Signed displacement added to the PC."), export]]; + (_): { + u16 opB = (rawOperands >> 8) & 0xFF01 [[name("Operand B"), export]]; + u16 opC = (rawOperands >> 17) & 0xFF01 [[name("Operand C"), export]]; + } + } +} [[format("fmtPLOp")]]; + +// CHUNKS +struct PLChunk { + char intro[0xC] [[name("Chunk Intro"), comment("Holds information on the function, but the format is unknown.")]]; + + u32 sizecode [[name("Instructions Count")]]; + u32 linesArray[sizecode] [[name("Line Numbers"), comment("The line numbers for the given operations, this should be interpreted with the operations array.")]]; + + u32 sizelocvars [[name("Locals Count")]]; + PLLocal localsArray[sizelocvars] [[name("Locals")]]; + + u32 sizeupvalues [[name("Upvalues Count")]]; + PLUpvalue upvalsArray[sizeupvalues] [[name("Upvalues")]]; + + u32 sizek [[name("Constants Count")]]; + PLConst constsArray[sizek] [[name("Constants")]]; + + u32 sizep [[name("Prototype Count")]]; + PLPrototype protoArray[sizep] [[name("Prototypes")]]; + + u32 sizecodeVerify [[hidden]]; + std::assert(sizecode == sizecodeVerify, std::format("sizecode ({}) did not match sizecodeVerify ({})!", sizecode, sizecodeVerify)); + + PLOp opsArray[sizecode] [[name("Instructions"), comment("The raw bytes for operations, should be interpreted with the line numbers array.")]]; +}; + +PLHeader Header @ 0x0; +PLChunk Body @ $; \ No newline at end of file From 2b73f338c536d3a349fa0786bc23799a1a0540dc Mon Sep 17 00:00:00 2001 From: luciacover Date: Thu, 23 Oct 2025 18:36:25 -0400 Subject: [PATCH 2/6] updated README to include new pattern --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3d2f2d70..6794caa5 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | Java Class | `application/x-java-applet` | [`patterns/java_class.hexpat`](patterns/java_class.hexpat) | Java Class files | | JPEG | `image/jpeg` | [`patterns/jpeg.hexpat`](patterns/jpeg.hexpat) | JPEG Image Format | | LOC | | [`patterns/loc.hexpat`](patterns/loc.hexpat) | Minecraft Legacy Console Edition Language file | +| LUC | | [`patterns/popcap_luc.hexpat`] | PopCap's proprietary Lua bytecode | | Lua 5.1 | | [`patterns/lua51.hexpat`](patterns/lua51.hexpat) | Lua 5.1 bytecode | | Lua 5.2 | | [`patterns/lua52.hexpat`](patterns/lua52.hexpat) | Lua 5.2 bytecode | | Lua 5.3 | | [`patterns/lua53.hexpat`](patterns/lua53.hexpat) | Lua 5.3 bytecode | From 1ebf43a4be93593ca53638dacb934a97104e8ca4 Mon Sep 17 00:00:00 2001 From: luciacover Date: Thu, 23 Oct 2025 18:37:10 -0400 Subject: [PATCH 3/6] fixed README link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6794caa5..83cf318f 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | Java Class | `application/x-java-applet` | [`patterns/java_class.hexpat`](patterns/java_class.hexpat) | Java Class files | | JPEG | `image/jpeg` | [`patterns/jpeg.hexpat`](patterns/jpeg.hexpat) | JPEG Image Format | | LOC | | [`patterns/loc.hexpat`](patterns/loc.hexpat) | Minecraft Legacy Console Edition Language file | -| LUC | | [`patterns/popcap_luc.hexpat`] | PopCap's proprietary Lua bytecode | +| LUC | | [`patterns/popcap_luc.hexpat`](patterns/popcap_luc.hexpat) | PopCap's proprietary Lua bytecode | | Lua 5.1 | | [`patterns/lua51.hexpat`](patterns/lua51.hexpat) | Lua 5.1 bytecode | | Lua 5.2 | | [`patterns/lua52.hexpat`](patterns/lua52.hexpat) | Lua 5.2 bytecode | | Lua 5.3 | | [`patterns/lua53.hexpat`](patterns/lua53.hexpat) | Lua 5.3 bytecode | From 0c530b65bb02b1f56d86126fa78c914de4802e27 Mon Sep 17 00:00:00 2001 From: luciacover Date: Thu, 23 Oct 2025 18:42:39 -0400 Subject: [PATCH 4/6] patterns/popcap_luc.hexpat: fixed comments and sources --- patterns/popcap_luc.hexpat | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/patterns/popcap_luc.hexpat b/patterns/popcap_luc.hexpat index b87fc668..09b4d4b9 100644 --- a/patterns/popcap_luc.hexpat +++ b/patterns/popcap_luc.hexpat @@ -3,10 +3,6 @@ #pragma endian little #pragma magic [ 1B 4C 75 61 56 ] @ 0x00; -// original credit: https://github.com/wxarmstrong/PopLua-Disassembler -// i used this code as a jumping off point for figuring out how the -// format works, though there is quite a bit of my own work here as well. - import std.io; import std.sys; @@ -21,7 +17,7 @@ using cType; struct PLHeader { char magic[5]; padding[18]; - + // *technically* this is a part of the first top level chunk, though // it is essentially just a part of the header since it only appears there. u32 filenameSize [[hidden]]; @@ -130,13 +126,13 @@ enum PLOpType : u8 { }; // this is for a different version of lua and its bytecode, but helped a LOT -// https://poga.github.io/lua53-notes/bytecode.html +// https://archive.org/details/a-no-frills-intro-to-lua-5.1-vm-instructions struct PLOp { u32 raw [[hidden]]; PLOpType opcode = raw & 0x3F [[name("Opcode"), hidden, export]]; u32 rawOperands = raw >> 6; u8 opA = rawOperands & 0xFF [[name("Operand A"), export]]; - + match(opcode) { (PLOpType::LOADK | PLOpType::CLOSURE): u32 Bx = (rawOperands >> 8) [[name("Operand Bx"), export]]; // 131071 is the bias for sBx to make it signed, formed from (2^18 - 1) >> 1 @@ -155,24 +151,24 @@ struct PLChunk { u32 sizecode [[name("Instructions Count")]]; u32 linesArray[sizecode] [[name("Line Numbers"), comment("The line numbers for the given operations, this should be interpreted with the operations array.")]]; - + u32 sizelocvars [[name("Locals Count")]]; PLLocal localsArray[sizelocvars] [[name("Locals")]]; - + u32 sizeupvalues [[name("Upvalues Count")]]; PLUpvalue upvalsArray[sizeupvalues] [[name("Upvalues")]]; - + u32 sizek [[name("Constants Count")]]; PLConst constsArray[sizek] [[name("Constants")]]; - + u32 sizep [[name("Prototype Count")]]; PLPrototype protoArray[sizep] [[name("Prototypes")]]; - + u32 sizecodeVerify [[hidden]]; std::assert(sizecode == sizecodeVerify, std::format("sizecode ({}) did not match sizecodeVerify ({})!", sizecode, sizecodeVerify)); - + PLOp opsArray[sizecode] [[name("Instructions"), comment("The raw bytes for operations, should be interpreted with the line numbers array.")]]; }; PLHeader Header @ 0x0; -PLChunk Body @ $; \ No newline at end of file +PLChunk Body @ $; From 6405af560f81b365aba7ea52dbfd8947229aa105 Mon Sep 17 00:00:00 2001 From: luciacover Date: Fri, 24 Oct 2025 11:44:24 -0400 Subject: [PATCH 5/6] patterns/popcap_luc.hexpat: Changed datatype of filename to be more clear about its structure --- patterns/popcap_luc.hexpat | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/patterns/popcap_luc.hexpat b/patterns/popcap_luc.hexpat index 09b4d4b9..f4fd62d3 100644 --- a/patterns/popcap_luc.hexpat +++ b/patterns/popcap_luc.hexpat @@ -20,8 +20,7 @@ struct PLHeader { // *technically* this is a part of the first top level chunk, though // it is essentially just a part of the header since it only appears there. - u32 filenameSize [[hidden]]; - char16 filename[filenameSize] [[name("Filename")]]; + PLStr filename [[name("Source Name")]]; }; @@ -171,4 +170,4 @@ struct PLChunk { }; PLHeader Header @ 0x0; -PLChunk Body @ $; +PLChunk Body @ $; \ No newline at end of file From d2fed081fe2023c4e1b8e769a6e1d74b88d9135e Mon Sep 17 00:00:00 2001 From: luciacover Date: Fri, 24 Oct 2025 12:15:02 -0400 Subject: [PATCH 6/6] patterns/popcap_luc.hexpat: fixed improper handling of Nil type and added test file --- patterns/popcap_luc.hexpat | 3 ++- tests/patterns/test_data/popcap_luc.hexpat.luc | Bin 0 -> 105 bytes 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 tests/patterns/test_data/popcap_luc.hexpat.luc diff --git a/patterns/popcap_luc.hexpat b/patterns/popcap_luc.hexpat index f4fd62d3..dca18cf7 100644 --- a/patterns/popcap_luc.hexpat +++ b/patterns/popcap_luc.hexpat @@ -83,7 +83,8 @@ struct PLConst { (cType::Float): double val; (cType::Int): s32 val; (cType::Str): PLStr val; - (cType::Nil): continue; + // theres definitely a better way to handle the Nil case + (cType::Nil): u8 val = 0; (_): std::error("unknown cType given"); } } [[format("fmtConst"), sealed]]; diff --git a/tests/patterns/test_data/popcap_luc.hexpat.luc b/tests/patterns/test_data/popcap_luc.hexpat.luc new file mode 100644 index 0000000000000000000000000000000000000000..7d95d37154e94dcc69f9673d772847ebf20877ef GIT binary patch literal 105 zcmb34DNPJxWMW}qVdLQB