auto_build_launcher/scripts/project_proguard.py

337 lines
12 KiB
Python
Raw Normal View History

2025-10-10 12:53:00 +00:00
import binascii
2025-07-08 00:58:35 +00:00
import hashlib
2025-10-10 12:53:00 +00:00
import json
2025-07-08 00:58:35 +00:00
import os
2025-07-10 16:10:25 +00:00
import re
2025-10-10 12:53:00 +00:00
import xml.etree.ElementTree as ET
from pathlib import Path
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
2025-07-08 00:58:35 +00:00
from scripts.context import Context
from scripts.task import Task
from utils import FileUtils
2025-10-10 12:53:00 +00:00
from utils.logger_utils import app_logger
def encrypt_content(key: str, content: str) -> str:
"""
加密内容与Kotlin版本算法保持一致
使用AES/ECB/PKCS5Padding模式MD5生成密钥
"""
if not key:
raise ValueError("Key cannot be null or empty.")
if not content:
raise ValueError("Content cannot be null or empty.")
# 生成密钥的MD5哈希
key_bytes = generate_key_hash(key)
# 将内容转换为UTF-8字节
content_bytes = content.encode('utf-8')
# 初始化AES加密器使用ECB模式和PKCS5填充
cipher = AES.new(key_bytes, AES.MODE_ECB)
# 对内容进行填充并加密
padded_content = pad(content_bytes, AES.block_size)
encrypted_bytes = cipher.encrypt(padded_content)
# 将加密后的字节转换为小写十六进制字符串
return binascii.hexlify(encrypted_bytes).decode().lower()
def generate_key_hash(key: str) -> bytes:
"""生成密钥的MD5哈希返回16字节的密钥"""
md5 = hashlib.md5()
md5.update(key.encode('utf-8'))
return md5.digest()
def simple_encrypt(text, key):
return encrypt_content(key, text)
def encrypt_xml_resources(file_path, backup=True, key=""):
"""读取XML资源文件加密所有string内容然后写回"""
try:
# 创建备份文件
if backup and os.path.exists(file_path):
backup_path = f"{file_path}.bak"
with open(file_path, 'r', encoding='utf-8') as f_in, \
open(backup_path, 'w', encoding='utf-8') as f_out:
f_out.write(f_in.read())
print(f"已创建备份文件: {backup_path}")
# 解析XML文件
tree = ET.parse(file_path)
root = tree.getroot()
# 遍历所有string元素并加密内容
for string_elem in root.findall('string'):
original_text = string_elem.text.replace("\\'", "'")
if original_text is not None:
# 加密文本
encrypted_text = simple_encrypt(original_text, key)
# 更新元素内容
string_elem.text = encrypted_text
print(f"加密: {original_text} -> {encrypted_text}")
# 写回加密后的内容
tree.write(file_path, encoding='utf-8', xml_declaration=True)
print(f"加密完成,已更新文件: {file_path}")
except Exception as e:
print(f"处理文件时出错: {str(e)}")
def get_all_string_names(xml_file):
# 解析XML文件
tree = ET.parse(xml_file)
root = tree.getroot()
# 存储所有name属性值
string_names = []
# 遍历所有string标签
for string_elem in root.findall('string'):
# 获取name属性值
name = string_elem.get('name')
if name:
string_names.append(name)
return string_names
def get_all_style_names(xml_file):
# 解析XML文件
tree = ET.parse(xml_file)
root = tree.getroot()
# 存储所有name属性值
string_names = []
# 遍历所有string标签
for string_elem in root.findall('style'):
# 获取name属性值
name = string_elem.get('name')
if name:
string_names.append(name)
return string_names
2025-07-08 00:58:35 +00:00
2025-07-10 16:10:25 +00:00
def extract_launcher_ids(xml_content):
"""
修复版提取所有以 "launcher_" 开头的 android:id
"""
pattern = r'android:id\s*=\s*"@\+id/(launcher_\w+)"'
matches = re.findall(pattern, xml_content)
return matches
2025-07-08 00:58:35 +00:00
def string_to_md5(text):
# 将字符串编码为UTF-8字节
text_bytes = text.encode('utf-8')
# 创建MD5哈希对象并更新字节数据
md5_hash = hashlib.md5(text_bytes)
# 返回十六进制哈希字符串
return md5_hash.hexdigest()
def generate_encryption_key(key: str, s_len: int = -1, target_package_name: str = "") -> str:
# if game_editor == "Cocos":
# return key
handle_key = target_package_name + key + target_package_name
processed_key = string_to_md5(handle_key)
while processed_key[0].isdigit():
processed_key = processed_key[1:] # 移除首字符
# 计算目标长度
if s_len > 0:
target_length = s_len
else:
target_length = len(key)
# 取前N位根据原始key长度不足则全部保留
# 如果处理后的key为空极小概率则返回空字符串
return processed_key[:target_length] if processed_key else ""
class ProjectProguard(Task):
def __init__(self, context: Context):
super().__init__(context)
2025-10-10 12:53:00 +00:00
self.root = self.context.temp_project_path
self.module_path = os.path.join(self.root, "launcher-game")
self.code_path = os.path.join(self.module_path, "src")
self.res_path = os.path.join(self.module_path, "res")
self.drawable_path = os.path.join(self.res_path, "drawable")
self.drawable_xxhdpi_path = os.path.join(self.res_path, "drawable-xxhdpi")
self.layout_path = os.path.join(self.res_path, "layout")
self.string_path = ""
self.launcher_xml = ""
self.launcher_java = ""
self.style_path = ""
2025-07-08 00:58:35 +00:00
def add_proguard_key(self, key: str) -> str:
value = self.context.proguard_dict.get(key)
if value:
return value
value = generate_encryption_key(key, target_package_name=self.context.package_name)
self.context.proguard_dict[key] = value
return value
2025-07-10 16:10:25 +00:00
def write_proguard(self):
file_path = os.path.join(self.context.temp_project_path, "proguard.pro")
lines = open(file_path, "r", encoding="UTF-8").readlines()
lines.append(f"""
-repackageclasses '{self.context.package_name}' # 将所有类移动到 '{self.context.package_name}' 包下
-flattenpackagehierarchy '{self.context.package_name}' # 扁平化包结构
""")
open(file_path, "w", encoding="UTF-8").writelines(lines)
pass
2025-10-10 12:53:00 +00:00
def proguard_code(self):
code_package_name_list = ["com.game.launcher.activity", "com.game.launcher.view", "com.game.launcher.service"]
for code_package in code_package_name_list:
code_dir = os.path.join(self.code_path, code_package.replace(".", os.sep))
files = os.listdir(code_dir)
self.context.proguard_dict[code_package] = self.context.package_name
for file in files:
file_path = Path(file)
file_name = file_path.stem
file_ext = file_path.suffix
target_class_name = self.add_proguard_key(file_name)
target_file_path = os.path.join(self.code_path, self.context.package_name.replace(".", os.sep),
target_class_name + file_ext)
o = os.path.join(code_dir, file)
self.context.proguard_dict[
code_package + "." + file_name] = self.context.package_name + "." + target_class_name
FileUtils.move(o, target_file_path)
pass
pass
def proguard_pag(self):
pag_file = os.path.join(self.module_path, "assets/pag_gl_slide.pag".replace("/", os.sep))
target_pag = self.add_proguard_key("pag_gl_slide")
FileUtils.move(pag_file, pag_file.replace("pag_gl_slide", target_pag))
pass
def proguard_file_name(self, dir_path: str):
for file in os.listdir(dir_path):
path = Path(file)
file_name = path.stem
target_file_name = self.add_proguard_key(file_name)
abspath = os.path.join(dir_path, file)
FileUtils.move(abspath, abspath.replace(file_name, target_file_name))
pass
def analysis_id_dir(self, dir_path: str):
for file in os.listdir(dir_path):
file = os.path.join(dir_path, file)
self.analysis_id_file(file)
pass
def analysis_id_file(self, path: str):
ids = extract_launcher_ids(open(path, "r", encoding="UTF-8").read())
for id in ids:
self.add_proguard_key(id)
pass
def analysis_string_name(self, file_path: str):
names = get_all_string_names(file_path)
for name in names:
self.add_proguard_key(name)
pass
def analysis_style_name(self, file_path: str):
names = get_all_style_names(file_path)
for name in names:
self.add_proguard_key(name)
pass
def update_proguard_file(self, file_path: str):
if not (file_path.endswith(".java") or file_path.endswith(".xml") or file_path.endswith(".kt")):
return
text = open(file_path, "r", encoding="UTF-8").read()
for key, value in self.context.proguard_dict.items():
text = text.replace(key, value)
open(file_path, "w", encoding="UTF-8").writelines(text)
def update_proguard_dir(self, dir_path: str):
for root, dirs, files in os.walk(dir_path):
for file in files:
file_path = os.path.join(root, file)
self.update_proguard_file(file_path)
pass
2025-07-08 00:58:35 +00:00
def execute(self):
2025-10-10 12:53:00 +00:00
self.root = self.context.temp_project_path
self.module_path = os.path.join(self.root, "launcher-game")
self.code_path = os.path.join(self.module_path, "src")
self.res_path = os.path.join(self.module_path, "res")
self.drawable_path = os.path.join(self.res_path, "drawable")
self.drawable_xxhdpi_path = os.path.join(self.res_path, "drawable-xxhdpi")
self.layout_path = os.path.join(self.res_path, "layout")
self.string_path = os.path.join(self.res_path, "values", "strings.xml")
self.style_path = os.path.join(self.res_path, "values", "styles.xml")
self.launcher_xml = os.path.join(self.root, "res/layout/launcher.xml".replace("/", os.sep))
self.launcher_java = os.path.join(self.root, "src/com/android/launcher3/Launcher.java".replace("/", os.sep))
2025-07-10 16:10:25 +00:00
self.write_proguard()
2025-10-10 12:53:00 +00:00
self.proguard_code()
self.proguard_pag()
self.proguard_file_name(self.drawable_path)
self.proguard_file_name(self.drawable_xxhdpi_path)
self.proguard_file_name(self.layout_path)
self.analysis_id_file(self.launcher_xml)
self.analysis_id_dir(self.layout_path)
self.analysis_string_name(self.string_path)
self.analysis_style_name(self.style_path)
self.add_proguard_key("gltaskaffinity")
2025-07-08 00:58:35 +00:00
self.context.proguard_dict = {
k: v for k, v in sorted(
self.context.proguard_dict.items(),
key=lambda item: len(item[0]),
reverse=True
)
}
2025-07-10 16:10:25 +00:00
2025-10-10 12:53:00 +00:00
self.update_proguard_file(os.path.join(self.context.temp_project_path,
"src/com/android/launcher3/views/OptionsPopupView.java".replace("/",
os.sep)))
self.update_proguard_file(self.launcher_xml)
self.update_proguard_file(self.launcher_java)
self.update_proguard_file(self.string_path)
self.update_proguard_dir(self.layout_path)
self.update_proguard_dir(self.drawable_xxhdpi_path)
self.update_proguard_dir(self.drawable_path)
self.update_proguard_dir(self.module_path)
self.update_proguard_dir(os.path.join(self.context.temp_project_path, "lawnchair/res/xml"))
encrypt_xml_resources(self.string_path, False, string_to_md5(self.context.package_name).upper())
app_logger().info(json.dumps(self.context.proguard_dict, indent=4))
# if __name__ == '__main__':
# encrypt_xml_resources(
# "/Users/luojian/Documents/project/zicp/Lawnchair_DollMasterLauncher/launcher-game/res/values/strings.xml")
# # key = string_to_md5("com.drop.meme.merge.game.fsaew.puzzle").upper()
# # print(key)
# # result = encrypt_content(key, "hello World")
# # print(result)
# pass