├── .github
└── FUNDING.yml
├── .gitignore
├── LICENSE
├── MANIFEST.in
├── README.md
├── changelog.md
├── pyproject.toml
├── setup.py
└── src
└── BCGM_Python
├── __init__.py
├── __main__.py
├── encrypt_decrypt
├── __init__.py
├── decrypt_pack.py
└── encrypt_pack.py
├── feature_handler.py
├── file_mods
├── __init__.py
├── enemy_mod.py
├── stage_mod.py
└── unit_mod.py
├── files
└── version.txt
└── helper.py
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: fieryhenry
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | *.egg*
3 | *__pychache__*
4 | *.pyc*
5 | /game_files
6 | /decrypted_lists
7 | /encrypted_files
8 | *.c
9 | *.so
10 | /.vscode
11 | .venv
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) [2022] [fieryhenry]
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include src/BCGM_Python/files *
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Battle Cats Game Modding Tool
2 |
3 | A python game modding tool for the mobile game The Battle Cats that can decrypt,
4 | encrypt, and modify some game files.
5 |
6 | Note: I am no longer actively working on this tool so there may be bugs.
7 |
8 | If you know how to program and want to do more powerful things with the game
9 | then you should use [TBCML](https://github.com/fieryhenry/tbcml) instead.
10 |
11 | Join the [discord server](https://discord.gg/DvmMgvn5ZB) if you want to suggest
12 | new features, report bugs or get help on how to use the modder. (Discord is the
13 | same one as for save editing as I haven't made a modding specific one yet)
14 |
15 | PyPi:
16 |
17 | If you want to support me then consider gifting me some ko-fi here:
18 |
19 |
20 | ## Thanks to
21 |
22 | - EasyMoneko for the original keys for decrypting/encrypting:
23 |
24 |
25 | - Battle Cats Ultimate for what some of the numbers mean in various csvs.
26 |
27 |
28 | - This resource for unit csvs:
29 |
30 | - Vi on discord for enemy csvs
31 |
32 | ## How to use
33 |
34 | 1. Install python (If you haven't already)
35 |
36 | 1. Enter the command: `py -m pip install -U battle-cats-game-modder` into cmd or
37 | another terminal to install the editor. If that doesn't work then use
38 | `python` instead of `py` in the command
39 |
40 | 1. Download the apk you want to edit from somewhere like
41 | [uptodown](https://the-battle-cats.en.uptodown.com/android/download) or
42 | [apkmirror](https://www.apkmirror.com/apk/ponos/the-battle-cats)
43 |
44 | 1. Unpack the apk file for the game using
45 | [Apktool](https://ibotpeaches.github.io/Apktool/) or
46 | [APKToolGui](https://github.com/AndnixSH/APKToolGUI).
47 |
48 | 1. You can then find the .pack and .list files in the assets folder of the
49 | extracted APK.
50 |
51 | 1. Get the .pack and .list files that contain the files you want to edit:
52 |
53 | - Most stats are in DataLocal
54 |
55 | - Most text is in resLocal
56 |
57 | - Sprites are in various Server files
58 |
59 | 1. Then enter the command: `py -m BCGM_Python` to run the tool. If that doesn't
60 | work then use `python` instead of `py` in the command
61 |
62 | 1. Select option to decrypt .pack files
63 |
64 | 1. Select .pack files that you want, they will be in the `assets` folder in the
65 | apk for local files, or `/data/data/jp.co.ponos.battlecatsen/files` on your
66 | device (if rooted) for downloaded server files
67 |
68 | 1. Also decrypt the DownloadLocal pack as you will need it for later
69 |
70 | 1. Once completed the files will be in a `game_files` folder in the folder you
71 | ran the command from
72 |
73 | 1. You can manually edit the data, or use the option in the tool that you want
74 |
75 | 1. Once edited, you should place any modified files in the DownloadLocal pack
76 | folder instead of the original pack folder. This is because the game does not
77 | check if DownloadLocal has been modified, but it does check if the original
78 | pack has been modified. The game also prioritises DownloadLocal over the
79 | original pack, so if you have a file in both, the game will use the one in
80 | DownloadLocal.
81 |
82 | 1. Open the tool again and select the `encrypt` option
83 |
84 | 1. Select the DownloadLocal folder
85 |
86 | 1. Once complete the encrypted .pack and .list files will be in
87 | an`encrypted_files` folder in the folder you ran the command from
88 |
89 | 1. If you are asked if you want to patch the libnative file, say no as this
90 | feature is broken and you do not need to do it if you placed your
91 | files in DownloadLocal
92 |
93 | 1. Then you need to place the encrypted .pack and .list files back into the
94 | assets folder of the apk
95 |
96 | 1. You then need to pack the apk using apktool or apktoolgui
97 |
98 | 1. You then need to sign the apk using apktool or apktoolgui
99 |
100 | 1. You then need to install the apk, you may have to uninstall the game first
101 | before installing the modified apk for the first time
102 |
103 | 1. Open the game and see if it works
104 |
105 | ## Install from source
106 |
107 | 1. Install python
108 |
109 | 1. Install git
110 |
111 | 1. Enter the command: `git clone https://github.com/fieryhenry/BCGM-Python.git`
112 |
113 | 1. Enter the command: `cd BCGM-Python`
114 |
115 | 1. Enter the command: `py -m pip install -e .` to install the tool. If that
116 | doesn't work then use `python` instead of `py` in the command
117 |
118 | 1. Enter the command: `py -m BCGM_Python` to run the tool. If that doesn't work
119 | then use `python` instead of `py` in the command
120 |
121 | 1. If you want to update the tool then enter the command: `git pull` in the
122 | `BCGM-Python` folder
123 |
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [1.0.6] - 2024-03-20
4 |
5 | #### Added
6 |
7 | - Ways to manually select files/folders without tkinter if it's not installed
8 |
9 | #### Fixed
10 |
11 | - Not removing padding from the end of the file after decrypting
12 |
13 | - Modifying files in order to add padding before encryption, this is now done in
14 | memory. This should fix any issues with incorrect file permissions (e.g
15 | desktop.ini)
16 |
17 | - Display error message if no files are in the folder that you are trying to
18 | encrypt
19 |
20 | #### Removed
21 |
22 | - Broken patch libnative option
23 |
24 | ## [1.0.5] - 2023-07-08
25 |
26 | #### Fixed
27 |
28 | - Changed colored to version 1.4.4 because newer versions of colored don't work with the tool
29 |
30 | ## [1.0.4] - 2022-05-24
31 |
32 | #### Fixed
33 |
34 | - Some adb issues with shell=True thanks to [!j0](https://github.com/j0912345)
35 |
36 | ## [1.0.3] - 2022-05-20
37 |
38 | #### Changed
39 |
40 | - Removed wxPython, and for stage mod the tool now uses tk, the reason for why i used it was because in tkinter the file filters automatically have a `*` before them.
41 |
42 | #### Fixed
43 |
44 | - Stage mod crashing
45 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools>=42"]
3 | build-backend = "setuptools.build_meta"
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import setuptools
2 |
3 | with open("README.md", "r", encoding="utf-8") as fh:
4 | long_description = fh.read()
5 |
6 | with open("src/BCGM_Python/files/version.txt", "r", encoding="utf-8") as fh:
7 | version = fh.read()
8 |
9 | setuptools.setup(
10 | name="battle-cats-game-modder",
11 | version=version,
12 | author="fieryhenry",
13 | description="A battle cats tool for modifying, encrypting, and decrypting game files",
14 | long_description=long_description,
15 | long_description_content_type="text/markdown",
16 | url="https://github.com/fieryhenry/BCGM-Python",
17 | classifiers=[
18 | "Programming Language :: Python :: 3",
19 | "License :: OSI Approved :: MIT License",
20 | "Operating System :: OS Independent",
21 | ],
22 | package_dir={"" : "src"},
23 | packages=setuptools.find_packages(where="src"),
24 | python_requires=">=3.6",
25 | install_requires=[
26 | "colored==1.4.4", "tk", "alive-progress", "pycryptodomex", "requests"
27 | ],
28 | include_package_data=True
29 | )
--------------------------------------------------------------------------------
/src/BCGM_Python/__init__.py:
--------------------------------------------------------------------------------
1 | from BCGM_Python import feature_handler, helper, file_mods, encrypt_decrypt
2 |
3 | root = None
4 | tk = None
5 | fd = None
6 |
7 | try:
8 | import tkinter as tk
9 | from tkinter import filedialog as fd
10 |
11 | root = tk.Tk()
12 | root.withdraw()
13 | except Exception:
14 | print(
15 | "Failed to initialize tkinter. File / folder selection will not be gui based."
16 | )
17 |
--------------------------------------------------------------------------------
/src/BCGM_Python/__main__.py:
--------------------------------------------------------------------------------
1 | from BCGM_Python import feature_handler, helper
2 |
3 |
4 | def main():
5 | helper.check_update()
6 | while True:
7 | feature_handler.menu()
8 | print()
9 |
10 |
11 | try:
12 | main()
13 | except KeyboardInterrupt:
14 | exit()
15 |
--------------------------------------------------------------------------------
/src/BCGM_Python/encrypt_decrypt/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fieryhenry/BCGM-Python/e401cfbd1a57417afc3bdc37de86fb60752e415c/src/BCGM_Python/encrypt_decrypt/__init__.py
--------------------------------------------------------------------------------
/src/BCGM_Python/encrypt_decrypt/decrypt_pack.py:
--------------------------------------------------------------------------------
1 | from BCGM_Python import helper
2 | import os
3 | from Cryptodome.Cipher import AES
4 | from alive_progress import alive_bar
5 |
6 | output_path = "game_files"
7 | lists_paths = "decrypted_lists"
8 |
9 |
10 | def unpack_list(ls_file):
11 | data = helper.open_file_b(ls_file)
12 | key = helper.md5_str("pack")
13 | cipher = AES.new(key, AES.MODE_ECB)
14 | decrypted_data = cipher.decrypt(data)
15 | decrypted_data = helper.remove_pkcs7_padding(data=decrypted_data)
16 | return decrypted_data
17 |
18 |
19 | def decrypt_pack(chunk_data, jp, pk_name):
20 | aes_mode = AES.MODE_CBC
21 | if jp:
22 | key = bytes.fromhex("d754868de89d717fa9e7b06da45ae9e3")
23 | iv = bytes.fromhex("40b2131a9f388ad4e5002a98118f6128")
24 | else:
25 | key = bytes.fromhex("0ad39e4aeaf55aa717feb1825edef521")
26 | iv = bytes.fromhex("d1d7e708091941d90cdf8aa5f30bb0c2")
27 |
28 | if "server" in pk_name.lower():
29 | key = helper.md5_str("battlecats")
30 | iv = None
31 | aes_mode = AES.MODE_ECB
32 | if iv:
33 | cipher = AES.new(key, aes_mode, iv)
34 | else:
35 | cipher = AES.new(key, aes_mode)
36 | decrypted_data = cipher.decrypt(chunk_data)
37 | decrypted_data = helper.remove_pkcs7_padding(data=decrypted_data)
38 | return decrypted_data
39 |
40 |
41 | def unpack_pack(pk_file_path, ls_data, jp, base_path):
42 | list_data = ls_data.decode("utf-8")
43 | split_data = helper.parse_csv_file(None, list_data.split("\n"), 3)
44 |
45 | pack_data = helper.open_file_b(pk_file_path)
46 |
47 | with alive_bar(len(split_data)) as bar:
48 | for i in range(len(split_data)):
49 | file = split_data[i]
50 |
51 | name = file[0]
52 | start_offset = int(file[1])
53 | length = int(file[2])
54 |
55 | pk_chunk = pack_data[start_offset : start_offset + length]
56 | base_name = os.path.basename(pk_file_path)
57 | if "imagedatalocal" in base_name.lower():
58 | pk_chunk_decrypted = pk_chunk
59 | else:
60 | pk_chunk_decrypted = decrypt_pack(pk_chunk, jp, base_name)
61 | helper.write_file_b(os.path.join(base_path, name), pk_chunk_decrypted)
62 | bar()
63 |
64 |
65 | def decrypt():
66 | jp = helper.coloured_text(
67 | "Are you using game version &jp& 10.8 and up? (y/n):", is_input=True
68 | )
69 | jp = helper.validate_bool(jp)
70 |
71 | pack_paths = helper.select_files(
72 | "Select .pack files", [(".pack files", "*.pack")], False
73 | )
74 | if not pack_paths:
75 | helper.coloured_text("Please select .pack files", base=helper.red)
76 | return
77 | helper.check_and_create_dir(output_path)
78 |
79 | file_groups = find_lists(pack_paths)
80 |
81 | for i in range(len(file_groups)):
82 | file_group = file_groups[i]
83 |
84 | ls_base_name = os.path.basename(file_group["list"])
85 | pk_base_name = os.path.basename(file_group["pack"])
86 |
87 | name = pk_base_name.rstrip(".pack")
88 | path = os.path.join(output_path, name)
89 |
90 | helper.check_and_create_dir(path)
91 | helper.check_and_create_dir(lists_paths)
92 |
93 | ls_data = unpack_list(file_group["list"])
94 | helper.write_file_b(os.path.join(lists_paths, ls_base_name), ls_data)
95 |
96 | helper.coloured_text(
97 | f"\n&{i+1}&\t\t{name}\t{i+1} / {len(file_groups)}",
98 | base=helper.green,
99 | new=helper.white,
100 | )
101 | unpack_pack(file_group["pack"], ls_data, jp, path)
102 | helper.coloured_text(
103 | f"\nSuccessfully decrypted all .pack files to: &{output_path}&",
104 | base=helper.green,
105 | new=helper.white,
106 | )
107 |
108 |
109 | def find_lists(pack_paths):
110 | files = []
111 | for pack_path in pack_paths:
112 | directory = os.path.dirname(pack_path)
113 | ls_path = os.path.join(directory, pack_path.rstrip(".pack") + ".list")
114 | group = {"pack": pack_path, "list": ls_path}
115 | files.append(group)
116 | return files
117 |
--------------------------------------------------------------------------------
/src/BCGM_Python/encrypt_decrypt/encrypt_pack.py:
--------------------------------------------------------------------------------
1 | from alive_progress import alive_bar
2 | from BCGM_Python import helper
3 | import os
4 | import glob
5 | from Cryptodome.Cipher import AES
6 |
7 | initial_dir = "game_files"
8 | output_path = "encrypted_files"
9 |
10 |
11 | def create_list(game_files_dir, pkname):
12 | # return open("decrypted_lists/DataLocal.list", "r").read()
13 | list_of_files = glob.glob(game_files_dir + "/*")
14 | files_with_size = [
15 | (file_path, os.stat(file_path).st_size)
16 | for file_path in list_of_files
17 | if os.path.isfile(file_path)
18 | ]
19 | files = files_with_size
20 | list_file = f"{len(files_with_size)}\n"
21 | address = 0
22 | for i in range(len(files_with_size)):
23 | file = files_with_size[i]
24 | if "imagedatalocal" not in pkname.lower():
25 | data = helper.open_file_b(file[0])
26 | data = helper.add_pkcs7_padding(data)
27 | file = (file[0], len(data))
28 | files[i] = file
29 |
30 | list_file += f"{os.path.basename(file[0])},{address},{file[1]}\n"
31 | address += file[1]
32 | if address == 0:
33 | helper.coloured_text(
34 | "No files found in the directory, please check the directory and try again",
35 | base=helper.red,
36 | )
37 | return None
38 | return list_file
39 |
40 |
41 | def create_pack(game_files_dir, ls_data, jp, pk_name):
42 | split_data = helper.parse_csv_file(None, ls_data.split("\n"), 3)
43 | pack_data = [0] * (int(split_data[-1][1]) + int(split_data[-1][2]))
44 | with alive_bar(len(split_data)) as bar:
45 | for i in range(len(split_data)):
46 | file = split_data[i]
47 |
48 | name = file[0]
49 | start_offset = int(file[1])
50 |
51 | file_data = helper.open_file_b(os.path.join(game_files_dir, name))
52 | if "imagedatalocal" in pk_name.lower():
53 | encrypted_data = file_data
54 | else:
55 | encrypted_data = encrypt_file(file_data, jp, pk_name)
56 | encrypted_data = list(encrypted_data)
57 | pack_data = helper.insert_list(pack_data, encrypted_data, start_offset)
58 | bar()
59 | return pack_data
60 |
61 |
62 | def encrypt_file(file_data, jp, pk_name):
63 | file_data = helper.add_pkcs7_padding(file_data)
64 | aes_mode = AES.MODE_CBC
65 | if jp:
66 | key = bytes.fromhex("d754868de89d717fa9e7b06da45ae9e3")
67 | iv = bytes.fromhex("40b2131a9f388ad4e5002a98118f6128")
68 | else:
69 | key = bytes.fromhex("0ad39e4aeaf55aa717feb1825edef521")
70 | iv = bytes.fromhex("d1d7e708091941d90cdf8aa5f30bb0c2")
71 |
72 | if "server" in pk_name.lower():
73 | key = helper.md5_str("battlecats")
74 | iv = None
75 | aes_mode = AES.MODE_ECB
76 | if iv:
77 | cipher = AES.new(key, aes_mode, iv)
78 | else:
79 | cipher = AES.new(key, aes_mode)
80 | encrypted_data = cipher.encrypt(file_data)
81 | return encrypted_data
82 |
83 |
84 | def encrypt_list(list_data):
85 | list_data = helper.add_pkcs7_padding(list_data)
86 | key = helper.md5_str("pack")
87 | cipher = AES.new(key, AES.MODE_ECB)
88 | encrypted_data = cipher.encrypt(list_data)
89 | return encrypted_data
90 |
91 |
92 | def encrypt():
93 | jp = helper.coloured_text(
94 | "Are you using game version &jp& 10.8 and up? (y/n):", is_input=True
95 | )
96 | jp = helper.validate_bool(jp)
97 |
98 | name = input(
99 | "Enter the name of the file to be outputed e.g DataLocal, ImageDataLocal etc:"
100 | )
101 |
102 | game_files_dir = helper.select_dir("Select a folder of game files", initial_dir)
103 | if not game_files_dir:
104 | helper.coloured_text("Please select a folder of game files", base=helper.red)
105 | return
106 |
107 | helper.check_and_create_dir(output_path)
108 | list_data = create_list(game_files_dir, name)
109 | if list_data is None:
110 | return
111 |
112 | encrypted_data_list = encrypt_list(list_data.encode("utf-8"))
113 | ls_output = os.path.join(output_path, name + ".list")
114 | helper.write_file_b(ls_output, encrypted_data_list)
115 |
116 | pack_data = create_pack(game_files_dir, list_data, jp, name)
117 | pk_output = os.path.join(output_path, name + ".pack")
118 | helper.write_file_b(pk_output, bytes(pack_data))
119 | print(f"Successfully created .pack and .list files to: {output_path}")
120 |
--------------------------------------------------------------------------------
/src/BCGM_Python/feature_handler.py:
--------------------------------------------------------------------------------
1 | from BCGM_Python import helper
2 | from BCGM_Python.encrypt_decrypt import decrypt_pack, encrypt_pack
3 | from BCGM_Python.file_mods import unit_mod, enemy_mod, stage_mod
4 |
5 | features = {
6 | "Pack File Encrypting / Decrypting": {
7 | "Decrypt .pack files": decrypt_pack.decrypt,
8 | "Encrypt game files to .pack file": encrypt_pack.encrypt,
9 | },
10 | "Game File Modding": {
11 | "Edit unit*.csv files (cat stats)": unit_mod.edit_unit,
12 | "Edit t_unit.csv file (enemy stats)": enemy_mod.edit_enemy,
13 | "Edit stage*.csv files (stage data)": stage_mod.edit_stage,
14 | },
15 | }
16 |
17 |
18 | def display_features():
19 | helper.create_list(list(features))
20 |
21 |
22 | def search_dict(dictionary, item, results=[]):
23 | for k, v in dictionary.items():
24 | if type(v) == dict:
25 | search_dict(v, item, results)
26 | else:
27 | if item.lower() in k.lower().replace(" ", ""):
28 | results.append({"Name": k, "Function": v})
29 | return results
30 |
31 |
32 | def show_options(user_input, feature_dict):
33 | result_input = helper.validate_int(user_input)
34 | to_search = user_input
35 |
36 | if result_input != None:
37 | if result_input > len(list(feature_dict)):
38 | print(f"Please enter a number between 1 and {len(list(feature_dict))}")
39 | return
40 | name = list(feature_dict)[result_input - 1]
41 | result_data = feature_dict[name]
42 | results = []
43 | if type(result_data) != dict:
44 | return result_data()
45 | for result in result_data:
46 | results.append({"Name": result, "Function": feature_dict[name][result]})
47 | else:
48 | to_search = to_search.replace(" ", "")
49 | results = search_dict(feature_dict, to_search, [])
50 | if len(results) == 1:
51 | return results[0]["Function"]()
52 | else:
53 | options = []
54 | for i in range(len(results)):
55 | options.append(results[i]["Name"])
56 | if not options:
57 | print(f"Error a feature with name: {user_input} doesn't exist")
58 | return
59 | helper.create_list(options)
60 | user_input = input("Enter an option:\n")
61 | index = helper.validate_int(user_input)
62 | if index != None:
63 | if index > len(results):
64 | print(f"Please enter a number between 1 and {len(results)}")
65 | return
66 | if type(results[index - 1]["Function"]) == dict:
67 | return show_options(user_input, feature_dict[name])
68 | return results[index - 1]["Function"]()
69 |
70 |
71 | def menu():
72 | display_features()
73 | user_input = input(
74 | "What do you want to do (some options contain other features within them)\nYou can enter a number to run a feature or a word to search for that feature (e.g entering decrypt will run the Decrypt .pack files feature, and entering csv will show you all the features that edit csv files)\nYou can press enter to see all of the features:\n"
75 | )
76 | show_options(user_input, features)
77 |
--------------------------------------------------------------------------------
/src/BCGM_Python/file_mods/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fieryhenry/BCGM-Python/e401cfbd1a57417afc3bdc37de86fb60752e415c/src/BCGM_Python/file_mods/__init__.py
--------------------------------------------------------------------------------
/src/BCGM_Python/file_mods/enemy_mod.py:
--------------------------------------------------------------------------------
1 | from BCGM_Python import helper
2 | from BCGM_Python.file_mods import unit_mod
3 |
4 | values = [
5 | "HP",
6 | "Knockback amount",
7 | "Movement Speed",
8 | "Attack Power",
9 | "Time between attacks",
10 | "Attack range",
11 | "Money Drop",
12 | "?",
13 | "Width",
14 | "?",
15 | "Red flag",
16 | "Area attack flag",
17 | "Foreswing",
18 | "Floating flag",
19 | "Black flag",
20 | "Metal Flag",
21 | "Traitless Flag",
22 | "Angel Flag",
23 | "Alien Flag",
24 | "Zombie Flag",
25 | "Knock back chance",
26 | "Freeze chance",
27 | "Freeze duration",
28 | "Slow Chance",
29 | "Slow Duration",
30 | "Crit Chance",
31 | "Base Destroyer Flag",
32 | "Wave Chance",
33 | "Wave Level",
34 | "Weaken Chance",
35 | "Weaken Duration",
36 | "Weaken %",
37 | "Strengthen Activation health",
38 | "Strengthen Multiplier",
39 | "Survive Lethal Chance",
40 | "Long distance start",
41 | "Long distance range",
42 | "Wave Immunity",
43 | "?",
44 | "Knockback immunity",
45 | "Freeze immunity",
46 | "Slow immunity",
47 | "Weaken immunity",
48 | "Burrow count",
49 | "Burrow distance",
50 | "Revive count",
51 | "Revive time",
52 | "Percentage of health after revive",
53 | "Witch Flag",
54 | "Is enemy base flag",
55 | "loop?",
56 | "?",
57 | "Self-Destruct Flag (2=self-destruct)",
58 | "?",
59 | "Soul when dead",
60 | "Second attack damage",
61 | "Third attack damage",
62 | "Second attack start frame",
63 | "Third attack start frame",
64 | "Use ability on first hit flag",
65 | "Second attack flag",
66 | "Third attack flag",
67 | "?",
68 | "Some flag for gudetama",
69 | "Barrier HP",
70 | "Warp Chance",
71 | "Warp Duration",
72 | "Warp min range",
73 | "Warp max range",
74 | "Starred Alien Flag (2-4 = god)",
75 | "Some flag for doge sun and the winds",
76 | "Eva angel flag",
77 | "Relic flag",
78 | "Curse Chance",
79 | "Cuse Duration",
80 | "Savage Blow Chance",
81 | "Savage Blow Multiplier",
82 | "Invincibility Chance",
83 | "Invincibility Duration",
84 | "Toxic Chance",
85 | "Toxic Percentage health",
86 | "Surge Chance",
87 | "Surge Min range",
88 | "Surge max range",
89 | "Surge Level",
90 | "Some flag for doge sun, wind enemies and doron",
91 | "Mini wave toggle",
92 | "Shield HP",
93 | "Percentage HP healed when knockbacked",
94 | "Surge Chance when killed",
95 | "Surge min range when killed",
96 | "Surge max range when killed",
97 | "Surge Level when killed",
98 | "Aku flag",
99 | "Baron Trait Flag",
100 | ]
101 |
102 |
103 | def edit_enemy():
104 | csv_path = helper.select_files(
105 | "Select t_unit.csv file",
106 | [("Enemy stats", "t_unit.csv")],
107 | default=unit_mod.get_initial_dir(),
108 | )
109 | if not csv_path:
110 | print("Please select an enemy data file")
111 | return
112 | csv_file_data = helper.parse_csv_file(
113 | csv_path, min_length=3, black_list=["//", "\n"]
114 | )
115 |
116 | ids = helper.get_range_input(
117 | helper.coloured_text(
118 | "Enter enemy ids (Look up enemy release order battle cats to find ids)(You can enter &all& to get all, a range e.g &1&-&50&, or ids separate by spaces e.g &5 4 7&):",
119 | is_input=True,
120 | ),
121 | len(csv_file_data),
122 | )
123 |
124 | for id in ids:
125 | enemy_data = csv_file_data[id]
126 |
127 | helper.coloured_text(f"Enemy unit &{id}& is selected")
128 | enemy_data = helper.edit_array_user(
129 | values, enemy_data, "Stats", "value", extra_values=enemy_data
130 | )
131 | csv_file_data[id] = enemy_data
132 |
133 | helper.write_csv_file(csv_path, csv_file_data)
134 | print("Successfully edited csv file")
135 |
--------------------------------------------------------------------------------
/src/BCGM_Python/file_mods/stage_mod.py:
--------------------------------------------------------------------------------
1 | import os
2 | from BCGM_Python import helper
3 | from BCGM_Python.file_mods import unit_mod
4 |
5 | stage_infos = [
6 | "Stage Width",
7 | "Base health",
8 | "Minimum spawn frame",
9 | "Maximum spawn frame",
10 | "Background type",
11 | "Maximum enemies",
12 | ]
13 |
14 | enemy_infos = [
15 | "Enemy ID",
16 | "Amount to spawn in total",
17 | "First spawn frame",
18 | "Time between spawns in frames min",
19 | "Time between spawns in frames max",
20 | "Spawn when base health has reached %",
21 | "Front z-layer",
22 | "Back z-layer",
23 | "Boss flag",
24 | "Strength multiplier",
25 | ]
26 |
27 |
28 | def edit_stage():
29 | df_path = os.path.abspath(unit_mod.get_initial_dir())
30 | csv_path = helper.select_files(
31 | "Select *stage.csv files", [("stage data files", "stage*.csv")], default=df_path
32 | )
33 | if not csv_path:
34 | print("Please select a stage data file")
35 | return
36 |
37 | csv_file_data = helper.parse_csv_file(
38 | csv_path, min_length=3, black_list=["//", "\n"]
39 | )
40 |
41 | stage_id = None
42 |
43 | if len(csv_file_data[0]) < 9:
44 | stage_id = helper.ls_to_str(csv_file_data[0])
45 | csv_file_data = csv_file_data[1:]
46 |
47 | csv_file_data[-1] = csv_file_data[-1][:-1]
48 |
49 | stage_data = helper.int_ls_to_str_ls(csv_file_data[0])
50 | enemy_slot_data = []
51 |
52 | for i in range(1, len(csv_file_data)):
53 | enemy_data = helper.int_ls_to_str_ls(csv_file_data[i])
54 | enemy_slot_data.append(enemy_data)
55 |
56 | options = ["Edit basic stage data", "Edit enemy slots"]
57 | if stage_id != None:
58 | options.append("Edit stage id")
59 | helper.create_list(options)
60 | option = helper.validate_int(input("What do you want to do?:"))
61 | if option == None:
62 | print("Please enter a valid number")
63 | return
64 | if option == 1:
65 | stage_data = helper.edit_array_user(
66 | stage_infos,
67 | stage_data,
68 | "Basic Stage Stats",
69 | "value",
70 | extra_values=stage_data,
71 | all_at_once=False,
72 | )
73 | elif option == 2:
74 | enemy_ids = []
75 | for i in range(len(enemy_slot_data)):
76 | slot = enemy_slot_data[i]
77 | if slot[0] != 0:
78 | enemy_ids.append(f"Enemy id {slot[0]}")
79 |
80 | helper.coloured_text(
81 | "Enter an enemy slot id (to add new slots just enter a number larger than the highest displayed slot id):"
82 | )
83 | ids = helper.selection_list(enemy_ids, "edit", all_at_once=False)
84 | for id in ids:
85 | id = helper.validate_int(id)
86 | if id == None:
87 | print("Please enter a valid number")
88 | return
89 | if id > len(enemy_slot_data):
90 | enemy_slot_data.append([0, 0, 0, 0, 0, 0, 0, 9, 0, 100])
91 | id = len(enemy_slot_data)
92 | id -= 1
93 | enemy_infos_trimmed = enemy_infos
94 | if len(enemy_slot_data[id]) < len(enemy_infos):
95 | enemy_infos_trimmed = enemy_infos[:-1]
96 | helper.coloured_text(f"Slot: &{id+1}& is currently selected")
97 | enemy_slot_data[id] = helper.edit_array_user(
98 | enemy_infos_trimmed,
99 | enemy_slot_data[id],
100 | "Enemy Slots",
101 | "value",
102 | extra_values=enemy_slot_data[id],
103 | )
104 | elif option == 3 and stage_id != None:
105 | stage_id = helper.coloured_text(
106 | f"Current stage id: &{stage_id}&\nWhat do you want to set the stage id to?:",
107 | is_input=True,
108 | )
109 |
110 | new_csv_data = []
111 |
112 | if stage_id != None:
113 | new_csv_data.append(helper.str_to_ls(stage_id))
114 | new_csv_data.append(stage_data)
115 | new_csv_data += enemy_slot_data
116 |
117 | helper.write_csv_file(csv_path, new_csv_data)
118 | print("Successfully modified csv file")
119 |
--------------------------------------------------------------------------------
/src/BCGM_Python/file_mods/unit_mod.py:
--------------------------------------------------------------------------------
1 | from BCGM_Python import helper
2 | from BCGM_Python.encrypt_decrypt import decrypt_pack
3 | import os
4 |
5 |
6 | def get_initial_dir():
7 | test_path = os.path.join(decrypt_pack.output_path, "DataLocal")
8 | if os.path.exists(test_path):
9 | return test_path
10 | else:
11 | return decrypt_pack.output_path
12 |
13 |
14 | values = [
15 | "HP",
16 | "Knockback amount",
17 | "Movement Speed",
18 | "Attack Power",
19 | "Time between attacks",
20 | "Attack Range",
21 | "Base cost",
22 | "Recharge time",
23 | "Hit box position",
24 | "Hit box size",
25 | "Red effective flag",
26 | "Always zero",
27 | "Area attack flag",
28 | "Attack animation",
29 | "Min z layer",
30 | "Max z layer",
31 | "Floating effective flag",
32 | "Black effective flag",
33 | "Metal effective flag",
34 | "White effective flag",
35 | "Angel effective flag",
36 | "Alien effective flag",
37 | "Zombie effective flag",
38 | "Strong against flag",
39 | "Knockback chance",
40 | "Freeze chance",
41 | "Freeze duration",
42 | "Slow chance",
43 | "Slow duration",
44 | "Resistant flag",
45 | "Triple damage flag",
46 | "Critical chance",
47 | "Attack only flag",
48 | "Extra money from enemies flag",
49 | "Base destroyer flag",
50 | "Wave chance",
51 | "Wave attack level",
52 | "Weaken chance",
53 | "Weaken duration",
54 | "Weaken to (decrease attack to percentage left)",
55 | "HP remain strength",
56 | "Boost strength multiplier",
57 | "Survive chance",
58 | "If unit is metal flag",
59 | "Long range start",
60 | "Long range append",
61 | "Immune to wave flag",
62 | "Block wave flag",
63 | "Resist knockbacks flag",
64 | "Resist freeze flag",
65 | "Resist slow flag",
66 | "Resist weaken flag",
67 | "Zombie killer flag",
68 | "Witch killer flag",
69 | "Witch effective flag",
70 | "Not effected by boss wave flag",
71 | "Frames before automatically dying -1 to never die automatically",
72 | "Always -1",
73 | "Death after attack flag",
74 | "Second attack power",
75 | "Third attack power",
76 | "Second attack animation",
77 | "Third attack animation",
78 | "Use ability on first hit flag",
79 | "Second attack flag",
80 | "Third attack flag",
81 | "Spawn animation, -1, 0",
82 | "Soul animation -1, 0, 1, 2, 3, 5, 6, 7",
83 | "Unike spawn animation",
84 | "Gudetama soul animation",
85 | "Barrier break chance",
86 | "Warp Chance",
87 | "Warp Duration",
88 | "Min warp distance",
89 | "Max warp Distance",
90 | "Warp blocker flag",
91 | "Eva Angel Effective",
92 | "Eva angel killer flag",
93 | "Relic effective flag",
94 | "Immune to curse flag",
95 | "Insanely tough flag",
96 | "Insane damage flag",
97 | "Savage blow chance",
98 | "Savage blow level",
99 | "Dodge attack chance",
100 | "Dodge attack duration",
101 | "Surge attack chance",
102 | "Surge attack min range",
103 | "Surge attack max range",
104 | "Surge attack level",
105 | "Toxic immunity flag",
106 | "Surge immunity flag",
107 | "Curse chance",
108 | "Curse duration",
109 | "Unkown",
110 | "Aku shield break chance",
111 | "Aku effective flag",
112 | "Colossus Slayer",
113 | ]
114 |
115 |
116 | def edit_unit():
117 | csv_path = helper.select_files(
118 | "Select a unit*.csv file",
119 | [("Unit csv files", "unit*.csv")],
120 | default=get_initial_dir(),
121 | )
122 | if not csv_path:
123 | print("Please select a unit data file")
124 | return
125 | csv_file_data = helper.parse_csv_file(
126 | csv_path, min_length=3, black_list=["//", "\n"]
127 | )
128 |
129 | forms = ["First Form", "Second Form", "Third Form"]
130 |
131 | choices = helper.selection_list(forms, "edit")
132 | for choice in choices:
133 |
134 | form_id = helper.validate_int(choice)
135 |
136 | if not form_id or form_id > len(forms):
137 | print("Please enter a valid number")
138 | continue
139 | form_id -= 1
140 | form_data = csv_file_data[form_id]
141 | form_data = extend_length(form_data, values)
142 |
143 | helper.coloured_text(f"The cat's &{forms[form_id]}& is selected")
144 | form_data = helper.edit_array_user(
145 | values, form_data, "Stats", "value", extra_values=form_data
146 | )
147 |
148 | csv_file_data[form_id] = form_data
149 |
150 | helper.write_csv_file(csv_path, csv_file_data)
151 | print("Successfully edited csv file")
152 |
153 |
154 | def extend_length(short_ls, long_ls):
155 | extra = len(long_ls) - len(short_ls)
156 | if extra > 0:
157 | short_ls += [0] * extra
158 | # values that are required so that the unit works properly
159 | short_ls[55] = -1
160 | short_ls[57] = -1
161 | short_ls[63] = 1
162 | short_ls[66] = -1
163 | return short_ls
164 |
--------------------------------------------------------------------------------
/src/BCGM_Python/files/version.txt:
--------------------------------------------------------------------------------
1 | 1.0.6
--------------------------------------------------------------------------------
/src/BCGM_Python/helper.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import hashlib
3 | import math
4 | import requests
5 | import os
6 | from colored import fg
7 |
8 | import BCGM_Python
9 |
10 |
11 | def did_tk_fail():
12 | return BCGM_Python.root is None or BCGM_Python.tk is None or BCGM_Python.fd is None
13 |
14 |
15 | green = "#008000"
16 | dark_yellow = "#d7c32a"
17 | red = "#ff0000"
18 | black = "#000000"
19 | white = "#ffffff"
20 | cyan = "#00ffff"
21 |
22 |
23 | def ls_to_str(list):
24 | val = ""
25 | for item in list:
26 | val += item
27 | return val
28 |
29 |
30 | def str_to_ls(str):
31 | ls = []
32 | for char in str:
33 | ls.append(char)
34 | return ls
35 |
36 |
37 | def create_list(list, index=True, extra_values=None, offset=None, color=True):
38 | output = ""
39 | for i in range(len(list)):
40 | if index:
41 | output += f"{i+1}. &{list[i]}&"
42 | else:
43 | output += str(list[i])
44 | if extra_values:
45 | if offset != None:
46 | output += f" &:& {extra_values[i]+offset}"
47 | else:
48 | output += f" &:& {extra_values[i]}"
49 | output += "\n"
50 | output = output.removesuffix("\n")
51 | if not color:
52 | output = output.replace("&", "")
53 | coloured_text(output)
54 |
55 |
56 | def coloured_text(text, base="#ffffff", new=dark_yellow, chr="&", is_input=False):
57 | color_new = fg(new)
58 | color_base = fg(base)
59 | color_reset = fg("#ffffff")
60 |
61 | text_split = text.split(chr)
62 | for i in range(len(text_split)):
63 | if i % 2:
64 | print(f"{color_new}{text_split[i]}{color_base}", end="")
65 | else:
66 | print(f"{color_base}{text_split[i]}{color_base}", end="")
67 | print(color_reset, end="")
68 | if is_input:
69 | return input()
70 | else:
71 | print()
72 |
73 |
74 | def validate_bool(string, true="y"):
75 | string = string.strip(" ")
76 |
77 | if string.lower() == true:
78 | return True
79 | else:
80 | return False
81 |
82 |
83 | def get_real_path(path):
84 | base_path = os.path.dirname(os.path.realpath(__file__))
85 | path = os.path.join(base_path, path)
86 | return path
87 |
88 |
89 | def open_file_s(path):
90 | return open(path, "r", encoding="utf-8").read()
91 |
92 |
93 | def get_files_path(path):
94 | base_path = get_real_path("files/")
95 | path = os.path.join(base_path, path)
96 | return path
97 |
98 |
99 | def get_version():
100 | path = get_files_path("version.txt")
101 | version = open_file_s(path)
102 | return version
103 |
104 |
105 | def get_latest_version():
106 | package_name = "battle-cats-game-modder"
107 | r = requests.get(f"https://pypi.org/pypi/{package_name}/json")
108 | if not r.ok:
109 | coloured_text(
110 | "An error has occurred while checking for a new version", base=red
111 | )
112 | return
113 | return r.json()["info"]["version"]
114 |
115 |
116 | def check_update():
117 | installed_version = get_version()
118 | coloured_text(
119 | f"\nYou currently have version &{installed_version}& installed", new=green
120 | )
121 | latest_version = get_latest_version()
122 | if not latest_version:
123 | return
124 | coloured_text(f"The latest version available is &{latest_version}&\n", new=green)
125 | if installed_version < latest_version:
126 | coloured_text(
127 | f"&A new version is available!&\n&Please run &py -m pip install -U battle-cats-game-modder& to install it&",
128 | base=cyan,
129 | new=green,
130 | )
131 | coloured_text(
132 | f"&See the changelog here: &https://github.com/fieryhenry/BCGM-Python/blob/master/changelog.md\n",
133 | base=cyan,
134 | new=green,
135 | )
136 |
137 |
138 | def select_files(title, file_types, single=True, default=""):
139 | if single:
140 | if did_tk_fail():
141 | path = input(f"Enter the path to {title}: ")
142 | if not os.path.exists(path):
143 | print(f"The path {path} does not exist")
144 | return
145 | else:
146 | path = BCGM_Python.fd.askopenfilename(
147 | title=title, filetypes=file_types, initialdir=default
148 | )
149 | else:
150 | if did_tk_fail():
151 | paths = []
152 | print(f"Enter paths:")
153 | while True:
154 | path = input(
155 | f"Enter a path to {title} (Leave blank to finish inputing): "
156 | )
157 | if path == "":
158 | break
159 | if not os.path.exists(path):
160 | print(f"The path {path} does not exist")
161 | continue
162 | paths.append(path)
163 | path = paths
164 | else:
165 | path = list(
166 | BCGM_Python.fd.askopenfilenames(
167 | title=title, filetypes=file_types, initialdir=default
168 | )
169 | )
170 | return path
171 |
172 |
173 | def get_range_input(input, length=None, min=0):
174 | ids = []
175 | if length != None and input.lower() == "all":
176 | return range(min, length)
177 | if "-" in input:
178 | content = input.split("-")
179 | first = validate_int(content[0])
180 | second = validate_int(content[1])
181 | if first == None or second == None:
182 | print(
183 | f"Please enter 2 valid numbers when making a range : {first} | {second}"
184 | )
185 | return []
186 | ids = range(first, second + 1)
187 | else:
188 | content = input.split(" ")
189 | for id in content:
190 | item_id = validate_int(id)
191 | if item_id == None:
192 | print(f"Please enter a valid number : {id}")
193 | continue
194 | ids.append(item_id)
195 | return ids
196 |
197 |
198 | def selection_list(
199 | names,
200 | mode="Edit",
201 | index_flag=True,
202 | include_at_once=False,
203 | extra_values=None,
204 | all_at_once=True,
205 | ):
206 | create_list(names, index_flag, extra_values)
207 |
208 | total = len(names) + 1
209 | if all_at_once:
210 | ids = coloured_text(
211 | f"{total}. &All at once&\nWhat do you want to {mode} (You can enter multiple values separated by spaces to edit multiple at once):",
212 | is_input=True,
213 | ).split(" ")
214 | else:
215 | ids = coloured_text(
216 | f"What do you want to {mode} (You can enter multiple values separated by spaces to edit multiple at once):",
217 | is_input=True,
218 | ).split(" ")
219 | individual = True
220 | if str(total) in ids and all_at_once:
221 | ids = range(1, total)
222 | ids = [format(x, "02d") for x in ids]
223 | individual = False
224 | if include_at_once:
225 | return {"ids": ids, "individual": individual}
226 | return ids
227 |
228 |
229 | def int_ls_to_str_ls(ls):
230 | new_ls = []
231 | for item in ls:
232 | try:
233 | new_ls.append(int(item))
234 | except:
235 | continue
236 | return new_ls
237 |
238 |
239 | def edit_array_user(
240 | names,
241 | data,
242 | name,
243 | type_name="level",
244 | range=False,
245 | length=None,
246 | item_name=None,
247 | offset=0,
248 | extra_values=None,
249 | all_at_once=True,
250 | ):
251 | individual = True
252 | if range:
253 | ids = get_range_input(
254 | coloured_text(
255 | f"Enter {name} ids(You can enter &all& to get all, a range e.g &1&-&50&, or ids separate by spaces e.g &5 4 7&):",
256 | is_input=True,
257 | ),
258 | length,
259 | )
260 | if len(ids) > 1:
261 | individual = coloured_text(
262 | f"Do you want to set the {name} for each {item_name} individually(&1&), or all at once(&2&):",
263 | is_input=True,
264 | )
265 | else:
266 | ids = selection_list(names, "edit", True, True, extra_values, all_at_once)
267 | individual = ids["individual"]
268 | ids = ids["ids"]
269 | first = True
270 | val = None
271 | for id in ids:
272 | id = validate_int(id)
273 | if id == None:
274 | print("Please enter a number")
275 | continue
276 | id -= 1
277 | if not individual and first:
278 | val = validate_int(
279 | coloured_text(
280 | f"What {type_name} do you want to set your &{name}& to?:",
281 | is_input=True,
282 | )
283 | )
284 | if val == None:
285 | print("Please enter a valid number")
286 | continue
287 | first = False
288 | if individual:
289 | val = validate_int(
290 | coloured_text(
291 | f"What &{type_name}& do you want to set your &{names[id]}& to?:",
292 | is_input=True,
293 | )
294 | )
295 | if val == None:
296 | print("Please enter a valid number")
297 | continue
298 | data[id] = val - offset
299 | return data
300 |
301 |
302 | def select_dir(title, initial_dir):
303 | if did_tk_fail():
304 | path = input(f"Enter the path to {title}: ")
305 | if not os.path.exists(path):
306 | print(f"The path {path} does not exist")
307 | return
308 | else:
309 | path = BCGM_Python.fd.askdirectory(title=title, initialdir=initial_dir)
310 | return path
311 |
312 |
313 | def write_csv_file(path, data):
314 | final = ""
315 | for row in data:
316 | for item in row:
317 | final += f"{item},"
318 | final += "\n"
319 | write_file_b(path, final.encode("utf-8"))
320 |
321 |
322 | def find_architecture(so_path):
323 | data = open_file_b(so_path)
324 |
325 | machine = data[18]
326 | if machine == 3:
327 | architecture = "x86"
328 | elif machine == 62:
329 | architecture = "x86_64"
330 | elif machine == 40:
331 | architecture = "armeabi-v7a"
332 | elif machine == 183:
333 | architecture = "arm64-v8a"
334 |
335 | return architecture
336 |
337 |
338 | def find_app_path(game_version):
339 | package_name = f"jp.co.ponos.battlecats{game_version}"
340 | output = subprocess.run(
341 | f"adb shell ls /data/app/ | grep {package_name}",
342 | capture_output=True,
343 | shell=True,
344 | )
345 | app_path_name = (
346 | output.stdout.decode("utf-8").split("\n")[0].strip("\n").strip("\r").strip()
347 | )
348 | return app_path_name
349 |
350 |
351 | def adb_push_lib(game_version, local_path):
352 | print("Pushing libnative-lib.so to the game")
353 | if game_version == "jp":
354 | game_version = ""
355 |
356 | app_path_name = find_app_path(game_version)
357 |
358 | architecture = find_architecture(local_path)
359 | path = f"/data/app/{app_path_name}/lib/{architecture}/libnative-lib.so"
360 |
361 | subprocess.run(f'adb push "{local_path}" "{path}"', shell=True)
362 |
363 |
364 | def parse_csv_file(path, lines=None, min_length=0, black_list=None):
365 | if not lines:
366 | lines = open(path, "r", encoding="utf-8").readlines()
367 | data = []
368 | for line in lines:
369 | line_data = line.split(",")
370 | if len(line_data) < min_length:
371 | continue
372 | if black_list:
373 | line_data = filter_list(line_data, black_list)
374 |
375 | data.append(line_data)
376 | return data
377 |
378 |
379 | def filter_list(data: list, black_list: list):
380 | trimmed_data = data
381 | for i in range(len(data)):
382 | item = data[i]
383 | for banned in black_list:
384 | if banned in item:
385 | index = item.index(banned)
386 | item = item[:index]
387 | trimmed_data[i] = item
388 | return trimmed_data
389 |
390 |
391 | def md5_str(string, length=8):
392 | return (
393 | bytearray(hashlib.md5(string.encode("utf-8")).digest()[:length])
394 | .hex()
395 | .encode("utf-8")
396 | )
397 |
398 |
399 | def check_and_create_dir(path):
400 | if not os.path.exists(path):
401 | os.mkdir(path)
402 |
403 |
404 | def open_file_b(path):
405 | f = open(path, "rb").read()
406 | return f
407 |
408 |
409 | def add_pkcs7_padding(data, block_size=16):
410 | padding = block_size - len(data) % block_size
411 | data += bytes([padding] * padding)
412 | return data
413 |
414 |
415 | def remove_pkcs7_padding(data):
416 | if not data:
417 | return data
418 | padding = data[-1]
419 | return data[:-padding]
420 |
421 |
422 | def insert_list(main, list, index):
423 | for i in range(len(list)):
424 | main[index + i] = list[i]
425 | return main
426 |
427 |
428 | def write_file_b(path, data):
429 | open(path, "wb").write(data)
430 |
431 |
432 | def validate_int(string):
433 | string = string.strip(" ")
434 | if string.isdigit():
435 | return int(string)
436 | else:
437 | return None
438 |
--------------------------------------------------------------------------------