Guest User

Luau Disassembler

a guest
Aug 9th, 2025
37
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 60.60 KB | None | 0 0
  1. --!native
  2. --!optimize 2
  3.  
  4. -- decompiler soon? :eyes:
  5. -- created with :heart: by 0x1437 (github: https://github.com/0x1437, discord: @0x01437 (971650839888416799))
  6.  
  7. -- localize all used functions for faster access, no need to invoke the __index metamethod over and over for the same functions like string.format
  8. local pairs = pairs
  9. local tostring = tostring
  10. local error = error
  11. local pcall = pcall
  12. local print = print
  13.  
  14. local string_format = string.format
  15. local string_rep = string.rep
  16. local string_lower = string.lower
  17. local string_byte = string.byte
  18. local string_sub = string.sub
  19.  
  20. local table_insert = table.insert
  21. local table_create = table.create
  22. local table_concat = table.concat
  23. local table_find = table.find
  24.  
  25. local bit32_band = bit32.band
  26. local bit32_rshift = bit32.rshift
  27. local bit32_lshift = bit32.lshift
  28. local bit32_bor = bit32.bor
  29.  
  30. local math_floor = math.floor
  31. local os_clock = os.clock
  32.  
  33. local buffer_readu8 = buffer.readu8
  34. local buffer_readu32 = buffer.readu32
  35. local buffer_readi32 = buffer.readi32
  36. local buffer_readf32 = buffer.readf32
  37. local buffer_readf64 = buffer.readf64
  38. local buffer_readstring = buffer.readstring
  39. local buffer_fromstring = buffer.fromstring
  40. local buffer_len = buffer.len
  41.  
  42. ---
  43.  
  44. local NewOpcode = function(Name, Type, Number, Aux)
  45. return {
  46. Name = Name,
  47. Type = Type,
  48. Number = Number,
  49. Aux = Aux or false,
  50. }
  51. end
  52.  
  53. local OpTableV5 = {}
  54. local OpTableV6 = {
  55. NewOpcode("NOP", "none", 0x00), -- NOP: no operation
  56. NewOpcode("BREAK", "none", 0xE3), -- BREAK: debugger break
  57.  
  58. -- LOADNIL: sets register to nil
  59. -- A: target register
  60. NewOpcode("LOADNIL", "iA", 0xC6),
  61.  
  62. -- LOADB: sets register to boolean and jumps to a given short offset (used to compile comparison results into a boolean)
  63. -- A: target register
  64. -- B: value (0/1)
  65. -- C: jump offset
  66. NewOpcode("LOADB", "iABC", 0xA9),
  67.  
  68. -- LOADN: sets register to a number literal
  69. -- A: target register
  70. -- D: value (-32768..32767)
  71. NewOpcode("LOADN", "iABx", 0x8C),
  72.  
  73. -- LOADK: sets register to an entry from the constant table from the proto (number/vector/string)
  74. -- A: target register
  75. -- D: constant table index (0..32767)
  76. NewOpcode("LOADK", "iABx", 0x6F),
  77.  
  78. -- MOVE: move (copy) value from one register to another
  79. -- A: target register
  80. -- B: source register
  81. NewOpcode("MOVE", "iAB", 0x52),
  82.  
  83. -- GETGLOBAL: load value from global table using constant string as a key
  84. -- A: target register
  85. -- C: predicted slot index (based on hash)
  86. -- AUX: constant table index
  87. NewOpcode("GETGLOBAL", "iAC", 0x35, true),
  88.  
  89. -- SETGLOBAL: set value in global table using constant string as a key
  90. -- A: source register
  91. -- C: predicted slot index (based on hash)
  92. -- AUX: constant table index
  93. NewOpcode("SETGLOBAL", "iAC", 0x18, true),
  94.  
  95. -- GETUPVAL: load upvalue from the upvalue table for the current function
  96. -- A: target register
  97. -- B: upvalue index
  98. NewOpcode("GETUPVAL", "iAB", 0xFB),
  99.  
  100. -- SETUPVAL: store value into the upvalue table for the current function
  101. -- A: target register
  102. -- B: upvalue index
  103. NewOpcode("SETUPVAL", "iAB", 0xDE),
  104.  
  105. -- CLOSEUPVALS: close (migrate to heap) all upvalues that were captured for registers >= target
  106. -- A: target register
  107. NewOpcode("CLOSEUPVALS", "iA", 0xC1),
  108.  
  109. -- GETIMPORT: load imported global table global from the constant table
  110. -- A: target register
  111. -- D: constant table index (0..32767); we assume that imports are loaded into the constant table
  112. -- AUX: 3 10-bit indices of constant strings that, combined, constitute an import path; length of the path is set by the top 2 bits (1,2,3)
  113. NewOpcode("GETIMPORT", "iABx", 0xA4, true),
  114.  
  115. -- GETTABLE: load value from table into target register using key from register
  116. -- A: target register
  117. -- B: table register
  118. -- C: index register
  119. NewOpcode("GETTABLE", "iABC", 0x87),
  120.  
  121. -- SETTABLE: store source register into table using key from register
  122. -- A: source register
  123. -- B: table register
  124. -- C: index register
  125. NewOpcode("SETTABLE", "iABC", 0x6A),
  126.  
  127. -- GETTABLEKS: load value from table into target register using constant string as a key
  128. -- A: target register
  129. -- B: table register
  130. -- C: predicted slot index (based on hash)
  131. -- AUX: constant table index
  132. NewOpcode("GETTABLEKS", "iABC", 0x4D, true),
  133.  
  134. -- SETTABLEKS: store source register into table using constant string as a key
  135. -- A: source register
  136. -- B: table register
  137. -- C: predicted slot index (based on hash)
  138. -- AUX: constant table index
  139. NewOpcode("SETTABLEKS", "iABC", 0x30, true),
  140.  
  141. -- GETTABLEN: load value from table into target register using small integer index as a key
  142. -- A: target register
  143. -- B: table register
  144. -- C: index-1 (index is 1..256)
  145. NewOpcode("GETTABLEN", "iABC", 0x13),
  146.  
  147. -- SETTABLEN: store source register into table using small integer index as a key
  148. -- A: source register
  149. -- B: table register
  150. -- C: index-1 (index is 1..256)
  151. NewOpcode("SETTABLEN", "iABC", 0xF6),
  152.  
  153. -- NEWCLOSURE: create closure from a child proto; followed by a CAPTURE instruction for each upvalue
  154. -- A: target register
  155. -- D: child proto index (0..32767)
  156. NewOpcode("NEWCLOSURE", "iABx", 0xD9),
  157.  
  158. -- NAMECALL: prepare to call specified method by name by loading function from source register using constant index into target register and copying source register into target register + 1
  159. -- A: target register
  160. -- B: source register
  161. -- C: predicted slot index (based on hash)
  162. -- AUX: constant table index
  163. -- Note that this instruction must be followed directly by CALL; it prepares the arguments
  164. -- This instruction is roughly equivalent to GETTABLEKS + MOVE pair, but we need a special instruction to support custom __namecall metamethod
  165. NewOpcode("NAMECALL", "iABC", 0xBC, true),
  166.  
  167. -- CALL: call specified function
  168. -- A: register where the function object lives, followed by arguments; results are placed starting from the same register
  169. -- B: argument count + 1, or 0 to preserve all arguments up to top (MULTRET)
  170. -- C: result count + 1, or 0 to preserve all values and adjust top (MULTRET)
  171. NewOpcode("CALL", "iABC", 0x9F),
  172.  
  173. -- RETURN: returns specified values from the function
  174. -- A: register where the returned values start
  175. -- B: number of returned values + 1, or 0 to return all values up to top (MULTRET)
  176. NewOpcode("RETURN", "iAB", 0x82),
  177.  
  178. -- JUMP: jumps to target offset
  179. -- D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
  180. NewOpcode("JUMP", "isBx", 0x65),
  181.  
  182. -- JUMPBACK: jumps to target offset; this is equivalent to JUMP but is used as a safepoint to be able to interrupt while/repeat loops
  183. -- D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
  184. NewOpcode("JUMPBACK", "isBx", 0x48),
  185.  
  186. -- JUMPIF: jumps to target offset if register is not nil/false
  187. -- A: source register
  188. -- D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
  189. NewOpcode("JUMPIF", "iAsBx", 0x2B),
  190.  
  191. -- JUMPIFNOT: jumps to target offset if register is nil/false
  192. -- A: source register
  193. -- D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
  194. NewOpcode("JUMPIFNOT", "iAsBx", 0x0E),
  195.  
  196. -- JUMPIFEQ, JUMPIFLE, JUMPIFLT, JUMPIFNOTEQ, JUMPIFNOTLE, JUMPIFNOTLT: jumps to target offset if the comparison is true (or false, for NOT variants)
  197. -- A: source register 1
  198. -- D: jump offset (-32768..32767; 1 means "next instruction" aka "don't jump")
  199. -- AUX: source register 2
  200. NewOpcode("JUMPIFEQ", "iAsBx", 0xF1, true),
  201. NewOpcode("JUMPIFLE", "iAsBx", 0xD4, true),
  202. NewOpcode("JUMPIFLT", "iAsBx", 0xB7, true),
  203. NewOpcode("JUMPIFNOTEQ", "iAsBx", 0x9A, true),
  204. NewOpcode("JUMPIFNOTLE", "iAsBx", 0x7D, true),
  205. NewOpcode("JUMPIFNOTLT", "iAsBx", 0x60, true),
  206.  
  207. -- ADD, SUB, MUL, DIV, MOD, POW: compute arithmetic operation between two source registers and put the result into target register
  208. -- A: target register
  209. -- B: source register 1
  210. -- C: source register 2
  211. NewOpcode("ADD", "iABC", 0x43),
  212. NewOpcode("SUB", "iABC", 0x26),
  213. NewOpcode("MUL", "iABC", 0x09),
  214. NewOpcode("DIV", "iABC", 0xEC),
  215. NewOpcode("MOD", "iABC", 0xCF),
  216. NewOpcode("POW", "iABC", 0xB2),
  217.  
  218. -- ADDK, SUBK, MULK, DIVK, MODK, POWK: compute arithmetic operation between the source register and a constant and put the result into target register
  219. -- A: target register
  220. -- B: source register
  221. -- C: constant table index (0..255); must refer to a number
  222. NewOpcode("ADDK", "iABC", 0x95),
  223. NewOpcode("SUBK", "iABC", 0x78),
  224. NewOpcode("MULK", "iABC", 0x5B),
  225. NewOpcode("DIVK", "iABC", 0x3E),
  226. NewOpcode("MODK", "iABC", 0x21),
  227. NewOpcode("POWK", "iABC", 0x04),
  228.  
  229. -- AND, OR: perform `and` or `or` operation (selecting first or second register based on whether the first one is truthy) and put the result into target register
  230. -- A: target register
  231. -- B: source register 1
  232. -- C: source register 2
  233. NewOpcode("AND", "iABC", 0xE7),
  234. NewOpcode("OR", "iABC", 0xCA),
  235.  
  236. -- ANDK, ORK: perform `and` or `or` operation (selecting source register or constant based on whether the source register is truthy) and put the result into target register
  237. -- A: target register
  238. -- B: source register
  239. -- C: constant table index (0..255)
  240. NewOpcode("ANDK", "iABC", 0xAD),
  241. NewOpcode("ORK", "iABC", 0x90),
  242.  
  243. -- CONCAT: concatenate all strings between B and C (inclusive) and put the result into A
  244. -- A: target register
  245. -- B: source register start
  246. -- C: source register end
  247. NewOpcode("CONCAT", "iABC", 0x73),
  248.  
  249. -- NOT, MINUS, LENGTH: compute unary operation for source register and put the result into target register
  250. -- A: target register
  251. -- B: source register
  252. NewOpcode("NOT", "iAB", 0x56),
  253. NewOpcode("MINUS", "iAB", 0x39),
  254. NewOpcode("LENGTH", "iAB", 0x1C),
  255.  
  256. -- NEWTABLE: create table in target register
  257. -- A: target register
  258. -- B: table size, stored as 0 for v=0 and ceil(log2(v))+1 for v!=0
  259. -- AUX: array size
  260. NewOpcode("NEWTABLE", "iAB", 0xFF, true),
  261.  
  262. -- DUPTABLE: duplicate table using the constant table template to target register
  263. -- A: target register
  264. -- D: constant table index (0..32767)
  265. NewOpcode("DUPTABLE", "iABx", 0xE2),
  266.  
  267. -- SETLIST: set a list of values to table in target register
  268. -- A: target register
  269. -- B: source register start
  270. -- C: value count + 1, or 0 to use all values up to top (MULTRET)
  271. -- AUX: table index to start from
  272. NewOpcode("SETLIST", "iABC", 0xC5, true),
  273.  
  274. -- FORNPREP: prepare a numeric for loop, jump over the loop if first iteration doesn't need to run
  275. -- A: target register; numeric for loops assume a register layout [limit, step, index, variable]
  276. -- D: jump offset (-32768..32767)
  277. -- limit/step are immutable, index isn't visible to user code since it's copied into variable
  278. NewOpcode("FORNPREP", "iABx", 0xA8),
  279.  
  280. -- FORNLOOP: adjust loop variables for one iteration, jump back to the loop header if loop needs to continue
  281. -- A: target register; see FORNPREP for register layout
  282. -- D: jump offset (-32768..32767)
  283. NewOpcode("FORNLOOP", "iABx", 0x8B),
  284.  
  285. -- FORGLOOP: adjust loop variables for one iteration of a generic for loop, jump back to the loop header if loop needs to continue
  286. -- A: target register; generic for loops assume a register layout [generator, state, index, variables...]
  287. -- D: jump offset (-32768..32767)
  288. -- AUX: variable count (1..255) in the low 8 bits, high bit indicates whether to use ipairs-style traversal in the fast path
  289. -- loop variables are adjusted by calling generator(state, index) and expecting it to return a tuple that's copied to the user variables
  290. -- the first variable is then copied into index; generator/state are immutable, index isn't visible to user code
  291. NewOpcode("FORGLOOP", "iABx", 0x6E, true),
  292.  
  293. -- FORGPREP_INEXT: prepare FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_inext, and jump to FORGLOOP
  294. -- A: target register (see FORGLOOP for register layout)
  295. NewOpcode("FORGPREP_INEXT", "none", 0x51),
  296.  
  297. -- FORGPREP_NEXT: prepare FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_next, and jump to FORGLOOP
  298. -- A: target register (see FORGLOOP for register layout)
  299. NewOpcode("FORGPREP_NEXT", "none", 0x17),
  300.  
  301. -- NATIVECALL: start executing new function in native code
  302. -- this is a pseudo-instruction that is never emitted by bytecode compiler, but can be constructed at runtime to accelerate native code dispatch
  303. NewOpcode("NATIVECALL", "none", 0xFA),
  304.  
  305. -- GETVARARGS: copy variables into the target register from vararg storage for current function
  306. -- A: target register
  307. -- B: variable count + 1, or 0 to copy all variables and adjust top (MULTRET)
  308. NewOpcode("GETVARARGS", "iAB", 0xDD),
  309.  
  310. -- DUPCLOSURE: create closure from a pre-created function object (reusing it unless environments diverge)
  311. -- A: target register
  312. -- D: constant table index (0..32767)
  313. NewOpcode("DUPCLOSURE", "iABx", 0xC0),
  314.  
  315. -- PREPVARARGS: prepare stack for variadic functions so that GETVARARGS works correctly
  316. -- A: number of fixed arguments
  317. NewOpcode("PREPVARARGS", "iA", 0xA3),
  318.  
  319. -- LOADKX: sets register to an entry from the constant table from the proto (number/string)
  320. -- A: target register
  321. -- AUX: constant table index
  322. NewOpcode("LOADKX", "iA", 0x86),
  323.  
  324. -- JUMPX: jumps to the target offset; like JUMPBACK, supports interruption
  325. -- E: jump offset (-2^23..2^23; 0 means "next instruction" aka "don't jump")
  326. NewOpcode("JUMPX", "isAx", 0x69),
  327.  
  328. -- FASTCALL: perform a fast call of a built-in function
  329. -- A: builtin function id (see BuiltInFunctionsMap)
  330. -- C: jump offset to get to following CALL
  331. -- FASTCALL is followed by one of (GETIMPORT, MOVE, GETUPVAL) instructions and by CALL instruction
  332. -- This is necessary so that if FASTCALL can't perform the call inline, it can continue normal execution
  333. -- If FASTCALL *can* perform the call, it jumps over the instructions *and* over the next CALL
  334. -- Note that FASTCALL will read the actual call arguments, such as argument/result registers and counts, from the CALL instruction
  335. NewOpcode("FASTCALL", "iAC", 0x4C),
  336.  
  337. -- COVERAGE: update coverage information stored in the instruction
  338. -- E: hit count for the instruction (0..2^23-1)
  339. -- The hit count is incremented by VM every time the instruction is executed, and saturates at 2^23-1
  340. NewOpcode("COVERAGE", "isAx", 0x2F),
  341.  
  342. -- CAPTURE: capture a local or an upvalue as an upvalue into a newly created closure; only valid after NEWCLOSURE
  343. -- A: capture type
  344. -- B: source register (for VAL/REF) or upvalue index (for UPVAL/UPREF)
  345. NewOpcode("CAPTURE", "iAB", 0x12),
  346.  
  347. -- SUBRK, DIVRK: compute arithmetic operation between the constant and a source register and put the result into target register
  348. -- A: target register
  349. -- B: constant table index (0..255); must refer to a number
  350. -- C: source register
  351. NewOpcode("SUBRK", "iABx", 0xF5, true),
  352. NewOpcode("DIVRK", "iABx", 0xD8, true),
  353.  
  354. -- FASTCALL1: perform a fast call of a built-in function using 1 register argument
  355. -- A: builtin function id (see BuiltInFunctionsMap)
  356. -- B: source argument register
  357. -- C: jump offset to get to following CALL
  358. NewOpcode("FASTCALL1", "iABC", 0xBB),
  359.  
  360. -- FASTCALL2: perform a fast call of a built-in function using 2 register arguments
  361. -- A: builtin function id (see BuiltInFunctionsMap)
  362. -- B: source argument register
  363. -- C: jump offset to get to following CALL
  364. -- AUX: source register 2 in least-significant byte
  365. NewOpcode("FASTCALL2", "iABC", 0x9E, true),
  366.  
  367. -- FASTCALL2K: perform a fast call of a built-in function using 1 register argument and 1 constant argument
  368. -- A: builtin function id (see BuiltInFunctionsMap)
  369. -- B: source argument register
  370. -- C: jump offset to get to following CALL
  371. -- AUX: constant index
  372. NewOpcode("FASTCALL2K", "iABC", 0x81, true),
  373.  
  374. -- FASTCALL3: perform a fast call of a built-in function using 3 register arguments
  375. -- A: builtin function id (see BuiltInFunctionsMap)
  376. -- B: source argument register
  377. -- C: jump offset to get to following CALL
  378. -- AUX: source register 2 in least-significant byte
  379. -- AUX: source register 3 in second least-significant byte
  380. NewOpcode("FASTCALL3", "iABC", 0x34, true),
  381.  
  382. -- FORGPREP: prepare loop variables for a generic for loop, jump to the loop backedge unconditionally
  383. -- A: target register; generic for loops assume a register layout [generator, state, index, variables...]
  384. -- D: jump offset (-32768..32767)
  385. NewOpcode("FORGPREP", "iAB", 0x64),
  386.  
  387. -- JUMPXEQKNIL, JUMPXEQKB: jumps to target offset if the comparison with constant is true (or false, see AUX)
  388. -- A: source register 1
  389. -- D: jump offset (-32768..32767; 1 means "next instruction" aka "don't jump")
  390. -- AUX: constant value (for boolean) in low bit, NOT flag (that flips comparison result) in high bit
  391. NewOpcode("JUMPXEQKNIL", "iAsBx", 0x47, true),
  392. NewOpcode("JUMPXEQKB", "iAsBx", 0x2A, true),
  393.  
  394. -- JUMPXEQKN, JUMPXEQKS: jumps to target offset if the comparison with constant is true (or false, see AUX)
  395. -- A: source register 1
  396. -- D: jump offset (-32768..32767; 1 means "next instruction" aka "don't jump")
  397. -- AUX: constant table index in low 24 bits, NOT flag (that flips comparison result) in high bit
  398. NewOpcode("JUMPXEQKN", "iAsBx", 0x0D, true),
  399. NewOpcode("JUMPXEQKS", "iAsBx", 0xF0, true),
  400.  
  401. -- IDIV: compute floor division between two source registers and put the result into target register
  402. -- A: target register
  403. -- B: source register 1
  404. -- C: source register 2
  405. NewOpcode("IDIV", "iABC", 0xD3),
  406.  
  407. -- IDIVK compute floor division between the source register and a constant and put the result into target register
  408. -- A: target register
  409. -- B: source register
  410. -- C: constant table index (0..255)
  411. NewOpcode("IDIVK", "iABC", 0xB6),
  412.  
  413. -- COUNT: count.. FRIGGIN count. COUNT. COUNT!!!!!!!!!!!!!!!!! COUNT!!@34-YTIWE-HAD VHI0HIOTGFE O-EOHJAOO-EOJgvspjodnkfbjokdnc0WUH98YFVIOIWUHO RUSHOJGD
  414. NewOpcode("COUNT", "none", 0x99),
  415. }
  416.  
  417. local BuiltInFunctionsMap = { -- should be updated when no longer up to date with new roblox functions
  418. [0] = "none",
  419. [1] = "assert",
  420. [2] = "math.abs",
  421. [3] = "math.acos",
  422. [4] = "math.asin",
  423. [5] = "math.atan2",
  424. [6] = "math.atan",
  425. [7] = "math.ceil",
  426. [8] = "math.cosh",
  427. [9] = "math.cos",
  428. [10] = "math.deg",
  429. [11] = "math.exp",
  430. [12] = "math.floor",
  431. [13] = "math.fmod",
  432. [14] = "math.frexp",
  433. [15] = "math.ldexp",
  434. [16] = "math.log10",
  435. [17] = "math.log",
  436. [18] = "math.max",
  437. [19] = "math.min",
  438. [20] = "math.modf",
  439. [21] = "math.pow",
  440. [22] = "math.rad",
  441. [23] = "math.sinh",
  442. [24] = "math.sin",
  443. [25] = "math.sqrt",
  444. [26] = "math.tanh",
  445. [27] = "math.tan",
  446. [28] = "bit32.arshift",
  447. [29] = "bit32.band",
  448. [30] = "bit32.bnot",
  449. [31] = "bit32.bor",
  450. [32] = "bit32.bxor",
  451. [33] = "bit32.btest",
  452. [34] = "bit32.extract",
  453. [35] = "bit32.lrotate",
  454. [36] = "bit32.lshift",
  455. [37] = "bit32.replace",
  456. [38] = "bit32.rrotate",
  457. [39] = "bit32.rshift",
  458. [40] = "type",
  459. [41] = "string.byte",
  460. [42] = "string.char",
  461. [43] = "string.len",
  462. [44] = "typeof",
  463. [45] = "string.sub",
  464. [46] = "math.clamp",
  465. [47] = "math.sign",
  466. [48] = "math.round",
  467. [49] = "rawset",
  468. [50] = "rawget",
  469. [51] = "rawequal",
  470. [52] = "table.insert",
  471. [53] = "table.unpack",
  472. [54] = "vector",
  473. [55] = "bit32.countlz",
  474. [56] = "bit32.countrz",
  475. [57] = "select",
  476. [58] = "rawlen",
  477. [59] = "bit32.extractk",
  478. [60] = "getmetatable",
  479. [61] = "setmetatable",
  480. [62] = "tonumber",
  481. [63] = "tostring",
  482. [64] = "bit32.byteswap",
  483. [65] = "buffer.readi8",
  484. [66] = "buffer.readu8",
  485. [67] = "buffer.writeu8",
  486. [68] = "buffer.readi16",
  487. [69] = "buffer.readu16",
  488. [70] = "buffer.writeu16",
  489. [71] = "buffer.readi32",
  490. [72] = "buffer.readu32",
  491. [73] = "buffer.writeu32",
  492. [74] = "buffer.readf32",
  493. [75] = "buffer.writef32",
  494. [76] = "buffer.readf64",
  495. [77] = "buffer.writef64",
  496. [78] = "vector.magnitude",
  497. [79] = "vector.normalize",
  498. [80] = "vector.cross",
  499. [81] = "vector.dot",
  500. [82] = "vector.floor",
  501. [83] = "vector.ceil",
  502. [84] = "vector.abs",
  503. [85] = "vector.sign",
  504. [86] = "vector.clamp",
  505. [87] = "vector.min",
  506. [88] = "vector.max",
  507. [89] = "math.lerp",
  508. }
  509.  
  510. local LbcConstantBoolean = 1
  511. local LbcConstantNumber = 2
  512. local LbcConstantString = 3
  513. local LbcConstantImport = 4
  514. local LbcConstantTable = 5
  515. local LbcConstantClosure = 6
  516. local LbcConstantVector = 7
  517.  
  518. for i = 1, #OpTableV6 do
  519. local Opcode = OpTableV6[i]
  520. if Opcode.Name ~= "FASTCALL3" then
  521. table_insert(OpTableV5, Opcode)
  522. end
  523. end
  524.  
  525. local OpInfoMaps = {}
  526. local OpTableVersionMap = {
  527. [6] = OpTableV6,
  528. [5] = OpTableV5,
  529. }
  530.  
  531. for Version, OpTable in pairs(OpTableVersionMap) do
  532. local Map = {}
  533.  
  534. for i = 1, #OpTable do
  535. local Info = OpTable[i]
  536.  
  537. Map[Info.Number] = Info
  538. end
  539.  
  540. OpInfoMaps[Version] = Map
  541. end
  542.  
  543. local function DecomposeImportId(Ids)
  544. local Count = bit32_rshift(Ids, 30)
  545. local Parts = {}
  546.  
  547. if Count > 0 then
  548. table_insert(Parts, bit32_band(bit32_rshift(Ids, 20), 1023))
  549. end
  550.  
  551. if Count > 1 then
  552. table_insert(Parts, bit32_band(bit32_rshift(Ids, 10), 1023))
  553. end
  554.  
  555. if Count > 2 then
  556. table_insert(Parts, bit32_band(Ids, 1023))
  557. end
  558.  
  559. return Parts
  560. end
  561.  
  562. local OpcodeHandlerMaps = {}
  563. for Version, OpTable in pairs(OpTableVersionMap) do
  564. local Handlers = {}
  565.  
  566. local KToStr = function(K)
  567. if not K then return "Invalid" end
  568.  
  569. local Value = K.Value
  570.  
  571. if K.Type == 3 then
  572. return string_format('%q', Value)
  573. end
  574.  
  575. return tostring(Value)
  576. end
  577.  
  578. Handlers.NOP = function(Instruction)
  579. return "", "-- do nothing (no-op / NOP)"
  580. end
  581.  
  582. Handlers.BREAK = function(Instruction)
  583. return "", "-- break"
  584. end
  585.  
  586. Handlers.PREPVARARGS = function(Instruction)
  587. local A = Instruction.A
  588.  
  589. if A > 0 then
  590. return A, "-- Prepare for " .. tostring(A - 1) .. " variables as ..."
  591. end
  592.  
  593. return A, "-- Prepare for any number (top) of variables as ..."
  594. end
  595.  
  596. Handlers.GETVARARGS = function(Instruction)
  597. local A = Instruction.A
  598. local B = Instruction.B
  599.  
  600. if B == 0 then
  601. return string_format("%d, %d", A, B), string_format("var%d, ... = ... -- Load all remaining variables", A)
  602. end
  603.  
  604. local VariableCount = B - 1
  605.  
  606. if VariableCount == 0 then
  607. return string_format("%d, %d", A, B), "-- GETVARARGS (0 variables)"
  608. end
  609.  
  610. local VarList = table_create(VariableCount)
  611.  
  612. for i = 0, VariableCount - 1 do
  613. table_insert(VarList, "var" .. tostring(A + i))
  614. end
  615.  
  616. local VarString = table_concat(VarList, ", ")
  617. local Plural = VariableCount == 1 and "" or "s"
  618.  
  619. return string_format("%d, %d", A, B), string_format("%s = ... -- Load %d variable%s", VarString, VariableCount, Plural)
  620. end
  621.  
  622. Handlers.LOADNIL = function(Instruction)
  623. local A = Instruction.A
  624.  
  625. return A, string_format("var%d = nil", A)
  626. end
  627.  
  628. Handlers.LOADB = function(Instruction)
  629. local A = Instruction.A
  630. local B = Instruction.B
  631. local C = Instruction.C
  632. local CodeIndex = Instruction.CodeIndex
  633. local Comment = string_format("var%d = %s", A, tostring(B ~= 0))
  634.  
  635. if C ~= 0 then
  636. Comment = Comment .. " -- goto [" .. tostring(CodeIndex + 1 + C) .. "]"
  637. end
  638.  
  639. return string_format("%d, %d, %d", A, B, C), Comment
  640. end
  641.  
  642. Handlers.LOADN = function(Instruction)
  643. local A = Instruction.A
  644. local Bx = Instruction.Bx
  645.  
  646. return string_format("%d, %d", A, Bx), string_format("var%d = %d", A, Bx)
  647. end
  648.  
  649. Handlers.LOADK = function(Instruction)
  650. local A = Instruction.A
  651. local Bx = Instruction.Bx
  652. local K = Instruction.Proto.KTable[Bx + 1]
  653.  
  654. return string_format("%d, %d", A, Bx), string_format("var%d = %s", A, KToStr(K))
  655. end
  656.  
  657. Handlers.LOADKX = function(Instruction)
  658. local A = Instruction.A
  659. local Aux = Instruction.Aux
  660. local K = Instruction.Proto.KTable[Aux + 1]
  661.  
  662. return A, string_format("var%d = %s", A, KToStr(K))
  663. end
  664.  
  665. Handlers.MOVE = function(Instruction)
  666. local A = Instruction.A
  667. local B = Instruction.B
  668.  
  669. return string_format("%d, %d", A, B), string_format("var%d = var%d", A, B)
  670. end
  671.  
  672. Handlers.GETGLOBAL = function(Instruction)
  673. local A = Instruction.A
  674. local C = Instruction.C
  675. local Aux = Instruction.Aux
  676. local Name = Instruction.StringTable[Aux + 1] or "Invalid"
  677.  
  678. return string_format("%d, %d [%d]", A, C, Aux), string_format("var%d = %s", A, Name)
  679. end
  680.  
  681. Handlers.SETGLOBAL = function(Instruction)
  682. local A = Instruction.A
  683. local C = Instruction.C
  684. local Aux = Instruction.Aux
  685. local Name = Instruction.StringTable[Aux + 1] or "Invalid"
  686.  
  687. return string_format("%d, %d [%d]", A, C, Aux), string_format("%s = var%d", Name, A)
  688. end
  689.  
  690. Handlers.GETUPVAL = function(Instruction)
  691. local A = Instruction.A
  692. local B = Instruction.B
  693.  
  694. return string_format("%d, %d", A, B), string_format("var%d = up%d", A, B)
  695. end
  696.  
  697. Handlers.SETUPVAL = function(Instruction)
  698. local A = Instruction.A
  699. local B = Instruction.B
  700.  
  701. return string_format("%d, %d", A, B), string_format("up%d = var%d", B, A)
  702. end
  703.  
  704. Handlers.CLOSEUPVALS = function(Instruction)
  705. local A = Instruction.A
  706.  
  707. return A, string_format("move_upvalues_to_heap(var%d->...)", A)
  708. end
  709.  
  710. Handlers.GETTABLE = function(Instruction)
  711. local A = Instruction.A
  712. local B = Instruction.B
  713. local C = Instruction.C
  714.  
  715. return string_format("%d, %d, %d", A, B, C), string_format("var%d = var%d[var%d]", A, B, C)
  716. end
  717.  
  718. Handlers.SETTABLE = function(Instruction)
  719. local A = Instruction.A
  720. local B = Instruction.B
  721. local C = Instruction.C
  722.  
  723. return string_format("%d, %d, %d", A, B, C), string_format("var%d[var%d] = var%d", B, C, A)
  724. end
  725.  
  726. Handlers.GETTABLEKS = function(Instruction)
  727. local A = Instruction.A
  728. local B = Instruction.B
  729. local C = Instruction.C
  730. local Aux = Instruction.Aux
  731. local K = Instruction.Proto.KTable[Aux + 1]
  732. local Name = KToStr(K)
  733.  
  734. return string_format("%d, %d, %d [%d]", A, B, C, Aux), string_format("var%d = var%d[%s]", A, B, Name)
  735. end
  736.  
  737. Handlers.SETTABLEKS = function(Instruction)
  738. local A = Instruction.A
  739. local B = Instruction.B
  740. local C = Instruction.C
  741. local Aux = Instruction.Aux
  742. local K = Instruction.Proto.KTable[Aux + 1] or "Invalid"
  743. local Name = KToStr(K)
  744.  
  745. return string_format("%d, %d, %d [%d]", A, B, C, Aux), string_format("var%d[%s] = var%d", B, Name, A)
  746. end
  747.  
  748. Handlers.GETTABLEN = function(Instruction)
  749. local A = Instruction.A
  750. local B = Instruction.B
  751. local C = Instruction.C
  752.  
  753. return string_format("%d, %d, %d", A, B, C), string_format("var%d = var%d[%d]", A, B, C + 1)
  754. end
  755.  
  756. Handlers.SETTABLEN = function(Instruction)
  757. local A = Instruction.A
  758. local B = Instruction.B
  759. local C = Instruction.C
  760.  
  761. return string_format("%d, %d, %d", A, B, C), string_format("var%d[%d] = var%d", B, C + 1, A)
  762. end
  763.  
  764. Handlers.NEWCLOSURE = function(Instruction)
  765. local A = Instruction.A
  766. local Bx = Instruction.Bx
  767. local ChildProto = Instruction.ChildProto
  768. local FuncName
  769.  
  770. if ChildProto and ChildProto.Source and ChildProto.Source ~= "Invalid source index" and #ChildProto.Source > 0 and #ChildProto.Source < 500 then
  771. FuncName = ChildProto.Source
  772. else
  773. local ProtoIndex = (ChildProto and ChildProto.ProtoIndex) or Bx
  774. FuncName = "anonymous_function_" .. ProtoIndex
  775. end
  776.  
  777. return string_format("%d, %d", A, Bx), string_format("var%d = %s", A, FuncName)
  778. end
  779.  
  780. Handlers.NAMECALL = function(Instruction)
  781. local A = Instruction.A
  782. local B = Instruction.B
  783. local C = Instruction.C
  784. local Aux = Instruction.Aux
  785. local K = Instruction.Proto.KTable[Aux + 1]
  786. local MethodName = KToStr(K)
  787. local Comment = string_format("var%d = var%d; var%d = var%d.%s -- Invokes __namecall", A + 1, B, A, B, MethodName)
  788.  
  789. return string_format("%d, %d, %d [%d]", A, B, C, Aux), Comment
  790. end
  791.  
  792. Handlers.CALL = function(Instruction)
  793. local A = Instruction.A
  794. local B = Instruction.B
  795. local C = Instruction.C
  796. local ArgCount = B - 1
  797. local RetCount = C - 1
  798. local Args = ""
  799.  
  800. if ArgCount > 0 then
  801. local ArgList = {}
  802.  
  803. for J = 1, ArgCount do
  804. table_insert(ArgList, "var" .. (A + J))
  805. end
  806.  
  807. Args = table_concat(ArgList, ", ")
  808. elseif B == 0 then
  809. Args = "var" .. (A + 1) .. "->(top)"
  810. end
  811.  
  812. local Rets = ""
  813. if RetCount >= 1 then
  814. local RetList = {}
  815.  
  816. for J = 0, RetCount - 1 do
  817. table_insert(RetList, "var" .. (A + J))
  818. end
  819.  
  820. Rets = table_concat(RetList, ", ")
  821. elseif C == 0 then
  822. Rets = "var" .. A .. "->(top)"
  823. end
  824.  
  825. local CallStr = string_format("var%d(%s)", A, Args)
  826.  
  827. if Rets ~= "" then
  828. CallStr = Rets .. " = " .. CallStr
  829. end
  830.  
  831. return string_format("%d, %d, %d", A, B, C), CallStr
  832. end
  833.  
  834. Handlers.RETURN = function(Instruction)
  835. local A = Instruction.A
  836. local B = Instruction.B
  837. local RetStr
  838.  
  839. if B == 0 then
  840. RetStr = "return var" .. A .. "->(top)"
  841. elseif B == 1 then
  842. RetStr = "return"
  843. else
  844. local Rets = {}
  845.  
  846. for J = 0, B - 2 do
  847. table_insert(Rets, "var" .. (A + J))
  848. end
  849.  
  850. RetStr = "return " .. table_concat(Rets, "->")
  851. end
  852.  
  853. return string_format("%d, %d", A, B), RetStr
  854. end
  855.  
  856. Handlers.JUMP = function(Instruction)
  857. local SBx = Instruction.SBx
  858. local CodeIndex = Instruction.CodeIndex
  859.  
  860. return SBx, string_format("-- goto [%d]", CodeIndex + 1 + SBx)
  861. end
  862.  
  863. Handlers.JUMPBACK = function(Instruction)
  864. local SBx = Instruction.SBx
  865. local CodeIndex = Instruction.CodeIndex
  866.  
  867. return SBx, string_format("-- goto [%d] (likely while/repeat loop)", CodeIndex + 1 + SBx)
  868. end
  869.  
  870. Handlers.JUMPX = function(Instruction)
  871. local SAx = Instruction.SAx
  872. local CodeIndex = Instruction.CodeIndex
  873.  
  874. return SAx, string_format("-- goto [%d]", CodeIndex + 1 + SAx)
  875. end
  876.  
  877. Handlers.FORNPREP = function(Instruction)
  878. local A = Instruction.A
  879. local Bx = Instruction.Bx
  880. local CodeIndex = Instruction.CodeIndex
  881. local Comment = string_format("for var%d = var%d, var%d, var%d do -- If loop shouldn't start (var%d > var%d) then goto [%d]", A + 3, A, A + 1, A + 2, A, A + 1, CodeIndex + 1 + Bx + 1)
  882.  
  883. return string_format("%d, %d", A, Bx), Comment
  884. end
  885.  
  886. Handlers.FORNLOOP = function(Instruction)
  887. local A = Instruction.A
  888. local SBx = Instruction.SBx
  889. local CodeIndex = Instruction.CodeIndex
  890. local Comment = string_format("var%d += var%d; if var%d <= var%d then goto [%d] end", A + 3, A + 2, A + 3, A + 1, CodeIndex + 1 + SBx)
  891.  
  892. return string_format("%d, %d", A, SBx), Comment
  893. end
  894.  
  895. Handlers.FORGPREP = function(Instruction)
  896. local A = Instruction.A
  897. local Bx = Instruction.Bx
  898. local CodeIndex = Instruction.CodeIndex
  899. local Comment = string_format("for var%d->... in var%d, var%d, var%d do -- If loop shouldn't start then goto [%d]", A + 3, A, A + 1, A + 2, CodeIndex + 1 + Bx + 1)
  900.  
  901. return string_format("%d, %d", A, Bx), Comment
  902. end
  903.  
  904. Handlers.FORGPREP_INEXT = Handlers.FORGPREP
  905. Handlers.FORGPREP_NEXT = Handlers.FORGPREP
  906. Handlers.FORGLOOP = function(Instruction)
  907. local A = Instruction.A
  908. local SBx = Instruction.SBx
  909. local Aux = Instruction.Aux or 0
  910. local CodeIndex = Instruction.CodeIndex
  911. local C = bit32.band(Aux, 0xFF)
  912. local Rets = {}
  913.  
  914. if C >= 1 then
  915. for J = 0, C - 1 do
  916. table_insert(Rets, "var" .. (A + 3 + J))
  917. end
  918. end
  919.  
  920. local ReturnVarsStr = table_concat(Rets, ", ")
  921. local JumpTarget = CodeIndex + SBx
  922. local OperandsStr = string_format("%d, %d [0x%08X]", A, SBx, Aux)
  923. local CommentStr = string_format("%s = var%d(var%d, var%d); if var%d ~= nil then goto [%d] end", ReturnVarsStr, A, A + 1, A + 2, A + 3, JumpTarget)
  924.  
  925. return OperandsStr, CommentStr
  926. end
  927.  
  928. Handlers.MINUS = function(Instruction)
  929. local A = Instruction.A
  930. local B = Instruction.B
  931.  
  932. return string_format("%d, %d", A, B), string_format("var%d = -var%d", A, B)
  933. end
  934.  
  935. Handlers.LENGTH = function(Instruction)
  936. local A = Instruction.A
  937. local B = Instruction.B
  938.  
  939. return string_format("%d, %d", A, B), string_format("var%d = #var%d", A, B)
  940. end
  941.  
  942. Handlers.NEWTABLE = function(Instruction)
  943. local A = Instruction.A
  944. local B = Instruction.B
  945. local Aux = Instruction.Aux or 0
  946. local Comment
  947.  
  948. if Aux == 0 then
  949. Comment = "{}"
  950. else
  951. Comment = "table.create(" .. Aux .. ")"
  952. end
  953.  
  954. return string_format("%d, %d [%d]", A, B, Aux), string_format("var%d = %s", A, Comment)
  955. end
  956.  
  957. Handlers.DUPTABLE = function(Instruction)
  958. local A = Instruction.A
  959. local Bx = Instruction.Bx
  960.  
  961. return string_format("%d, %d", A, Bx), string_format("var%d = {} -- from template %d", A, Bx)
  962. end
  963.  
  964. Handlers.SETLIST = function(Instruction)
  965. local A = Instruction.A
  966. local B = Instruction.B
  967. local C = Instruction.C
  968. local Aux = Instruction.Aux
  969. local Count = C - 1
  970. local StartIndex = Aux
  971. local EndIndex = StartIndex + Count - 1
  972. local SourceVars = {}
  973.  
  974. for J = 1, Count do
  975. table_insert(SourceVars, "var" .. (A + J))
  976. end
  977.  
  978. local Comment = string_format("var%d[%d->%d] = %s", A, StartIndex, EndIndex, table_concat(SourceVars, "->"))
  979.  
  980. return string_format("%d, %d, %d [%d]", A, B, C, Aux), Comment
  981. end
  982.  
  983. Handlers.CONCAT = function(Instruction)
  984. local A = Instruction.A
  985. local B = Instruction.B
  986. local C = Instruction.C
  987.  
  988. return string_format("%d, %d, %d", A, B, C), string_format("var%d = var%d .. var%d", A, B, C)
  989. end
  990.  
  991. Handlers.NOT = function(Instruction)
  992. local A = Instruction.A
  993. local B = Instruction.B
  994.  
  995. return string_format("%d, %d", A, B), string_format("var%d = not var%d", A, B)
  996. end
  997.  
  998. Handlers.DUPCLOSURE = function(Instruction)
  999. local A = Instruction.A
  1000. local Bx = Instruction.Bx
  1001. local K = Instruction.Proto.KTable[Bx + 1]
  1002. local ProtoIndex = K.Value
  1003. local TargetProtoData
  1004.  
  1005. if Instruction.AllProtos then
  1006. TargetProtoData = Instruction.AllProtos[ProtoIndex + 1]
  1007. end
  1008.  
  1009. local FuncName
  1010.  
  1011. if TargetProtoData and TargetProtoData.RawProto.Source and TargetProtoData.RawProto.Source ~= "Invalid source index" and #TargetProtoData.RawProto.Source < 500 then
  1012. FuncName = TargetProtoData.RawProto.Source
  1013. else
  1014. FuncName = "anonymous_function_" .. ProtoIndex
  1015. end
  1016.  
  1017. return string_format("%d, %d", A, Bx), string_format("var%d = %s", A, FuncName)
  1018. end
  1019.  
  1020. Handlers.GETIMPORT = function(Instruction)
  1021. local A = Instruction.A
  1022. local Bx = Instruction.Bx
  1023. local Aux = Instruction.Aux or 0
  1024. local Proto = Instruction.Proto
  1025. local ImportKonstant = Proto.KTable[Bx + 1]
  1026.  
  1027. if not ImportKonstant or ImportKonstant.Type ~= 4 then
  1028. return string_format("%d, %d", A, Bx), string_format("var%d = --[[ Invalid Import Constant at K%d ]]", A, Bx)
  1029. end
  1030.  
  1031. local ImportId = ImportKonstant.Value
  1032. local Ids = DecomposeImportId(ImportId)
  1033. local ImportedPathParts = {}
  1034. local IdsLen = #Ids
  1035.  
  1036. for i = 1, IdsLen do
  1037. local StringKIndex = Ids[i]
  1038. local StringK = Proto.KTable[StringKIndex + 1]
  1039.  
  1040. if StringK and StringK.Type == 3 then
  1041. table_insert(ImportedPathParts, StringK.Value)
  1042. else
  1043. table_insert(ImportedPathParts, "InvalidImportPart")
  1044. end
  1045. end
  1046.  
  1047. local ImportedPath = table_concat(ImportedPathParts, ".")
  1048.  
  1049. if #ImportedPath == 0 then ImportedPath = "unknown_import" end
  1050.  
  1051. local OperandsStr = string_format("%d, %d [0x%X]", A, Bx, Aux)
  1052. local CommentStr = string_format("var%d = %s", A, ImportedPath)
  1053.  
  1054. return OperandsStr, CommentStr
  1055. end
  1056.  
  1057. Handlers.CAPTURE = function(Instruction)
  1058. local A = Instruction.A
  1059. local B = Instruction.B
  1060. local UpvalIndex = Instruction.UpvalIndex
  1061. local T = { [0] = "Readable", [1] = "Readable and writable", [2] = "Upvalue" }
  1062.  
  1063. return string_format("%d, %d", A, B), string_format("up%d = var%d -- %s", UpvalIndex, B, T[A] or "UNKOWN")
  1064. end
  1065.  
  1066. local function MakeJumpHandler(Op, Sym, NotOp)
  1067. return function(Instruction)
  1068. local A = Instruction.A
  1069. local C = Instruction.C
  1070. local AuxIsK = Instruction.AuxIsK
  1071. local Proto = Instruction.Proto
  1072. local CodeIndex = Instruction.CodeIndex
  1073. local SBx = Instruction.SBx
  1074. local KStr = AuxIsK and KToStr(Proto.KTable[C + 1]) or ("var" .. C)
  1075. local Condition = string_format("var%d %s %s", A, Sym, KStr)
  1076.  
  1077. if NotOp then Condition = "not (" .. Condition .. ")" end
  1078.  
  1079. local Comment = string_format("if %s then goto [%d] end", Condition, CodeIndex + 1 + SBx)
  1080.  
  1081. return string_format("%d, %d [%d]", A, C, Instruction.Aux), Comment
  1082. end
  1083. end
  1084.  
  1085. local JumpOps = { EQ = "==", LE = "<=", LT = "<" }
  1086.  
  1087. for Op, Sym in pairs(JumpOps) do
  1088. Handlers["JUMPIF" .. Op] = MakeJumpHandler(Op, Sym, false)
  1089. Handlers["JUMPIFNOT" .. Op] = MakeJumpHandler(Op, Sym, true)
  1090. end
  1091.  
  1092. Handlers["JUMPIFEQ"] = function(Instruction)
  1093. local A = Instruction.A
  1094. local C = Instruction.C
  1095. local Aux = Instruction.Aux
  1096. local CodeIndex = Instruction.CodeIndex
  1097. local SBx = Instruction.SBx
  1098.  
  1099. return string_format("%d, %d [%d]", A, C, Aux), string_format("if var%d == var%d then goto [%d] end", A, C, CodeIndex + 1 + SBx)
  1100. end
  1101.  
  1102. Handlers["JUMPIFNOTEQ"] = function(Instruction)
  1103. local A = Instruction.A
  1104. local C = Instruction.C
  1105. local Aux = Instruction.Aux
  1106. local CodeIndex = Instruction.CodeIndex
  1107. local SBx = Instruction.SBx
  1108.  
  1109. return string_format("%d, %d [%d]", A, C, Aux), string_format("if var%d ~= var%d then goto [%d] end", A, C, CodeIndex + 1 + SBx)
  1110. end
  1111.  
  1112. Handlers["JUMPIF"] = function(Instruction)
  1113. local A = Instruction.A
  1114. local SBx = Instruction.SBx
  1115. local CodeIndex = Instruction.CodeIndex
  1116.  
  1117. return string_format("%d, %d", A, SBx), string_format("if var%d then goto [%d] end", A, CodeIndex + 1 + SBx)
  1118. end
  1119.  
  1120. Handlers["JUMPIFNOT"] = function(Instruction)
  1121. local A = Instruction.A
  1122. local SBx = Instruction.SBx
  1123. local CodeIndex = Instruction.CodeIndex
  1124.  
  1125. return string_format("%d, %d", A, SBx), string_format("if not var%d then goto [%d] end", A, CodeIndex + 1 + SBx)
  1126. end
  1127.  
  1128. local function MakeJUMPXEQKHandler(ConstType)
  1129. return function(Instruction)
  1130. local A = Instruction.A
  1131. local C = Instruction.C
  1132. local Aux = Instruction.Aux
  1133. local SBx = Instruction.SBx
  1134. local CodeIndex = Instruction.CodeIndex
  1135. local Proto = Instruction.Proto
  1136. local IsNot = bit32_band(Aux, 0x80000000) ~= 0
  1137. local OpSym = IsNot and "~=" or "=="
  1138. local ConstData = bit32_band(Aux, 0x7FFFFFFF)
  1139. local TargetPc = CodeIndex + 1 + SBx
  1140. local ConstStr = "???"
  1141.  
  1142. if ConstType == "NIL" then
  1143. ConstStr = "nil"
  1144. elseif ConstType == "B" then
  1145. ConstStr = (ConstData == 1) and "true" or "false"
  1146. elseif ConstType == "N" or ConstType == "S" then
  1147. local K = Proto.KTable[ConstData + 1]
  1148. ConstStr = KToStr(K)
  1149. end
  1150.  
  1151. local OperandsStr = string_format("%d, %d [0x%08X]", A, C, Aux)
  1152. local CommentStr = string_format("if var%d %s %s then goto [%d] end", A, OpSym, ConstStr, TargetPc)
  1153.  
  1154. return OperandsStr, CommentStr
  1155. end
  1156. end
  1157.  
  1158. Handlers["JUMPXEQKNIL"] = MakeJUMPXEQKHandler("NIL")
  1159. Handlers["JUMPXEQKB"] = MakeJUMPXEQKHandler("B")
  1160. Handlers["JUMPXEQKN"] = MakeJUMPXEQKHandler("N")
  1161. Handlers["JUMPXEQKS"] = MakeJUMPXEQKHandler("S")
  1162.  
  1163. local MathOps = { ADD = "+", SUB = "-", MUL = "*", DIV = "/", IDIV = "//", MOD = "%", POW = "^" }
  1164.  
  1165. for Op, Sym in pairs(MathOps) do
  1166. Handlers[Op] = function(Instruction)
  1167. local A = Instruction.A
  1168. local B = Instruction.B
  1169. local C = Instruction.C
  1170.  
  1171. return string_format("%d, %d, %d", A, B, C), string_format("var%d = var%d %s var%d", A, B, Sym, C)
  1172. end
  1173.  
  1174. Handlers[Op .. "K"] = function(Instruction)
  1175. local A = Instruction.A
  1176. local B = Instruction.B
  1177. local C = Instruction.C
  1178. local K = Instruction.Proto.KTable[C + 1]
  1179.  
  1180. return string_format("%d, %d, %d", A, B, C), string_format("var%d = var%d %s %s", A, B, Sym, KToStr(K))
  1181. end
  1182. end
  1183.  
  1184. Handlers["SUBRK"] = function(Instruction)
  1185. local A = Instruction.A
  1186. local B = Instruction.B
  1187. local C = Instruction.C
  1188. local K = Instruction.Proto.KTable[B + 1]
  1189.  
  1190. return string_format("%d, %d, %d", A, B, C), string_format("var%d = %s - var%d", A, KToStr(K), A)
  1191. end
  1192.  
  1193. Handlers["DIVRK"] = function(Instruction)
  1194. local A = Instruction.A
  1195. local B = Instruction.B
  1196. local C = Instruction.C
  1197. local K = Instruction.Proto.KTable[B + 1]
  1198.  
  1199. return string_format("%d, %d, %d", A, B, C), string_format("var%d = %s / var%d", A, KToStr(K), A)
  1200. end
  1201.  
  1202. local AndOrOps = {"AND", "OR"}
  1203.  
  1204. for i = 1, #AndOrOps do
  1205. local Op = AndOrOps[i]
  1206. local Sym = string_lower(Op)
  1207.  
  1208. Handlers[Op] = function(Instruction)
  1209. local A = Instruction.A
  1210. local B = Instruction.B
  1211. local C = Instruction.C
  1212.  
  1213. return string_format("%d, %d, %d", A, B, C), string_format("var%d = var%d %s var%d", A, B, Sym, C)
  1214. end
  1215.  
  1216. Handlers[Op .. "K"] = function(Instruction)
  1217. local A = Instruction.A
  1218. local B = Instruction.B
  1219. local C = Instruction.C
  1220. local K = Instruction.Proto.KTable[C + 1]
  1221.  
  1222. return string_format("%d, %d, %d", A, B, C), string_format("var%d = var%d %s %s", A, B, Sym, KToStr(K))
  1223. end
  1224. end
  1225.  
  1226. local function GetBuiltinName(Id)
  1227. return BuiltInFunctionsMap[Id] or "unknown_function"
  1228. end
  1229.  
  1230. Handlers.FASTCALL = function(Instruction)
  1231. local A = Instruction.A
  1232. local C = Instruction.C
  1233. local FuncName = GetBuiltinName(A)
  1234.  
  1235. return string_format("%d, %d", A, C), string_format("... = %s(...)", FuncName)
  1236. end
  1237.  
  1238. Handlers.FASTCALL1 = function(Instruction)
  1239. local A = Instruction.A
  1240. local B = Instruction.B
  1241. local C = Instruction.C
  1242. local FuncName = GetBuiltinName(A)
  1243.  
  1244. return string_format("%d, %d, %d", A, B, C), string_format("... = %s(var%d)", FuncName, B)
  1245. end
  1246.  
  1247. Handlers.FASTCALL2 = function(Instruction)
  1248. local A = Instruction.A
  1249. local B = Instruction.B
  1250. local C = Instruction.C
  1251. local Aux = Instruction.Aux
  1252. local FuncName = GetBuiltinName(A)
  1253. local Arg2 = bit32_band(Aux, 0xFF)
  1254.  
  1255. return string_format("%d, %d, %d [0x%X]", A, B, C, Aux), string_format("... = %s(var%d, var%d)", FuncName, B, Arg2)
  1256. end
  1257.  
  1258. Handlers.FASTCALL2K = function(Instruction)
  1259. local A = Instruction.A
  1260. local B = Instruction.B
  1261. local C = Instruction.C
  1262. local Aux = Instruction.Aux
  1263. local Proto = Instruction.Proto
  1264. local FuncName = GetBuiltinName(A)
  1265. local K = Proto.KTable[Aux + 1]
  1266.  
  1267. return string_format("%d, %d, %d [0x%X]", A, B, C, Aux), string_format("... = %s(var%d, %s)", FuncName, B, KToStr(K))
  1268. end
  1269.  
  1270. Handlers.FASTCALL3 = function(Instruction)
  1271. local A = Instruction.A
  1272. local B = Instruction.B
  1273. local C = Instruction.C
  1274. local Aux = Instruction.Aux
  1275. local FuncName = GetBuiltinName(A)
  1276. local Arg2 = bit32_band(Aux, 0xFF)
  1277. local Arg3 = bit32_band(bit32_rshift(Aux, 8), 0xFF)
  1278.  
  1279. return string_format("%d, %d, %d [0x%X]", A, B, C, Aux), string_format("... = %s(var%d, var%d, var%d)", FuncName, B, Arg2, Arg3)
  1280. end
  1281.  
  1282. OpcodeHandlerMaps[Version] = Handlers
  1283. end
  1284.  
  1285. local function GetOpcode(Instruction, IsFromRoblox)
  1286. if not IsFromRoblox then
  1287. Instruction = Instruction * 227
  1288. end
  1289.  
  1290. local Opcode = bit32_band(Instruction, 0xFF)
  1291.  
  1292. if IsFromRoblox then
  1293. Opcode = (Opcode * 203) % 256 -- inverse of 227 mod 256
  1294. end
  1295.  
  1296. return Opcode
  1297. end
  1298.  
  1299. local function GetArgA(Instruction)
  1300. return bit32_band(bit32_rshift(Instruction, 8), 0xFF)
  1301. end
  1302.  
  1303. local function GetArgB(Instruction)
  1304. return bit32_band(bit32_rshift(Instruction, 16), 0xFF)
  1305. end
  1306.  
  1307. local function GetArgC(Instruction)
  1308. return bit32_band(bit32_rshift(Instruction, 24), 0xFF)
  1309. end
  1310.  
  1311. local function GetArgBx(Instruction)
  1312. return bit32_rshift(Instruction, 16)
  1313. end
  1314.  
  1315. local function GetArgSBx(Instruction)
  1316. local Bx = bit32_rshift(Instruction, 16)
  1317.  
  1318. return Bx >= 0x8000 and Bx - 0x10000 or Bx
  1319. end
  1320.  
  1321. local function GetArgSAx(Instruction)
  1322. return bit32_rshift(Instruction, 8)
  1323. end
  1324.  
  1325. ---
  1326.  
  1327. local ReadProto
  1328. local Deserialize
  1329.  
  1330. local Disassembler = {}
  1331.  
  1332. local function CreateEmptyProto()
  1333. return {
  1334. CodeTable = {},
  1335. KTable = {},
  1336. PTable = {},
  1337. SmallLineInfo = {},
  1338. LargeLineInfo = {},
  1339. LineInfoCompKey = 0,
  1340. Locals = {},
  1341. UpvalNames = {},
  1342. }
  1343. end
  1344.  
  1345. local Buf, Offset, CurrentBufferLen
  1346.  
  1347. local function CanRead(N)
  1348. return Offset + N <= CurrentBufferLen
  1349. end
  1350.  
  1351. local function CheckRead(N, ErrorMsg)
  1352. if not CanRead(N) then
  1353. error(string_format("%s at position %d, but buffer length is %d", ErrorMsg, Offset, CurrentBufferLen))
  1354. end
  1355. end
  1356.  
  1357. local function NextByte()
  1358. CheckRead(1, "Attempted to read 1 byte")
  1359. local Value = buffer_readu8(Buf, Offset)
  1360.  
  1361. Offset = Offset + 1
  1362.  
  1363. return Value
  1364. end
  1365.  
  1366. local function NextUint32()
  1367. CheckRead(4, "Attempted to read 4 bytes for Uint32")
  1368. local Value = buffer_readu32(Buf, Offset)
  1369.  
  1370. Offset = Offset + 4
  1371.  
  1372. return Value
  1373. end
  1374.  
  1375. local function NextInt()
  1376. CheckRead(4, "Attempted to read 4 bytes for Int32")
  1377. local Value = buffer_readi32(Buf, Offset)
  1378.  
  1379. Offset = Offset + 4
  1380.  
  1381. return Value
  1382. end
  1383.  
  1384. local function NextFloat()
  1385. CheckRead(4, "Attempted to read 4 bytes for Float32")
  1386. local Value = buffer_readf32(Buf, Offset)
  1387.  
  1388. Offset = Offset + 4
  1389.  
  1390. return Value
  1391. end
  1392.  
  1393. local function NextDouble()
  1394. CheckRead(8, "Attempted to read 8 bytes for Float64")
  1395. local Value = buffer_readf64(Buf, Offset)
  1396.  
  1397. Offset = Offset + 8
  1398.  
  1399. return Value
  1400. end
  1401.  
  1402. local function NextVarInt()
  1403. local Result = 0
  1404. local Shift = 0
  1405.  
  1406. while true do
  1407. local B = NextByte()
  1408.  
  1409. Result = bit32_bor(Result, bit32_lshift(bit32_band(B, 0x7F), Shift))
  1410.  
  1411. if bit32_band(B, 0x80) == 0 then
  1412. break
  1413. end
  1414.  
  1415. Shift = Shift + 7
  1416. end
  1417.  
  1418. return Result
  1419. end
  1420.  
  1421. local function NextString()
  1422. local Length = NextVarInt()
  1423.  
  1424. if Length == 0 then
  1425. return ""
  1426. end
  1427.  
  1428. CheckRead(Length, ("Attempted to read string of length %d"):format(Length))
  1429. local Result = buffer_readstring(Buf, Offset, Length)
  1430.  
  1431. Offset = Offset + Length
  1432.  
  1433. return Result
  1434. end
  1435.  
  1436. function Disassembler.ReadConstant(StringTable)
  1437. local K = { Type = NextByte() }
  1438.  
  1439. if K.Type == LbcConstantBoolean then
  1440. K.Value = NextByte() == 1
  1441. elseif K.Type == LbcConstantNumber then
  1442. K.Value = NextDouble()
  1443. elseif K.Type == LbcConstantString then
  1444. local Index = NextVarInt()
  1445. K.Value = StringTable[Index] or "Invalid string index"
  1446. elseif K.Type == LbcConstantImport then
  1447. K.Value = NextUint32()
  1448. elseif K.Type == LbcConstantTable then
  1449. local SizeKey = NextVarInt()
  1450. local Keys = table_create(SizeKey)
  1451.  
  1452. for I = 1, SizeKey do
  1453. Keys[I] = NextVarInt() + 1
  1454. end
  1455.  
  1456. K.Value = { Size = SizeKey, Ids = Keys }
  1457. elseif K.Type == LbcConstantClosure then
  1458. K.Value = NextVarInt()
  1459. elseif K.Type == LbcConstantVector then
  1460. local Vec = table_create(4)
  1461.  
  1462. for I = 1, 4 do
  1463. Vec[I] = NextFloat()
  1464. end
  1465.  
  1466. K.Value = Vec
  1467. elseif K.Type ~= 0 then
  1468. error("Unrecognized constant type: " .. tostring(K.Type))
  1469. end
  1470.  
  1471. return K
  1472. end
  1473.  
  1474. local function ReadProtoSource(StringTable)
  1475. local ProtoSourceId = NextVarInt()
  1476.  
  1477. return StringTable[ProtoSourceId] or "Invalid source index"
  1478. end
  1479.  
  1480. local function ReadLineInfo(Proto)
  1481. Proto.LineInfoCompKey = NextByte()
  1482.  
  1483. local LineInterval = 2 ^ Proto.LineInfoCompKey
  1484. local SmallLineInfo = table_create(Proto.SizeCode)
  1485. local LastOffset = 0
  1486.  
  1487. for I = 1, Proto.SizeCode do
  1488. LastOffset = bit32_band(LastOffset + NextByte(), 0xFF)
  1489. SmallLineInfo[I] = LastOffset
  1490. end
  1491.  
  1492. local Intervals = math_floor((Proto.SizeCode - 1) / LineInterval) + 1
  1493. local LargeLineInfo = table_create(Intervals)
  1494. local LastLine = 0
  1495.  
  1496. for I = 1, Intervals do
  1497. LastLine = LastLine + NextInt()
  1498. LargeLineInfo[I] = LastLine
  1499. end
  1500.  
  1501. Proto.InstructionLines = table_create(Proto.SizeCode)
  1502.  
  1503. for Pc = 1, Proto.SizeCode do
  1504. local IntervalIndex = math_floor((Pc - 1) / LineInterval) + 1
  1505. Proto.InstructionLines[Pc] = LargeLineInfo[IntervalIndex] + SmallLineInfo[Pc]
  1506. end
  1507. end
  1508.  
  1509. local function ReadProtoData(Proto, StringTable, ProtoTableRef)
  1510. Proto.MaxStackSize = NextByte()
  1511. Proto.NumParams = NextByte()
  1512. Proto.NumUpvalues = NextByte()
  1513. Proto.IsVarArg = NextByte() ~= 0
  1514. Proto.Flags = NextByte()
  1515.  
  1516. local TypeSize = NextVarInt()
  1517.  
  1518. Proto.TypeInfo = TypeSize > 0 and table_create(TypeSize) or {}
  1519.  
  1520. for I = 1, TypeSize do
  1521. Proto.TypeInfo[I] = NextByte()
  1522. end
  1523.  
  1524. Proto.SizeCode = NextVarInt()
  1525. Proto.CodeTable = table_create(Proto.SizeCode)
  1526.  
  1527. for I = 1, Proto.SizeCode do
  1528. Proto.CodeTable[I] = NextUint32()
  1529. end
  1530.  
  1531. Proto.SizeConsts = NextVarInt()
  1532. Proto.KTable = table_create(Proto.SizeConsts)
  1533.  
  1534. for I = 1, Proto.SizeConsts do
  1535. Proto.KTable[I] = Disassembler.ReadConstant(StringTable)
  1536. end
  1537.  
  1538. Proto.SizeProtos = NextVarInt()
  1539. Proto.PTable = Proto.SizeProtos > 0 and table_create(Proto.SizeProtos) or {}
  1540.  
  1541. for I = 1, Proto.SizeProtos do
  1542. Proto.PTable[I] = ProtoTableRef[NextVarInt() + 1]
  1543. end
  1544.  
  1545. Proto.LineDefined = NextVarInt()
  1546. Proto.Source = ReadProtoSource(StringTable)
  1547.  
  1548. if NextByte() == 1 then
  1549. ReadLineInfo(Proto)
  1550. else
  1551. Proto.InstructionLines = nil
  1552. end
  1553.  
  1554. if NextByte() == 1 then
  1555. local SizeLocals = NextVarInt()
  1556.  
  1557. Proto.Locals = SizeLocals > 0 and table_create(SizeLocals) or {}
  1558.  
  1559. for _ = 1, SizeLocals do
  1560. table_insert(Proto.Locals, {
  1561. Name = StringTable[NextVarInt()] or "invalid_local_name",
  1562. StartPC = NextVarInt(),
  1563. EndPC = NextVarInt(),
  1564. Register = NextByte(),
  1565. })
  1566. end
  1567.  
  1568. local SizeUpvals = NextVarInt()
  1569.  
  1570. Proto.UpvalNames = SizeUpvals > 0 and table_create(SizeUpvals) or {}
  1571.  
  1572. for _ = 1, SizeUpvals do
  1573. table_insert(Proto.UpvalNames, StringTable[NextVarInt()] or "invalid_upval_name")
  1574. end
  1575. end
  1576. end
  1577.  
  1578. local function DeserializeInternal(Version)
  1579. local TypesVersion = NextByte()
  1580.  
  1581. if not table_find({ 1, 2, 3 }, TypesVersion) then
  1582. error("Invalid types version (types version: " .. tostring(TypesVersion) .. ")")
  1583. end
  1584.  
  1585. local SizeStrings = NextVarInt()
  1586. local StringTable = table_create(SizeStrings)
  1587.  
  1588. for I = 1, SizeStrings do
  1589. StringTable[I] = NextString()
  1590. end
  1591.  
  1592. if TypesVersion >= 3 then
  1593. while NextByte() ~= 0 do end
  1594. end
  1595.  
  1596. local SizeProtos = NextVarInt()
  1597. local ProtoTable = table_create(SizeProtos)
  1598.  
  1599. for I = 1, SizeProtos do
  1600. ProtoTable[I] = CreateEmptyProto()
  1601. ProtoTable[I].ProtoIndex = I - 1
  1602. end
  1603.  
  1604. for I = 1, SizeProtos do
  1605. ReadProtoData(ProtoTable[I], StringTable, ProtoTable)
  1606. end
  1607.  
  1608. local MainProtoId = NextVarInt() + 1
  1609.  
  1610. if MainProtoId > #ProtoTable then
  1611. error("Index " .. MainProtoId .. " out of range for ProtoTable with length " .. #ProtoTable)
  1612. end
  1613.  
  1614. return ProtoTable[MainProtoId], ProtoTable, StringTable, Version, TypesVersion
  1615. end
  1616.  
  1617. Deserialize = function(InputBytecode)
  1618. Buf = buffer_fromstring(InputBytecode)
  1619. Offset = 0
  1620. CurrentBufferLen = buffer_len(Buf)
  1621.  
  1622. local Version = NextByte()
  1623.  
  1624. if Version == 5 or Version == 6 then
  1625. return DeserializeInternal(Version)
  1626. else
  1627. return error("Unsupported bytecode version: " .. tostring(Version))
  1628. end
  1629. end
  1630.  
  1631. ReadProto = function(Proto, Depth, ProtoTable, StringTable, LuauVersion, IsFromRoblox)
  1632. if not OpInfoMaps[LuauVersion] then
  1633. error("Unsupported Luau version for disassembly: " .. tostring(LuauVersion))
  1634. end
  1635.  
  1636. local ProtoOutput = {
  1637. ProtoIndex = Proto.ProtoIndex,
  1638. LineDefined = Proto.LineDefined,
  1639. Source = Proto.Source,
  1640. NumParams = Proto.NumParams,
  1641. IsVarArg = Proto.IsVarArg,
  1642. MaxStackSize = Proto.MaxStackSize,
  1643. Instructions = table_create(Proto.SizeCode),
  1644. Constants = {},
  1645. Protos = {},
  1646. RawProto = Proto,
  1647. }
  1648.  
  1649. local KTable = Proto.KTable
  1650. local KTableLen = #KTable
  1651.  
  1652. for i = 1, KTableLen do
  1653. table_insert(ProtoOutput.Constants, KTable[i])
  1654. end
  1655.  
  1656. local OpInfoMap = OpInfoMaps[LuauVersion]
  1657. local CodeTable = Proto.CodeTable
  1658. local CurrentPc = 1
  1659.  
  1660. while CurrentPc <= #CodeTable do
  1661. local InstructionInt = CodeTable[CurrentPc]
  1662. local OpcodeNum = GetOpcode(InstructionInt, IsFromRoblox)
  1663. local OpInfo = OpInfoMap[OpcodeNum]
  1664. local Opname = OpInfo and OpInfo.Name or "UNKNOWN"
  1665.  
  1666. local InstructionData = {
  1667. Name = Opname,
  1668. Opcode = OpcodeNum,
  1669. A = GetArgA(InstructionInt),
  1670. B = GetArgB(InstructionInt),
  1671. C = GetArgC(InstructionInt),
  1672. Bx = GetArgBx(InstructionInt),
  1673. SBx = GetArgSBx(InstructionInt),
  1674. SAx = GetArgSAx(InstructionInt),
  1675. Line = Proto.InstructionLines and Proto.InstructionLines[CurrentPc] or -1,
  1676. Pc = CurrentPc,
  1677. }
  1678.  
  1679. if OpInfo and OpInfo.Aux then
  1680. CurrentPc = CurrentPc + 1
  1681.  
  1682. if CurrentPc <= #CodeTable then
  1683. InstructionData.Aux = CodeTable[CurrentPc]
  1684. else
  1685. InstructionData.Aux = "Missing AUX value"
  1686. end
  1687. end
  1688.  
  1689. table_insert(ProtoOutput.Instructions, InstructionData)
  1690. CurrentPc = CurrentPc + 1
  1691. end
  1692.  
  1693. local PTable = Proto.PTable
  1694. local PTableLen = #PTable
  1695.  
  1696. for i = 1, PTableLen do
  1697. table_insert(ProtoOutput.Protos, ReadProto(PTable[i], Depth + 1, ProtoTable, StringTable, LuauVersion))
  1698. end
  1699.  
  1700. return ProtoOutput
  1701. end
  1702.  
  1703. function Disassembler.Disassemble(Bytecode, IsFromRoblox)
  1704. if string_byte(Bytecode, 1) == 0 then
  1705. local sourceOutput = {{
  1706. type = "source",
  1707. content = string_sub(Bytecode, 2),
  1708. }}
  1709.  
  1710. return sourceOutput, 0, -1, -1
  1711. end
  1712.  
  1713. local Ok, MainProto, ProtoTable, StringTable, LuauVersion, TypesVersion = pcall(Deserialize, Bytecode)
  1714.  
  1715. if not Ok then
  1716. error("Failed to deserialize bytecode: " .. tostring(MainProto))
  1717. end
  1718.  
  1719. local DisassembledProtos = table_create(#ProtoTable)
  1720. local MainProtoData
  1721. local ProtoTableLen = #ProtoTable
  1722.  
  1723. for i = 1, ProtoTableLen do
  1724. local Proto = ProtoTable[i]
  1725. local ProcessedProto = ReadProto(Proto, 1, ProtoTable, StringTable, LuauVersion, IsFromRoblox)
  1726. DisassembledProtos[i] = ProcessedProto
  1727.  
  1728. if Proto == MainProto then
  1729. MainProtoData = ProcessedProto
  1730. end
  1731. end
  1732.  
  1733. return {
  1734. MainProto = MainProtoData,
  1735. Protos = DisassembledProtos,
  1736. ProtoCount = #ProtoTable,
  1737. LuauVersion = LuauVersion,
  1738. TypesVersion = TypesVersion,
  1739. StringTable = StringTable
  1740. }
  1741. end
  1742.  
  1743. function Disassembler.FancyDisassemble(Bytecode, ShoveIntoOneString, IsFromRoblox)
  1744. ShoveIntoOneString = ShoveIntoOneString or false
  1745. local OutputParts = {}
  1746.  
  1747. local function PrintOrConcat(Text)
  1748. if ShoveIntoOneString then
  1749. table_insert(OutputParts, Text)
  1750. else
  1751. print(Text)
  1752. end
  1753. end
  1754.  
  1755. local StartBenchmark = os_clock()
  1756. local Result = { pcall(Disassembler.Disassemble, Bytecode, IsFromRoblox) }
  1757. local EndBenchmark = os_clock() - StartBenchmark
  1758.  
  1759. if not Result[1] then
  1760. PrintOrConcat("Disassembly failed: " .. tostring(Result[2]))
  1761.  
  1762. if ShoveIntoOneString then
  1763. return table_concat(OutputParts, "\n")
  1764. end
  1765.  
  1766. return
  1767. end
  1768.  
  1769. local Metadata = Result[2]
  1770. local DisassembledProtos = Metadata.Protos
  1771. local ProtosCount = Metadata.ProtoCount
  1772. local LuauVersion = Metadata.LuauVersion
  1773. local TypesVersion = Metadata.TypesVersion
  1774. local StringTable = Metadata.StringTable
  1775.  
  1776. PrintOrConcat("-- Disassembled with 0x1437's disassembler")
  1777. PrintOrConcat("-- https://github.com/0x1437/Luau-Bytecode-Diassembler")
  1778. PrintOrConcat("-- Inspired by @plusgiant5's Konstant Disassembler")
  1779. PrintOrConcat("-- Disassembled on " .. os.date("%Y-%m-%d %H:%M:%S", os.time()))
  1780. PrintOrConcat("-- Luau version " .. tostring(LuauVersion) .. ", Types version " .. tostring(TypesVersion))
  1781. PrintOrConcat("-- Time taken: " .. string_format("%.8fs", EndBenchmark))
  1782. PrintOrConcat("")
  1783.  
  1784. if ProtosCount == 0 and DisassembledProtos and #DisassembledProtos > 0 and DisassembledProtos[1].type == "source" then
  1785. PrintOrConcat(DisassembledProtos[1].content)
  1786.  
  1787. if ShoveIntoOneString then
  1788. return table_concat(OutputParts, "\n")
  1789. end
  1790.  
  1791. return
  1792. end
  1793.  
  1794. if not DisassembledProtos then
  1795. PrintOrConcat("Disassembly failed.")
  1796.  
  1797. if ShoveIntoOneString then
  1798. return table_concat(OutputParts, "\n")
  1799. end
  1800.  
  1801. return
  1802. end
  1803.  
  1804. local PrintedProtos = {}
  1805. local function PrintProtoRecursive(ProtoData, Depth, IsMain, AllProtos)
  1806. local TabSpace = string_rep(" ", Depth)
  1807. local RawProto = ProtoData.RawProto
  1808.  
  1809. if not IsMain then
  1810. local FuncName
  1811.  
  1812. if RawProto.Source and RawProto.Source ~= "Invalid source index" and #RawProto.Source > 0 and #RawProto.Source < 500 then
  1813. FuncName = RawProto.Source
  1814. else
  1815. FuncName = "anonymous_function_" .. ProtoData.ProtoIndex
  1816. end
  1817.  
  1818. local ParamNames = table_create(ProtoData.NumParams)
  1819.  
  1820. for i = 1, ProtoData.NumParams do
  1821. ParamNames[i] = "var" .. (i - 1)
  1822. end
  1823.  
  1824. if RawProto.Locals then
  1825. local RawProtoLocals = RawProto.Locals
  1826. local RawProtoLocalsLen = #RawProtoLocals
  1827.  
  1828. for i = 1, RawProtoLocalsLen do
  1829. local LocalInfo = RawProtoLocals[i]
  1830.  
  1831. if LocalInfo.StartPC == 0 and LocalInfo.Register < ProtoData.NumParams then
  1832. ParamNames[LocalInfo.Register + 1] = LocalInfo.Name
  1833. end
  1834. end
  1835. end
  1836.  
  1837. local Params = table_create(ProtoData.NumParams + (ProtoData.IsVarArg and 1 or 0))
  1838.  
  1839. for i = 1, ProtoData.NumParams do
  1840. table_insert(Params, ParamNames[i])
  1841. end
  1842.  
  1843. if ProtoData.IsVarArg then
  1844. table_insert(Params, "...")
  1845. end
  1846.  
  1847. local ParamString = table_concat(Params, ", ")
  1848. local Signature = FuncName .. "(" .. ParamString .. ")"
  1849.  
  1850. if ProtoData.LineDefined > 0 then
  1851. Signature = Signature .. string_format(" -- Line %d", ProtoData.LineDefined)
  1852. end
  1853.  
  1854. local ProtoInfoComment = string_format("-- %d parameters, %d upvalues, %d constants, %d functions", RawProto.NumParams, RawProto.NumUpvalues, RawProto.SizeConsts, RawProto.SizeProtos)
  1855.  
  1856. if not ShoveIntoOneString then
  1857. print(TabSpace .. "")
  1858. PrintOrConcat(TabSpace .. ProtoInfoComment)
  1859. PrintOrConcat(TabSpace .. "local function " .. Signature)
  1860. else
  1861. PrintOrConcat("\n" .. TabSpace .. ProtoInfoComment)
  1862. PrintOrConcat(TabSpace .. "local function " .. Signature)
  1863. end
  1864. end
  1865.  
  1866. local InnerTabSpace = string_rep(" ", Depth + (IsMain and 0 or 1))
  1867. local JumpTargets = {}
  1868. local OpInfoMap_v = OpInfoMaps[LuauVersion]
  1869. local Handlers_v = OpcodeHandlerMaps[LuauVersion]
  1870. local CodeTable = RawProto.CodeTable
  1871. local CodeTableLength = #CodeTable
  1872. local ProtoInstructionLines = RawProto.InstructionLines
  1873.  
  1874. for Index = 1, CodeTableLength do
  1875. local Instruction = CodeTable[Index]
  1876. local OpcodeNum = GetOpcode(Instruction, IsFromRoblox)
  1877. local OpInfo = OpInfoMap_v[OpcodeNum]
  1878.  
  1879. if OpInfo and OpInfo.Type:find("sBx") then
  1880. local SBx = GetArgSBx(Instruction)
  1881. local Target = Index + 1 + SBx
  1882.  
  1883. JumpTargets[Target] = (JumpTargets[Target] or "") .. "::" .. tostring(Index) .. "::, "
  1884. elseif OpInfo and OpInfo.Name == "JUMPBACK" then
  1885. local Bx = GetArgBx(Instruction)
  1886. local Target = Index + 1 - Bx
  1887.  
  1888. JumpTargets[Target] = (JumpTargets[Target] or "") .. "::" .. tostring(Index) .. "::, "
  1889. end
  1890. end
  1891.  
  1892. local UpvalueCounter = 0
  1893. local CurrentPc = 1
  1894.  
  1895. while CurrentPc <= CodeTableLength do
  1896. if JumpTargets[CurrentPc] then
  1897. PrintOrConcat(string_format("%s%s", InnerTabSpace, JumpTargets[CurrentPc]:sub(1, -3)))
  1898. end
  1899.  
  1900. local Instruction = CodeTable[CurrentPc]
  1901. local OpcodeNum = GetOpcode(Instruction, IsFromRoblox)
  1902. local OpInfo = OpInfoMap_v[OpcodeNum]
  1903. local Opname = OpInfo and OpInfo.Name or "UNKNOWN"
  1904.  
  1905. if Opname == "NEWCLOSURE" then
  1906. local Bx = GetArgBx(Instruction)
  1907. local ChildProtoData = ProtoData.Protos[Bx + 1]
  1908.  
  1909. if ChildProtoData and not PrintedProtos[ChildProtoData.ProtoIndex] then
  1910. PrintedProtos[ChildProtoData.ProtoIndex] = true
  1911.  
  1912. PrintProtoRecursive(ChildProtoData, Depth + (IsMain and 0 or 1), false, AllProtos)
  1913. end
  1914. elseif Opname == "DUPCLOSURE" then
  1915. local Bx = GetArgBx(Instruction)
  1916. local Konstant = RawProto.KTable[Bx + 1]
  1917. local ProtoIndex = Konstant.Value
  1918. local TargetProtoData = AllProtos[ProtoIndex + 1]
  1919.  
  1920. if TargetProtoData and not PrintedProtos[ProtoIndex] then
  1921. PrintedProtos[ProtoIndex] = true
  1922.  
  1923. PrintProtoRecursive(TargetProtoData, Depth + (IsMain and 0 or 1), false, AllProtos)
  1924. end
  1925. end
  1926.  
  1927. local InstructionArgs = {
  1928. A = GetArgA(Instruction),
  1929. B = GetArgB(Instruction),
  1930. C = GetArgC(Instruction),
  1931. Bx = GetArgBx(Instruction),
  1932. SBx = GetArgSBx(Instruction),
  1933. SAx = GetArgSAx(Instruction),
  1934. CodeIndex = CurrentPc - 1,
  1935. Proto = RawProto,
  1936. StringTable = StringTable,
  1937. AllProtos = AllProtos,
  1938. }
  1939.  
  1940. if Opname == "NEWCLOSURE" then
  1941. local Bx = InstructionArgs.Bx
  1942. InstructionArgs.ChildProto = RawProto.PTable[Bx + 1]
  1943. end
  1944.  
  1945. if OpInfo and OpInfo.Aux then
  1946. CurrentPc = CurrentPc + 1
  1947. InstructionArgs.Aux = CodeTable[CurrentPc]
  1948. end
  1949.  
  1950. local Handler = Handlers_v[Opname]
  1951. local OperandsStr, CommentStr = "", ""
  1952.  
  1953. if Handler then
  1954. if Opname == "CAPTURE" then
  1955. InstructionArgs.UpvalIndex = UpvalueCounter
  1956. UpvalueCounter = UpvalueCounter + 1
  1957. end
  1958. OperandsStr, CommentStr = Handler(InstructionArgs)
  1959. else
  1960. CommentStr = "Unimplemented Opcode: " .. Opname
  1961. end
  1962.  
  1963. local HexStr = string_format("[0x%08X]", Instruction)
  1964. local OpAndOperands = Opname
  1965.  
  1966. if OperandsStr and OperandsStr ~= "" then
  1967. OpAndOperands = OpAndOperands .. " " .. OperandsStr
  1968. end
  1969.  
  1970. local PaddedOpSection = string_format("%-40s", OpAndOperands)
  1971. local LineNumber = ProtoInstructionLines and ProtoInstructionLines[CurrentPc] or "?"
  1972. local Line = string_format("%s[Line %s] [%d] %s %s; %s", InnerTabSpace, tostring(LineNumber), CurrentPc - 1, HexStr, PaddedOpSection, CommentStr)
  1973. PrintOrConcat(Line)
  1974.  
  1975. CurrentPc = CurrentPc + 1
  1976. end
  1977.  
  1978. if not IsMain then
  1979. if not ShoveIntoOneString then
  1980. PrintOrConcat(TabSpace .. "end")
  1981. print(TabSpace .. "")
  1982. else
  1983. PrintOrConcat(TabSpace .. "end\n")
  1984. end
  1985. end
  1986. end
  1987.  
  1988. local MainProto
  1989. local LastProtoIndex = #DisassembledProtos
  1990.  
  1991. for i = 1, #DisassembledProtos do
  1992. local Proto = DisassembledProtos[i]
  1993.  
  1994. if Proto.ProtoIndex == LastProtoIndex - 1 then
  1995. MainProto = Proto
  1996.  
  1997. break
  1998. end
  1999. end
  2000.  
  2001. if MainProto then
  2002. PrintProtoRecursive(MainProto, 0, true, DisassembledProtos)
  2003. end
  2004.  
  2005. PrintOrConcat(string_format("[END OF DISASSEMBLY]"))
  2006.  
  2007. if ShoveIntoOneString then
  2008. return table_concat(OutputParts, "\n")
  2009. end
  2010.  
  2011. return nil
  2012. end
  2013.  
  2014. return Disassembler
Advertisement
Add Comment
Please, Sign In to add comment