-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathil2cpppatch.py
1480 lines (1205 loc) · 66.5 KB
/
il2cpppatch.py
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
# https://github.com/HorridModz/Il2cppPatch
def main():
"""
Welcome!
Edit this function according to the instructions to generate your script!
"""
"""
Path to the dumpcs file
Make sure not to remove the r before the opening quotation mark!
"""
dumpcspath = r"C:\Users\zachy\Documents\Work\Projects\Pixel Gun 3D\Pixel Gun 3D 22.6.0\dump.cs" # noqa
"""
Path to the .so library file (usually libil2cpp.so)
Make sure not to remove the r before the opening quotation mark!
"""
libpath = r"C:\Users\zachy\Documents\Work\Projects\Pixel Gun 3D\Pixel Gun 3D 22.6.0\libil2cpp.so" # noqa
"""
Path to the output script file
Make sure not to remove the r before the opening quotation mark!
"""
outputpath = r"C:\Users\zachy\Downloads\il2cpppatchgeneratedscript.lua" # noqa
"""
Settings for generated script
scripttitle: Title of the script.
scriptauthor: Your name. To make it anonymous, set to None.
scriptdescription: Description of what the script does. Note that all special characters will be escaped.
Also note that double quotes (") must be escaped (written as \") or in a multiline string.
To not have a description, set to None.
scriptalert: Script will alert this message when it is run. Note that all special characters will be escaped.
Also note that double quotes (") must be escaped (written as \") or in a multiline string.
To disable, set to None.
scripttoast: Script will toast this message when it is run. Note that all special characters will be escaped.
Also note that double quotes (") must be escaped (written as \") or in a multiline string.
To disable, set to None.
Example alert (demonstrating with multiline raw string) - note that the opening and closing quotes are escaped with
backward slashes, so pretend the backslashes aren't there):
scriptalert = r\"\"\"Welcome to my script!
This script will give you 10000 gems. Enjoy! As they say, "A happy man is a wealthy man."\"\"\"
"""
scripttitle = "My Il2cppPatch Script"
scriptdescription = None
scriptauthor = "User123456789#6424"
scriptalert = None
scripttoast = None
"""
Game info
gamename: Name of the game the script is for.
gameversion: Game version the script is for.
architecture: Game architecture the script is for (32bit or 64bit).
"""
gamename = "Pixel Gun 3D"
gameversion = "22.6.0"
global architecture
architecture = "64bit" # 32bit or 64bit
"""
Initialization of script (do not touch this)
"""
script = Script()
script.addopeningcomment(scripttitle, scriptdescription, scriptauthor, gamename, gameversion, architecture)
if scriptalert is not None:
# noinspection PyTypeChecker
script.addalert(scriptalert)
if scripttoast is not None:
# noinspection PyTypeChecker
script.addtoast(scripttoast)
script.addarchitecturecheck(architecture)
""" Patching time! """
"""
Patching functions
getmethodandclassfromoffset: Function to get the class name and method name of a method from its offset
Arguments:
Offset: Offset of the method
Use it in place of classname and methodname, with * at the beginning, like this:
*getmethodandclassfromoffset(offset)
For example:
patchmethod(*getmethodandclassfromoffset(0x69420), 69, "int")
getfieldfromclassandoffset: Function to get the name of a field by the name of its class and the field offset
Arguments:
classname: Name of the class the field is in
offset: Offset of the field (mutually exclusive with offset)
Use it in place of fieldname, like this:
patchfield(getfieldfromclassandoffset("MyClass", 0x69), 69, "int")
Patchmethod: Function to patch one method.
Arguments:
classname: Name of the class the method is in.
methodname: Name of the method to patch.
patchto: The value to patch to.
patchtype: The type of data the patch is.
Patchfield: Function to patch one field.
Arguments:
classname: Name of the class the field is in.
fieldname: Name of the field to patch.
patchto: The value to patch to.
patchtype: The type of data the patch is.
freeze: Whether to freeze the field, so it cannot be modified by the game after it is patched. Default false.
NOTE: If there is no Update method in the field's class, another class's Update method will be hooked to
freeze the field (it will take the first Update() method it finds).
updatemethod: Optional, only applicable is freeze is true. Force the program to update (freeze) the fields every
time this method is called. Using an Update method is recommended, as these are called at the
beginning of every frame. This is only useful if there is no Update method in the field's class;
however, this should still not be needed since the program will search for other Update methods in the
event that the field's class does not have one.
Freezefield: Function to freeze one field, so it cannot be modified by the game after it is patched.
NOTE: If there is no Update method in the field's class and updatemethod is not provided,
another class's Update method will be hooked to freeze the field (it will take the first Update
method it finds).
Arguments:
classname: Name of the class the freeze is in.
fieldname: Name of the field to freeze.
updatemethod: Optional. Force the program to update (freeze) the field every time this method is called. Using
an Update method is recommended, as these are called at the beginning of every frame. This is
only useful if there is no Update method in the field's class; however, this should still not be
needed since the program will search for other Update methods in the event that the field's class
does not have one.
Restoremethod: Function to revert patched method back to original
Arguments:
classname: Name of the class the method is in.
methodname: Name of the method to restore.
Restorefield: Function to revert patched field back to original value
Arguments:
classname: Name of the class the field is in.
fieldname: Name of the field to restore.
unfreeze: Whether to unfreeze the field (default true).
Patchallmethods: Function to patch all methods in a class.
Arguments:
classname: Name of the class to patch.
namecontains: Only patch methods that contain this substring in their name.
This is not case sensitive. To disable, set to None.
datatype: Only patch methods that are of this data type. Only supports primitive data types.
Nullable types are automatically included along with the basic, non-nullable type.
Defaults to any (though it will fail on methods with data types incompatible with your patchtype).
modifiers: Only patch methods that contain these modifiers (list).
Examples: ["public", "static"], "overload"
Defaults to None.
patchto: The value to patch to.
patchtype: The type of data the patch is.
Patchallfields: Function to patch all fields in a class.
Arguments:
classname: Name of the class to patch.
namecontains: Only patch fields that contain this substring in their name.
This is not case sensitive. To disable, set to None.
datatype: Only patch fields that are of this data type. Only supports primitive data types.
Nullable types are automatically included along with the basic, non-nullable type.
This is not case sensitive.
Defaults to any (though it will fail on fields with data types incompatible with your patchtype).
modifiers: Only patch fields that contain these modifiers (list).
Examples: ["public", "protected"], "readonly"
Defaults to None.
patchto: The value to patch to.
patchtype: The type of data the patch is.
freeze: Whether to freeze all patched fields
NOTE: If there is no Update method in the class and updatemethod is not provided,
another class's Update method will be hooked to freeze the fields (it will take the first Update method it
finds).
updatemethod: Optional, only applicable is freeze is true. Force the program to update (freeze) the fields every
time this method is called. Using an Update method is recommended, as these are called at the
beginning of every frame. This is only useful if there is no Update method in the field's class;
however, this should still not be needed since the program will search for other Update methods in the
event that the field's class does not have one.
Freezeallfields: Function to freeze all fields in a class.
NOTE: If there is no Update method in the class and updatemethod is not provided,
another class's Update method will be hooked to freeze the fields (it will take the first Update
method it finds).
Arguments:
classname: Name of the class to patch.
namecontains: Only freeze fields that contain this substring in their name.
This is not case sensitive. To disable, set to None.
datatype: Only freeze fields that are of this data type. Only supports primitive data types.
Nullable types are automatically included along with the basic, non-nullable type.
This is not case sensitive.
Defaults to any (though it will fail on methods with data types incompatible with your patchtype).
modifiers: Only freeze fields that contain these modifiers (list).
Examples: ["public", "protected"], "readonly"
Defaults to None.
updatemethod: Optional, only applicable is freeze is true. Force the program to update (freeze) the fields every
time this method is called. Using an Update method is recommended, as these are called at the
beginning of every frame. This is only useful if there is no Update method in the field's class;
however, this should still not be needed since the program will search for other Update methods in the
event that the class does not have one.
Unfreezeallfields: Function to unfreeze all fields in a class.
Arguments:
classname: Name of the class to patch.
namecontains: Only unfreeze fields that contain this substring in their name.
This is not case sensitive. To disable, set to None.
datatype: Only unfreeze fields that are of this data type. Only supports primitive data types.
Nullable types are automatically included along with the basic, non-nullable type.
This is not case sensitive.
Defaults to any (though it will fail on methods with data types incompatible with your patchtype).
modifiers: Only unfreeze fields that contain these modifiers (list).
Examples: ["public", "protected"], "readonly"
Defaults to None.
Restoreallmethodsinclass: Function to revert all patched methods in a class
Arguments:
classname: Name of the class to revert methods / fields in.
Restoreallfieldsinclass: Function to revert all patched fields in a class
Arguments:
classname: Name of the class to revert methods / fields in.
unfreeze: Whether to unfreeze all fields in class
callmethod: Function to call one method
Arguments:
classname: Name of the class the method is in.
methodname: Name of the method to call.
params: List of parameters to call the method with, in this format: datatype value
Only supports primitive types (as well as their nullable variants).
If the data type is nullable, just put the basic, non-nullable type.
To use the value null (for nullable data types), put None (without quotes).
Examples: "int": 1, "string": "hello", "char": None
If there are no parameters, pass an empty list or None.
Params much match method's signature (it will also work if convertparams is true
and the params can be converted to match the signature).
times: Number of times to call the method. Must be greater than 0.
Defaults to 1.
delaymillisecs: The delay (in milliseconds) between calling the method. Only matters if times is greater than 1.
Set to 0 or None to have no delay. This delay will not apply before the first call(s) or after the
last call(s), just in between.
Defaults to None (no delay).
convertparams: Whether to attempt to convert parameters to other data types in order to match
the method's signature.
Defaults to True.
redirectmethod: Function to redirect a methods in a class to call another method.
Arguments:
classname: Name of the class the method is in.
methodname: Name of the method to call.
methodtocall: Method to redirect this method to.
params: Dictionary of parameters to call the method it is redirected to with, in this format: "data type": value
Only supports primitive types (as well as their nullable variants).
If the data type is nullable, just put the basic, non-nullable type.
If you want it to use one of the original method's parameters:
Set the value to MethodParam (NUMBEROFPARAMETER). For example: "int": MethodParam(1).
Of course, you can also use your own parameters:
To use the value null (for nullable data types), put None (without quotes).
Examples: "int": 1, "string": "hello", "char": None
callandredirectmethod: Same as redirectmethod, but calls the original method as well. Function to redirect
a method to call another method.
Arguments:
classname: Name of the class the method is in.
methodname: Name of the method to call.
methodtocall: Method to redirect this method to.
params: Dictionary of parameters to call the method it is redirected to with, in this format: "data type": value
Only supports primitive types (as well as their nullable variants).
If the data type is nullable, just put the basic, non-nullable type.
If you want it to use one of the original method's parameters:
Set the value to MethodParam (NUMBEROFPARAMETER). For example: "int": MethodParam(1).
Of course, you can also use your own parameters:
To use the value null (for nullable data types), put None (without quotes).
Examples: "int": 1, "string": "hello", "char": None
callafter: Whether to call the original method before or after the redirected one (False is before, True is after).
This default to False (before).
callallmethods: Function to call all methods in a class.
Arguments:
classname: Name of the class to call the methods from.
namecontains: Only call methods that contain this substring in their name.
This is not case sensitive. To disable, set to None.
datatype: Only call methods that are of this data type. Only supports primitive data types.
Nullable types are automatically included along with the basic, non-nullable type.
Defaults to any (though it will fail on methods with data types incompatible with your patchtype).
modifiers: Only call methods that contain these modifiers (list).
Examples: ["public", "static"], "overload"
Defaults to None.
params: Dictionary of parameters to call the method with, in this format: "data type": value
Only supports primitive types (as well as their nullable variants).
If the data type is nullable, just put the basic, non-nullable type.
To use the value null (for nullable data types), put None (without quotes).
Examples: "int": 1, "string": "hello", "char": None
Only calls methods that follow this signature of params / have default arguments for unprovided params (or
if convertparams is true and the params can be converted to match the signature).
times: Number of times to call the methods. Must be greater than 0.
Defaults to 1.
delaymillisecs: The delay (in milliseconds) between calling the methods. Only matters if times is greater than 1.
Set to 0 or None to have no delay. This delay will not apply before the first call(s) or after the
last call(s), just in between.
Defaults to None (no delay).
convertparams: Whether to attempt to convert parameters to other data types in order to match method
signatures.
Defaults to True.
redirectallmethods: Function to redirect all methods in a class to call another method.
Arguments:
classname: Name of the class to patch.
namecontains: Only redirect methods that contain this substring in their name.
This is not case sensitive. To disable, set to None.
datatype: Only redirect methods that are of this data type. Only supports primitive data types.
Nullable types are automatically included along with the basic, non-nullable type.
Defaults to any (though it will fail on methods with data types incompatible with your patchtype).
modifiers: Only redirect methods that contain these modifiers (list).
Examples: ["public", "static"], "overload"
Defaults to None.
methodtocall: Method to redirect these methods to.
params: Dictionary of parameters to call the method they are redirected to with, in this format: "data type": value
Only supports primitive types (as well as their nullable variants).
If the data type is nullable, just put the basic, non-nullable type.
If you want it to use one of the original method's parameters:
Set the value to MethodParam (NUMBEROFPARAMETER). For example: "int": MethodParam(1). If the
original method has too little parameters, it will be skipped.
You can also make it use the first method parameter of a certain data type:
Set the value to MethodParamofType(DATATYPE). (If you do this twice
with the same data type, the next instance will use the next parameter of that data type, and so on).
If the original method does not have enough parameters of this data type, it will be skipped.
For example: MethodParamofType("int")
Of course, you can also use your own parameters:
To use the value null (for nullable data types), put None (without quotes).
Examples: "int": 1, "string": "hello", "char": None
usenullabledatatypesinparams: If you specify a parameter using MethodParamofType (which tells the program to use
the first method parameter of this data type), nullable parameters in the original
method will be ignored. Set this to True (default False) to not ignore nullable
parameters.
WARNING: If the original method's argument for a nullable parameter is null,
and because of this flag being true the argument gets passed to the method it
redirects to, the game will crash if the receiving method's parameter is not
nullable - because null cannot be passed as an argument to a non-nullable parameter.
callandredirectallmethods: Same as redirectallmethods, but calls the original methods as well. Function to redirect
all methods in a class to call another method.
Arguments:
classname: Name of the class to patch.
namecontains: Only redirect methods that contain this substring in their name.
This is not case sensitive. To disable, set to None.
datatype: Only supports primitive types (as well as their nullable variants).
If the data type is nullable, just put the basic, non-nullable type.
Defaults to any (though it will fail on methods with data types incompatible with your patchtype).
modifiers: Only redirect methods that contain these modifiers (list).
Examples: ["public", "static"], "overload"
Defaults to None.
methodtocall: Method to redirect these methods to.
params: Dictionary of parameters to call the method they are redirected to with, in this format: "data type": value
Only supports primitive types (as well as their nullable variants).
If the data type is nullable, just put the basic, non-nullable type.
If you want it to use one of the original method's parameters:
Set the value to MethodParam (NUMBEROFPARAMETER). For example: "int": MethodParam(1). If the
original method has too little parameters, it will be skipped.
You can also make it use the first method parameter of a certain data type:
Set the value to MethodParamofType(DATATYPE). (If you do this twice
with the same data type, the next instance will use the next parameter of that data type, and so on).
If the original method does not have enough parameters of this data type, it will be skipped.
For example: MethodParamofType("int")
Of course, you can also use your own parameters:
To use the value null (for nullable data types), put None (without quotes).
Examples: "int": 1, "string": "hello", "char": None
usenullabledatatypesinparams: If you specify a parameter using MethodParamofType (which tells the program to use
the first method parameter of this data type), nullable parameters in the original
method will be ignored. Set this to True (default False) to not ignore nullable
parameters.
WARNING: If the original method's argument for a nullable parameter is null,
and because of this flag being true the argument gets passed to the method it
redirects to, the game will crash if the receiving method's parameter is not
nullable - because null cannot be passed as an argument to a non-nullable parameter.
callafter: Whether to call the original method before or after the redirected one (False is before, True is after).
This default to False (before).
getmethodreturn: Function to call one method and get its return. The same as callmethod, but for getting the method
return. Not applicable to void methods. Can be used in other patches, such as for a
method parameter. Can also be embedded into your code in addcustomcode().
Arguments:
classname: Name of the class the method is in.
methodname: Name of the method to call.
params: List of parameters to call the method with, in this format: datatype value
Only supports primitive data types.
Nullable types are automatically included along with the basic, non-nullable type.
If there are no parameters, pass an empty list or None.
Params much match method's signature (it will also work if convertparams is true
and the params can be converted to match the signature).
convertparams: Whether to attempt to convert parameters to other data types in order to match
the method's signature.
Defaults to True.
getfieldvalue: Function to get value of a field within the script. Can be used in other patches, such as for a
method parameter. Can also be embedded into your code in addcustomcode().
Arguments:
classname: Name of the class the field is in
name: Name of the field (mutually exclusive with offset)
addcustomcode: Function to your own gameguardian lua code to the script along with all your patches. You can include
patches in your own code, which is especially useful for user-controlled mod menus, debugging,
etc. This function just gives you more control than using the patches themselves and using this tool
to write the entire script.
Arguments:
code: Code to add to the script. Will be added as the next line after the most recent patch. It is recommended to
a multiline string for readability (so you don't have to escape quotes and you can include newlines).
Beware of special characters and escape sequences such as backslash (\) and newline (\n) - you may want to
create a raw string by placing "r" before your opening quotes, so special characters and escape sequences
are ignored. Use an f-string or python's format function to embed the result of other functions in your code.
Example (note that the opening and closing quotes are escaped with backward slashes, so pretend the backslashes
aren't there):
addcustomcode(r\"\"\"
gg.alert("Welcome to my script!")
gg.alert("You have: {} gems")
gg.alert("Adding 10,000 more!")\"\"\".format(getfield("PlayerCurrency", "gems"))
callmethod("PlayerCurrency", "AddGems", {"int": 10000})
addcustomcode(r\"\"\"
gg.alert("Success! You now have: {} gems").format(getfield("PlayerCurrency", "gems"))
"""
"""
Patchtype:
The type of data a patch is.
If the patch is invalid for a field or method, the program will try to convert the patch value
to another representation that means the same thing. If it fails to do so, it will throw an error and
skip the method / field.
. HexPatch: Arm instructions in hexadecimal representation. If used on a field, it
will fail.
. ArmPatch: Arm assembly code for 32bit (arm) or 64bit (arm64).
Separate instructions with newlines or semicolons. If used on a field, it will fail.
. NOPPatch: Use on void methods to make them do nothing. An example usage is implementing
antiban by nopping a Ban method. When this type of patch is used, the patchto value does not matter.
For consistency, I recommend setting patchto to None. If used on a field, it
will fail.
. IntPatch: Whole number. If patchto is a decimal number, the value will be
rounded to the nearest whole number and a warning will be given. Works for int, char, float, double,
byte, and boolean data types. If the value exceeds the integer limit of the data type of the method
or field, it will fail. Can be negative, but if the data type of the method or field is unsigned,
it will fail.
. BoolPatch: True or False. Can be python built-in True or False.
Can also be a string with the value of "true" or "false", which is not case-sensitive.
Can also be 1 (true) or 0 (false).
. FloatPatch: Whole number or decimal number. Works for both float
and double data types. If the number cannot be represented, it will be rounded and a warning will
be giveh. If the value exceeds the float / double limit of the data type of the method or field,
it will fail. Also works for int, char, float, double, byte, and boolean data types if the value
is a whole number, though it is recommended to use Int instead of Float in this case.
Can be negative, but if the data type of the method or field is unsigned, it will fail.
. StringPatch: Text. If any of the characters in the string are not in the UTF-16 charset,
it will fail. Also works for char data type if the value is only one character long.
- CharPatch: Single character that is in the UTF-16 charset. Can be a unicode code point,
a unicode sequence, or a one-character-long string.
If it is a string that is shorter or longer than one character, it will fail. If it is a unicode
code point or unicode sequence that is not in the UTF-16 charset, it will fail.
Also works for string data type.
"""
# noinspection IncorrectFormatting
patches.append(MethodPatch(NOPPatch(), Method("Player", "Ban", "void", "0x69")))
"""
Building and outputting your script (do not touch this)
"""
script.addpatches(patches)
try:
with open(outputpath, "w") as f:
f.write(script.code)
except UnicodeEncodeError:
# In case there's non-ascii characters and locale is not set to utf-8 - as is default as windows. Fixes that
# pesky problem.
with open(outputpath, "w", encoding="utf8") as f:
f.write(script.code)
"""
Everything below here is the code that makes it work - you don't need to look at this!
It's all in one file, so the code is kind of cluttered and messy.
"""
def patchallmethods():
"""
Patches all methods, fields, or both in a class.
"""
def patchmethod():
"""
Patches a single method
"""
def patchfield():
"""
Patches a single field.
"""
def callmethod():
"""
Calls a single method.
"""
# TODO: Still create hex patch (just don't call .Modify()) - so it can call .Restore() later.
def callall():
"""
Calls all methods in an entire class.
"""
"""
Requiring python 3.11 or later
"""
import sys
# From https://stackoverflow.com/a/34911547/20558255
if sys.version_info < (3, 11):
# noinspection PyStringFormat
sys.exit("""Python 3.11 or later is required. You currently have Python %s.%s installed.
Download the latest version of Python from https://www.python.org/downloads/""" % sys.version_info[:2])
"""
Installing and importing modules
"""
from typing import List, Dict, Sequence, Any, Optional, Union, TypeVar, overload
import os
import importlib
import pkg_resources
import subprocess
import re
import json
from weakref import finalize
from enum import Enum
from abc import ABC, abstractmethod
from dataclasses import dataclass
def install_module(requirement: str):
# Get name of requirement (separate from version)
requirementname = re.split(r"\s|~|=|>|<", requirement)[0]
try:
pkg_resources.get_distribution(requirement)
except pkg_resources.ResolutionError:
print(f"Installing {requirementname} module...")
try:
subprocess.check_call([sys.executable, "-m", "pip", "install", requirement, "--disable-pip-version-check"],
stdout=subprocess.DEVNULL)
except subprocess.CalledProcessError:
raise ImportError(f"Failed to install {requirementname} module") from None
install_module("colorama~=0.4.6")
install_module("keystone-engine~=0.9.2")
install_module("capstone~=4.0.2")
"""
Ugh, keystone and capstone imports are ugly. There has to be a better way to do this...
"""
from keystone import Ks, KsError, KS_ARCH_ARM, KS_MODE_ARM, KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN
from capstone import Cs, CsError, CS_ARCH_ARM, CS_MODE_ARM, CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN
import colorama
"""
Logging
"""
class LoggingLevel(Enum):
Debug = 1
Info = 2
Important = 3
VeryImportant = 4
SuperImportant = 5
Warning = 6
class Logging:
def __init__(self, usedefaults=True, **kwargs) -> None:
if usedefaults:
self._fromoptions(colorized=True, printwarnings=True, printdebug=False, printinfo=True, printimportant=True,
printveryimportant=True, printsuperimportant=True, printspecial=True,
donotprintspecial=False, donotprintsuccessinfo=False, allowoverride=True, printall=True,
printnone=False)
else:
self._fromoptions(**kwargs)
def _fromoptions(self, colorized=True, printwarnings=True, printdebug=False, printinfo=True, printimportant=True,
printveryimportant=True, printsuperimportant=True, printspecial=True, donotprintspecial=False,
donotprintsuccessinfo=False, allowoverride=True, printall=True, printnone=False) -> None:
self.colorized = colorized
self.printwarnings = printwarnings
self.printdebug = printdebug
self.printinfo = printinfo
self.printimportant = printimportant
self.printveryimportant = printveryimportant
self.printsuperimportant = printsuperimportant
self.printspecial = printspecial
self.donotprintspecial = donotprintspecial
self.donotprintsuccessinfo = donotprintsuccessinfo
self.allowoverride = allowoverride
self.printall = printall
self.printnone = printnone
self.Log = []
def log(self, message: str, level: LoggingLevel = 2, override=False, successinfo=False, special=False):
self.Log.append(message)
if self.printnone:
return
if not (override and self.allowoverride):
if successinfo and self.donotprintsuccessinfo:
return
if special and self.donotprintspecial:
return
if self.printall:
toprint = True
elif level == LoggingLevel.Debug and self.printdebug:
toprint = True
elif level == LoggingLevel.Info and self.printinfo:
toprint = True
elif level == LoggingLevel.Important and self.printimportant:
toprint = True
elif level == LoggingLevel.VeryImportant and self.printveryimportant:
toprint = True
elif level == LoggingLevel.SuperImportant and self.printsuperimportant:
toprint = True
elif special and self.printspecial:
toprint = True
else:
toprint = False
if toprint:
self.printmessage(message, level, special, self.colorized)
def printlog(self) -> None:
print(self.Log)
@staticmethod
def printmessage(message: str, level: LoggingLevel, special, colorized):
colors = {"Debug": "\033[0m", "Info": "\033[94m", "Important": "\033[95m", "VeryImportant": "\033[96m",
"SuperImportant": "\033[93m", "Warning": "\033[91m", "Special": "\033[92m", "reset": "\033[0m"}
if colorized:
if special:
print(f"{colors['Special']}[{level.name}] [Special]: {message}{colors['reset']}")
else:
if level.name in colors:
print(f"{colors[level.name]}[{level.name}]: {message}{colors['reset']}")
else:
print(f"[{level.name}]: {message}")
else:
if special:
print(f"[{level.name}] [Special]: {message}")
else:
print(f"[{level.name}]: {message}")
def warning(self, message: str, warningtype: BaseException = None):
if warningtype:
self.Log.append(f"[Warning]: {warningtype}: {message}")
if self.printwarnings and _enabled and self.enabled:
self.printmessage(f"{warningtype}: {message}", LoggingLevel.Warning, False, self.colorized)
else:
self.Log.append(f"[Warning]: {message}")
if self.printwarnings and _enabled and self.enabled:
self.printmessage(message, LoggingLevel.Warning, False, self.colorized)
colorama.just_fix_windows_console()
logging = Logging(usedefaults=True)
"""
Arm Hex Conversion
"""
class ArmHex:
def __init__(self, arm64=False) -> None:
self.arm64 = arm64
self.architecture = "arm64" if arm64 else "arm"
if self.arm64:
self.ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN)
self.cs = Cs(CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN)
else:
self.ks = Ks(KS_ARCH_ARM, KS_MODE_ARM)
self.cs = Cs(CS_ARCH_ARM, CS_MODE_ARM)
def armtohex(self, armcode: str):
try:
"""
1. Assemble armcode to bytes with ks.asm, and grab the result (first item of tuple)
2. Convert the list of encoded instructions to bytes
3. Convert the bytes to a hex value
4. Make the hex uppercase
"""
return bytes(self.ks.asm(armcode)[0]).hex().upper()
except KsError as e:
# Todo: Error handling
raise
def hextoarm(self, hexcode: str, offset=0x0, delimiter: str = "\n"):
"""
To-Do: Add documentation
:raises ValueError: hexcode is not valid hex
"""
if delimiter is None:
delimiter = ""
try:
int(hexcode, 16)
except ValueError:
raise ValueError("hexcode is not a valid hex")
try:
"""
1. Convert hexcode to bytes (because capstone takes bytes, not hex)
2. Disassemble hexcode to arm code with cs.dsm
3. Loop over all the disassembled instructions
4. Get instruction as string using mnemonic and op_str attributes
5. Concatenate list of instruction strings with delimiter
"""
return delimiter.join([f"{instruction.mnemonic} {instruction.op_str}" for instruction in
self.cs.disasm(bytes.fromhex(hexcode), offset)])
except CsError as e:
# Todo: Error handling
raise
"""
Dumpcs Parsing
"""
def getactualdatatype(datatype: str) -> str:
"""
Gets actual data type
Removes modifiers and access modifiers
"""
...
def getmodifiers(datatype: str) -> list[str]:
"""
Gets list of modifiers
Removes actual data type and access modifiers
"""
...
def getmethodandclassfromoffset(offset: str) -> tuple[str, str]:
"""
Gets the name of a method and the name of its class by the method offset
:param offset: Offset of the method
:return: Tuple with class name and method name: (classname, methodname)
:raises ValueError: Method with this offset was not found in dumpcs
"""
def getfieldfromclassandoffset(classname: str, offset: str) -> str:
"""
Gets the name of a field by the name of its class and the field offset
:param classname: Name of the class the field is in
:param offset: Offset of the field
:return: Tuple with class name and method name: (classname, methodname)
:raises ValueError: Class was not done
:raises ValueError: Field with this offset was not found in class
"""
"""
Patches
"""
@dataclass
class Method:
"""
:param classname: The name of the class the method is in
:param name: The name of the method
:param datatype: The data type of the method
:param offset: The offset of the method
"""
classname: str
name: str
datatype: str
offset: str
@dataclass
class MethodParam:
"""
:param paramnum: The number of the parameter in the method's parameter list (from 1 to # of parameters)
"""
paramnum: int
def __postinit__(self):
if not isinstance(paramnum, int):
raise TypeError("paramnum must be an integer")
if paramnum <= 0:
raise ValueError("paramnum must be at least 1 (corresponds to index of parameter in method)")
@dataclass
class Field:
"""
:param classname: The name of the class the field is in
:param name: The name of the field
:param datatype: The data type of the field
:param offset: The offset of the field. None if no offset.
"""
classname: str
name: str
datatype: str
offset: Optional[str] = None
class PatchType(Enum):
"""
The type of data a patch is.
If the patch is invalid for a field or method, the program will try to convert the patch value
to another representation that means the same thing. If it fails to do so, it will throw an error and
skip the method / field.
. HexPatch: Arm instructions in hexadecimal representation. If used on a field, it
will fail.
. ArmPatch: Arm assembly code for 32bit (arm) or 64bit (arm64).
Separate instructions with newlines or semicolons. If used on a field, it will fail.
. NOPPatch: Use on void methods to make them do nothing. An example usage is implementing
antiban by nopping a Ban method. When this type of patch is used, the patchto value does not matter.
For consistency, I recommend setting patchto to None. If used on a field, it
will fail.
. IntPatch: Whole number. If patchto is a decimal number, the value will be
rounded to the nearest whole number and a warning will be given. Works for int, char, float, double,
byte, and boolean data types. If the value exceeds the integer limit of the data type of the method
or field, it will fail. Can be negative, but if the data type of the method or field is unsigned,
it will fail.
. BoolPatch: True or False. Can be python built-in True or False.
Can also be a string with the value of "true" or "false", which is not case-sensitive.
Can also be 1 (true) or 0 (false).
. FloatPatch: Whole number or decimal number. Works for both float
and double data types. If the number cannot be represented, it will be rounded and a warning will
be giveh. If the value exceeds the float / double limit of the data type of the method or field,
it will fail. Also works for int, char, float, double, byte, and boolean data types if the value
is a whole number, though it is recommended to use Int instead of Float in this case.
Can be negative, but if the data type of the method or field is unsigned, it will fail.
. StringPatch: Text. If any of the characters in the string are not in the UTF-16 charset,
it will fail. Also works for char data type if the value is only one character long.
- CharPatch: Single character that is in the UTF-16 charset. Can be a unicode code point,
a unicode sequence, or a one-character-long string.
If it is a string that is shorter or longer than one character, it will fail. If it is a unicode
code point or unicode sequence that is not in the UTF-16 charset, it will fail.
Also works for string data type.
"""
HexPatch = "HexPatch",
ArmPatch = "ArmPatch",
NOPPatch = "NOPPatch",
IntPatch = "IntPatch",
BoolPatch = "BoolPatch",
FloatPatch = "FloatPatch",
StringPatch = "StringPatch",
CharPatch = "CharPatch",
Patch = TypeVar('Patch', bound='PatchBase')
# Sentinel value for empty patch data, for use in PatchBase class.
class _EmptyPatchData:
pass
_emptypatchdata = _EmptyPatchData()
class PatchBase(ABC):
# In the type hints for this function, we use union [Patch, None] rather than Optional[Patch] here because it
# makes it explicit that None represents no value. This is necessary because for patchdata, _emptypatchdata
# represents no value, not None.
# noinspection PyProtectedMember
def __init__(self, patchdata: Union[Any, _EmptyPatchData] = _emptypatchdata,
patch: Union[Patch, None] = None) -> None:
"""
Attempts to create a Patch of this type from patch data or an existing Patch
May do implicit conversions of the patch data
Implementations of PatchBase SHOULD NOT OVERRIDE THIS METHOD. Instead, they should override the methods it
internally uses: _frompatchdata, _frompatch, _fromnodata, _setupresources, and _cleanupresouces.
:param patchdata: The patch data
:param patch: The patch to create a new Patch from
patchdata and patch are mutually exclusive; the caller may provide neither if this patch implements
_fromnodata()
:raises TypeError: Both patchdata and patch were provided - they are mutually exclusive
:raises TypeError: Neither patchdata nor patch was provided and this patch does not implement _fromnodata()
:raises ValueError: The Patch is invalid and cannot be created
"""
if patchdata != _emptypatchdata and patch is not None:
raise TypeError("patchdata and patch are mutually exclusive")
if patchdata != _emptypatchdata:
try:
self._frompatchdata(patchdata)
except ValueError:
raise ValueError(f"{type(self).__name__} patch cannot be created from this data") from None
elif patch is not None:
try:
self._frompatch(patch)
except ValueError:
raise ValueError(f"{type(patch).__name__} patch cannot be converted to"
f" {type(self).__name__} patch") from None
else:
try:
self._fromnodata()
except ValueError:
raise TypeError("This patch requires either patchdata or patch to be specified")
# We set the finalizer before calling _setupresources so if an exception occurs during
# _setupresources, _cleanupresources is still called.
finalize(self, self._cleanupresources)
self._setupresources()
self.isinstantiated = True
def _frompatchdata(self, patchdata: Any) -> None:
"""
Attempts to create a new Patch of this type from the patch data
Do not implement this method if a Patch of this type does not take any data.
If the Patch is invalid and cannot be created, raises a ValueError
:param patchdata: The patch data to create this patch with
:raises ValueError: The Patch is invalid and cannot be created
"""
raise ValueError
def _frompatch(self, patch: Patch) -> None:
"""
Attempts to create a Patch of this type from a Patch of another Patch type
Do not implement this method if a Patch of this type cannot be created from another Patch type.
If the Patch is invalid and cannot be created, raises a ValueError