luau-obfuscator 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +56 -56
- package/dist/index.cjs +2186 -95
- package/dist/index.d.cts +34 -2
- package/dist/index.d.ts +34 -2
- package/dist/index.js +2186 -95
- package/generated/grammers.luau +13643 -0
- package/generated/tests.luau +5567 -0
- package/package.json +2 -2
- package/scripts/test.js +24 -8
- package/{scripts/example.luau → smoketest/grammers.luau} +1174 -1174
- package/smoketest/tests.luau +115 -0
- package/src/config.ts +2 -1
- package/src/passes/ConstantArray.ts +90 -89
- package/src/passes/EncryptNumbers.ts +122 -65
- package/src/passes/EncryptStrings.ts +162 -82
- package/src/passes/GlobalMapping.ts +193 -193
- package/src/passes/InsertJunk.ts +184 -184
- package/src/passes/Minify.ts +5 -5
- package/src/passes/NumbersToExpressions.ts +183 -2
- package/src/passes/RenameVariables.ts +2 -9
- package/src/passes/StringsToExpressions.ts +195 -6
- package/src/passes/StripTypes.ts +191 -191
- package/src/passes/Vmify.ts +87 -0
- package/src/passes/WrapInFunction.ts +31 -31
- package/src/passes/nodeFactory.ts +36 -1
- package/src/passes/vmify/chunk.ts +54 -0
- package/src/passes/vmify/compiler.ts +1026 -0
- package/src/passes/vmify/names.ts +72 -0
- package/src/passes/vmify/opcodes.ts +80 -0
- package/src/passes/vmify/registers.ts +63 -0
- package/src/passes/vmify/runtime.ts +334 -0
- package/src/passes/vmify/scope-walk.ts +175 -0
- package/src/passes/vmify/serialize.ts +68 -0
- package/src/passes/walk.ts +141 -43
- package/src/pipeline.ts +5 -6
- package/tsconfig.json +20 -20
- package/tsup.config.ts +9 -9
- package/generated/final.luau +0 -1029
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vmify 런타임/청크에서 쓰는 모든 이름(전역 식별자 + Luau 테이블 필드 키)을 빌드마다
|
|
3
|
+
* 무작위로 새로 뽑는다. opcode 번호 셔플(opcodes.ts)만으로는 부족한 게, "__VM_GLOBALS",
|
|
4
|
+
* "__vm_execute", "frame.R/K/code" 같은 이름 자체가 모든 산출물에서 동일하면 그 문자열
|
|
5
|
+
* 만으로 grep 한 방에 VM 시그니처를 찾아낼 수 있기 때문. 여기서 만든 이름들은
|
|
6
|
+
* runtime.ts(핸들러 본문), serialize.ts(Proto/Instr을 Luau 테이블로 찍을 때 쓰는 키),
|
|
7
|
+
* Vmify.ts(바깥에서 __vm_execute를 부르는 wrapper 코드) 세 곳이 전부 같은 VmNames
|
|
8
|
+
* 인스턴스를 공유해야 앞뒤가 맞는다.
|
|
9
|
+
*/
|
|
10
|
+
export interface VmNames {
|
|
11
|
+
/** local __VM_GLOBALS = { ... } 에 해당하는 최상위 local 이름 */
|
|
12
|
+
globals: string
|
|
13
|
+
/** local __VM_PROTO = { ... } */
|
|
14
|
+
protoRoot: string
|
|
15
|
+
/** function __vm_execute(proto, upvals, ...) */
|
|
16
|
+
execute: string
|
|
17
|
+
/** local __vm_handlers = {} */
|
|
18
|
+
handlers: string
|
|
19
|
+
/** local function __vm_dispatch(frame, pc) */
|
|
20
|
+
dispatch: string
|
|
21
|
+
/** local function __vm_rk(frame, x) */
|
|
22
|
+
rk: string
|
|
23
|
+
/** __vm_execute/핸들러의 frame 파라미터 이름 */
|
|
24
|
+
frame: string
|
|
25
|
+
// frame 테이블 필드
|
|
26
|
+
R: string
|
|
27
|
+
K: string
|
|
28
|
+
code: string
|
|
29
|
+
protos: string
|
|
30
|
+
upvals: string
|
|
31
|
+
varargs: string
|
|
32
|
+
nVarargs: string
|
|
33
|
+
multiTop: string
|
|
34
|
+
// Proto 테이블 필드 (인스트럭션 자체는 이제 [op,a,b,c] 위치 기반 테이블이라 이름이 필요 없음)
|
|
35
|
+
numParams: string
|
|
36
|
+
hasVarargs: string
|
|
37
|
+
maxRegs: string
|
|
38
|
+
consts: string
|
|
39
|
+
upvalDescs: string
|
|
40
|
+
kind: string
|
|
41
|
+
index: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const ID_CHARS_HEAD = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"
|
|
45
|
+
const ID_CHARS_TAIL = ID_CHARS_HEAD + "0123456789"
|
|
46
|
+
|
|
47
|
+
function makeIdGenerator(random: () => number): () => string {
|
|
48
|
+
const used = new Set<string>()
|
|
49
|
+
return () => {
|
|
50
|
+
let id: string
|
|
51
|
+
do {
|
|
52
|
+
const len = 5 + Math.floor(random() * 5)
|
|
53
|
+
let s = ID_CHARS_HEAD[Math.floor(random() * ID_CHARS_HEAD.length)]
|
|
54
|
+
for (let i = 1; i < len; i++) s += ID_CHARS_TAIL[Math.floor(random() * ID_CHARS_TAIL.length)]
|
|
55
|
+
id = s
|
|
56
|
+
} while (used.has(id))
|
|
57
|
+
used.add(id)
|
|
58
|
+
return id
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function generateVmNames(random: () => number = Math.random): VmNames {
|
|
63
|
+
const id = makeIdGenerator(random)
|
|
64
|
+
return {
|
|
65
|
+
globals: id(), protoRoot: id(), execute: id(), handlers: id(), dispatch: id(), rk: id(),
|
|
66
|
+
frame: id(),
|
|
67
|
+
R: id(), K: id(), code: id(), protos: id(), upvals: id(), varargs: id(),
|
|
68
|
+
nVarargs: id(), multiTop: id(),
|
|
69
|
+
numParams: id(), hasVarargs: id(), maxRegs: id(), consts: id(), upvalDescs: id(),
|
|
70
|
+
kind: id(), index: id(),
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lua 5.1 스타일 레지스터 기반 opcode 세트 (Prometheus VM 모듈과 동일한 접근).
|
|
3
|
+
* 각 opcode의 "구현"은 런타임 인터프리터(runtime.ts)에서 실제 Luau 연산자로
|
|
4
|
+
* 그대로 매핑되므로, 여기서는 opcode 자체의 의미와 A/B/C 피연산자 규약만 정의한다.
|
|
5
|
+
*
|
|
6
|
+
* 규약 (Lua 5.1 바이트코드와 동일):
|
|
7
|
+
* A = 목적 레지스터
|
|
8
|
+
* B, C = 소스 레지스터 또는 상수 인덱스(RK) 또는 즉시값(점프 오프셋 등)
|
|
9
|
+
* RK(x): x >= 0 이면 레지스터 R[x], x < 0 이면 상수 K[-x-1] (상수/레지스터 구분을
|
|
10
|
+
* 비트 하나로 안 하고 부호로 하는 이유: 구현 단순화. 실제 Lua는 비트 플래그를 씀)
|
|
11
|
+
*/
|
|
12
|
+
export enum Opcode {
|
|
13
|
+
MOVE, // R[A] = R[B]
|
|
14
|
+
LOADK, // R[A] = K[B]
|
|
15
|
+
LOADBOOL, // R[A] = (B != 0); C != 0 이면 다음 명령 skip (조건부 분기 최적화용, 지금은 미사용)
|
|
16
|
+
LOADNIL, // R[A..B] = nil
|
|
17
|
+
GETUPVAL, // R[A] = U[B]
|
|
18
|
+
SETUPVAL, // U[B] = R[A]
|
|
19
|
+
GETGLOBAL, // R[A] = _G-equivalent[K[B]] (아래 GlobalMapping 참고: 실제로는 GLOBAL 테이블 인덱싱)
|
|
20
|
+
SETGLOBAL, // GLOBAL[K[B]] = R[A]
|
|
21
|
+
GETTABLE, // R[A] = R[B][RK(C)]
|
|
22
|
+
SETTABLE, // R[A][RK(B)] = RK(C)
|
|
23
|
+
NEWTABLE, // R[A] = {}
|
|
24
|
+
SELF, // R[A+1] = R[B]; R[A] = R[B][RK(C)] (메서드 호출 준비, TODO: 아직 컴파일러 미구현)
|
|
25
|
+
ADD, SUB, MUL, DIV, MOD, POW, // R[A] = RK(B) <op> RK(C)
|
|
26
|
+
CONCAT, // R[A] = R[B] .. .. R[C] (B..C 레지스터 범위 전부 이어붙임)
|
|
27
|
+
UNM, // R[A] = -R[B]
|
|
28
|
+
NOT, // R[A] = not R[B]
|
|
29
|
+
LEN, // R[A] = #R[B]
|
|
30
|
+
JMP, // pc += A (무조건 점프, A는 부호 있는 오프셋)
|
|
31
|
+
EQ, LT, LE, // if (RK(B) <op> RK(C)) != A then pc++ (다음 명령은 보통 JMP)
|
|
32
|
+
TEST, // if (not R[A]) != C then pc++ (and/or 단락 평가, if 조건 등)
|
|
33
|
+
CALL, // R[A], ..., R[A+C-2] = R[A](R[A+1], ..., R[A+B-1])
|
|
34
|
+
// B=0: 인자 전부 스택 끝까지(가변인자 호출), C=0: 반환값 전부 받음(가변반환)
|
|
35
|
+
RETURN, // return R[A], ..., R[A+B-2] (B=0: R[A]부터 스택 끝까지 전부 반환)
|
|
36
|
+
FORPREP, // numeric for 초기화 + 조건 미충족 시 FORLOOP 뒤로 점프
|
|
37
|
+
FORLOOP, // numeric for 증분 + 조건 충족 시 루프 헤더로 점프
|
|
38
|
+
CLOSURE, // R[A] = 클로저(protos[B], 캡처된 upvalue 목록은 뒤따르는 pseudo-instr로 인코딩)
|
|
39
|
+
VARARG, // R[A..A+B-2] = ... (B=0: 가변인자 전부)
|
|
40
|
+
SETLIST, // R[A][C], R[A][C+1], ... = R[B], R[B+1], ... (frame.multiTop까지) — 테이블
|
|
41
|
+
// 생성자 마지막 필드가 Call/MethodCall/Vararg로 다중값을 낼 때 씀 (`{f()}`, `{...}`)
|
|
42
|
+
IDIV, // R[A] = RK(B) // RK(C) (Luau 바닥 나눗셈 연산자, 뒤에 추가되어 기존 opcode
|
|
43
|
+
// 번호를 안 건드림 — enum 중간에 끼워넣으면 runtime.ts의 숫자 인덱스가 전부 밀림)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface Instr {
|
|
47
|
+
op: Opcode
|
|
48
|
+
a: number
|
|
49
|
+
b: number
|
|
50
|
+
c: number
|
|
51
|
+
/** JMP/EQ/LT/LE/TEST/CALL 등에서 사람이 읽을 디버깅용 주석. 런타임엔 안 쓰임. */
|
|
52
|
+
comment?: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** RK 인코딩 헬퍼: 상수는 음수(-idx-1), 레지스터는 그대로 양수. */
|
|
56
|
+
export function RK(regOrConstIdx: number, isConst: boolean): number {
|
|
57
|
+
return isConst ? -(regOrConstIdx + 1) : regOrConstIdx
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** 논리 opcode(Opcode enum) -> 실제 바이트코드 숫자. 빌드마다 무작위로 재배치된다. */
|
|
61
|
+
export type OpcodeMap = number[]
|
|
62
|
+
|
|
63
|
+
const OPCODE_COUNT = Object.keys(Opcode).filter((k) => Number.isNaN(Number(k))).length
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* opcode 번호를 빌드마다 무작위로 섞는다. 이게 없으면 "MOVE=0, LOADK=1, ..." 같은
|
|
67
|
+
* 고정 번호가 모든 산출물에서 동일해서, 한 번 리버싱해두면 opcode 핸들러 배열 인덱스만
|
|
68
|
+
* 보고 바로 어떤 명령인지 알 수 있는 상수 시그니처가 되어버린다(디컴파일러가 제일
|
|
69
|
+
* 먼저 노리는 지점). random을 주입받게 해서 테스트에서는 결정적 시퀀스도 쓸 수 있게 함.
|
|
70
|
+
*/
|
|
71
|
+
export function createOpcodeMap(random: () => number = Math.random): OpcodeMap {
|
|
72
|
+
const perm = Array.from({ length: OPCODE_COUNT }, (_, i) => i)
|
|
73
|
+
for (let i = perm.length - 1; i > 0; i--) {
|
|
74
|
+
const j = Math.floor(random() * (i + 1))
|
|
75
|
+
const tmp = perm[i]
|
|
76
|
+
perm[i] = perm[j]
|
|
77
|
+
perm[j] = tmp
|
|
78
|
+
}
|
|
79
|
+
return perm
|
|
80
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lua 레퍼런스 컴파일러와 동일한 방식의 레지스터 할당기.
|
|
3
|
+
* - 지역변수는 선언 시점에 고정 슬롯을 받고, 그 슬롯은 스코프가 끝날 때까지 유지.
|
|
4
|
+
* - 표현식 평가용 임시 레지스터는 `freereg`(다음으로 쓸 수 있는 첫 슬롯) 하나로 관리:
|
|
5
|
+
* 필요하면 freereg 위치를 하나 배정하고 freereg++, 다 쓰면 다시 freereg--.
|
|
6
|
+
* "스택처럼" 위에서부터 빌렸다 반납하기 때문에, 지역변수 슬롯보다 아래로는
|
|
7
|
+
* 절대 freereg가 내려가지 않는다(지역변수 보호).
|
|
8
|
+
*/
|
|
9
|
+
export class RegisterAllocator {
|
|
10
|
+
/** 현재 스코프에서 지역변수가 차지하고 있는, 즉 "보호된" 레지스터 개수. */
|
|
11
|
+
private localCount = 0
|
|
12
|
+
/** 다음에 할당할 임시 레지스터 인덱스. */
|
|
13
|
+
private freereg = 0
|
|
14
|
+
/** 지금까지 이 함수에서 동시에 살아있었던 최대 레지스터 수(Proto.maxRegs에 기록). */
|
|
15
|
+
maxUsed = 0
|
|
16
|
+
|
|
17
|
+
/** 지역변수 하나를 위한 슬롯을 새로 확정한다. 반환값이 그 변수의 레지스터 번호. */
|
|
18
|
+
declareLocal(): number {
|
|
19
|
+
const slot = this.localCount
|
|
20
|
+
this.localCount++
|
|
21
|
+
if (this.freereg < this.localCount) this.freereg = this.localCount
|
|
22
|
+
this.track()
|
|
23
|
+
return slot
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 블록/함수 스코프가 끝날 때, 그 스코프에서 선언된 지역변수 슬롯들을 반납. */
|
|
27
|
+
releaseLocalsTo(savedLocalCount: number): void {
|
|
28
|
+
this.localCount = savedLocalCount
|
|
29
|
+
// 무조건 리셋(위로도 아래로도) — 스코프가 끝나면 그 안에서 쓴 지역/임시 레지스터는
|
|
30
|
+
// 전부 죽은 값이다. freereg가 이미 더 높았다고 해서 그대로 두면(예전 버그), 그
|
|
31
|
+
// 누수가 이후 모든 문장의 "top()" 기준점을 계속 밀어 올려서 눈덩이처럼 커진다.
|
|
32
|
+
this.freereg = savedLocalCount
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
saveLocalCount(): number {
|
|
36
|
+
return this.localCount
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 표현식 평가용 임시 레지스터 하나 할당. */
|
|
40
|
+
allocTemp(): number {
|
|
41
|
+
const r = this.freereg
|
|
42
|
+
this.freereg++
|
|
43
|
+
this.track()
|
|
44
|
+
return r
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 임시 레지스터 n개를 반납(freereg를 내림). 지역변수 보호 슬롯 밑으로는
|
|
49
|
+
* 절대 안 내려가도록 clamp — 호출 순서가 꼬여도 지역변수는 항상 안전.
|
|
50
|
+
*/
|
|
51
|
+
freeTemp(toReg: number): void {
|
|
52
|
+
this.freereg = Math.max(toReg, this.localCount)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 지금 이 시점의 freereg. 임시 레지스터 블록 할당 전 저장해뒀다가 freeTemp에 씀. */
|
|
56
|
+
top(): number {
|
|
57
|
+
return this.freereg
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private track(): void {
|
|
61
|
+
if (this.freereg > this.maxUsed) this.maxUsed = this.freereg
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { Opcode, type OpcodeMap } from "./opcodes"
|
|
2
|
+
import type { VmNames } from "./names"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* VM 런타임 v3 — opcode마다 독립된 핸들러 함수를 만들고 서로 tail call로 이어지는
|
|
6
|
+
* 구조는 v2와 동일하다. 다만 이제 모든 전역 식별자(__vm_execute, __vm_handlers, ...)와
|
|
7
|
+
* frame/Instr/Proto의 테이블 필드 키가 고정 문자열이 아니라, 빌드마다 무작위로 뽑힌
|
|
8
|
+
* names(names.ts)와 opcode 번호 재배치 opcodeMap(opcodes.ts)을 받아 그때그때
|
|
9
|
+
* 소스를 새로 찍어낸다.
|
|
10
|
+
*
|
|
11
|
+
* 왜 이렇게 하나:
|
|
12
|
+
* - Luau는 proper tail call을 보장하므로(스택 안 쌓임, 성능도 loop와 동급),
|
|
13
|
+
* "재귀처럼 보이지만 실제론 loop"인 구조를 공짜로 얻을 수 있다.
|
|
14
|
+
* - "거대한 while+if-chain" 자체가 디컴파일러가 제일 먼저 찾는 VM 시그니처인데,
|
|
15
|
+
* 이 구조는 그 패턴이 아예 없다.
|
|
16
|
+
* - opcode 번호 + 식별자 이름을 빌드마다 다르게 뽑기 때문에, 한 번 리버싱해서
|
|
17
|
+
* "MOVE=0, __vm_execute라는 함수가 진입점" 같은 지식을 얻어도 다음 산출물에는
|
|
18
|
+
* 그대로 안 먹힌다 — 매번 새로 분석해야 함.
|
|
19
|
+
*
|
|
20
|
+
* opcode 번호는 opcodeMap[Opcode.X]로 이 빌드에서 실제로 쓰인 숫자를 얻는다
|
|
21
|
+
* (compiler.ts가 emit() 시점에 같은 opcodeMap으로 인스트럭션에 번호를 박아 넣으므로
|
|
22
|
+
* 반드시 컴파일과 런타임 생성에 동일한 opcodeMap 인스턴스를 넘겨야 함).
|
|
23
|
+
*/
|
|
24
|
+
export function buildVmRuntimeSource(names: VmNames, opcodeMap: OpcodeMap): string {
|
|
25
|
+
const N = names
|
|
26
|
+
const op = (o: Opcode) => opcodeMap[o]
|
|
27
|
+
|
|
28
|
+
return `
|
|
29
|
+
local ${N.handlers} = {}
|
|
30
|
+
|
|
31
|
+
local function ${N.dispatch}(${N.frame}, pc)
|
|
32
|
+
local instr = ${N.frame}.${N.code}[pc]
|
|
33
|
+
return ${N.handlers}[instr[1]](${N.frame}, pc, instr)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
local function ${N.rk}(${N.frame}, x)
|
|
37
|
+
if x < 0 then
|
|
38
|
+
return ${N.frame}.${N.K}[-x - 1 + 1]
|
|
39
|
+
else
|
|
40
|
+
return ${N.frame}.${N.R}[x]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
-- MOVE
|
|
45
|
+
${N.handlers}[${op(Opcode.MOVE)}] = function(${N.frame}, pc, instr)
|
|
46
|
+
${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]]
|
|
47
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
-- LOADK
|
|
51
|
+
${N.handlers}[${op(Opcode.LOADK)}] = function(${N.frame}, pc, instr)
|
|
52
|
+
${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.K}[instr[3] + 1]
|
|
53
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
-- LOADBOOL
|
|
57
|
+
${N.handlers}[${op(Opcode.LOADBOOL)}] = function(${N.frame}, pc, instr)
|
|
58
|
+
${N.frame}.${N.R}[instr[2]] = (instr[3] ~= 0)
|
|
59
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
-- LOADNIL
|
|
63
|
+
${N.handlers}[${op(Opcode.LOADNIL)}] = function(${N.frame}, pc, instr)
|
|
64
|
+
for i = instr[2], instr[3] do ${N.frame}.${N.R}[i] = nil end
|
|
65
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
-- GETUPVAL
|
|
69
|
+
${N.handlers}[${op(Opcode.GETUPVAL)}] = function(${N.frame}, pc, instr)
|
|
70
|
+
${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.upvals}[instr[3] + 1]
|
|
71
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
-- SETUPVAL
|
|
75
|
+
${N.handlers}[${op(Opcode.SETUPVAL)}] = function(${N.frame}, pc, instr)
|
|
76
|
+
${N.frame}.${N.upvals}[instr[3] + 1] = ${N.frame}.${N.R}[instr[2]]
|
|
77
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
-- GETGLOBAL (b는 RK가 아니라 상수풀 순수 인덱스)
|
|
81
|
+
${N.handlers}[${op(Opcode.GETGLOBAL)}] = function(${N.frame}, pc, instr)
|
|
82
|
+
${N.frame}.${N.R}[instr[2]] = ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]]
|
|
83
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
-- SETGLOBAL
|
|
87
|
+
${N.handlers}[${op(Opcode.SETGLOBAL)}] = function(${N.frame}, pc, instr)
|
|
88
|
+
${N.globals}[${N.frame}.${N.K}[instr[3] + 1]] = ${N.frame}.${N.R}[instr[2]]
|
|
89
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
-- GETTABLE
|
|
93
|
+
${N.handlers}[${op(Opcode.GETTABLE)}] = function(${N.frame}, pc, instr)
|
|
94
|
+
${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]][${N.rk}(${N.frame}, instr[4])]
|
|
95
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
-- SETTABLE
|
|
99
|
+
${N.handlers}[${op(Opcode.SETTABLE)}] = function(${N.frame}, pc, instr)
|
|
100
|
+
${N.frame}.${N.R}[instr[2]][${N.rk}(${N.frame}, instr[3])] = ${N.rk}(${N.frame}, instr[4])
|
|
101
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
-- NEWTABLE
|
|
105
|
+
${N.handlers}[${op(Opcode.NEWTABLE)}] = function(${N.frame}, pc, instr)
|
|
106
|
+
${N.frame}.${N.R}[instr[2]] = {}
|
|
107
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
-- SELF (R[a] = R[b][key]; R[a+1] = R[b])
|
|
111
|
+
${N.handlers}[${op(Opcode.SELF)}] = function(${N.frame}, pc, instr)
|
|
112
|
+
local obj = ${N.frame}.${N.R}[instr[3]]
|
|
113
|
+
${N.frame}.${N.R}[instr[2]] = obj[${N.rk}(${N.frame}, instr[4])]
|
|
114
|
+
${N.frame}.${N.R}[instr[2] + 1] = obj
|
|
115
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
-- ADD SUB MUL DIV MOD POW
|
|
119
|
+
${N.handlers}[${op(Opcode.ADD)}] = function(${N.frame}, pc, instr)
|
|
120
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) + ${N.rk}(${N.frame}, instr[4])
|
|
121
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
122
|
+
end
|
|
123
|
+
${N.handlers}[${op(Opcode.SUB)}] = function(${N.frame}, pc, instr)
|
|
124
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) - ${N.rk}(${N.frame}, instr[4])
|
|
125
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
126
|
+
end
|
|
127
|
+
${N.handlers}[${op(Opcode.MUL)}] = function(${N.frame}, pc, instr)
|
|
128
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) * ${N.rk}(${N.frame}, instr[4])
|
|
129
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
130
|
+
end
|
|
131
|
+
${N.handlers}[${op(Opcode.DIV)}] = function(${N.frame}, pc, instr)
|
|
132
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) / ${N.rk}(${N.frame}, instr[4])
|
|
133
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
134
|
+
end
|
|
135
|
+
${N.handlers}[${op(Opcode.MOD)}] = function(${N.frame}, pc, instr)
|
|
136
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) % ${N.rk}(${N.frame}, instr[4])
|
|
137
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
138
|
+
end
|
|
139
|
+
${N.handlers}[${op(Opcode.POW)}] = function(${N.frame}, pc, instr)
|
|
140
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) ^ ${N.rk}(${N.frame}, instr[4])
|
|
141
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
-- IDIV (Luau 바닥 나눗셈)
|
|
145
|
+
${N.handlers}[${op(Opcode.IDIV)}] = function(${N.frame}, pc, instr)
|
|
146
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) // ${N.rk}(${N.frame}, instr[4])
|
|
147
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
-- CONCAT
|
|
151
|
+
${N.handlers}[${op(Opcode.CONCAT)}] = function(${N.frame}, pc, instr)
|
|
152
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) .. ${N.rk}(${N.frame}, instr[4])
|
|
153
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
-- UNM
|
|
157
|
+
${N.handlers}[${op(Opcode.UNM)}] = function(${N.frame}, pc, instr)
|
|
158
|
+
${N.frame}.${N.R}[instr[2]] = -${N.frame}.${N.R}[instr[3]]
|
|
159
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
-- NOT
|
|
163
|
+
${N.handlers}[${op(Opcode.NOT)}] = function(${N.frame}, pc, instr)
|
|
164
|
+
${N.frame}.${N.R}[instr[2]] = not ${N.frame}.${N.R}[instr[3]]
|
|
165
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
-- LEN
|
|
169
|
+
${N.handlers}[${op(Opcode.LEN)}] = function(${N.frame}, pc, instr)
|
|
170
|
+
${N.frame}.${N.R}[instr[2]] = #${N.frame}.${N.R}[instr[3]]
|
|
171
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
-- JMP (pc += a)
|
|
175
|
+
${N.handlers}[${op(Opcode.JMP)}] = function(${N.frame}, pc, instr)
|
|
176
|
+
return ${N.dispatch}(${N.frame}, pc + instr[2])
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
-- EQ LT LE (값 생성형 비교)
|
|
180
|
+
${N.handlers}[${op(Opcode.EQ)}] = function(${N.frame}, pc, instr)
|
|
181
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) == ${N.rk}(${N.frame}, instr[4])
|
|
182
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
183
|
+
end
|
|
184
|
+
${N.handlers}[${op(Opcode.LT)}] = function(${N.frame}, pc, instr)
|
|
185
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) < ${N.rk}(${N.frame}, instr[4])
|
|
186
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
187
|
+
end
|
|
188
|
+
${N.handlers}[${op(Opcode.LE)}] = function(${N.frame}, pc, instr)
|
|
189
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) <= ${N.rk}(${N.frame}, instr[4])
|
|
190
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
-- TEST (if (not R[a]) != (c!=0) then pc += 2 else pc += 1 — 보통 다음 명령은 JMP)
|
|
194
|
+
${N.handlers}[${op(Opcode.TEST)}] = function(${N.frame}, pc, instr)
|
|
195
|
+
if (not ${N.frame}.${N.R}[instr[2]]) ~= (instr[4] ~= 0) then
|
|
196
|
+
return ${N.dispatch}(${N.frame}, pc + 2)
|
|
197
|
+
end
|
|
198
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
-- CALL
|
|
202
|
+
${N.handlers}[${op(Opcode.CALL)}] = function(${N.frame}, pc, instr)
|
|
203
|
+
local a, b, c = instr[2], instr[3], instr[4]
|
|
204
|
+
local fn = ${N.frame}.${N.R}[a]
|
|
205
|
+
local nargs
|
|
206
|
+
if b == 0 then
|
|
207
|
+
nargs = (${N.frame}.${N.multiTop} or (a + 1)) - (a + 1)
|
|
208
|
+
else
|
|
209
|
+
nargs = b - 1
|
|
210
|
+
end
|
|
211
|
+
local args = table.create(nargs)
|
|
212
|
+
for i = 1, nargs do args[i] = ${N.frame}.${N.R}[a + i] end
|
|
213
|
+
local rets = table.pack(fn(table.unpack(args, 1, nargs)))
|
|
214
|
+
if c == 1 then
|
|
215
|
+
-- discard
|
|
216
|
+
elseif c == 0 then
|
|
217
|
+
for i = 1, rets.n do ${N.frame}.${N.R}[a + i - 1] = rets[i] end
|
|
218
|
+
${N.frame}.${N.multiTop} = a + rets.n
|
|
219
|
+
else
|
|
220
|
+
local want = c - 1
|
|
221
|
+
for i = 1, want do ${N.frame}.${N.R}[a + i - 1] = rets[i] end
|
|
222
|
+
end
|
|
223
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
-- RETURN
|
|
227
|
+
${N.handlers}[${op(Opcode.RETURN)}] = function(${N.frame}, pc, instr)
|
|
228
|
+
local a, b = instr[2], instr[3]
|
|
229
|
+
if b == 0 then
|
|
230
|
+
return table.unpack(${N.frame}.${N.R}, a, (${N.frame}.${N.multiTop} or (a + 1)) - 1)
|
|
231
|
+
end
|
|
232
|
+
return table.unpack(${N.frame}.${N.R}, a, a + b - 2)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
-- FORPREP
|
|
236
|
+
${N.handlers}[${op(Opcode.FORPREP)}] = function(${N.frame}, pc, instr)
|
|
237
|
+
local a = instr[2]
|
|
238
|
+
${N.frame}.${N.R}[a] = ${N.frame}.${N.R}[a] - ${N.frame}.${N.R}[a + 2]
|
|
239
|
+
return ${N.dispatch}(${N.frame}, pc + instr[3])
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
-- FORLOOP
|
|
243
|
+
${N.handlers}[${op(Opcode.FORLOOP)}] = function(${N.frame}, pc, instr)
|
|
244
|
+
local a = instr[2]
|
|
245
|
+
local step = ${N.frame}.${N.R}[a + 2]
|
|
246
|
+
${N.frame}.${N.R}[a] = ${N.frame}.${N.R}[a] + step
|
|
247
|
+
local ok
|
|
248
|
+
if step > 0 then
|
|
249
|
+
ok = ${N.frame}.${N.R}[a] <= ${N.frame}.${N.R}[a + 1]
|
|
250
|
+
else
|
|
251
|
+
ok = ${N.frame}.${N.R}[a] >= ${N.frame}.${N.R}[a + 1]
|
|
252
|
+
end
|
|
253
|
+
if ok then
|
|
254
|
+
${N.frame}.${N.R}[a + 3] = ${N.frame}.${N.R}[a]
|
|
255
|
+
return ${N.dispatch}(${N.frame}, pc + instr[3])
|
|
256
|
+
end
|
|
257
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
-- CLOSURE
|
|
261
|
+
${N.handlers}[${op(Opcode.CLOSURE)}] = function(${N.frame}, pc, instr)
|
|
262
|
+
local childProto = ${N.frame}.${N.protos}[instr[3] + 1]
|
|
263
|
+
local n = #childProto.${N.upvalDescs}
|
|
264
|
+
local capturedUpvals = table.create(n)
|
|
265
|
+
for i = 1, n do
|
|
266
|
+
local desc = childProto.${N.upvalDescs}[i]
|
|
267
|
+
local captureInstr = ${N.frame}.${N.code}[pc + i]
|
|
268
|
+
if desc.${N.kind} == "local" then
|
|
269
|
+
capturedUpvals[i] = ${N.frame}.${N.R}[captureInstr[3]]
|
|
270
|
+
else
|
|
271
|
+
capturedUpvals[i] = ${N.frame}.${N.upvals}[captureInstr[3] + 1]
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
${N.frame}.${N.R}[instr[2]] = function(...)
|
|
275
|
+
return ${N.execute}(childProto, capturedUpvals, ...)
|
|
276
|
+
end
|
|
277
|
+
return ${N.dispatch}(${N.frame}, pc + 1 + n)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
-- VARARG
|
|
281
|
+
${N.handlers}[${op(Opcode.VARARG)}] = function(${N.frame}, pc, instr)
|
|
282
|
+
local count = instr[3] == 0 and ${N.frame}.${N.nVarargs} or (instr[3] - 1)
|
|
283
|
+
for i = 1, count do
|
|
284
|
+
${N.frame}.${N.R}[instr[2] + i - 1] = ${N.frame}.${N.varargs}[i]
|
|
285
|
+
end
|
|
286
|
+
if instr[3] == 0 then
|
|
287
|
+
${N.frame}.${N.multiTop} = instr[2] + count
|
|
288
|
+
end
|
|
289
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
-- SETLIST
|
|
293
|
+
${N.handlers}[${op(Opcode.SETLIST)}] = function(${N.frame}, pc, instr)
|
|
294
|
+
local tbl = ${N.frame}.${N.R}[instr[2]]
|
|
295
|
+
local valuesBase = instr[3]
|
|
296
|
+
local top = ${N.frame}.${N.multiTop} or (valuesBase + 1)
|
|
297
|
+
local startIdx = instr[4]
|
|
298
|
+
local n = top - valuesBase
|
|
299
|
+
for i = 0, n - 1 do
|
|
300
|
+
tbl[startIdx + i] = ${N.frame}.${N.R}[valuesBase + i]
|
|
301
|
+
end
|
|
302
|
+
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
function ${N.execute}(proto, upvals, ...)
|
|
306
|
+
local nArgs = select("#", ...)
|
|
307
|
+
local packedArgs = table.pack(...)
|
|
308
|
+
local R = table.create(proto.${N.maxRegs} + 8)
|
|
309
|
+
for i = 1, proto.${N.numParams} do
|
|
310
|
+
R[i - 1] = packedArgs[i]
|
|
311
|
+
end
|
|
312
|
+
local nVarargs = 0
|
|
313
|
+
local varargs = {}
|
|
314
|
+
if proto.${N.hasVarargs} and nArgs > proto.${N.numParams} then
|
|
315
|
+
nVarargs = nArgs - proto.${N.numParams}
|
|
316
|
+
for i = 1, nVarargs do
|
|
317
|
+
varargs[i] = packedArgs[proto.${N.numParams} + i]
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
local ${N.frame} = {
|
|
322
|
+
${N.R} = R,
|
|
323
|
+
${N.K} = proto.${N.consts},
|
|
324
|
+
${N.code} = proto.${N.code},
|
|
325
|
+
${N.protos} = proto.${N.protos},
|
|
326
|
+
${N.upvals} = upvals,
|
|
327
|
+
${N.varargs} = varargs,
|
|
328
|
+
${N.nVarargs} = nVarargs,
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return ${N.dispatch}(${N.frame}, 1)
|
|
332
|
+
end
|
|
333
|
+
`
|
|
334
|
+
}
|