From 00918d42a3525117171cf3f26e5b6e43ca290990 Mon Sep 17 00:00:00 2001 From: 0O0o0oOoO00 <11174151+0O0o0oOoO00@users.noreply.github.com> Date: Sun, 31 Aug 2025 22:39:41 +0800 Subject: [PATCH] add: base support for luahook --- .gitignore | 29 +- alas.py | 77 ++++ config/template.json | 58 +++ module/config/argument/args.json | 221 ++++++++++++ module/config/argument/argument.yaml | 64 ++++ module/config/argument/menu.json | 7 + module/config/argument/task.yaml | 13 + module/config/config_generated.py | 53 +++ module/config/i18n/en-US.json | 227 ++++++++++++ module/config/i18n/ja-JP.json | 227 ++++++++++++ module/config/i18n/zh-CN.json | 227 ++++++++++++ module/config/i18n/zh-TW.json | 227 ++++++++++++ module/luahook/api.py | 300 ++++++++++++++++ module/luahook/crack.py | 518 +++++++++++++++++++++++++++ module/luahook/exception.py | 2 + module/luahook/op.py | 114 ++++++ 16 files changed, 2363 insertions(+), 1 deletion(-) create mode 100644 module/luahook/api.py create mode 100644 module/luahook/crack.py create mode 100644 module/luahook/exception.py create mode 100644 module/luahook/op.py diff --git a/.gitignore b/.gitignore index 73ce58dae..30859666e 100644 --- a/.gitignore +++ b/.gitignore @@ -254,4 +254,31 @@ console.bat .vscode/* # Pylance configuration file -pyrightconfig.json \ No newline at end of file +pyrightconfig.json + +# blcrack +blcrack/cmake-build-*/ +blcrack/.env +blcrack/http/test.http +blcrack/build/ +blcrack/bin/ +blcrack/vcpkgs/ +blcrack/out/ +blcrack/tolua/ +bin/hook/ +blcrack/patchelf/build/ +blcrack/test/ +blcrack/cracker/pb/ + +report/ + +.DS_Store + +blcrack/cracker/webpage.cpp +blcrack/cracker/webui/node_modules/ +blcrack/cracker/webui/dist/ +blcrack/cracker/webui/.vscode/ +blcrack/cracker/webui/.idea/ + +3rdparty/build/ +3rdparty/pkgs/ \ No newline at end of file diff --git a/alas.py b/alas.py index 6268c638a..a723d1988 100644 --- a/alas.py +++ b/alas.py @@ -586,6 +586,83 @@ class AzurLaneAutoScript: break +from module.luahook.crack import * + + +class AzurLaneAutoScript(AzurLaneAutoScript): + def __init__(self, config_name='alas'): + self.pre_init() + super().__init__(config_name) + + self.class_name = self.__class__.__name__ + self.is_azur = False + self.is_ark = False + if self.class_name == "AzurLaneAutoScript": + self.is_azur = True + elif self.class_name == "ArknightsAutoScript": + self.is_ark = True + + def pre_init(self): + from PIL import Image + Image.init() + + # Manually register PngImagePlugin + from PIL import PngImagePlugin + Image.register_open(PngImagePlugin.PngImageFile.format, PngImagePlugin.PngImageFile, PngImagePlugin._accept) + Image.register_save(PngImagePlugin.PngImageFile.format, PngImagePlugin._save) + Image.register_save_all(PngImagePlugin.PngImageFile.format, PngImagePlugin._save_all) + + Image.register_extensions(PngImagePlugin.PngImageFile.format, [".png", ".apng"]) + + Image.register_mime(PngImagePlugin.PngImageFile.format, "image/png") + + def run(self, command, skip_first_screenshot=False): + if self.is_azur: + luahook_crack_all(self.config, self.device) + super().run(command, skip_first_screenshot=skip_first_screenshot) + + @disable_all_crack + def minigame(self): + super().minigame() + + @disable_all_crack + @crack_op( + before_call=[CrackOp.EnableExerciseGodMod, CrackOp.EnableExerciseMorePower], + after_call=[CrackOp.DisableExerciseGodMod, CrackOp.DisableExerciseMorePower] + ) + def exercise(self): + super().exercise() + + @disable_all_crack + def raid_daily(self): + super().raid_daily() + + @disable_all_crack + def opsi_ash_assist(self): + super().opsi_ash_assist() + + @disable_all_crack + def opsi_ash_beacon(self): + super().opsi_ash_beacon() + + @disable_all_crack + def raid(self): + super().raid() + + @disable_all_crack + def coalition(self): + super().coalition() + + @disable_all_crack + def coalition_sp(self): + super().coalition_sp() + + def loop(self): + if self.is_azur: + CrackResource(self.config, self.device).ensure() + super().loop() + + if __name__ == '__main__': alas = AzurLaneAutoScript() alas.loop() diff --git a/config/template.json b/config/template.json index 4c2a397fd..2977b1146 100644 --- a/config/template.json +++ b/config/template.json @@ -77,6 +77,64 @@ "Storage": {} } }, + "Hook": { + "HookGeneral": { + "Enable": false, + "PushEveryTime": true, + "Architecture": "auto", + "InjectMethod": "local_patch", + "RequestTimeLimit": 10, + "UpdateServer": null, + "GameLibDir": null + }, + "Misc": { + "GlobalSpeedup": 1.0, + "BetterGlobalSpeedup": 1.0, + "ChapterMove": false, + "OpsiMove": false, + "RemoveHardMapLimit": "disable", + "NoBBAnimation": false, + "NoEmotionWarning": false, + "ExerciseGodMod": false, + "ExerciseMorePower": -1.0, + "FastWave": false, + "MonsterKillSelf": false, + "SkipBattleCelebrate": false + }, + "ShipProperty": { + "Method": "disable", + "Factor": 1.0, + "Armor": -1, + "Speed": -1, + "AntiAircraft": -1, + "OxyRecoveryBench": -1, + "Torpedo": -1, + "Hit": -1, + "SonarRange": -1, + "AttackDuration": -1, + "RaidDistance": -1, + "OxyRecoverySurface": -1, + "OxyRecovery": -1, + "Dodge": -1, + "Luck": -1, + "Reload": -1, + "OxyCost": -1, + "Durability": -1, + "Air": -1, + "OxyMax": -1, + "Cannon": -1, + "AntiSub": -1 + }, + "FakePlayer": { + "Enable": false, + "Name": null, + "Level": null, + "Id": null + }, + "Storage": { + "Storage": {} + } + }, "Main": { "Scheduler": { "Enable": false, diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 7cf544ff6..4f0514741 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -422,6 +422,227 @@ } } }, + "Hook": { + "HookGeneral": { + "Enable": { + "type": "checkbox", + "value": false + }, + "PushEveryTime": { + "type": "checkbox", + "value": true + }, + "Architecture": { + "type": "select", + "value": "auto", + "option": [ + "auto", + "x86", + "x86_64", + "arm64-v8a", + "armeabi-v7a" + ] + }, + "InjectMethod": { + "type": "select", + "value": "local_patch", + "option": [ + "local_patch", + "global_patch", + "outer_inject" + ] + }, + "RequestTimeLimit": { + "type": "input", + "value": 10 + }, + "UpdateServer": { + "type": "textarea", + "value": "" + }, + "GameLibDir": { + "type": "textarea", + "value": "" + } + }, + "Misc": { + "GlobalSpeedup": { + "type": "input", + "value": 1.0 + }, + "BetterGlobalSpeedup": { + "type": "input", + "value": 1.0 + }, + "ChapterMove": { + "type": "checkbox", + "value": false + }, + "OpsiMove": { + "type": "checkbox", + "value": false + }, + "RemoveHardMapLimit": { + "type": "select", + "value": "disable", + "option": [ + "disable", + "remove_ship_properties_limit", + "remove_ship_type_limit", + "remove_both" + ] + }, + "NoBBAnimation": { + "type": "checkbox", + "value": false + }, + "NoEmotionWarning": { + "type": "checkbox", + "value": false + }, + "ExerciseGodMod": { + "type": "checkbox", + "value": false + }, + "ExerciseMorePower": { + "type": "input", + "value": -1.0 + }, + "FastWave": { + "type": "checkbox", + "value": false + }, + "MonsterKillSelf": { + "type": "checkbox", + "value": false + }, + "SkipBattleCelebrate": { + "type": "checkbox", + "value": false + } + }, + "ShipProperty": { + "Method": { + "type": "select", + "value": "disable", + "option": [ + "disable", + "gg_factor", + "final_properties" + ] + }, + "Factor": { + "type": "input", + "value": 1.0 + }, + "Armor": { + "type": "input", + "value": -1 + }, + "Speed": { + "type": "input", + "value": -1 + }, + "AntiAircraft": { + "type": "input", + "value": -1 + }, + "OxyRecoveryBench": { + "type": "input", + "value": -1 + }, + "Torpedo": { + "type": "input", + "value": -1 + }, + "Hit": { + "type": "input", + "value": -1 + }, + "SonarRange": { + "type": "input", + "value": -1 + }, + "AttackDuration": { + "type": "input", + "value": -1 + }, + "RaidDistance": { + "type": "input", + "value": -1 + }, + "OxyRecoverySurface": { + "type": "input", + "value": -1 + }, + "OxyRecovery": { + "type": "input", + "value": -1 + }, + "Dodge": { + "type": "input", + "value": -1 + }, + "Luck": { + "type": "input", + "value": -1 + }, + "Reload": { + "type": "input", + "value": -1 + }, + "OxyCost": { + "type": "input", + "value": -1 + }, + "Durability": { + "type": "input", + "value": -1 + }, + "Air": { + "type": "input", + "value": -1 + }, + "OxyMax": { + "type": "input", + "value": -1 + }, + "Cannon": { + "type": "input", + "value": -1 + }, + "AntiSub": { + "type": "input", + "value": -1 + } + }, + "FakePlayer": { + "Enable": { + "type": "checkbox", + "value": false + }, + "Name": { + "type": "input", + "value": "" + }, + "Level": { + "type": "input", + "value": "" + }, + "Id": { + "type": "input", + "value": "" + } + }, + "Storage": { + "Storage": { + "type": "storage", + "value": {}, + "valuetype": "ignore", + "display": "disabled" + } + } + }, "Main": { "Scheduler": { "Enable": { diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index 717d63502..57b170ae3 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -766,3 +766,67 @@ AzurLaneUncensored: display: disabled GameManager: AutoRestart: true + +# ==================== Cheat ==================== + +HookGeneral: + Enable: false + PushEveryTime: true + Architecture: + value: auto + option: [ auto, x86, x86_64, arm64-v8a, armeabi-v7a ] + InjectMethod: + value: local_patch + option: [local_patch, global_patch, outer_inject] + RequestTimeLimit: 10 + UpdateServer: + value: "" + type: textarea + GameLibDir: + value: "" + type: textarea +ShipProperty: + Method: + value: disable + option: [ disable, gg_factor, final_properties ] + Factor: 1.0 + Armor: -1 + Speed: -1 + AntiAircraft: -1 + OxyRecoveryBench: -1 + Torpedo: -1 + Hit: -1 + SonarRange: -1 + AttackDuration: -1 + RaidDistance: -1 + OxyRecoverySurface: -1 + OxyRecovery: -1 + Dodge: -1 + Luck: -1 + Reload: -1 + OxyCost: -1 + Durability: -1 + Air: -1 + OxyMax: -1 + Cannon: -1 + AntiSub: -1 +FakePlayer: + Enable: false + Name: "" + Level: "" + Id: "" +Misc: + GlobalSpeedup: 1.0 + BetterGlobalSpeedup: 1.0 + ChapterMove: false + OpsiMove: false + RemoveHardMapLimit: + value: disable + option: [disable, remove_ship_properties_limit, remove_ship_type_limit, remove_both] + NoBBAnimation: false + NoEmotionWarning: false + ExerciseGodMod: false + ExerciseMorePower: -1.0 + FastWave: false + MonsterKillSelf: false + SkipBattleCelebrate: false \ No newline at end of file diff --git a/module/config/argument/menu.json b/module/config/argument/menu.json index abdfe9c9b..ffb651a60 100644 --- a/module/config/argument/menu.json +++ b/module/config/argument/menu.json @@ -8,6 +8,13 @@ "Restart" ] }, + "Cheat": { + "menu": "collapse", + "page": "setting", + "tasks": [ + "Hook" + ] + }, "Farm": { "menu": "collapse", "page": "setting", diff --git a/module/config/argument/task.yaml b/module/config/argument/task.yaml index c6f20bb00..0659df554 100644 --- a/module/config/argument/task.yaml +++ b/module/config/argument/task.yaml @@ -22,6 +22,19 @@ Alas: Restart: - Scheduler +# ==================== Cheat ==================== + +Cheat: + menu: 'collapse' + page: 'setting' + tasks: + Hook: + - HookGeneral + - Misc + - ShipProperty + - FakePlayer + + # ==================== Farm ==================== Farm: diff --git a/module/config/config_generated.py b/module/config/config_generated.py index 2d1805e89..8d636e00f 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -456,5 +456,58 @@ class GeneratedConfig: # Group `GameManager` GameManager_AutoRestart = True + # Group `HookGeneral` + HookGeneral_Enable = False + HookGeneral_PushEveryTime = True + HookGeneral_Architecture = 'auto' # auto, x86, x86_64, arm64-v8a, armeabi-v7a + HookGeneral_InjectMethod = 'local_patch' # local_patch, global_patch, outer_inject + HookGeneral_RequestTimeLimit = 10 + HookGeneral_UpdateServer = None + HookGeneral_GameLibDir = None + + # Group `ShipProperty` + ShipProperty_Method = 'disable' # disable, gg_factor, final_properties + ShipProperty_Factor = 1.0 + ShipProperty_Armor = -1 + ShipProperty_Speed = -1 + ShipProperty_AntiAircraft = -1 + ShipProperty_OxyRecoveryBench = -1 + ShipProperty_Torpedo = -1 + ShipProperty_Hit = -1 + ShipProperty_SonarRange = -1 + ShipProperty_AttackDuration = -1 + ShipProperty_RaidDistance = -1 + ShipProperty_OxyRecoverySurface = -1 + ShipProperty_OxyRecovery = -1 + ShipProperty_Dodge = -1 + ShipProperty_Luck = -1 + ShipProperty_Reload = -1 + ShipProperty_OxyCost = -1 + ShipProperty_Durability = -1 + ShipProperty_Air = -1 + ShipProperty_OxyMax = -1 + ShipProperty_Cannon = -1 + ShipProperty_AntiSub = -1 + + # Group `FakePlayer` + FakePlayer_Enable = False + FakePlayer_Name = None + FakePlayer_Level = None + FakePlayer_Id = None + + # Group `Misc` + Misc_GlobalSpeedup = 1.0 + Misc_BetterGlobalSpeedup = 1.0 + Misc_ChapterMove = False + Misc_OpsiMove = False + Misc_RemoveHardMapLimit = 'disable' # disable, remove_ship_properties_limit, remove_ship_type_limit, remove_both + Misc_NoBBAnimation = False + Misc_NoEmotionWarning = False + Misc_ExerciseGodMod = False + Misc_ExerciseMorePower = -1.0 + Misc_FastWave = False + Misc_MonsterKillSelf = False + Misc_SkipBattleCelebrate = False + # Group `Storage` Storage_Storage = {} diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index 1276308e0..aaa6fd136 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -4,6 +4,10 @@ "name": "Alas", "help": "" }, + "Cheat": { + "name": "Menu.Cheat.name", + "help": "Menu.Cheat.help" + }, "Farm": { "name": "Farm", "help": "" @@ -46,6 +50,10 @@ "name": "Restart", "help": "" }, + "Hook": { + "name": "Task.Hook.name", + "help": "Task.Hook.help" + }, "Main": { "name": "Main", "help": "" @@ -2612,6 +2620,225 @@ "help": "Log back into the game when the game has been ended." } }, + "HookGeneral": { + "_info": { + "name": "HookGeneral._info.name", + "help": "HookGeneral._info.help" + }, + "Enable": { + "name": "HookGeneral.Enable.name", + "help": "HookGeneral.Enable.help" + }, + "PushEveryTime": { + "name": "HookGeneral.PushEveryTime.name", + "help": "HookGeneral.PushEveryTime.help" + }, + "Architecture": { + "name": "HookGeneral.Architecture.name", + "help": "HookGeneral.Architecture.help", + "auto": "auto", + "x86": "x86", + "x86_64": "x86_64", + "arm64-v8a": "arm64-v8a", + "armeabi-v7a": "armeabi-v7a" + }, + "InjectMethod": { + "name": "HookGeneral.InjectMethod.name", + "help": "HookGeneral.InjectMethod.help", + "local_patch": "local_patch", + "global_patch": "global_patch", + "outer_inject": "outer_inject" + }, + "RequestTimeLimit": { + "name": "HookGeneral.RequestTimeLimit.name", + "help": "HookGeneral.RequestTimeLimit.help" + }, + "UpdateServer": { + "name": "HookGeneral.UpdateServer.name", + "help": "HookGeneral.UpdateServer.help" + }, + "GameLibDir": { + "name": "HookGeneral.GameLibDir.name", + "help": "HookGeneral.GameLibDir.help" + } + }, + "ShipProperty": { + "_info": { + "name": "ShipProperty._info.name", + "help": "ShipProperty._info.help" + }, + "Method": { + "name": "ShipProperty.Method.name", + "help": "ShipProperty.Method.help", + "disable": "disable", + "gg_factor": "gg_factor", + "final_properties": "final_properties" + }, + "Factor": { + "name": "ShipProperty.Factor.name", + "help": "ShipProperty.Factor.help" + }, + "Armor": { + "name": "ShipProperty.Armor.name", + "help": "ShipProperty.Armor.help" + }, + "Speed": { + "name": "ShipProperty.Speed.name", + "help": "ShipProperty.Speed.help" + }, + "AntiAircraft": { + "name": "ShipProperty.AntiAircraft.name", + "help": "ShipProperty.AntiAircraft.help" + }, + "OxyRecoveryBench": { + "name": "ShipProperty.OxyRecoveryBench.name", + "help": "ShipProperty.OxyRecoveryBench.help" + }, + "Torpedo": { + "name": "ShipProperty.Torpedo.name", + "help": "ShipProperty.Torpedo.help" + }, + "Hit": { + "name": "ShipProperty.Hit.name", + "help": "ShipProperty.Hit.help" + }, + "SonarRange": { + "name": "ShipProperty.SonarRange.name", + "help": "ShipProperty.SonarRange.help" + }, + "AttackDuration": { + "name": "ShipProperty.AttackDuration.name", + "help": "ShipProperty.AttackDuration.help" + }, + "RaidDistance": { + "name": "ShipProperty.RaidDistance.name", + "help": "ShipProperty.RaidDistance.help" + }, + "OxyRecoverySurface": { + "name": "ShipProperty.OxyRecoverySurface.name", + "help": "ShipProperty.OxyRecoverySurface.help" + }, + "OxyRecovery": { + "name": "ShipProperty.OxyRecovery.name", + "help": "ShipProperty.OxyRecovery.help" + }, + "Dodge": { + "name": "ShipProperty.Dodge.name", + "help": "ShipProperty.Dodge.help" + }, + "Luck": { + "name": "ShipProperty.Luck.name", + "help": "ShipProperty.Luck.help" + }, + "Reload": { + "name": "ShipProperty.Reload.name", + "help": "ShipProperty.Reload.help" + }, + "OxyCost": { + "name": "ShipProperty.OxyCost.name", + "help": "ShipProperty.OxyCost.help" + }, + "Durability": { + "name": "ShipProperty.Durability.name", + "help": "ShipProperty.Durability.help" + }, + "Air": { + "name": "ShipProperty.Air.name", + "help": "ShipProperty.Air.help" + }, + "OxyMax": { + "name": "ShipProperty.OxyMax.name", + "help": "ShipProperty.OxyMax.help" + }, + "Cannon": { + "name": "ShipProperty.Cannon.name", + "help": "ShipProperty.Cannon.help" + }, + "AntiSub": { + "name": "ShipProperty.AntiSub.name", + "help": "ShipProperty.AntiSub.help" + } + }, + "FakePlayer": { + "_info": { + "name": "FakePlayer._info.name", + "help": "FakePlayer._info.help" + }, + "Enable": { + "name": "FakePlayer.Enable.name", + "help": "FakePlayer.Enable.help" + }, + "Name": { + "name": "FakePlayer.Name.name", + "help": "FakePlayer.Name.help" + }, + "Level": { + "name": "FakePlayer.Level.name", + "help": "FakePlayer.Level.help" + }, + "Id": { + "name": "FakePlayer.Id.name", + "help": "FakePlayer.Id.help" + } + }, + "Misc": { + "_info": { + "name": "Misc._info.name", + "help": "Misc._info.help" + }, + "GlobalSpeedup": { + "name": "Misc.GlobalSpeedup.name", + "help": "Misc.GlobalSpeedup.help" + }, + "BetterGlobalSpeedup": { + "name": "Misc.BetterGlobalSpeedup.name", + "help": "Misc.BetterGlobalSpeedup.help" + }, + "ChapterMove": { + "name": "Misc.ChapterMove.name", + "help": "Misc.ChapterMove.help" + }, + "OpsiMove": { + "name": "Misc.OpsiMove.name", + "help": "Misc.OpsiMove.help" + }, + "RemoveHardMapLimit": { + "name": "Misc.RemoveHardMapLimit.name", + "help": "Misc.RemoveHardMapLimit.help", + "disable": "disable", + "remove_ship_properties_limit": "remove_ship_properties_limit", + "remove_ship_type_limit": "remove_ship_type_limit", + "remove_both": "remove_both" + }, + "NoBBAnimation": { + "name": "Misc.NoBBAnimation.name", + "help": "Misc.NoBBAnimation.help" + }, + "NoEmotionWarning": { + "name": "Misc.NoEmotionWarning.name", + "help": "Misc.NoEmotionWarning.help" + }, + "ExerciseGodMod": { + "name": "Misc.ExerciseGodMod.name", + "help": "Misc.ExerciseGodMod.help" + }, + "ExerciseMorePower": { + "name": "Misc.ExerciseMorePower.name", + "help": "Misc.ExerciseMorePower.help" + }, + "FastWave": { + "name": "Misc.FastWave.name", + "help": "Misc.FastWave.help" + }, + "MonsterKillSelf": { + "name": "Misc.MonsterKillSelf.name", + "help": "Misc.MonsterKillSelf.help" + }, + "SkipBattleCelebrate": { + "name": "Misc.SkipBattleCelebrate.name", + "help": "Misc.SkipBattleCelebrate.help" + } + }, "Storage": { "_info": { "name": "Task status", diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index 1ebde4ca7..d0df93287 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -4,6 +4,10 @@ "name": "Alas", "help": "" }, + "Cheat": { + "name": "Menu.Cheat.name", + "help": "Menu.Cheat.help" + }, "Farm": { "name": "出撃", "help": "" @@ -46,6 +50,10 @@ "name": "再起動設定", "help": "" }, + "Hook": { + "name": "Task.Hook.name", + "help": "Task.Hook.help" + }, "Main": { "name": "メイン海域", "help": "" @@ -2612,6 +2620,225 @@ "help": "GameManager.AutoRestart.help" } }, + "HookGeneral": { + "_info": { + "name": "HookGeneral._info.name", + "help": "HookGeneral._info.help" + }, + "Enable": { + "name": "HookGeneral.Enable.name", + "help": "HookGeneral.Enable.help" + }, + "PushEveryTime": { + "name": "HookGeneral.PushEveryTime.name", + "help": "HookGeneral.PushEveryTime.help" + }, + "Architecture": { + "name": "HookGeneral.Architecture.name", + "help": "HookGeneral.Architecture.help", + "auto": "auto", + "x86": "x86", + "x86_64": "x86_64", + "arm64-v8a": "arm64-v8a", + "armeabi-v7a": "armeabi-v7a" + }, + "InjectMethod": { + "name": "HookGeneral.InjectMethod.name", + "help": "HookGeneral.InjectMethod.help", + "local_patch": "local_patch", + "global_patch": "global_patch", + "outer_inject": "outer_inject" + }, + "RequestTimeLimit": { + "name": "HookGeneral.RequestTimeLimit.name", + "help": "HookGeneral.RequestTimeLimit.help" + }, + "UpdateServer": { + "name": "HookGeneral.UpdateServer.name", + "help": "HookGeneral.UpdateServer.help" + }, + "GameLibDir": { + "name": "HookGeneral.GameLibDir.name", + "help": "HookGeneral.GameLibDir.help" + } + }, + "ShipProperty": { + "_info": { + "name": "ShipProperty._info.name", + "help": "ShipProperty._info.help" + }, + "Method": { + "name": "ShipProperty.Method.name", + "help": "ShipProperty.Method.help", + "disable": "disable", + "gg_factor": "gg_factor", + "final_properties": "final_properties" + }, + "Factor": { + "name": "ShipProperty.Factor.name", + "help": "ShipProperty.Factor.help" + }, + "Armor": { + "name": "ShipProperty.Armor.name", + "help": "ShipProperty.Armor.help" + }, + "Speed": { + "name": "ShipProperty.Speed.name", + "help": "ShipProperty.Speed.help" + }, + "AntiAircraft": { + "name": "ShipProperty.AntiAircraft.name", + "help": "ShipProperty.AntiAircraft.help" + }, + "OxyRecoveryBench": { + "name": "ShipProperty.OxyRecoveryBench.name", + "help": "ShipProperty.OxyRecoveryBench.help" + }, + "Torpedo": { + "name": "ShipProperty.Torpedo.name", + "help": "ShipProperty.Torpedo.help" + }, + "Hit": { + "name": "ShipProperty.Hit.name", + "help": "ShipProperty.Hit.help" + }, + "SonarRange": { + "name": "ShipProperty.SonarRange.name", + "help": "ShipProperty.SonarRange.help" + }, + "AttackDuration": { + "name": "ShipProperty.AttackDuration.name", + "help": "ShipProperty.AttackDuration.help" + }, + "RaidDistance": { + "name": "ShipProperty.RaidDistance.name", + "help": "ShipProperty.RaidDistance.help" + }, + "OxyRecoverySurface": { + "name": "ShipProperty.OxyRecoverySurface.name", + "help": "ShipProperty.OxyRecoverySurface.help" + }, + "OxyRecovery": { + "name": "ShipProperty.OxyRecovery.name", + "help": "ShipProperty.OxyRecovery.help" + }, + "Dodge": { + "name": "ShipProperty.Dodge.name", + "help": "ShipProperty.Dodge.help" + }, + "Luck": { + "name": "ShipProperty.Luck.name", + "help": "ShipProperty.Luck.help" + }, + "Reload": { + "name": "ShipProperty.Reload.name", + "help": "ShipProperty.Reload.help" + }, + "OxyCost": { + "name": "ShipProperty.OxyCost.name", + "help": "ShipProperty.OxyCost.help" + }, + "Durability": { + "name": "ShipProperty.Durability.name", + "help": "ShipProperty.Durability.help" + }, + "Air": { + "name": "ShipProperty.Air.name", + "help": "ShipProperty.Air.help" + }, + "OxyMax": { + "name": "ShipProperty.OxyMax.name", + "help": "ShipProperty.OxyMax.help" + }, + "Cannon": { + "name": "ShipProperty.Cannon.name", + "help": "ShipProperty.Cannon.help" + }, + "AntiSub": { + "name": "ShipProperty.AntiSub.name", + "help": "ShipProperty.AntiSub.help" + } + }, + "FakePlayer": { + "_info": { + "name": "FakePlayer._info.name", + "help": "FakePlayer._info.help" + }, + "Enable": { + "name": "FakePlayer.Enable.name", + "help": "FakePlayer.Enable.help" + }, + "Name": { + "name": "FakePlayer.Name.name", + "help": "FakePlayer.Name.help" + }, + "Level": { + "name": "FakePlayer.Level.name", + "help": "FakePlayer.Level.help" + }, + "Id": { + "name": "FakePlayer.Id.name", + "help": "FakePlayer.Id.help" + } + }, + "Misc": { + "_info": { + "name": "Misc._info.name", + "help": "Misc._info.help" + }, + "GlobalSpeedup": { + "name": "Misc.GlobalSpeedup.name", + "help": "Misc.GlobalSpeedup.help" + }, + "BetterGlobalSpeedup": { + "name": "Misc.BetterGlobalSpeedup.name", + "help": "Misc.BetterGlobalSpeedup.help" + }, + "ChapterMove": { + "name": "Misc.ChapterMove.name", + "help": "Misc.ChapterMove.help" + }, + "OpsiMove": { + "name": "Misc.OpsiMove.name", + "help": "Misc.OpsiMove.help" + }, + "RemoveHardMapLimit": { + "name": "Misc.RemoveHardMapLimit.name", + "help": "Misc.RemoveHardMapLimit.help", + "disable": "disable", + "remove_ship_properties_limit": "remove_ship_properties_limit", + "remove_ship_type_limit": "remove_ship_type_limit", + "remove_both": "remove_both" + }, + "NoBBAnimation": { + "name": "Misc.NoBBAnimation.name", + "help": "Misc.NoBBAnimation.help" + }, + "NoEmotionWarning": { + "name": "Misc.NoEmotionWarning.name", + "help": "Misc.NoEmotionWarning.help" + }, + "ExerciseGodMod": { + "name": "Misc.ExerciseGodMod.name", + "help": "Misc.ExerciseGodMod.help" + }, + "ExerciseMorePower": { + "name": "Misc.ExerciseMorePower.name", + "help": "Misc.ExerciseMorePower.help" + }, + "FastWave": { + "name": "Misc.FastWave.name", + "help": "Misc.FastWave.help" + }, + "MonsterKillSelf": { + "name": "Misc.MonsterKillSelf.name", + "help": "Misc.MonsterKillSelf.help" + }, + "SkipBattleCelebrate": { + "name": "Misc.SkipBattleCelebrate.name", + "help": "Misc.SkipBattleCelebrate.help" + } + }, "Storage": { "_info": { "name": "Storage._info.name", diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index 2696ba1e6..f575f82f4 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -4,6 +4,10 @@ "name": "Alas", "help": "" }, + "Cheat": { + "name": "作弊", + "help": "" + }, "Farm": { "name": "出击", "help": "" @@ -46,6 +50,10 @@ "name": "重启设置", "help": "" }, + "Hook": { + "name": "Hook", + "help": "" + }, "Main": { "name": "主线图", "help": "" @@ -2612,6 +2620,225 @@ "help": "游戏被强制结束后自动登录游戏" } }, + "HookGeneral": { + "_info": { + "name": "基本设置", + "help": "" + }, + "Enable": { + "name": "总开关", + "help": "" + }, + "PushEveryTime": { + "name": "每次重新推送资源", + "help": "视情况开关,一般更新时推送" + }, + "Architecture": { + "name": "架构", + "help": "选择使用的架构,如果自动检测失败,请手动选择\n游戏库目录中看到的架构和选项的对应关系一般如下:\nx86 -> x86\nx86_64 -> x86_64\narm -> armeabi-v7a\narm64 -> arm64-v8a", + "auto": "自动检测", + "x86": "x86", + "x86_64": "x86_64", + "arm64-v8a": "arm64-v8a", + "armeabi-v7a": "armeabi-v7a" + }, + "InjectMethod": { + "name": "注入方式", + "help": "", + "local_patch": "仅注入游戏", + "global_patch": "全局注入", + "outer_inject": "外部注入" + }, + "RequestTimeLimit": { + "name": "请求时限(s)", + "help": "" + }, + "UpdateServer": { + "name": "资源更新服务器", + "help": "更新LuaHook资源的服务器,不填则表示不使用,将会使用本地资源" + }, + "GameLibDir": { + "name": "游戏库目录", + "help": "以x86架构为例,目录一般为/data/app/<一串乱码>/<一串乱码>/lib/x86" + } + }, + "ShipProperty": { + "_info": { + "name": "修改舰船属性", + "help": "该功能下所有的属性设置均为-1表示不修改该属性,默认为-1" + }, + "Method": { + "name": "修改方式", + "help": "", + "disable": "不使用", + "gg_factor": "倍率", + "final_properties": "修改最终属性" + }, + "Factor": { + "name": "倍率", + "help": "可以是小数,默认为1.0,表示不修改" + }, + "Armor": { + "name": "装甲类型", + "help": "修改不生效" + }, + "Speed": { + "name": "航速", + "help": "" + }, + "AntiAircraft": { + "name": "防空", + "help": "" + }, + "OxyRecoveryBench": { + "name": "OxyRecoveryBench", + "help": "未知属性" + }, + "Torpedo": { + "name": "雷击", + "help": "" + }, + "Hit": { + "name": "命中", + "help": "" + }, + "SonarRange": { + "name": "反潜范围", + "help": "尚不明确" + }, + "AttackDuration": { + "name": "攻击间隔", + "help": "尚不明确" + }, + "RaidDistance": { + "name": "雷达范围", + "help": "尚不明确" + }, + "OxyRecoverySurface": { + "name": "OxyRecoverySurface", + "help": "未知属性" + }, + "OxyRecovery": { + "name": "OxyRecovery", + "help": "未知属性" + }, + "Dodge": { + "name": "机动", + "help": "" + }, + "Luck": { + "name": "幸运", + "help": "" + }, + "Reload": { + "name": "装填", + "help": "" + }, + "OxyCost": { + "name": "氧气消耗", + "help": "尚不明确" + }, + "Durability": { + "name": "耐久", + "help": "" + }, + "Air": { + "name": "航空", + "help": "" + }, + "OxyMax": { + "name": "氧气", + "help": "" + }, + "Cannon": { + "name": "炮击", + "help": "" + }, + "AntiSub": { + "name": "反潜", + "help": "" + } + }, + "FakePlayer": { + "_info": { + "name": "虚假玩家信息", + "help": "该功能下所有的属性设置均为空表示不修改该属性,默认为空" + }, + "Enable": { + "name": "启用", + "help": "" + }, + "Name": { + "name": "名称", + "help": "" + }, + "Level": { + "name": "等级", + "help": "使用修改该信息会影响指挥官等级识别" + }, + "Id": { + "name": "Id", + "help": "" + } + }, + "Misc": { + "_info": { + "name": "杂项", + "help": "" + }, + "GlobalSpeedup": { + "name": "全局加速", + "help": "1表示不使用,不要开太高!\n两个全局加速用一个就行了" + }, + "BetterGlobalSpeedup": { + "name": "更好的全局加速", + "help": "1表示不使用,不要开太高!\n两个全局加速用一个就行了" + }, + "ChapterMove": { + "name": "章节图移动加速", + "help": "" + }, + "OpsiMove": { + "name": "大世界移动加速", + "help": "" + }, + "RemoveHardMapLimit": { + "name": "移除困难图限制", + "help": "", + "disable": "不使用", + "remove_ship_properties_limit": "移除舰船属性限制", + "remove_ship_type_limit": "移除舰船类型限制", + "remove_both": "移除所有" + }, + "NoBBAnimation": { + "name": "移除战列开火的子弹时间", + "help": "" + }, + "NoEmotionWarning": { + "name": "移除低心情警告", + "help": "" + }, + "ExerciseGodMod": { + "name": "演习锁血", + "help": "" + }, + "ExerciseMorePower": { + "name": "演习我方伤害增加 X %", + "help": "-1表示不使用" + }, + "FastWave": { + "name": "战斗秒出怪", + "help": "" + }, + "MonsterKillSelf": { + "name": "怪自杀", + "help": "" + }, + "SkipBattleCelebrate": { + "name": "跳过战斗胜利庆祝", + "help": "" + } + }, "Storage": { "_info": { "name": "任务状态", diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index 9a8e10792..f29aeac04 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -4,6 +4,10 @@ "name": "Alas", "help": "" }, + "Cheat": { + "name": "Menu.Cheat.name", + "help": "Menu.Cheat.help" + }, "Farm": { "name": "出擊", "help": "" @@ -46,6 +50,10 @@ "name": "重啟設定", "help": "" }, + "Hook": { + "name": "Task.Hook.name", + "help": "Task.Hook.help" + }, "Main": { "name": "主線圖", "help": "" @@ -2612,6 +2620,225 @@ "help": "遊戲被強制結束後自動登錄遊戲" } }, + "HookGeneral": { + "_info": { + "name": "HookGeneral._info.name", + "help": "HookGeneral._info.help" + }, + "Enable": { + "name": "HookGeneral.Enable.name", + "help": "HookGeneral.Enable.help" + }, + "PushEveryTime": { + "name": "HookGeneral.PushEveryTime.name", + "help": "HookGeneral.PushEveryTime.help" + }, + "Architecture": { + "name": "HookGeneral.Architecture.name", + "help": "HookGeneral.Architecture.help", + "auto": "auto", + "x86": "x86", + "x86_64": "x86_64", + "arm64-v8a": "arm64-v8a", + "armeabi-v7a": "armeabi-v7a" + }, + "InjectMethod": { + "name": "HookGeneral.InjectMethod.name", + "help": "HookGeneral.InjectMethod.help", + "local_patch": "local_patch", + "global_patch": "global_patch", + "outer_inject": "outer_inject" + }, + "RequestTimeLimit": { + "name": "HookGeneral.RequestTimeLimit.name", + "help": "HookGeneral.RequestTimeLimit.help" + }, + "UpdateServer": { + "name": "HookGeneral.UpdateServer.name", + "help": "HookGeneral.UpdateServer.help" + }, + "GameLibDir": { + "name": "HookGeneral.GameLibDir.name", + "help": "HookGeneral.GameLibDir.help" + } + }, + "ShipProperty": { + "_info": { + "name": "ShipProperty._info.name", + "help": "ShipProperty._info.help" + }, + "Method": { + "name": "ShipProperty.Method.name", + "help": "ShipProperty.Method.help", + "disable": "disable", + "gg_factor": "gg_factor", + "final_properties": "final_properties" + }, + "Factor": { + "name": "ShipProperty.Factor.name", + "help": "ShipProperty.Factor.help" + }, + "Armor": { + "name": "ShipProperty.Armor.name", + "help": "ShipProperty.Armor.help" + }, + "Speed": { + "name": "ShipProperty.Speed.name", + "help": "ShipProperty.Speed.help" + }, + "AntiAircraft": { + "name": "ShipProperty.AntiAircraft.name", + "help": "ShipProperty.AntiAircraft.help" + }, + "OxyRecoveryBench": { + "name": "ShipProperty.OxyRecoveryBench.name", + "help": "ShipProperty.OxyRecoveryBench.help" + }, + "Torpedo": { + "name": "ShipProperty.Torpedo.name", + "help": "ShipProperty.Torpedo.help" + }, + "Hit": { + "name": "ShipProperty.Hit.name", + "help": "ShipProperty.Hit.help" + }, + "SonarRange": { + "name": "ShipProperty.SonarRange.name", + "help": "ShipProperty.SonarRange.help" + }, + "AttackDuration": { + "name": "ShipProperty.AttackDuration.name", + "help": "ShipProperty.AttackDuration.help" + }, + "RaidDistance": { + "name": "ShipProperty.RaidDistance.name", + "help": "ShipProperty.RaidDistance.help" + }, + "OxyRecoverySurface": { + "name": "ShipProperty.OxyRecoverySurface.name", + "help": "ShipProperty.OxyRecoverySurface.help" + }, + "OxyRecovery": { + "name": "ShipProperty.OxyRecovery.name", + "help": "ShipProperty.OxyRecovery.help" + }, + "Dodge": { + "name": "ShipProperty.Dodge.name", + "help": "ShipProperty.Dodge.help" + }, + "Luck": { + "name": "ShipProperty.Luck.name", + "help": "ShipProperty.Luck.help" + }, + "Reload": { + "name": "ShipProperty.Reload.name", + "help": "ShipProperty.Reload.help" + }, + "OxyCost": { + "name": "ShipProperty.OxyCost.name", + "help": "ShipProperty.OxyCost.help" + }, + "Durability": { + "name": "ShipProperty.Durability.name", + "help": "ShipProperty.Durability.help" + }, + "Air": { + "name": "ShipProperty.Air.name", + "help": "ShipProperty.Air.help" + }, + "OxyMax": { + "name": "ShipProperty.OxyMax.name", + "help": "ShipProperty.OxyMax.help" + }, + "Cannon": { + "name": "ShipProperty.Cannon.name", + "help": "ShipProperty.Cannon.help" + }, + "AntiSub": { + "name": "ShipProperty.AntiSub.name", + "help": "ShipProperty.AntiSub.help" + } + }, + "FakePlayer": { + "_info": { + "name": "FakePlayer._info.name", + "help": "FakePlayer._info.help" + }, + "Enable": { + "name": "FakePlayer.Enable.name", + "help": "FakePlayer.Enable.help" + }, + "Name": { + "name": "FakePlayer.Name.name", + "help": "FakePlayer.Name.help" + }, + "Level": { + "name": "FakePlayer.Level.name", + "help": "FakePlayer.Level.help" + }, + "Id": { + "name": "FakePlayer.Id.name", + "help": "FakePlayer.Id.help" + } + }, + "Misc": { + "_info": { + "name": "Misc._info.name", + "help": "Misc._info.help" + }, + "GlobalSpeedup": { + "name": "Misc.GlobalSpeedup.name", + "help": "Misc.GlobalSpeedup.help" + }, + "BetterGlobalSpeedup": { + "name": "Misc.BetterGlobalSpeedup.name", + "help": "Misc.BetterGlobalSpeedup.help" + }, + "ChapterMove": { + "name": "Misc.ChapterMove.name", + "help": "Misc.ChapterMove.help" + }, + "OpsiMove": { + "name": "Misc.OpsiMove.name", + "help": "Misc.OpsiMove.help" + }, + "RemoveHardMapLimit": { + "name": "Misc.RemoveHardMapLimit.name", + "help": "Misc.RemoveHardMapLimit.help", + "disable": "disable", + "remove_ship_properties_limit": "remove_ship_properties_limit", + "remove_ship_type_limit": "remove_ship_type_limit", + "remove_both": "remove_both" + }, + "NoBBAnimation": { + "name": "Misc.NoBBAnimation.name", + "help": "Misc.NoBBAnimation.help" + }, + "NoEmotionWarning": { + "name": "Misc.NoEmotionWarning.name", + "help": "Misc.NoEmotionWarning.help" + }, + "ExerciseGodMod": { + "name": "Misc.ExerciseGodMod.name", + "help": "Misc.ExerciseGodMod.help" + }, + "ExerciseMorePower": { + "name": "Misc.ExerciseMorePower.name", + "help": "Misc.ExerciseMorePower.help" + }, + "FastWave": { + "name": "Misc.FastWave.name", + "help": "Misc.FastWave.help" + }, + "MonsterKillSelf": { + "name": "Misc.MonsterKillSelf.name", + "help": "Misc.MonsterKillSelf.help" + }, + "SkipBattleCelebrate": { + "name": "Misc.SkipBattleCelebrate.name", + "help": "Misc.SkipBattleCelebrate.help" + } + }, "Storage": { "_info": { "name": "任務狀態", diff --git a/module/luahook/api.py b/module/luahook/api.py new file mode 100644 index 000000000..94c101182 --- /dev/null +++ b/module/luahook/api.py @@ -0,0 +1,300 @@ +import json +from typing import Dict + +import requests +import adbutils +from pydantic import BaseModel + +from module.luahook.exception import CrackerError + + +class CrackApi: + class ShipProperties(BaseModel): + armor: float = -1 + speed: float = -1 + antiaircraft: float = -1 + oxy_recovery_bench: float = -1 + torpedo: float = -1 + hit: float = -1 + sonarRange: float = -1 + attack_duration: float = -1 + raid_distance: float = -1 + oxy_recovery_surface: float = -1 + oxy_recovery: float = -1 + dodge: float = -1 + luck: float = -1 + reload: float = -1 + oxy_cost: float = -1 + durability: float = -1 + air: float = -1 + oxy_max: float = -1 + cannon: float = -1 + antisub: float = -1 + + class FakePlayerInfo(BaseModel): + name: str = "" + level: str = "" + id: str = "" + + class ShipInfo(BaseModel): + config_id: int + emotion: int + rarity: int + level: int + exp: int + curr_star: int + + class GlobalSpeedupRate(BaseModel): + rate: float + + class BetterGlobalSpeedupRate(BaseModel): + rate: float + + class ExerciseMorePowerRate(BaseModel): + rate: float + + def __init__(self, base_url, timeout=10): + self.api_url = base_url + self.timeout = timeout + + def get(self, path, **kwargs): + args = [] + for k, v in kwargs.items(): + if v is None: + continue + args.append(f'{k}={v}') + if args: + url = f'{self.api_url}/{path}?{"&".join(args)}' + else: + url = f'{self.api_url}/{path}' + try: + response = requests.get(url, headers={ + "Connection": "close", + }, timeout=self.timeout) + except requests.exceptions.Timeout: + raise CrackerError('CrackApi request timeout') + except Exception as e: + raise CrackerError(f'CrackApi request error: {e}') + if response.status_code != 200: + raise CrackerError(f'CrackApi response error: {response.status_code}') + return response + + def post(self, path, data=None): + url = f'{self.api_url}/{path}' + if data is not None and isinstance(data, dict): + data_str = json.dumps(data) + else: + data_str = data + try: + response = requests.post(url, data=data_str, headers={ + "Connection": "close", + }, timeout=self.timeout) + except requests.exceptions.Timeout: + raise CrackerError('CrackApi request timeout') + except Exception as e: + raise CrackerError(f'CrackApi request error: {e}') + if response.status_code != 200: + raise CrackerError(f'CrackApi response error: {response.status_code}') + + def get_coin(self) -> int: + response = self.get('get_coin') + return int(response.text) + + def get_oil(self) -> int: + response = self.get('get_oil') + return int(response.text) + + def get_gems(self) -> int: + response = self.get('get_gems') + return int(response.text) + + def get_level(self) -> int: + response = self.get('get_level') + return int(response.text) + + def get_exp(self) -> int: + response = self.get('get_exp') + return int(response.text) + + def get_merit(self) -> int: + response = self.get('get_merit') + return int(response.text) + + def get_guild_coin(self) -> int: + response = self.get('get_guild_coin') + return int(response.text) + + def get_design_prt(self) -> int: + response = self.get('get_design_prt') + return int(response.text) + + def get_core_data(self) -> str: + response = self.get('get_core_data') + return response.text + + def get_medal(self) -> int: + response = self.get('get_medal') + return int(response.text) + + def get_pt(self) -> int: + response = self.get('get_pt') + return int(response.text) + + def get_specialized_core(self) -> int: + response = self.get('get_specialized_core') + return int(response.text) + + def get_curr_action_points(self) -> int: + response = self.get('get_curr_action_points') + return int(response.text) + + def scan_dock(self) -> Dict[int, ShipInfo]: + response = self.get('scan_dock') + json_data = json.loads(response.text) + res = {} + for k, v in json_data.items(): + res[int(k)] = CrackApi.ShipInfo(**v) + return res + + def scan_storage(self) -> Dict[int, int]: + response = self.get('scan_storage') + json_data = json.loads(response.text) + res = {} + for k, v in json_data.items(): + res[int(k)] = int(v) + return res + + def get_ship_info(self, ship_id: int) -> ShipInfo: + response = self.get('get_ship_info', id=ship_id) + json_data = json.loads(response.text) + return CrackApi.ShipInfo(**json_data) + + def get_storage_item_count(self, item_id: int) -> int: + response = self.get('get_storage_item_count', id=item_id) + return int(response.text) + + def enable_gg_factor(self): + self.post('enable_gg_factor') + + def disable_gg_factor(self): + self.post('disable_gg_factor') + + def update_gg_factor(self, gg_factor: float): + self.post('update_gg_factor', data={'factor': gg_factor}) + + def disable_all(self): + self.post('disable_all') + + def enable_global_ship_properties_crack(self): + self.post('enable_global_ship_properties_crack') + + def disable_global_ship_properties_crack(self): + self.post('disable_global_ship_properties_crack') + + def update_global_ship_properties(self, ship_properties: ShipProperties): + self.post('update_global_ship_properties', data=ship_properties.json()) + + def enable_fast_stage_move(self): + self.post("enable_fast_stage_move") + + def disable_fast_stage_move(self): + self.post("disable_fast_stage_move") + + def enable_remove_hard_mode_ship_properties_limit(self): + self.post("enable_remove_hard_mode_ship_properties_limit") + + def disable_remove_hard_mode_ship_properties_limit(self): + self.post("disable_remove_hard_mode_ship_properties_limit") + + def enable_remove_hard_mode_ship_type_limit(self): + self.post("enable_remove_hard_mode_ship_type_limit") + + def disable_remove_hard_mode_ship_type_limit(self): + self.post("disable_remove_hard_mode_ship_type_limit") + + def enable_remove_hard_mode_limit(self): + self.post("enable_remove_hard_mode_limit") + + def disable_remove_hard_mode_limit(self): + self.post("disable_remove_hard_mode_limit") + + def enable_fake_player(self): + self.post("enable_fake_player") + + def disable_fake_player(self): + self.post("disable_fake_player") + + def update_fake_player_info(self, fake_player_info: FakePlayerInfo): + self.post("update_fake_player_info", data=fake_player_info.json()) + + def enable_no_bb_animation(self): + self.post("enable_no_bb_animation") + + def disable_no_bb_animation(self): + self.post("disable_no_bb_animation") + + def enable_no_emotion_warning(self): + self.post("enable_no_emotion_warning") + + def disable_no_emotion_warning(self): + self.post("disable_no_emotion_warning") + + def enable_opsi_fast_move(self): + self.post("enable_opsi_fast_move") + + def disable_opsi_fast_move(self): + self.post("disable_opsi_fast_move") + + def enable_global_speedup(self): + self.post("enable_global_speedup") + + def update_global_speedup_rate(self, rate: GlobalSpeedupRate): + self.post("update_global_speedup_rate", data=rate.json()) + + def disable_global_speedup(self): + self.post("disable_global_speedup") + + def enable_better_global_speedup(self): + self.post("enable_better_global_speedup") + + def update_better_global_speedup_rate(self, rate: BetterGlobalSpeedupRate): + self.post("update_better_global_speedup_rate", data=rate.json()) + + def disable_better_global_speedup(self): + self.post("disable_better_global_speedup") + + def is_alive(self): + self.post("is_alive") + + def enable_exercise_more_power(self): + self.post("enable_exercise_more_power") + + def update_exercise_more_power_rate(self, rate: ExerciseMorePowerRate): + self.post("update_exercise_more_power_rate", data=rate.json()) + + def disable_exercise_more_power(self): + self.post("disable_exercise_more_power") + + def enable_exercise_god_mode(self): + self.post("enable_exercise_god_mode") + + def disable_exercise_god_mode(self): + self.post("disable_exercise_god_mode") + + def enable_fast_wave(self): + self.post("enable_fast_wave") + + def disable_fast_wave(self): + self.post("disable_fast_wave") + + def enable_monster_kill_self(self): + self.post("enable_monster_kill_self") + + def disable_monster_kill_self(self): + self.post("disable_monster_kill_self") + + def enable_skip_battle_celebrate(self): + self.post("enable_skip_battle_celebrate") + + def disable_skip_battle_celebrate(self): + self.post("disable_skip_battle_celebrate") diff --git a/module/luahook/crack.py b/module/luahook/crack.py new file mode 100644 index 000000000..b42f41b9d --- /dev/null +++ b/module/luahook/crack.py @@ -0,0 +1,518 @@ +import hashlib +import json +import os +from pathlib import Path +from typing import Union, List, Type + +import requests +from pydantic import BaseModel + +from module.config.config import AzurLaneConfig +from module.config.deep import deep_get +from module.device.device import Device +from module.luahook.exception import CrackerError +from module.logger import logger + +from module.luahook.api import CrackApi +from module.luahook.op import CrackOp + +ALL_ENABLE_OPS = [ + CrackOp.EnableGlobalShipProperties, + CrackOp.EnableChapterFastMove, + CrackOp.EnableRemoveHardModeLimit, + CrackOp.EnableFakePlayer, + CrackOp.EnableNoBBAnimation, + CrackOp.EnableNoEmotionWarning, + CrackOp.EnableOpsiFastMove, + CrackOp.EnableRemoveHardModeShipTypeLimit, + CrackOp.EnableRemoveHardModeShipPropertiesLimit, + CrackOp.EnableGGFactor, + CrackOp.EnableGlobalSpeedup, + CrackOp.EnableBetterGlobalSpeedup, + CrackOp.EnableExerciseGodMod, + CrackOp.EnableExerciseMorePower, + CrackOp.EnableFastWave, + CrackOp.EnableMonsterKillSelf, + CrackOp.EnableSkipBattleCelebrate, +] + +REMOTE_PORT = 23897 + + +class Cracker(CrackApi): + def __init__(self, config: AzurLaneConfig, device: Device): + self.config = config + self.device = device + super().__init__(f"http://127.0.0.1:{device.adb.forward_port(REMOTE_PORT)}") + + +def fix_Hook_ShipProperty_config(config: AzurLaneConfig): + enable = deep_get(config.data, "Hook.ShipProperty.Enable", False) + if not enable: + return + else: + config.modified["Hook.ShipProperty.Method"] = "final_properties" + +def do_crack_op_on_func( + before_call: Union[Type[CrackOp.Op], List[Type[CrackOp.Op]]] = None, + after_call: Union[Type[CrackOp.Op], List[Type[CrackOp.Op]]] = None +): + def wrapper(func): + def inner(*args, **kwargs): + obj = args[0] + if before_call is not None: + do_crack_op(obj.config, obj.device, before_call) + try: + ret = func(*args, **kwargs) + except Exception as e: + if after_call is not None: + do_crack_op(obj.config, obj.device, after_call) + raise e + if after_call is not None: + do_crack_op(obj.config, obj.device, after_call) + return ret + return inner + + return wrapper + + +def do_crack_op(config: AzurLaneConfig, device: Device, ops: Union[Type[CrackOp.Op], List[Type[CrackOp.Op]]]): + if not deep_get(config.data, "Hook.HookGeneral.Enable", False): + return + if isinstance(ops, list): + l = ops + else: + if ops == CrackOp.EnableAll: + l = ALL_ENABLE_OPS + else: + l = [ops] + + base_url = f"http://127.0.0.1:{device.adb.forward_port(REMOTE_PORT)}" + timeout = deep_get(config.data, "Hook.HookGeneral.RequestTimeLimit", 10) + api = CrackApi(base_url, timeout=timeout) + for op in l: + if op == CrackOp.DisableAll: + api.disable_all() + elif op == CrackOp.EnableGlobalShipProperties: + fix_Hook_ShipProperty_config(config) + if deep_get(config.data, "Hook.ShipProperty.Method", "disable") != "final_properties": + continue + api.update_global_ship_properties( + CrackApi.ShipProperties( + armor=float(deep_get(config.data, "Hook.ShipProperty.Armor", -1)), + speed=float(deep_get(config.data, "Hook.ShipProperty.Speed", -1)), + antiaircraft=float(deep_get(config.data, "Hook.ShipProperty.AntiAircraft", -1)), + oxy_recovery_bench=float(deep_get(config.data, "Hook.ShipProperty.OxyRecoveryBench", -1)), + torpedo=float(deep_get(config.data, "Hook.ShipProperty.Torpedo", -1)), + hit=float(deep_get(config.data, "Hook.ShipProperty.Hit", -1)), + sonarRange=float(deep_get(config.data, "Hook.ShipProperty.SonarRange", -1)), + attack_duration=float(deep_get(config.data, "Hook.ShipProperty.AttackDuration", -1)), + raid_distance=float(deep_get(config.data, "Hook.ShipProperty.RaidDistance", -1)), + oxy_recovery_surface=float(deep_get(config.data, "Hook.ShipProperty.OxyRecoverySurface", -1)), + oxy_recovery=float(deep_get(config.data, "Hook.ShipProperty.OxyRecovery", -1)), + dodge=float(deep_get(config.data, "Hook.ShipProperty.Dodge", -1)), + luck=float(deep_get(config.data, "Hook.ShipProperty.Luck", -1)), + reload=float(deep_get(config.data, "Hook.ShipProperty.Reload", -1)), + oxy_cost=float(deep_get(config.data, "Hook.ShipProperty.OxyCost", -1)), + durability=float(deep_get(config.data, "Hook.ShipProperty.Durability", -1)), + air=float(deep_get(config.data, "Hook.ShipProperty.Air", -1)), + oxy_max=float(deep_get(config.data, "Hook.ShipProperty.OxyMax", -1)), + cannon=float(deep_get(config.data, "Hook.ShipProperty.Cannon", -1)), + antisub=float(deep_get(config.data, "Hook.ShipProperty.AntiSub", -1)), + ) + ) + api.enable_global_ship_properties_crack() + elif op == CrackOp.DisableGlobalShipProperties: + api.disable_global_ship_properties_crack() + elif op == CrackOp.EnableChapterFastMove: + if not deep_get(config.data, "Hook.Misc.ChapterMove", False): + continue + api.enable_fast_stage_move() + elif op == CrackOp.DisableChapterFastMove: + api.disable_fast_stage_move() + elif op == CrackOp.EnableRemoveHardModeShipPropertiesLimit: + if deep_get(config.data, "Hook.Misc.RemoveHardMapLimit", "") != "remove_ship_properties_limit": + continue + api.enable_remove_hard_mode_ship_properties_limit() + elif op == CrackOp.DisableRemoveHardModeShipPropertiesLimit: + api.disable_remove_hard_mode_ship_properties_limit() + elif op == CrackOp.EnableRemoveHardModeShipTypeLimit: + if deep_get(config.data, "Hook.Misc.RemoveHardMapLimit", "") != "remove_ship_type_limit": + continue + api.enable_remove_hard_mode_ship_type_limit() + elif op == CrackOp.DisableRemoveHardModeShipTypeLimit: + api.disable_remove_hard_mode_ship_type_limit() + elif op == CrackOp.EnableRemoveHardModeLimit: + if deep_get(config.data, "Hook.Misc.RemoveHardMapLimit", "") != "remove_both": + continue + api.enable_remove_hard_mode_limit() + elif op == CrackOp.DisableRemoveHardModeLimit: + api.disable_remove_hard_mode_limit() + elif op == CrackOp.EnableFakePlayer: + if not deep_get(config.data, "Hook.FakePlayer.Enable", False): + continue + api.update_fake_player_info( + CrackApi.FakePlayerInfo( + name=str(deep_get(config.data, "Hook.FakePlayer.Name", "")), + level=str(deep_get(config.data, "Hook.FakePlayer.Level", "")), + id=str(deep_get(config.data, "Hook.FakePlayer.Id", "")), + ) + ) + api.enable_fake_player() + elif op == CrackOp.DisableFakePlayer: + api.disable_fake_player() + elif op == CrackOp.EnableNoBBAnimation: + if not deep_get(config.data, "Hook.Misc.NoBBAnimation", False): + continue + api.enable_no_bb_animation() + elif op == CrackOp.DisableNoBBAnimation: + api.disable_no_bb_animation() + elif op == CrackOp.EnableNoEmotionWarning: + if not deep_get(config.data, "Hook.Misc.NoEmotionWarning", False): + continue + api.enable_no_emotion_warning() + elif op == CrackOp.DisableNoEmotionWarning: + api.disable_no_emotion_warning() + elif op == CrackOp.IsAlive: + api.is_alive() + elif op == CrackOp.EnableOpsiFastMove: + api.enable_opsi_fast_move() + elif op == CrackOp.DisableOpsiFastMove: + api.disable_opsi_fast_move() + elif op == CrackOp.EnableGGFactor: + if deep_get(config.data, "Hook.ShipProperty.Method", "disable") != "gg_factor": + continue + api.update_gg_factor(float(deep_get(config.data, "Hook.ShipProperty.Factor", 1.0))) + api.enable_gg_factor() + elif op == CrackOp.DisableGGFactor: + api.disable_gg_factor() + elif op == CrackOp.EnableGlobalSpeedup: + rate = float(deep_get(config.data, "Hook.Misc.GlobalSpeedup", 1.0)) + if rate == 1.0: + continue + api.update_global_speedup_rate(CrackApi.GlobalSpeedupRate(rate=rate)) + api.enable_global_speedup() + elif op == CrackOp.DisableGlobalSpeedup: + api.disable_global_speedup() + elif op == CrackOp.EnableBetterGlobalSpeedup: + rate = float(deep_get(config.data, "Hook.Misc.BetterGlobalSpeedup", 1.0)) + if rate == 1.0: + continue + api.update_better_global_speedup_rate(CrackApi.BetterGlobalSpeedupRate(rate=rate)) + api.enable_better_global_speedup() + elif op == CrackOp.DisableBetterGlobalSpeedup: + api.disable_better_global_speedup() + elif op == CrackOp.EnableExerciseGodMod: + if deep_get(config.data, "Hook.Misc.ExerciseGodMod", False): + api.enable_exercise_god_mode() + elif op == CrackOp.DisableExerciseGodMod: + api.disable_exercise_god_mode() + elif op == CrackOp.EnableExerciseMorePower: + rate = float(deep_get(config.data, "Hook.Misc.ExerciseMorePower", -1.0)) + if rate == -1.0: + continue + api.update_exercise_more_power_rate(CrackApi.ExerciseMorePowerRate(rate=rate)) + api.enable_exercise_more_power() + elif op == CrackOp.DisableExerciseMorePower: + api.disable_exercise_more_power() + elif op == CrackOp.EnableFastWave: + is_enable_fast_wave = deep_get(config.data, "Hook.Misc.FastWave", False) + if is_enable_fast_wave: + api.enable_fast_wave() + elif op == CrackOp.DisableFastWave: + api.disable_fast_wave() + elif op == CrackOp.EnableMonsterKillSelf: + is_enable_monster_kill_self = deep_get(config.data, "Hook.Misc.MonsterKillSelf", False) + if is_enable_monster_kill_self: + api.enable_monster_kill_self() + elif op == CrackOp.DisableMonsterKillSelf: + api.disable_monster_kill_self() + elif op == CrackOp.EnableSkipBattleCelebrate: + is_enable_skip_battle_celebrate = deep_get(config.data, "Hook.Misc.SkipBattleCelebrate", False) + if is_enable_skip_battle_celebrate: + api.enable_skip_battle_celebrate() + elif op == CrackOp.DisableSkipBattleCelebrate: + api.disable_skip_battle_celebrate() + else: + logger.error(f"Unsupported op: {op}") + +crack_op = do_crack_op_on_func + +def disable_all_crack(f): + def wrapper(*args, **kwargs): + obj = args[0] + logger.info("Disabe all luahook cracks") + do_crack_op(obj.config, obj.device, CrackOp.DisableAll) + return f(*args, **kwargs) + + return wrapper + + +def enable_all_crack(f): + def wrapper(*args, **kwargs): + obj = args[0] + do_crack_op(obj.config, obj.device, CrackOp.EnableAll) + return f(*args, **kwargs) + + return wrapper + + +def luahook_crack_all(config: AzurLaneConfig, device: Device): + logger.info("Crack all with luahook") + do_crack_op(config, device, CrackOp.EnableAll) + +def luahook_disable_all(config: AzurLaneConfig, device: Device): + logger.info("Disable all luahook") + do_crack_op(config, device, CrackOp.DisableAll) + + +CHAPTER_CRACK_OPS = [ + CrackOp.EnableChapterFastMove, + CrackOp.EnableNoBBAnimation, + CrackOp.EnableNoEmotionWarning, + CrackOp.EnableGlobalShipProperties, + CrackOp.EnableRemoveHardModeLimit, + CrackOp.EnableFakePlayer, + CrackOp.EnableGGFactor, + CrackOp.EnableGlobalSpeedup, + CrackOp.EnableBetterGlobalSpeedup, + CrackOp.EnableFastWave, + CrackOp.EnableMonsterKillSelf, + CrackOp.EnableSkipBattleCelebrate, +] + + +def chapter_task_crack(f): + def wrapper(*args, **kwargs): + obj = args[0] + do_crack_op(obj.config, obj.device, CHAPTER_CRACK_OPS) + return f(*args, **kwargs) + + return wrapper + + +OPSI_CRACK_OPS = [ + CrackOp.EnableNoBBAnimation, + CrackOp.EnableOpsiFastMove, + CrackOp.EnableGlobalShipProperties, + CrackOp.EnableFakePlayer, + CrackOp.EnableGGFactor, + CrackOp.EnableGlobalSpeedup, + CrackOp.EnableBetterGlobalSpeedup, + CrackOp.EnableFastWave, + CrackOp.EnableMonsterKillSelf, + CrackOp.EnableSkipBattleCelebrate, +] + + +def opsi_task_crack(f): + def wrapper(*args, **kwargs): + obj = args[0] + do_crack_op(obj.config, obj.device, OPSI_CRACK_OPS) + return f(*args, **kwargs) + + return wrapper + + +class UpdateServerApi: + class ResourceFile(BaseModel): + arch: str + file: str + + def __init__(self, api_url: str = ""): + self.api_url = api_url + + def post(self, path: str, data=None): + logger.info(f"UpdateServerApi post: {path}") + url = f'{self.api_url}/{path}' + try: + response = requests.post(url, data=data) # TODO: add timeout + except requests.exceptions.Timeout: + raise CrackerError('UpdateServerApi request timeout') + except Exception as e: + raise CrackerError(f'UpdateServerApi request error: {e}') + if response.status_code != 200: + raise CrackerError(f'UpdateServerApi response error: {response.status_code}') + return response + + def download_file(self, arch: str, file_name: str) -> bytes: + res = self.post("files", UpdateServerApi.ResourceFile(arch=arch, file=file_name).json()) + return res.content + + def get_hash(self, arch: str, file_name: str) -> str: + res = self.post("get_hash", UpdateServerApi.ResourceFile(arch=arch, file=file_name).json()) + return res.text + + +class CrackResource: + ARCH_MAP = { + "x86": "x86", + "x86_64": "x86", + "armv7": "armeabi-v7a", + "arm64": "arm64-v8a", + "aarch64": "arm64-v8a", + "aarch": "armeabi-v7a", + } + + def __init__(self, config: AzurLaneConfig, device: Device): + self.config = config + self.device = device + + arch_conf = deep_get(config.data, "Hook.HookGeneral.Architecture") + + if arch_conf == "auto": + arch_ret = device.adb_shell("uname -m").lower() + self.arch = self.ARCH_MAP.get(arch_ret, "") + if not self.arch: + raise CrackerError(f"Unsupported arch: {arch_ret}") + logger.info(f"Arch: {arch_ret} -> {self.arch}") + else: + self.arch = arch_conf + logger.info(f"Use arch: {self.arch}") + + self.resource_root = f"./bin/hook" + self.resource_dir = f"{self.resource_root}/{self.arch}" + self.version_file = f"{self.resource_dir}/version.json" + + self.first_init = False + self.has_new_version = False + + self.update_server = self.__get_update_server() + + self.upload_dir = f"/data/hook/{self.arch}" + + self.game_lib_dir = Path(self.__format_str(deep_get(self.config.data, "Hook.HookGeneral.GameLibDir", ""))).as_posix() + + def __file_hash(self, file_name): + with open(file_name, "rb") as f: + return hashlib.md5(f.read()).hexdigest() + + def __gen_version_file_from_local(self): + d = {} + for file_name in os.listdir(self.resource_dir): + f = f"{self.resource_dir}/{file_name}" + if not os.path.isfile(f): + continue + if file_name.endswith(".json"): + continue + + hash_value = self.__file_hash(f).upper() + d[file_name] = hash_value + with open(self.version_file, "w") as f: + json.dump(d, f, indent=4) + + def __ensure_local_resource(self): + if self.update_server is None: + logger.info("No update server, skip ensure local resource") + return + + if not os.path.exists(f"{self.resource_dir}/libcracker.so"): + self.__download_resource("libcracker.so") + self.first_init = True + + if not os.path.exists(f"{self.resource_dir}/patchelf"): + self.__download_resource("patchelf") + self.first_init = True + + __update_local_version = __gen_version_file_from_local + + def __ensure_crack_resource(self, ): + if not os.path.exists(self.resource_root): + os.makedirs(self.resource_root) + if not os.path.exists(self.resource_dir): + os.makedirs(self.resource_dir) + self.__ensure_local_resource() + if not os.path.exists(self.version_file): + self.__gen_version_file_from_local() + + def __download_resource(self, file_name: str): + if self.update_server is None: + logger.info("No update server, skip download resource") + return + logger.info(f"Download resource: {self.arch}/{file_name}") + res = self.update_server.download_file(self.arch, file_name) + with open(f"{self.resource_dir}/{file_name}", "wb") as f: + f.write(res) + + def __is_same_version(self, version_json: dict, file_name: str): + h = self.update_server.get_hash(self.arch, file_name) + return h == version_json.get(file_name, "") + + def __check_update(self): + if self.update_server is None: + logger.info("No update server, skip update check, use local resource") + return + with open(self.version_file, "r") as f: + local_version = json.load(f) + + if not self.__is_same_version(local_version, "libcracker.so"): + self.__download_resource("libcracker.so") + self.has_new_version = True + if not self.__is_same_version(local_version, "patchelf"): + self.__download_resource("patchelf") + self.has_new_version = True + + if self.has_new_version: + self.__update_local_version() + + def __format_str(self, s: str) -> str: + return ("".join([i for i in s if i.isprintable()])).replace(" ", "").replace("\n", "") + + def __get_update_server(self) -> Union[UpdateServerApi, None]: + update_server_url: str = deep_get(self.config.data, "Hook.HookGeneral.UpdateServer", "") + update_server_url = self.__format_str(update_server_url) + if not update_server_url: + return None + return UpdateServerApi(update_server_url) + + def __push(self, file_name: str): + self.device.adb_shell(f"mkdir -p {self.upload_dir}") + self.device.adb_shell(f"rm {self.upload_dir}/{file_name}") + self.device.adb_push(f"{self.resource_dir}/{file_name}", f"{self.upload_dir}/{file_name}", timeout=300) + self.device.adb_shell(f"chmod 777 {self.upload_dir}/{file_name}") + + def __push_resource(self): + logger.info("Push resource to device") + self.__push("libcracker.so") + self.__push("patchelf") + + def __do_inject(self): + if not self.game_lib_dir: + raise CrackerError("GameLibDir not set") + + logger.info(f"GameLibDir: {self.game_lib_dir}, do inject") + cmd = f"cd {self.upload_dir} && ./patchelf --local-patch --game-lib-dir '{self.game_lib_dir}'" + self.device.adb_shell(cmd) + + def __is_exist(self, path): + cmd = f"if [ -f {path} ]; then echo 'exist'; else if [ -d {path} ]; then echo 'exist'; else echo 'not exist'; fi; fi" + return self.device.adb_shell(cmd) == "exist" + + def __is_remote_file_exist(self) -> bool: + return self.__is_exist(self.upload_dir) and self.__is_exist(f"{self.upload_dir}/libcracker.so") and self.__is_exist(f"{self.upload_dir}/patchelf") + + def __adb_su_do(self, cmd: str): + return self.device.adb_shell(f"su -c '{cmd}'") + + def __adb_root(self): + self.device.adb_command(["root"]) + + def __check_game_lib_dir(self): + return self.__is_exist(f"{self.game_lib_dir}/libil2cpp.so") and self.__is_exist(f"{self.game_lib_dir}/libtolua.so") + + def ensure(self): + if not deep_get(self.config.data, "Hook.HookGeneral.Enable", False): + return + + if not self.__check_game_lib_dir(): + raise CrackerError(f"GameLibDir '{self.game_lib_dir}' is invalid") + + self.__adb_root() + self.__ensure_crack_resource() + if not self.first_init: + self.__check_update() + if self.has_new_version or not self.__is_remote_file_exist() or deep_get(self.config.data, "Hook.HookGeneral.PushEveryTime", True): + self.__push_resource() + self.device.app_stop() + self.config.task_call("Restart") + self.__do_inject() diff --git a/module/luahook/exception.py b/module/luahook/exception.py new file mode 100644 index 000000000..ab94a3441 --- /dev/null +++ b/module/luahook/exception.py @@ -0,0 +1,2 @@ +class CrackerError(Exception): + pass \ No newline at end of file diff --git a/module/luahook/op.py b/module/luahook/op.py new file mode 100644 index 000000000..a0b7370db --- /dev/null +++ b/module/luahook/op.py @@ -0,0 +1,114 @@ +class CrackOp: + class Op: + ... + + class EnableAll(Op): + ... + + class DisableAll(Op): + ... + + class EnableGlobalShipProperties(Op): + ... + + class DisableGlobalShipProperties(Op): + ... + + class EnableChapterFastMove(Op): + ... + + class DisableChapterFastMove(Op): + ... + + class EnableOpsiFastMove(Op): + ... + + class DisableOpsiFastMove(Op): + ... + + class EnableRemoveHardModeShipPropertiesLimit(Op): + ... + + class DisableRemoveHardModeShipPropertiesLimit(Op): + ... + + class EnableRemoveHardModeShipTypeLimit(Op): + ... + + class DisableRemoveHardModeShipTypeLimit(Op): + ... + + class EnableRemoveHardModeLimit(Op): + ... + + class DisableRemoveHardModeLimit(Op): + ... + + class EnableFakePlayer(Op): + ... + + class DisableFakePlayer(Op): + ... + + class EnableGlobalSpeedup(Op): + ... + + class DisableGlobalSpeedup(Op): + ... + + class EnableBetterGlobalSpeedup(Op): + ... + + class DisableBetterGlobalSpeedup(Op): + ... + + class EnableNoBBAnimation(Op): + ... + + class DisableNoBBAnimation(Op): + ... + + class EnableNoEmotionWarning(Op): + ... + + class DisableNoEmotionWarning(Op): + ... + + class IsAlive(Op): + ... + + class EnableGGFactor(Op): + ... + + class DisableGGFactor(Op): + ... + + class EnableExerciseGodMod(Op): + ... + + class DisableExerciseGodMod(Op): + ... + + class EnableExerciseMorePower(Op): + ... + + class DisableExerciseMorePower(Op): + ... + + class EnableFastWave(Op): + ... + + class DisableFastWave(Op): + ... + + class EnableMonsterKillSelf(Op): + ... + + class DisableMonsterKillSelf(Op): + ... + + class EnableSkipBattleCelebrate(Op): + ... + + class DisableSkipBattleCelebrate(Op): + ...