-- This Script is Part of the Prometheus Obfuscator by levno-710 -- -- emit.lua -- -- This Script contains the container function body emission for the compiler. local Ast = require("prometheus.ast"); local Scope = require("prometheus.scope"); local util = require("prometheus.util"); local constants = require("prometheus.compiler.constants"); local AstKind = Ast.AstKind; local MAX_REGS = constants.MAX_REGS; return function(Compiler) local function hasAnyEntries(tbl) return type(tbl) == "table" and next(tbl) ~= nil; end local function unionLookupTables(a, b) local out = {}; for k, v in pairs(a or {}) do out[k] = v; end for k, v in pairs(b or {}) do out[k] = v; end return out; end local function canMergeParallelAssignmentStatements(statA, statB) if type(statA) ~= "table" or type(statB) ~= "table" then return false; end if statA.usesUpvals or statB.usesUpvals then return false; end local a = statA.statement; local b = statB.statement; if type(a) ~= "table" or type(b) ~= "table" then return false; end if a.kind ~= AstKind.AssignmentStatement or b.kind ~= AstKind.AssignmentStatement then return false; end if type(a.lhs) ~= "table" or type(a.rhs) ~= "table" or type(b.lhs) ~= "table" or type(b.rhs) ~= "table" then return false; end if #a.lhs ~= #a.rhs or #b.lhs ~= #b.rhs then return false; end -- Avoid merging vararg/call assignments because they can affect multi-return behavior. local function hasUnsafeRhs(rhsList) for _, rhsExpr in ipairs(rhsList) do if type(rhsExpr) ~= "table" then return true; end local kind = rhsExpr.kind; if kind == AstKind.FunctionCallExpression or kind == AstKind.PassSelfFunctionCallExpression or kind == AstKind.VarargExpression then return true; end end return false; end if hasUnsafeRhs(a.rhs) or hasUnsafeRhs(b.rhs) then return false; end local aReads = type(statA.reads) == "table" and statA.reads or {}; local aWrites = type(statA.writes) == "table" and statA.writes or {}; local bReads = type(statB.reads) == "table" and statB.reads or {}; local bWrites = type(statB.writes) == "table" and statB.writes or {}; -- Allow merging even if one statement has no writes (e.g., x = o(x) style assignments) -- Only require that at least one of them has writes if not hasAnyEntries(aWrites) and not hasAnyEntries(bWrites) then return false; end for r in pairs(aReads) do if bWrites[r] then return false; end end for r, b in pairs(aWrites) do if bWrites[r] or bReads[r] then return false; end end return true; end local function mergeParallelAssignmentStatements(statA, statB) local lhs = {}; local rhs = {}; local aLhs, bLhs = statA.statement.lhs, statB.statement.lhs; local aRhs, bRhs = statA.statement.rhs, statB.statement.rhs; for i = 1, #aLhs do lhs[i] = aLhs[i]; end for i = 1, #bLhs do lhs[#aLhs + i] = bLhs[i]; end for i = 1, #aRhs do rhs[i] = aRhs[i]; end for i = 1, #bRhs do rhs[#aRhs + i] = bRhs[i]; end return { statement = Ast.AssignmentStatement(lhs, rhs), writes = unionLookupTables(statA.writes, statB.writes), reads = unionLookupTables(statA.reads, statB.reads), usesUpvals = statA.usesUpvals or statB.usesUpvals, }; end local function mergeAdjacentParallelAssignments(blockstats) local merged = {}; local i = 1; while i <= #blockstats do local stat = blockstats[i]; i = i + 1; while i <= #blockstats and canMergeParallelAssignmentStatements(stat, blockstats[i]) do stat = mergeParallelAssignmentStatements(stat, blockstats[i]); i = i + 1; end table.insert(merged, stat); end return merged; end function Compiler:emitContainerFuncBody() local blocks = {}; util.shuffle(self.blocks); for i, block in ipairs(self.blocks) do local id = block.id; local blockstats = block.statements; for i = 2, #blockstats do local stat = blockstats[i]; local reads = stat.reads; local writes = stat.writes; local maxShift = 0; local usesUpvals = stat.usesUpvals; for shift = 1, i - 1 do local stat2 = blockstats[i - shift]; if stat2.usesUpvals and usesUpvals then break; end local reads2 = stat2.reads; local writes2 = stat2.writes; local f = true; for r, b in pairs(reads2) do if(writes[r]) then f = false; break; end end if f then for r, b in pairs(writes2) do if(writes[r]) then f = false; break; end if(reads[r]) then f = false; break; end end end if not f then break end maxShift = shift; end local shift = math.random(0, maxShift); for j = 1, shift do blockstats[i - j], blockstats[i - j + 1] = blockstats[i - j + 1], blockstats[i - j]; end end local mergedBlockStats = mergeAdjacentParallelAssignments(blockstats); for _=1, 7 do mergedBlockStats = mergeAdjacentParallelAssignments(mergedBlockStats); end blockstats = {}; for _, stat in ipairs(mergedBlockStats) do table.insert(blockstats, stat.statement); end local block = { id = id, index = i, block = Ast.Block(blockstats, block.scope) } table.insert(blocks, block); blocks[id] = block; end table.sort(blocks, function(a, b) return a.id < b.id end); -- Build a strict threshold condition between adjacent block IDs. -- Using a midpoint avoids exact-id comparisons while preserving dispatch. local function buildBlockThresholdCondition(scope, leftId, rightId, useAndOr) local bound = math.floor((leftId + rightId) / 2); local posExpr = self:pos(scope); local boundExpr = Ast.NumberExpression(bound); if useAndOr then -- Kept for compatibility with caller variations. return Ast.LessThanExpression(posExpr, boundExpr); else local variant = math.random(1, 2); if variant == 1 then return Ast.LessThanExpression(posExpr, boundExpr); else return Ast.GreaterThanExpression(boundExpr, posExpr); end end end -- Build an elseif chain for a range of blocks local function buildElseifChain(tb, l, r, pScope) -- Handle invalid range by returning an empty block if r < l then local emptyScope = Scope:new(pScope); return Ast.Block({}, emptyScope); end local len = r - l + 1; -- For single block if len == 1 then tb[l].block.scope:setParent(pScope); return tb[l].block; end -- For small ranges, use elseif chain if len <= 4 then local ifScope = Scope:new(pScope); local elseifs = {}; -- First block uses the first midpoint threshold tb[l].block.scope:setParent(ifScope); local firstCondition = buildBlockThresholdCondition(ifScope, tb[l].id, tb[l + 1].id, false); local firstBlock = tb[l].block; -- Middle blocks use their upper midpoint threshold for i = l + 1, r - 1 do tb[i].block.scope:setParent(ifScope); local condition = buildBlockThresholdCondition(ifScope, tb[i].id, tb[i + 1].id, false); table.insert(elseifs, { condition = condition, body = tb[i].block }); end -- Last block becomes else tb[r].block.scope:setParent(ifScope); local elseBlock = tb[r].block; return Ast.Block({ Ast.IfStatement(firstCondition, firstBlock, elseifs, elseBlock); }, ifScope); end -- For larger ranges, use binary split with and/or chaining local mid = l + math.ceil(len / 2); local leftMaxId = tb[mid - 1].id; local rightMinId = tb[mid].id; -- Float-safe split: any bound strictly between adjacent IDs works. -- Midpoint avoids integer-only math.random(min, max) behavior. local bound = math.floor((leftMaxId + rightMinId) / 2); local ifScope = Scope:new(pScope); local lBlock = buildElseifChain(tb, l, mid - 1, ifScope); local rBlock = buildElseifChain(tb, mid, r, ifScope); -- Randomly choose between different condition styles local condStyle = math.random(1, 3); local condition; local trueBlock, falseBlock; if condStyle == 1 then -- pos < bound condition = Ast.LessThanExpression(self:pos(ifScope), Ast.NumberExpression(bound)); trueBlock, falseBlock = lBlock, rBlock; elseif condStyle == 2 then -- bound > pos condition = Ast.GreaterThanExpression(Ast.NumberExpression(bound), self:pos(ifScope)); trueBlock, falseBlock = lBlock, rBlock; else -- Equivalent split using strict > with branches reversed. condition = Ast.GreaterThanExpression(self:pos(ifScope), Ast.NumberExpression(bound)); trueBlock, falseBlock = rBlock, lBlock; end return Ast.Block({ Ast.IfStatement(condition, trueBlock, {}, falseBlock); }, ifScope); end local whileBody = buildElseifChain(blocks, 1, #blocks, self.containerFuncScope); if self.whileScope then -- Ensure whileScope is properly connected self.whileScope:setParent(self.containerFuncScope); end self.whileScope:addReferenceToHigherScope(self.containerFuncScope, self.returnVar, 1); self.whileScope:addReferenceToHigherScope(self.containerFuncScope, self.posVar); self.containerFuncScope:addReferenceToHigherScope(self.scope, self.unpackVar); local declarations = { self.returnVar, } for i, var in pairs(self.registerVars) do if(i ~= MAX_REGS) then table.insert(declarations, var); end end local stats = {} if self.maxUsedRegister >= MAX_REGS then table.insert(stats, Ast.LocalVariableDeclaration(self.containerFuncScope, {self.registerVars[MAX_REGS]}, {Ast.TableConstructorExpression({})})); end table.insert(stats, Ast.LocalVariableDeclaration(self.containerFuncScope, util.shuffle(declarations), {})); table.insert(stats, Ast.WhileStatement(whileBody, Ast.VariableExpression(self.containerFuncScope, self.posVar))); table.insert(stats, Ast.AssignmentStatement({ Ast.AssignmentVariable(self.containerFuncScope, self.posVar) }, { Ast.LenExpression(Ast.VariableExpression(self.containerFuncScope, self.detectGcCollectVar)) })); table.insert(stats, Ast.ReturnStatement{ Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.unpackVar), { Ast.VariableExpression(self.containerFuncScope, self.returnVar) }); }); return Ast.Block(stats, self.containerFuncScope); end end