auto_build_launcher/scripts/project_proguard.py

337 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import binascii
import hashlib
import json
import os
import re
import xml.etree.ElementTree as ET
from pathlib import Path
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from scripts.context import Context
from scripts.task import Task
from utils import FileUtils
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
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
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)
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 = ""
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
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
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
def execute(self):
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))
self.write_proguard()
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")
self.context.proguard_dict = {
k: v for k, v in sorted(
self.context.proguard_dict.items(),
key=lambda item: len(item[0]),
reverse=True
)
}
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