Files
ImHex-Patterns/patterns/popcap_luc.hexpat
Lucia a525160243 patterns: Added pattern for PopCap's Lua bytecode (#458)
* patterns: Added PopCap's proprietary Lua bytecode pattern.

* updated README to include new pattern

* fixed README link

* patterns/popcap_luc.hexpat: fixed comments and sources

* patterns/popcap_luc.hexpat: Changed datatype of filename to be more clear about its structure

* patterns/popcap_luc.hexpat: fixed improper handling of Nil type and added test file
2025-12-05 21:14:53 +01:00

174 lines
4.7 KiB
Rust

#pragma author gluecia
#pragma description PopCap's proprietary Lua bytecode (.luc)
#pragma endian little
#pragma magic [ 1B 4C 75 61 56 ] @ 0x00;
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.
PLStr filename [[name("Source Name")]];
};
// 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;
// theres definitely a better way to handle the Nil case
(cType::Nil): u8 val = 0;
(_): 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://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
// 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 @ $;