import os.path from pathlib import Path import re import javaproperties import requests from lxml import etree from scripts.context import Context from scripts.task import Task from utils import FileUtils from utils.logger_utils import app_logger def update_dependency_version(content, dependency_name, new_version): """ 更新 Gradle 依赖的版本号 :param content: Gradle 文件内容 :param dependency_name: 依赖名称(可以是完整字符串或部分匹配) :param new_version: 新版本号 :return: 更新后的内容 """ # 匹配 implementation 声明,捕获组用于保留前缀和后缀 pattern = rf"(implementation\s*\(\s*['\"]{re.escape(dependency_name)}:)([^'\"]+)(['\"]\s*\))" # 替换版本号部分 updated_content = re.sub(pattern, rf"\g<1>{new_version}\g<3>", content) return updated_content def get_latest_version(url): try: response = requests.get(url) root = etree.fromstring(response.content) latest = root.xpath("//latest/text()") or root.xpath("//version[last()]/text()") return latest[0] if latest else None except Exception as e: app_logger().error(f"Error: {e}") return None def uncomment_line(content, line_pattern): """ 取消指定行的注释 :param content: 文件内容 :param line_pattern: 要取消注释的行内容(不含前导//和空格) :return: 更新后的内容 """ # 匹配以//开头,后跟任意空格,然后是目标行内容 pattern = rf'^(\s*)//\s*({re.escape(line_pattern)}\s*)$' # 替换为去注释版本(保留原有缩进) replacement = rf'\1\2' updated_content = re.sub(pattern, replacement, content, flags=re.MULTILINE) return updated_content def update_gradle_variable(content, variable_name, new_value): """ 更新 Gradle 文件中的 final def 变量值 :param content: Gradle 文件内容 :param variable_name: 变量名 (如 "releaseName") :param new_value: 新值 (字符串或数字) :return: 更新后的内容 """ # 处理字符串值(带引号) if isinstance(new_value, str): # 匹配带引号的字符串赋值 pattern = rf'(final\s+def\s+{re.escape(variable_name)}\s*=\s*["\'])(.*?)(["\'])' replacement = rf'\g<1>{new_value}\g<3>' else: # 匹配数字值(不带引号) pattern = rf'(final\s+def\s+{re.escape(variable_name)}\s*=\s*)(\d+)' replacement = rf'\g<1>{new_value}' updated_content = re.sub(pattern, replacement, content) return updated_content def update_gradle_property(content, key, new_value): # 匹配两种格式: # 1. resValue "string", "key", "value" # 2. resValue("string", "key", "value") pattern = rf'(resValue\s*\(?\s*["\']string["\']\s*,\s*["\']{re.escape(key)}["\']\s*,\s*["\'])(.*?)(["\']\s*\)?)' # 替换为新值 updated_content = re.sub(pattern, rf'\g<1>{new_value}\g<3>', content) return updated_content GAME_ACTIVITY_PATH = f"LauncherCode/src/com/launchercode/GameActivity.kt".replace("/", os.sep) ANDROID_MANIFEST_PATH = f"lawnchair/AndroidManifest.xml".replace("/", os.sep) class ProjectUpdate(Task): def __init__(self, context: Context): super().__init__(context) self.build_gradle_path = None def update_package_name(self): """ 更新包名 :return: """ build_gradle_path = os.path.join(self.context.temp_project_path, "build.gradle") text = open(build_gradle_path, "r", encoding="utf-8").read() text = text.replace("com.fingerheart.launcher.game.free.sdjws", self.context.package_name) open(build_gradle_path, "w", encoding="utf-8").write(text) xml_path = os.path.join(self.context.temp_project_path, "lawnchair/res/xml") # com.launchercode.SplashActivity # TODO 这里还需要改启动项 for root, dirs, files in os.walk(xml_path): for file in files: temp_xml_path = os.path.join(root, file) text = open(temp_xml_path, "r", encoding="utf-8").read() text = text.replace("com.fingerheart.launcher.game.free.sdjws", self.context.package_name) open(temp_xml_path, "w", encoding="utf-8").write(text) pass pass def update_keystore(self): root_dir = os.path.join("game_config", self.context.package_name) for file in os.listdir(root_dir): if file.endswith(".keystore"): name = file.replace(".keystore", "") FileUtils.copy(os.path.join(root_dir, file), os.path.join(self.context.temp_project_path, file)) open(os.path.join(self.context.temp_project_path, "keystore.properties"), "w", encoding="utf-8").write( f""" keyAlias={name} keyPassword=123456 storeFile=./{name}.keystore storePassword=123456 """ ) return raise Exception("keystore not found") def update_config(self): """ 更新配置文件 :return: """ root_dir = os.path.join("game_config", self.context.package_name) config_path = list( filter(lambda f: f.endswith(".zip") and f.startswith(self.context.package_name), os.listdir(root_dir))) if len(config_path) <= 0: raise Exception("config not found") target_path = os.path.join(root_dir, config_path[0]) dst = os.path.join(self.context.temp_project_path, config_path[0].replace(".zip", "")) result = FileUtils.decompress(target_path, dst) app_logger().debug(f"{target_path} -> {dst} , 解压结果: {result}") mainly_path = os.path.join(dst, "mainly") google_services_json_path = os.path.join(dst, "google-services.json") FileUtils.copy(google_services_json_path, os.path.join(self.context.temp_project_path, "google-services.json"), True) dst_path = os.path.join(self.context.temp_project_path, f"lawnchair{os.sep}assets") for file in list(filter(lambda f: f != "google_fonts.json", os.listdir(dst_path))): FileUtils.delete(os.path.join(dst_path, file), True) pass for file in list(filter(lambda f: f.find(".") <= 0, os.listdir(mainly_path))): FileUtils.copy(os.path.join(mainly_path, file), os.path.join(dst_path, file)) with open(os.path.join(mainly_path, "tkg_config_mainly.properties"), 'rb') as f: self.context.config = javaproperties.load(f) if self.context.admob_app_id is None or self.context.admob_app_id == "": self.context.admob_app_id = self.context.get_config("admob_id") pass def update_icon(self): """ 更新游戏Icon :return: """ target_icon_path = os.path.join("game_config", self.context.package_name, "icon.zip") tag = "res_icon_resources" dst = os.path.join(self.context.temp_project_path, tag) FileUtils.decompress(target_icon_path, dst) for root, dirs, files in os.walk(dst): for file in files: temp_tart_path = os.path.join(root, file) if temp_tart_path.find("__MACOSX") > 0: continue temp_dst = temp_tart_path.replace(tag, "res") app_logger().debug(f"copy icon = {temp_tart_path} -> {temp_dst}") FileUtils.copy(temp_tart_path, temp_dst, True) pass def update_game_result(self): """ 更新游戏资源 :return: """ root_dir = os.path.join("game_config", self.context.package_name) if self.context.game_type == "unity_native": res_path = os.path.join(root_dir, "unityLibrary.zip") if not os.path.exists(res_path): raise Exception("unity library not found") dst = os.path.join(self.context.temp_project_path, "unityLibrary") temp_dst = dst + "_res" if os.path.exists(dst): FileUtils.delete(dst, True) FileUtils.decompress(res_path, temp_dst) if os.listdir(temp_dst).index("unityLibrary") >= 0: FileUtils.copy(os.path.join(temp_dst, "unityLibrary"), dst) else: FileUtils.copy(temp_dst, dst) text = open(os.path.join(dst, "build.gradle"), "r", encoding="utf-8").read() text = text.replace("implementation", "api") open(os.path.join(dst, "build.gradle"), "w", encoding="utf-8").write(text) # 引用Unity项目 text = open(self.build_gradle_path, "r", encoding="utf-8").read() text = uncomment_line(text, "implementation projects.unityLibrary") open(self.build_gradle_path, "w", encoding="utf-8").write(text) # launcher 引用 unityActivity text = open(os.path.join(self.context.temp_project_path, GAME_ACTIVITY_PATH), "r", encoding="utf-8").read() text = text.replace("WebActivity", "com.unity3d.player.UnityPlayerActivity") open(os.path.join(self.context.temp_project_path, GAME_ACTIVITY_PATH), "w", encoding="utf-8").write(text) text = open(os.path.join(self.context.temp_project_path, ANDROID_MANIFEST_PATH), "r", encoding="utf-8").read() # text = text.replace("WebActivity", "com.unity3d.player.UnityPlayerActivity") open(os.path.join(self.context.temp_project_path, ANDROID_MANIFEST_PATH), "w", encoding="utf-8").write(text) else: raise Exception(f"不支持的游戏类型 : {self.context.game_type}") pass def update_image(self): """ 更新游戏的资源 :return: """ root_dir = os.path.join("game_config", self.context.package_name) drawable_path = os.path.join(root_dir, "drawable-xxhdpi.zip") if not os.path.exists(drawable_path): raise Exception("drawable not found") dst = os.path.join(self.context.temp_project_path, "drawable_res") FileUtils.decompress(drawable_path, dst) if os.path.join(dst, "drawable-xxhdpi"): dst = os.path.join(dst, "drawable-xxhdpi") target_root_path = os.path.join(self.context.temp_project_path, f"LauncherCode{os.sep}res{os.sep}drawable-xxhdpi") image_list = list(map(lambda f: Path(f).stem, os.listdir(target_root_path))) for file in os.listdir(dst): temp_tar = os.path.join(dst, file) temp_dst = os.path.join(target_root_path, file) file_name = Path(file).stem if file_name in image_list: FileUtils.delete(os.path.join(target_root_path, file_name + ".png")) FileUtils.delete(os.path.join(target_root_path, file_name + ".jpg")) pass FileUtils.copy(temp_tar, temp_dst, True) pass def update_gradle_config(self): """ 更新gradle里面的版本号 :return: """ build_gradle_path = os.path.join(self.context.temp_project_path, "build.gradle") text = open(build_gradle_path, "r", encoding="UTF-8").read() text = update_gradle_property(text, "admob_app_id", self.context.admob_app_id) text = update_gradle_property(text, "game_services_project_id", self.context.game_services_project_id) text = update_gradle_property(text, "facebook_app_id", self.context.facebook_app_id) text = update_gradle_property(text, "facebook_client_token", self.context.facebook_client_token) text = update_gradle_property(text, "derived_app_name", self.context.get_app_name()) text = update_gradle_variable(text, "versionDisplayName", self.context.version_display_name) text = update_gradle_variable(text, "version_code", self.context.version_code) text = update_dependency_version(text, f"straw:hachisdk_unity_{self.context.package_name}", self.context.sdk_prolink_version) text = update_dependency_version(text, f"com.game:hachisdk_unity_{self.context.package_name}", self.context.sdk_version) open(build_gradle_path, "w", encoding="UTF-8").write(text) pass def execute(self): self.build_gradle_path = os.path.join(self.context.temp_project_path, "build.gradle") self.init_sdk_version() self.update_package_name() self.update_keystore() self.update_config() self.update_icon() self.update_image() self.update_game_result() self.update_gradle_config() pass def get_sdk_version(self) -> str: for i in range(3): try: url = f"https://repo.dgtverse.cn/repository/tk_my/com/game/hachisdk_unity_{self.context.package_name}/maven-metadata.xml" version = get_latest_version(url) if version: return version pass except Exception as e: pass raise Exception("sdk error.") def get_prolink_version(self) -> str: for i in range(3): try: url = f"https://repo.dgtverse.cn/repository/tk_my/straw/hachisdk_unity_{self.context.package_name}/maven-metadata.xml" version = get_latest_version(url) if version: return version except Exception as e: pass raise Exception("sdk prolink error.") def init_sdk_version(self): self.context.sdk_version = self.get_sdk_version() self.context.sdk_prolink_version = self.get_prolink_version() pass