-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathinit.lua
More file actions
1882 lines (1551 loc) · 66 KB
/
init.lua
File metadata and controls
1882 lines (1551 loc) · 66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
--!optimize 2
--!nolint UnknownGlobal
local ENABLED_REMARKS = {
COLD_REMARK = false,
INLINE_REMARK = false -- currently unused
}
local DECOMPILER_TIMEOUT = 2 -- seconds
local READER_FLOAT_PRECISION = 7 -- up to 99
local DECOMPILER_MODE = "disasm" -- disasm/optdec
local SHOW_DEBUG_INFORMATION = true -- show trivial function and array allocation details
local SHOW_INSTRUCTION_LINES = true -- show lines as they are in the source code
local SHOW_OPERATION_NAMES = true
local SHOW_OPERATION_INDEX = true -- show instruction index. used in jumps #n.
local SHOW_TRIVIAL_OPERATIONS = true
local USE_TYPE_INFO = true -- allow adding types to function parameters (ex. p1: string, p2: number)
local LIST_USED_GLOBALS = true -- list all (non-Roblox!!) globals used in the script as a top comment
local RETURN_ELAPSED_TIME = true -- return time it took to finish processing the bytecode
local DECODE_AS_BASE64 = false -- Decodes the bytecode as base64 if it's returned as such.
local USE_IN_STUDIO = false -- Toggles Roblox Studio mode, which allows for this to be used in
-- For studio, put your bytecode here.
local input = ``
local LoadFromUrl
if USE_IN_STUDIO then
-- A bit of an annoying thing, but I don't want 2 separate names for this
LoadFromUrl = function(moduleName)
return require(workspace["Disassembler"][moduleName])
end
else
LoadFromUrl = function(x)
local BASE_USER = "BOXLEGENDARY"
local BASE_BRANCH = "main"
local BASE_URL = "https://raw.githubusercontent.com/%s/Advanced-Luau-Decompiler/%s/%s.lua"
local loadSuccess, loadResult = pcall(function()
local formattedUrl = string.format(BASE_URL, BASE_USER, BASE_BRANCH, x)
return game:HttpGet(formattedUrl, true)
end)
if not loadSuccess then
warn(`({math.random()}) MОDULE FАILЕD ТO LOАD FRОM URL: {loadResult}.`)
return
end
local success, result = pcall(loadstring, loadResult)
if not success then
warn(`({math.random()}) MОDULE FАILЕD ТO LOАDSТRING: {result}.`)
return
end
if type(result) ~= "function" then
warn(`MОDULE IS {tostring(result)} (function expected)`)
return
end
return result()
end
end
local Implementations = LoadFromUrl("Implementations")
local Reader = LoadFromUrl("Reader")
local Strings = LoadFromUrl("Strings")
local Luau = LoadFromUrl("Luau")
local Base64 = LoadFromUrl("Base64")
local function LoadFlag(name)
local success, result = pcall(function()
return game:GetFastFlag(name)
end)
if success then
return result
end
return true -- assume the test ended and it was successful
end
local LuauCompileUserdataInfo = LoadFlag("LuauCompileUserdataInfo")
local LuauOpCode = Luau.OpCode
local LuauBytecodeTag = Luau.BytecodeTag
local LuauBytecodeType = Luau.BytecodeType
local LuauCaptureType = Luau.CaptureType
local LuauBuiltinFunction = Luau.BuiltinFunction
local LuauProtoFlag = Luau.ProtoFlag
local toBoolean = Implementations.toBoolean
local toEscapedString = Implementations.toEscapedString
local formatIndexString = Implementations.formatIndexString
local padLeft = Implementations.padLeft
local padRight = Implementations.padRight
local isGlobal = Implementations.isGlobal
Reader:Set(READER_FLOAT_PRECISION)
local function Decompile(bytecode)
local bytecodeVersion, typeEncodingVersion
local reader = Reader.new(bytecode)
-- step 1: collect information from the bytecode
local function disassemble()
if bytecodeVersion >= 4 then
-- type encoding did not exist before this version
typeEncodingVersion = reader:nextByte()
end
local stringTable = {}
local function readStringTable()
local amountOfStrings = reader:nextVarInt() -- or, well, stringTable size.
for i = 1, amountOfStrings do
stringTable[i] = reader:nextString()
end
end
local userdataTypes = {}
local hasUserdataTypes = false
local function readUserdataTypes()
if LuauCompileUserdataInfo then
while true do
local index = reader:nextByte()
if index == 0 then
-- zero marks the end of type mapping
break
end
local nameRef = reader:nextVarInt()
userdataTypes[index] = stringTable[nameRef]
hasUserdataTypes = true
end
end
end
local protoTable = {}
local function readProtoTable()
local amountOfProtos = reader:nextVarInt() -- or protoTable size
for i = 1, amountOfProtos do
local protoId = i - 1 -- account for main proto
local proto = {
id = protoId,
instructions = {},
constants = {},
captures = {}, -- upvalue references
innerProtos = {},
instructionLineInfo = {}
}
protoTable[protoId] = proto
-- read header
proto.maxStackSize = reader:nextByte()
proto.numParams = reader:nextByte()
proto.numUpvalues = reader:nextByte()
proto.isVarArg = toBoolean(reader:nextByte())
-- read flags and typeInfo if bytecode version includes that information
if bytecodeVersion >= 4 then
proto.flags = reader:nextByte()
-- collect type info
local resultTypedParams = {}
local resultTypedUpvalues = {}
local resultTypedLocals = {}
-- refer to: https://github.com/luau-lang/luau/blob/0.655/Compiler/src/BytecodeBuilder.cpp#L752
local allTypeInfoSize = reader:nextVarInt()
local hasTypeInfo = allTypeInfoSize > 0 -- we don't have any type info if the size is zero.
proto.hasTypeInfo = hasTypeInfo
if hasTypeInfo then
local totalTypedParams = allTypeInfoSize
local totalTypedUpvalues = 0
local totalTypedLocals = 0
if typeEncodingVersion > 1 then
-- much more info is encoded in next versions
totalTypedParams = reader:nextVarInt()
totalTypedUpvalues = reader:nextVarInt()
totalTypedLocals = reader:nextVarInt()
end
local function readTypedParams()
local typedParams = resultTypedParams
if totalTypedParams > 0 then
typedParams = reader:nextBytes(totalTypedParams) -- array of uint8
-- first value is always "function"
-- we don't care about that.
table.remove(typedParams, 1)
-- second value is the amount of typed params
table.remove(typedParams, 1)
end
return typedParams
end
local function readTypedUpvalues()
local typedUpvalues = resultTypedUpvalues
if totalTypedUpvalues > 0 then
for i = 1, totalTypedUpvalues do
local upvalueType = reader:nextByte()
-- info on the upvalue at index `i`
local info = {
type = upvalueType
}
typedUpvalues[i] = info
end
end
return typedUpvalues
end
local function readTypedLocals()
local typedLocals = resultTypedLocals
if totalTypedLocals > 0 then
for i = 1, totalTypedLocals do
local localType = reader:nextByte()
-- Register is locals' place in the stack
local localRegister = reader:nextByte() -- accounts for function params!
-- PC - Program Counter
local localStartPC = reader:nextVarInt() + 1
-- refer to: https://github.com/luau-lang/luau/blob/0.655/Compiler/src/BytecodeBuilder.cpp#L749
-- if you want to know why we get endPC like that
local localEndPC = reader:nextVarInt() + localStartPC - 1
-- info on the local at index `i`
local info = {
type = localType,
register = localRegister,
startPC = localStartPC,
--endPC = localEndPC -- unused in the disassembler
}
typedLocals[i] = info
end
end
return typedLocals
end
resultTypedParams = readTypedParams()
resultTypedUpvalues = readTypedUpvalues()
resultTypedLocals = readTypedLocals()
end
proto.typedParams = resultTypedParams
proto.typedUpvalues = resultTypedUpvalues
proto.typedLocals = resultTypedLocals
end
-- total number of instructions
proto.sizeInstructions = reader:nextVarInt()
for i = 1, proto.sizeInstructions do
local encodedInstruction = reader:nextUInt32()
proto.instructions[i] = encodedInstruction
end
-- total number of constants
proto.sizeConstants = reader:nextVarInt()
for i = 1, proto.sizeConstants do
local constValue
local constType = reader:nextByte()
if constType == LuauBytecodeTag.LBC_CONSTANT_BOOLEAN then
-- 1 = true, 0 = false
constValue = toBoolean(reader:nextByte())
elseif constType == LuauBytecodeTag.LBC_CONSTANT_NUMBER then
constValue = reader:nextDouble()
elseif constType == LuauBytecodeTag.LBC_CONSTANT_STRING then
local stringId = reader:nextVarInt()
constValue = stringTable[stringId]
elseif constType == LuauBytecodeTag.LBC_CONSTANT_IMPORT then
-- imports are globals from the environment
-- examples: math.random, print, coroutine.wrap
local id = reader:nextUInt32()
local indexCount = bit32.rshift(id, 30)
local cacheIndex1 = bit32.band(bit32.rshift(id, 20), 0x3FF)
local cacheIndex2 = bit32.band(bit32.rshift(id, 10), 0x3FF)
local cacheIndex3 = bit32.band(id, 0x3FF)
local path = {}
if indexCount >= 1 then table.insert(path, stringTable[cacheIndex1]) end
if indexCount >= 2 then table.insert(path, stringTable[cacheIndex2]) end
if indexCount >= 3 then table.insert(path, stringTable[cacheIndex3]) end
constValue = table.concat(path, ".")
elseif constType == LuauBytecodeTag.LBC_CONSTANT_TABLE then
local sizeTable = reader:nextVarInt()
local tableKeys = {}
for i = 1, sizeTable do
local keyStringId = reader:nextVarInt() + 1
table.insert(tableKeys, keyStringId)
end
constValue = {
size = sizeTable,
keys = tableKeys
}
elseif constType == LuauBytecodeTag.LBC_CONSTANT_CLOSURE then
local closureId = reader:nextVarInt() + 1
constValue = closureId
elseif constType == LuauBytecodeTag.LBC_CONSTANT_VECTOR then
local x, y, z, w = reader:nextFloat(), reader:nextFloat(), reader:nextFloat(), reader:nextFloat()
if w == 0 then
constValue = "Vector3.new(".. x ..", ".. y ..", ".. z ..")"
else
constValue = "vector.create(".. x ..", ".. y ..", ".. z ..", ".. w ..")"
end
elseif constType == LuauBytecodeTag.LBC_CONSTANT_TABLE_WITH_CONSTANTS then
local sizeTable = reader:nextVarInt()
local tableKeys = {}
for i = 1, sizeTable do
local keyStringId = reader:nextVarInt() + 1
table.insert(tableKeys, keyStringId)
end
constValue = {
size = sizeTable,
keys = tableKeys,
withConstants = true
}
elseif constType == LuauBytecodeTag.LBC_CONSTANT_INTEGER then
if reader.nextInt64 then
constValue = reader:nextInt64()
else
local hi = reader:nextUInt32()
local lo = reader:nextUInt32()
constValue = string.format("0x%08X%08X", hi, lo)
end
elseif constType ~= LuauBytecodeTag.LBC_CONSTANT_NIL then
-- this is not supposed to happen. result is likely malformed
end
-- info on the constant at index `i`
local info = {
type = constType,
value = constValue
}
proto.constants[i] = info
end
-- total number of protos inside this proto
proto.sizeInnerProtos = reader:nextVarInt()
for i = 1, proto.sizeInnerProtos do
local protoId = reader:nextVarInt()
proto.innerProtos[i] = protoTable[protoId]
end
-- lineDefined is the line function starts on
proto.lineDefined = reader:nextVarInt()
-- protoDebugNameId is the string id of the function's name if it is not unnamed
local protoDebugNameId = reader:nextVarInt()
proto.name = stringTable[protoDebugNameId]
-- references:
-- https://github.com/luau-lang/luau/blob/0.655/Compiler/src/BytecodeBuilder.cpp#L888
-- https://github.com/uniquadev/LuauVM/blob/master/VM/luau/lobject.lua
local hasLineInfo = toBoolean(reader:nextByte())
proto.hasLineInfo = hasLineInfo
if hasLineInfo then
-- log2 of the line gap between instructions
local lineGapLog2 = reader:nextByte()
local baselineSize = bit32.rshift(proto.sizeInstructions - 1, lineGapLog2) + 1
local lastOffset = 0
local lastLine = 0
-- line number as a delta from baseline for each instruction
local smallLineInfo = {}
-- one entry for each bit32.lshift(1, lineGapLog2) instructions
local absLineInfo = {}
-- ready to read line info
local resultLineInfo = {}
for i, instruction in proto.instructions do
-- i don't understand how this works. we mostly need signed, but sometimes we need unsigned?
-- help please. if you understand
local byte = reader:nextSignedByte()
-- line numbers unexpectedly dropped/increased by 255 (or 256?) because i set delta to just lastOffset + byte
-- the solution: (lastOffset + byte) & 0xFF.
-- shoutout to https://github.com/ActualMasterOogway/Iridium/ for finding this fix
local delta = bit32.band(lastOffset + byte, 0xFF)
smallLineInfo[i] = delta
lastOffset = delta
end
for i = 1, baselineSize do
-- if we read unsigned int32 here we're doomed!!!!!! for eternity!!!!!!!!!
local largeLineChange = lastLine + reader:nextInt32()
absLineInfo[i] = largeLineChange
lastLine = largeLineChange
end
for i, line in smallLineInfo do
local absIndex = bit32.rshift(i - 1, lineGapLog2) + 1
local absLine = absLineInfo[absIndex]
local resultLine = line + absLine
resultLineInfo[i] = resultLine
end
proto.lineInfoSize = lineGapLog2
proto.instructionLineInfo = resultLineInfo
end
-- debug info is not present in Roblox and that's sad
-- no variable names...
local hasDebugInfo = toBoolean(reader:nextByte())
proto.hasDebugInfo = hasDebugInfo
if hasDebugInfo then
local totalDebugLocals = reader:nextVarInt()
local function readDebugLocals()
local debugLocals = {}
for i = 1, totalDebugLocals do
local localName = stringTable[reader:nextVarInt()]
local localStartPC = reader:nextVarInt()
local localEndPC = reader:nextVarInt()
local localRegister = reader:nextByte()
-- debug info on the local at index `i`
local info = {
name = localName,
startPC = localStartPC,
endPC = localEndPC,
register = localRegister
}
debugLocals[i] = info
end
return debugLocals
end
proto.debugLocals = readDebugLocals()
local totalDebugUpvalues = reader:nextVarInt()
local function readDebugUpvalues()
local debugUpvalues = {}
for i = 1, totalDebugUpvalues do
local upvalueName = stringTable[reader:nextVarInt()]
-- debug info on the upvalue at index `i`
local info = {
name = upvalueName
}
debugUpvalues[i] = info
end
return debugUpvalues
end
proto.debugUpvalues = readDebugUpvalues()
end
end
end
-- read needs to be done in proper order
readStringTable()
if bytecodeVersion > 5 then
readUserdataTypes()
if Luau.SetUserdataTypeNames then
Luau:SetUserdataTypeNames(userdataTypes)
end
end
readProtoTable()
if hasUserdataTypes and not Luau.SetUserdataTypeNames then
warn("userdata type names were found in the bytecode, but this build cannot register them")
end
local mainProtoId = reader:nextVarInt()
return mainProtoId, protoTable
end
-- step 2: organize information for decompilation
local function organize()
-- provides proto name and line along with the issue in a warning message
local function reportProtoIssue(proto, issue)
local protoIdentifier = `[{proto.name or "unnamed"}:{proto.lineDefined or -1}]`
warn(protoIdentifier .. ": " .. issue)
end
local mainProtoId, protoTable = disassemble()
local mainProto = protoTable[mainProtoId]
mainProto.main = true
-- collected operation data
local registerActions = {}
local function baseProto(proto)
local protoRegisterActions = {}
-- this needs to be done here.
local protoActionData = {
proto = proto,
actions = protoRegisterActions
}
registerActions[proto.id] = protoActionData
local instructions = proto.instructions
local innerProtos = proto.innerProtos
local constants = proto.constants
local captures = proto.captures
local flags = proto.flags
-- collect all captures past the base instruction index
local function collectCaptures(baseIndex, proto)
local numUpvalues = proto.numUpvalues
if numUpvalues > 0 then
local _captures = proto.captures
for i = 1, numUpvalues do
local capture = instructions[baseIndex + i]
local captureType = Luau:INSN_A(capture)
local sourceRegister = Luau:INSN_B(capture)
if captureType == LuauCaptureType.LCT_VAL or captureType == LuauCaptureType.LCT_REF then
_captures[i - 1] = sourceRegister
elseif captureType == LuauCaptureType.LCT_UPVAL then
-- capture of a capture. haha..
_captures[i - 1] = captures[sourceRegister]
end
end
end
end
local function writeFlags()
local decodedFlags = {}
if proto.main then
-- what we are dealing with here is mainFlags
-- refer to: https://github.com/luau-lang/luau/blob/0.655/Compiler/src/Compiler.cpp#L4188
--decodedFlags.native = toBoolean(bit32.band(flags, LuauProtoFlag.LPF_NATIVE_MODULE))
else
-- normal protoFlags
-- refer to: https://github.com/luau-lang/luau/blob/0.655/Compiler/src/Compiler.cpp#L287
--decodedFlags.native = toBoolean(bit32.band(flags, LuauProtoFlag.LPF_NATIVE_FUNCTION))
--decodedFlags.cold = toBoolean(bit32.band(flags, LuauProtoFlag.LPF_NATIVE_COLD))
end
-- update flags entry
flags = decodedFlags
proto.flags = decodedFlags
end
local function writeInstructions()
local auxSkip = false
for index, instruction in instructions do
if auxSkip then
-- we are currently on an aux of a previous instruction
-- there is no need to do any work here.
auxSkip = false
continue
end
local opCodeInfo = LuauOpCode[Luau:INSN_OP(instruction)]
if not opCodeInfo then
-- this is serious!
reportProtoIssue(proto, `invalid instruction at index "{index}"!`)
continue
end
local opCodeName = opCodeInfo.name
local opCodeType = opCodeInfo.type
local opCodeIsAux = opCodeInfo.aux == true
-- information in the instruction that we will use
local A, B, C
local sD, D, E
local aux
-- creates an action from provided data and registers it.
local function registerAction(usedRegisters, extraData, hide)
local data = {
usedRegisters = usedRegisters or {},
extraData = extraData,
opCode = opCodeInfo,
hide = hide
}
table.insert(protoRegisterActions, data)
end
-- handle reading information based on the op code type
if opCodeType == "A" then
A = Luau:INSN_A(instruction)
elseif opCodeType == "E" then
E = Luau:INSN_E(instruction)
elseif opCodeType == "AB" then
A = Luau:INSN_A(instruction)
B = Luau:INSN_B(instruction)
elseif opCodeType == "AC" then
A = Luau:INSN_A(instruction)
C = Luau:INSN_C(instruction)
elseif opCodeType == "ABC" then
A = Luau:INSN_A(instruction)
B = Luau:INSN_B(instruction)
C = Luau:INSN_C(instruction)
elseif opCodeType == "AD" then
A = Luau:INSN_A(instruction)
D = Luau:INSN_D(instruction)
elseif opCodeType == "AsD" then
A = Luau:INSN_A(instruction)
sD = Luau:INSN_sD(instruction)
elseif opCodeType == "sD" then
sD = Luau:INSN_sD(instruction)
end
-- handle aux
if opCodeIsAux then
auxSkip = true
-- empty action for aux
registerAction(nil, nil, true)
-- aux is the next instruction
aux = instructions[index + 1]
end
-- it would be faster if we did this comparing opCode index
-- rather than name, but it would be suffering to code and read
if opCodeName == "NOP" or opCodeName == "BREAK" or opCodeName == "NATIVECALL" then
-- empty action for these
registerAction(nil, nil, not SHOW_TRIVIAL_OPERATIONS)
elseif opCodeName == "LOADNIL" then
registerAction({A})
elseif opCodeName == "LOADB" then -- load boolean
registerAction({A}, {B, C})
elseif opCodeName == "LOADN" then -- load number literal
registerAction({A}, {sD})
elseif opCodeName == "LOADK" then -- load constant
registerAction({A}, {D})
elseif opCodeName == "MOVE" then
registerAction({A, B})
elseif opCodeName == "GETGLOBAL" or opCodeName == "SETGLOBAL" then
-- we most likely will not ever use C here.
registerAction({A}, {aux}) --({A}, {C, aux})
elseif opCodeName == "GETUPVAL" or opCodeName == "SETUPVAL" then
registerAction({A}, {B})
elseif opCodeName == "CLOSEUPVALS" then
registerAction({A}, nil, not SHOW_TRIVIAL_OPERATIONS)
elseif opCodeName == "GETIMPORT" then
registerAction({A}, {D, aux})
elseif opCodeName == "GETTABLE" or opCodeName == "SETTABLE" then
registerAction({A, B, C})
elseif opCodeName == "GETTABLEKS" or opCodeName == "SETTABLEKS" or opCodeName == "GETUDATAKS" or opCodeName == "SETUDATAKS" then
registerAction({A, B}, {C, aux})
elseif opCodeName == "GETTABLEN" or opCodeName == "SETTABLEN" then
registerAction({A, B}, {C})
elseif opCodeName == "NEWCLOSURE" then
registerAction({A}, {D})
local proto = innerProtos[D + 1]
collectCaptures(index, proto)
baseProto(proto)
elseif opCodeName == "DUPCLOSURE" then
registerAction({A}, {D})
local proto = protoTable[constants[D + 1].value - 1]
collectCaptures(index, proto)
baseProto(proto)
elseif opCodeName == "NAMECALL" or opCodeName == "NAMECALLUDATA" then -- must be followed by CALL
registerAction({A, B}, {C, aux}, not SHOW_TRIVIAL_OPERATIONS)
elseif opCodeName == "CALL" then
registerAction({A}, {B, C})
elseif opCodeName == "RETURN" then
registerAction({A}, {B})
elseif opCodeName == "JUMP" or opCodeName == "JUMPBACK" then
registerAction({}, {sD})
elseif opCodeName == "JUMPIF" or opCodeName == "JUMPIFNOT" then
registerAction({A}, {sD})
elseif
opCodeName == "JUMPIFEQ" or opCodeName == "JUMPIFLE" or opCodeName == "JUMPIFLT" or
opCodeName == "JUMPIFNOTEQ" or opCodeName == "JUMPIFNOTLE" or opCodeName == "JUMPIFNOTLT"
then
registerAction({A, aux}, {sD})
elseif
opCodeName == "ADD" or opCodeName == "SUB" or opCodeName == "MUL" or
opCodeName == "DIV" or opCodeName == "MOD" or opCodeName == "POW"
then
registerAction({A, B, C})
elseif
opCodeName == "ADDK" or opCodeName == "SUBK" or opCodeName == "MULK" or
opCodeName == "DIVK" or opCodeName == "MODK" or opCodeName == "POWK"
then
registerAction({A, B}, {C})
elseif opCodeName == "IDIV" then -- floor division
registerAction({A, B, C})
elseif opCodeName == "IDIVK" then -- floor division with 1 constant argument
registerAction({A, B}, {C})
elseif opCodeName == "JUMPX" then
registerAction({}, {E})
elseif opCodeName == "AND" or opCodeName == "OR" then
registerAction({A, B, C})
elseif opCodeName == "ANDK" or opCodeName == "ORK" then
registerAction({A, B}, {C})
elseif opCodeName == "CONCAT" then
local registers = {A}
for reg = B, C do
table.insert(registers, reg)
end
registerAction(registers)
elseif opCodeName == "NOT" or opCodeName == "MINUS" or opCodeName == "LENGTH" then
registerAction({A, B})
elseif opCodeName == "NEWTABLE" then
registerAction({A}, {B, aux})
elseif opCodeName == "DUPTABLE" then
registerAction({A}, {D})
elseif opCodeName == "SETLIST" then
if C ~= 0 then
local registers = {A, B}
for i = 1, C - 2 do -- account for target and source registers
table.insert(registers, B + i)
end
registerAction(registers, {aux, C})
else
registerAction({A, B}, {aux, C})
end
elseif opCodeName == "FORNPREP" then
registerAction({A, A+1, A+2}, {sD})
elseif opCodeName == "FORNLOOP" then
registerAction({A}, {sD})
elseif opCodeName == "FORGLOOP" then
local numVariableRegisters = bit32.band(aux, 0xFF)
local registers = {}
for regIndex = 1, numVariableRegisters do
table.insert(registers, A + regIndex)
end
registerAction(registers, {sD, aux})
elseif opCodeName == "FORGPREP_INEXT" or opCodeName == "FORGPREP_NEXT" then
registerAction({A}, {sD})
elseif opCodeName == "FORGPREP" then
registerAction({A}, {sD})
elseif opCodeName == "GETVARARGS" then
if B ~= 0 then
local registers = {}
for reg = 0, B - 2 do
table.insert(registers, A + reg)
end
registerAction(registers, {B})
else
registerAction({A}, {B})
end
elseif opCodeName == "PREPVARARGS" then
registerAction({}, {A}, not SHOW_TRIVIAL_OPERATIONS)
elseif opCodeName == "LOADKX" then
registerAction({A}, {aux})
elseif opCodeName == "COVERAGE" then
registerAction({}, {E}, not SHOW_TRIVIAL_OPERATIONS)
elseif
opCodeName == "JUMPXEQKNIL" or opCodeName == "JUMPXEQKB" or
opCodeName == "JUMPXEQKN" or opCodeName == "JUMPXEQKS"
then
registerAction({A}, {sD, aux})
elseif opCodeName == "CAPTURE" then
-- empty action here
registerAction(nil, nil, not SHOW_TRIVIAL_OPERATIONS)
elseif opCodeName == "SUBRK" or opCodeName == "DIVRK" then -- constant sub/div
registerAction({A, C}, {B})
elseif opCodeName == "FASTCALL" then -- reads info from the CALL instruction
registerAction({}, {A, C}, not SHOW_TRIVIAL_OPERATIONS)
elseif opCodeName == "FASTCALL1" then -- 1 register argument
registerAction({B}, {A, C}, not SHOW_TRIVIAL_OPERATIONS)
elseif opCodeName == "FASTCALL2" then -- 2 register arguments
local sourceArgumentRegister2 = bit32.band(aux, 0xFF)
registerAction({B, sourceArgumentRegister2}, {A, C}, not SHOW_TRIVIAL_OPERATIONS)
elseif opCodeName == "FASTCALL2K" then -- 1 register argument and 1 constant argument
registerAction({B}, {A, C, aux}, not SHOW_TRIVIAL_OPERATIONS)
elseif opCodeName == "FASTCALL3" then
local sourceArgumentRegister2 = bit32.band(aux, 0xFF)
local sourceArgumentRegister3 = bit32.band(bit32.rshift(aux, 8), 0xFF)
registerAction({B, sourceArgumentRegister2, sourceArgumentRegister3}, {A, C}, not SHOW_TRIVIAL_OPERATIONS)
end
end
end
writeFlags()
writeInstructions()
end
baseProto(mainProto)
return mainProtoId, registerActions, protoTable
end
-- step 3: turn the result into a string
local function finalize(mainProtoId, registerActions, protoTable)
local finalResult = ""
local totalParameters = 0
-- array of used globals for further output
local usedGlobals = {}
local usedGlobalsSet = {}
-- should `key` be logged in usedGlobals?
local function isValidGlobal(key)
return not usedGlobalsSet[key] and not isGlobal(key)
end
-- received result. embed final things here.
local function processResult(result)
local embed = ""
if LIST_USED_GLOBALS and #usedGlobals > 0 then
embed ..= string.format(Strings.USED_GLOBALS, table.concat(usedGlobals, ", "))
end
return embed .. result
end
-- now proceed based off mode
if DECOMPILER_MODE == "disasm" then -- disassembler
local result = {}
local function writeActions(protoActions)
local actions = protoActions.actions
local proto = protoActions.proto
local instructionLineInfo = proto.instructionLineInfo
local innerProtos = proto.innerProtos
local constants = proto.constants
local captures = proto.captures
local flags = proto.flags
local numParams = proto.numParams
local showInstructionLines = SHOW_INSTRUCTION_LINES and #instructionLineInfo > 0
-- for proper `goto` handling
local jumpMarkers = {}
local function makeJumpMarker(index)
index -= 1
local numMarkers = jumpMarkers[index] or 0
jumpMarkers[index] = numMarkers + 1
end
-- for easier parameter differentiation
totalParameters += numParams
-- support for mainFlags
if proto.main then
-- if there is a possible way to check for --!optimize please let me know
if flags.native then
result[#result + 1] = "--!native" .. "\n"
end
end
for i, action in actions do
if action.hide then
-- skip this action. either hidden or just aux that is needed for proper line info
continue
end
local usedRegisters = action.usedRegisters
local extraData = action.extraData
local opCodeInfo = action.opCode
local opCodeName = opCodeInfo.name
local function handleJumpMarkers()
local numJumpMarkers = jumpMarkers[i]
if numJumpMarkers then
jumpMarkers[i] = nil
--if string.find(opCodeName, "JUMP") then
-- it's much more complicated
-- result[#result + 1] = "else\n"
-- local newJumpOffset = i + extraData[1] + 1
-- makeJumpMarker(newJumpOffset)
--else
-- it's just a one way condition
for i = 1, numJumpMarkers do
result[#result + 1] = "end\n"
end
--end
end
end
local function writeHeader()
local index
if SHOW_OPERATION_INDEX then
index = "[".. padLeft(i, "0", 3) .."]"
else
index = ""
end
local name
if SHOW_OPERATION_NAMES then
name = padRight(opCodeName, " ", 15)
else
name = ""
end
local line
if showInstructionLines then
line = ":".. padLeft(instructionLineInfo[i], "0", 3) ..":"
else
line = ""
end
result[#result + 1] = index .." ".. line .. name
end
local function writeOperationBody()
local function formatRegister(register)
local parameterRegister = register + 1 -- parameter registers start from 0
if parameterRegister < numParams + 1 then
-- this means we are using preserved parameter register
return "p".. ((totalParameters - numParams) + parameterRegister)
end
return "v".. (register - numParams)
end
local function formatUpvalue(register)
return "u_v".. register
end
local function formatProto(proto)
local name = proto.name
local numParams = proto.numParams
local isVarArg = proto.isVarArg
local isTyped = proto.hasTypeInfo and USE_TYPE_INFO
local flags = proto.flags
local typedParams = proto.typedParams
local protoBody = ""
-- attribute support
if flags.native then
if flags.cold and ENABLED_REMARKS.COLD_REMARK then
-- function is marked cold and is deemed not profitable to compile natively
-- refer to: https://github.com/luau-lang/luau/blob/0.655/Compiler/src/Compiler.cpp#L285
protoBody ..= string.format(Strings.DECOMPILER_REMARK, "This function is marked cold and is not compiled natively")
end
protoBody ..= "@native "
end
-- if function has a name, add it
if name then
protoBody = "local function ".. name
else
protoBody = "function"
end
-- now build parameters
protoBody ..= "("
for index = 1, numParams do
local parameterBody = "p".. (totalParameters + index)
-- if has type info, apply it
if isTyped then
local parameterType = typedParams[index]
-- not sure if parameterType always exists
if parameterType then
parameterBody ..= ": ".. Luau:GetBaseTypeString(parameterType, true)
end
end
-- if not last parameter
if index ~= numParams then
parameterBody ..= ", "
end
protoBody ..= parameterBody
end
if isVarArg then
if numParams > 0 then
-- top it off with ...
protoBody ..= ", ..."
else
protoBody ..= "..."
end
end
protoBody ..= ")\n"
-- additional debug information
if SHOW_DEBUG_INFORMATION then
protoBody ..= "-- proto pool id: ".. proto.id .. "\n"
protoBody ..= "-- num upvalues: ".. proto.numUpvalues .. "\n"
protoBody ..= "-- num inner protos: ".. proto.sizeInnerProtos .. "\n"
protoBody ..= "-- size instructions: ".. proto.sizeInstructions .. "\n"
protoBody ..= "-- size constants: ".. proto.sizeConstants .. "\n"
protoBody ..= "-- lineinfo gap: ".. proto.lineInfoSize .. "\n"
protoBody ..= "-- max stack size: ".. proto.maxStackSize .. "\n"
protoBody ..= "-- is typed: ".. tostring(proto.hasTypeInfo) .. "\n"
end