This commit is contained in:
		
							parent
							
								
									b8ddea3a9a
								
							
						
					
					
						commit
						eb9da1489d
					
				| 
						 | 
				
			
			@ -9,6 +9,11 @@ from .project_proguard import ProjectProguard
 | 
			
		|||
from .project_res_md5 import ProjectResMd5
 | 
			
		||||
from .project_res_string import ProjectResString
 | 
			
		||||
from .project_update import ProjectUpdate
 | 
			
		||||
from .project_update_config import ProjectUpdateConfig
 | 
			
		||||
from .project_update_game_res import ProjectUpdateGameRes
 | 
			
		||||
from .project_update_icon import ProjectUpdateIcon
 | 
			
		||||
from .project_update_image import ProjectUpdateImage
 | 
			
		||||
from .project_update_keystore import ProjectUpdateKeystore
 | 
			
		||||
from .project_upload import ProjectUpload
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -18,6 +23,11 @@ def run(context: Context):
 | 
			
		|||
        ProjectInit(context),
 | 
			
		||||
        ProjectCopy(context),
 | 
			
		||||
        ProjectResMd5(context),
 | 
			
		||||
        ProjectUpdateKeystore(context),
 | 
			
		||||
        ProjectUpdateConfig(context),
 | 
			
		||||
        ProjectUpdateIcon(context),
 | 
			
		||||
        ProjectUpdateImage(context),
 | 
			
		||||
        ProjectUpdateGameRes(context),
 | 
			
		||||
        ProjectUpdate(context),
 | 
			
		||||
        ProjectResString(context),
 | 
			
		||||
        ProjectInterface(context),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +1,12 @@
 | 
			
		|||
import os
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@dataclass
 | 
			
		||||
class Context:
 | 
			
		||||
    project_original_path: str
 | 
			
		||||
    local_repo_commit: str
 | 
			
		||||
    repo_url: str = ""
 | 
			
		||||
    repo_branch: str = ""
 | 
			
		||||
    repo_commit: str = ""
 | 
			
		||||
| 
						 | 
				
			
			@ -18,6 +21,7 @@ class Context:
 | 
			
		|||
    project_original_path: str = "project/original"
 | 
			
		||||
 | 
			
		||||
    temp_project_path: str = ""
 | 
			
		||||
    temp_project_config_path: str = ""
 | 
			
		||||
    # 本地的版本号
 | 
			
		||||
    local_repo_branch: str = ""
 | 
			
		||||
    local_repo_commit: str = ""
 | 
			
		||||
| 
						 | 
				
			
			@ -44,6 +48,24 @@ class Context:
 | 
			
		|||
 | 
			
		||||
    string: dict = None
 | 
			
		||||
 | 
			
		||||
    update_code: bool = True
 | 
			
		||||
    update_config: bool = True
 | 
			
		||||
    update_keystore: bool = True
 | 
			
		||||
    update_res_img: bool = True
 | 
			
		||||
    update_res_icon: bool = True
 | 
			
		||||
    update_res_unity: bool = True
 | 
			
		||||
    config_path: str = ""
 | 
			
		||||
    keystore_path: str = ""
 | 
			
		||||
    res_img_path: str = ""
 | 
			
		||||
    res_icon_path: str = ""
 | 
			
		||||
    res_unity_path: str = ""
 | 
			
		||||
 | 
			
		||||
    config_config_md5: str = ""
 | 
			
		||||
    config_keystore_md5: str = ""
 | 
			
		||||
    config_res_img_md5: str = ""
 | 
			
		||||
    config_res_icon_md5: str = ""
 | 
			
		||||
    config_res_unity_md5: str = ""
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def from_json(cls, json_str: str):
 | 
			
		||||
        data = json.loads(json_str)
 | 
			
		||||
| 
						 | 
				
			
			@ -58,3 +80,37 @@ class Context:
 | 
			
		|||
        if self.app_name:
 | 
			
		||||
            return self.app_name
 | 
			
		||||
        return self.get_config("app_name")
 | 
			
		||||
 | 
			
		||||
    def get_cache_config(self) -> dict[str, str]:
 | 
			
		||||
        try:
 | 
			
		||||
            path = os.path.join(self.temp_project_config_path, "config.json")
 | 
			
		||||
            args = json.load(open(path, "r", encoding="utf-8"))
 | 
			
		||||
            return args
 | 
			
		||||
        except:
 | 
			
		||||
            return {}
 | 
			
		||||
 | 
			
		||||
    def get_cache_config_from_key(self, key: str, default: str = "") -> str:
 | 
			
		||||
        try:
 | 
			
		||||
            return self.get_cache_config().get(key, default)
 | 
			
		||||
        except:
 | 
			
		||||
            return default
 | 
			
		||||
 | 
			
		||||
    def save_cache_config(self, key: str, value: str):
 | 
			
		||||
        path = os.path.join(self.temp_project_config_path, "config.json")
 | 
			
		||||
        config = self.get_cache_config()
 | 
			
		||||
        config[key] = value
 | 
			
		||||
        json.dump(config, open(path, "w", encoding="utf-8"), indent=4)
 | 
			
		||||
 | 
			
		||||
    def get_map(self) -> dict[str, str]:
 | 
			
		||||
        try:
 | 
			
		||||
            path = os.path.join(self.temp_project_config_path, "map.json")
 | 
			
		||||
            return json.load(open(path, "r", encoding="utf-8"))
 | 
			
		||||
        except:
 | 
			
		||||
            return {}
 | 
			
		||||
 | 
			
		||||
    def save_map(self, map: dict[str, str]):
 | 
			
		||||
        path = os.path.join(self.temp_project_config_path, "map.json")
 | 
			
		||||
        json.dump(map, open(path, "w", encoding="utf-8"), indent=4)
 | 
			
		||||
 | 
			
		||||
    def get_map_from_key(self, file_name) -> str:
 | 
			
		||||
        return self.get_map().get(file_name, file_name)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,7 +39,7 @@ class ProjectBuild(Task):
 | 
			
		|||
    def execute(self):
 | 
			
		||||
        self.init()
 | 
			
		||||
        # self.save_project()
 | 
			
		||||
        self.build_apk()
 | 
			
		||||
        # self.build_apk()
 | 
			
		||||
        self.build_aab()
 | 
			
		||||
        self.copy_to_out()
 | 
			
		||||
        pass
 | 
			
		||||
| 
						 | 
				
			
			@ -54,12 +54,12 @@ class ProjectBuild(Task):
 | 
			
		|||
 | 
			
		||||
    def copy_to_out(self):
 | 
			
		||||
        app_logger().debug(f"copy_to_out start.")
 | 
			
		||||
        target = f"{self.context.temp_project_path}{os.sep}build{os.sep}outputs{os.sep}apk{os.sep}lawnWithQuickstepPlay{os.sep}debug"
 | 
			
		||||
        FileUtils.copy(find_path(target, "apk"), self.context.out_debug_apk)
 | 
			
		||||
        app_logger().debug(f"copy_to_out debug apk = {self.context.out_debug_apk}")
 | 
			
		||||
        target = f"{self.context.temp_project_path}{os.sep}build{os.sep}outputs{os.sep}apk{os.sep}lawnWithQuickstepPlay{os.sep}release"
 | 
			
		||||
        FileUtils.copy(find_path(target, "apk"), self.context.out_release_apk)
 | 
			
		||||
        app_logger().debug(f"copy_to_out release apk = {self.context.out_release_apk}")
 | 
			
		||||
        # target = f"{self.context.temp_project_path}{os.sep}build{os.sep}outputs{os.sep}apk{os.sep}lawnWithQuickstepPlay{os.sep}debug"
 | 
			
		||||
        # FileUtils.copy(find_path(target, "apk"), self.context.out_debug_apk)
 | 
			
		||||
        # app_logger().debug(f"copy_to_out debug apk = {self.context.out_debug_apk}")
 | 
			
		||||
        # target = f"{self.context.temp_project_path}{os.sep}build{os.sep}outputs{os.sep}apk{os.sep}lawnWithQuickstepPlay{os.sep}release"
 | 
			
		||||
        # FileUtils.copy(find_path(target, "apk"), self.context.out_release_apk)
 | 
			
		||||
        # app_logger().debug(f"copy_to_out release apk = {self.context.out_release_apk}")
 | 
			
		||||
        target = f"{self.context.temp_project_path}{os.sep}build{os.sep}outputs{os.sep}bundle{os.sep}lawnWithQuickstepPlayRelease"
 | 
			
		||||
        FileUtils.copy(find_path(target, "aab"), self.context.out_release_aab)
 | 
			
		||||
        app_logger().debug(f"copy_to_out end.")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,21 +1,63 @@
 | 
			
		|||
import os.path
 | 
			
		||||
import shutil
 | 
			
		||||
 | 
			
		||||
from git import Repo
 | 
			
		||||
 | 
			
		||||
from scripts.task import Task
 | 
			
		||||
from utils import FileUtils
 | 
			
		||||
from utils import TimeUtils
 | 
			
		||||
from utils.logger_utils import app_logger
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def clean_workspace(repo):
 | 
			
		||||
    # 1. 放弃工作区所有已跟踪文件的修改(包括恢复被删除的已跟踪文件)
 | 
			
		||||
    #   - checkout 命令会将工作区文件重置为当前 HEAD 版本的状态
 | 
			
		||||
    repo.git.checkout('.')
 | 
			
		||||
 | 
			
		||||
    # 2. 删除工作区所有未跟踪的文件和目录(包括空目录)
 | 
			
		||||
    #   - -f:强制删除(避免询问)
 | 
			
		||||
    #   - -d:同时删除未跟踪的目录
 | 
			
		||||
    repo.git.clean('-fd')
 | 
			
		||||
 | 
			
		||||
    print("工作空间已清空,恢复到当前分支最新提交状态")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProjectCopy(Task):
 | 
			
		||||
 | 
			
		||||
    def execute(self):
 | 
			
		||||
        self.init()
 | 
			
		||||
        if os.path.exists(self.context.temp_project_path):
 | 
			
		||||
            temp_path = os.path.join(self.context.temp_project_path, "build", "outputs")
 | 
			
		||||
            if os.path.exists(temp_path):
 | 
			
		||||
                shutil.rmtree(temp_path)
 | 
			
		||||
 | 
			
		||||
            temp_path = os.path.join(self.context.temp_project_path, "build", "generated")
 | 
			
		||||
            if os.path.exists(temp_path):
 | 
			
		||||
                shutil.rmtree(temp_path)
 | 
			
		||||
 | 
			
		||||
            app_logger().debug("project '{}' to '{}'".format(self.context.temp_project_path, "项目已经存在了"))
 | 
			
		||||
            repo = Repo(self.context.temp_project_path)
 | 
			
		||||
            temp_repo_commit = repo.head.commit.hexsha[:10]
 | 
			
		||||
            app_logger().debug(
 | 
			
		||||
                f"project '{self.context.temp_project_path}' , local '{temp_repo_commit}' remote '{self.context.local_repo_commit}")
 | 
			
		||||
            if temp_repo_commit != self.context.local_repo_commit:
 | 
			
		||||
                app_logger().debug("本地代码有变动,需要更新临时的目录")
 | 
			
		||||
                # 本地分支和远程的分支不一样
 | 
			
		||||
                clean_workspace(repo)
 | 
			
		||||
                origin = repo.remote("origin")
 | 
			
		||||
                origin.fetch()
 | 
			
		||||
                repo.git.checkout(self.context.local_repo_commit)
 | 
			
		||||
                self.context.update_code = True
 | 
			
		||||
 | 
			
		||||
                self.context.update_config = True
 | 
			
		||||
                self.context.update_keystore = True
 | 
			
		||||
                self.context.update_res_img = True
 | 
			
		||||
                self.context.update_res_icon = True
 | 
			
		||||
                self.context.update_res_unity = True
 | 
			
		||||
            else:
 | 
			
		||||
                self.context.update_code = False
 | 
			
		||||
                app_logger().debug("本地的代码没有变动,无需更新")
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        self.context.update_code = True
 | 
			
		||||
        result = FileUtils.copy(self.context.project_original_path, self.context.temp_project_path)
 | 
			
		||||
        app_logger().debug("Copied project '{}' to '{}'".format(self.context.project_original_path, result))
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def init(self):
 | 
			
		||||
        self.context.temp_project_path = self.context.project_original_path.replace(
 | 
			
		||||
            "original", self.context.package_name.replace(".", "_") + TimeUtils.get_formatted_time(format_str="%H%M%S")
 | 
			
		||||
        )
 | 
			
		||||
        # self.context.temp_project_path = self.context.project_original_path
 | 
			
		||||
 | 
			
		||||
        pass
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,5 +4,5 @@ from utils import FileUtils
 | 
			
		|||
 | 
			
		||||
class ProjectEnd(Task):
 | 
			
		||||
    def execute(self):
 | 
			
		||||
        FileUtils.delete(self.context.temp_project_path, True)
 | 
			
		||||
        # FileUtils.delete(self.context.temp_project_path, True)
 | 
			
		||||
        pass
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,11 @@
 | 
			
		|||
import os
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
from git import Repo, RemoteProgress
 | 
			
		||||
 | 
			
		||||
from utils import FileUtils
 | 
			
		||||
from utils.logger_utils import app_logger
 | 
			
		||||
from .context import Context
 | 
			
		||||
from .task import Task
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -10,7 +15,72 @@ def progress(op_code, cur_count, max_count=None, message=''):
 | 
			
		|||
    print(f"操作: {op_code}, 进度: {cur_count}/{max_count}, 消息: {message}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def check_config_exists(*file_paths):
 | 
			
		||||
    # game_config/com.diy.emoticon.free.zcaqf.game.launcher/com.diy.emoticon.free.zcaqf.game.launcher_android.zip
 | 
			
		||||
    # game_config/com.emoticon.diy.znfav.launcher.free/com.emoticon.diy.znfav.launcher.free_android.zip
 | 
			
		||||
    # 遍历检查每个文件
 | 
			
		||||
    if file_paths is None:
 | 
			
		||||
        file_paths = []
 | 
			
		||||
    for file_path in file_paths:
 | 
			
		||||
        if not os.path.exists(file_path):
 | 
			
		||||
            # 抛出异常并说明缺失的文件路径
 | 
			
		||||
            raise FileNotFoundError(f"配置文件不存在: {file_path}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProjectInit(Task):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, context: Context):
 | 
			
		||||
        super().__init__(context)
 | 
			
		||||
        self.context.temp_project_path = self.context.project_original_path.replace("original",
 | 
			
		||||
                                                                                    "V1_" + self.context.package_name.replace(
 | 
			
		||||
                                                                                        ".", "_"))
 | 
			
		||||
        self.context.temp_project_config_path = self.context.temp_project_path + "_config"
 | 
			
		||||
        if not os.path.exists(self.context.temp_project_config_path):
 | 
			
		||||
            os.makedirs(self.context.temp_project_config_path)
 | 
			
		||||
 | 
			
		||||
        config_md5 = context.get_cache_config_from_key("config", "")
 | 
			
		||||
        keystore_md5 = context.get_cache_config_from_key("keystore", "")
 | 
			
		||||
        res_img_md5 = context.get_cache_config_from_key("res_img", "")
 | 
			
		||||
        res_icon_md5 = context.get_cache_config_from_key("res_icon", "")
 | 
			
		||||
        res_unity_md5 = context.get_cache_config_from_key("res_unity", "")
 | 
			
		||||
 | 
			
		||||
        app_logger().debug(f"temp project res md5 , "
 | 
			
		||||
                           f"config : {config_md5} , "
 | 
			
		||||
                           f"keystore : {keystore_md5} , "
 | 
			
		||||
                           f"res_img : {res_img_md5} , "
 | 
			
		||||
                           f"res_icon : {res_icon_md5} , "
 | 
			
		||||
                           f"res_unity : {res_unity_md5}")
 | 
			
		||||
 | 
			
		||||
        config_root_path = os.path.join("game_config", self.context.package_name)
 | 
			
		||||
 | 
			
		||||
        self.context.config_path = os.path.join(config_root_path, f"{self.context.package_name}_android.zip")
 | 
			
		||||
        self.context.keystore_path = ""
 | 
			
		||||
        for i in os.listdir(config_root_path):
 | 
			
		||||
            if i.endswith(".keystore"):
 | 
			
		||||
                self.context.keystore_path = os.path.join(config_root_path, i)
 | 
			
		||||
 | 
			
		||||
        self.context.res_img_path = os.path.join(config_root_path, "drawable-xxhdpi.zip")
 | 
			
		||||
        self.context.res_icon_path = os.path.join(config_root_path, "icon.zip")
 | 
			
		||||
        self.context.res_unity_path = os.path.join(config_root_path, "unityLibrary.zip")
 | 
			
		||||
 | 
			
		||||
        check_config_exists(self.context.config_path,
 | 
			
		||||
                            self.context.keystore_path,
 | 
			
		||||
                            self.context.res_img_path,
 | 
			
		||||
                            self.context.res_icon_path,
 | 
			
		||||
                            self.context.res_unity_path)
 | 
			
		||||
 | 
			
		||||
        self.context.config_config_md5 = FileUtils.get_md5(self.context.config_path)
 | 
			
		||||
        self.context.config_keystore_md5 = FileUtils.get_md5(self.context.keystore_path)
 | 
			
		||||
        self.context.config_res_img_md5 = FileUtils.get_md5(self.context.res_img_path)
 | 
			
		||||
        self.context.config_res_icon_md5 = FileUtils.get_md5(self.context.res_icon_path)
 | 
			
		||||
        self.context.config_res_unity_md5 = FileUtils.get_md5(self.context.res_unity_path)
 | 
			
		||||
 | 
			
		||||
        self.context.update_config = config_md5 != self.context.config_config_md5
 | 
			
		||||
        self.context.update_keystore = keystore_md5 != self.context.config_keystore_md5
 | 
			
		||||
        self.context.update_res_img = res_img_md5 != self.context.config_res_img_md5
 | 
			
		||||
        self.context.update_res_icon = res_icon_md5 != self.context.config_res_icon_md5
 | 
			
		||||
        self.context.update_res_unity = res_unity_md5 != self.context.config_res_unity_md5
 | 
			
		||||
 | 
			
		||||
    def execute(self):
 | 
			
		||||
        try:
 | 
			
		||||
            repo = Repo(self.context.project_original_path)
 | 
			
		||||
| 
						 | 
				
			
			@ -45,12 +115,13 @@ class ProjectInit(Task):
 | 
			
		|||
                    local_branch.set_tracking_branch(repo.remotes[remote_name].refs[branch_name])  # 设置跟踪
 | 
			
		||||
                    local_branch.checkout()  # 切换到该分支
 | 
			
		||||
 | 
			
		||||
            # 拉取最新代码
 | 
			
		||||
            repo.remotes.origin.pull()
 | 
			
		||||
 | 
			
		||||
            self.context.local_repo_branch = repo.active_branch.name
 | 
			
		||||
            self.context.local_repo_commit = repo.head.commit.hexsha[:10]
 | 
			
		||||
 | 
			
		||||
            # 拉取最新代码
 | 
			
		||||
            repo.remotes.origin.pull()
 | 
			
		||||
            print("当前分支:" + repo.active_branch.name)
 | 
			
		||||
            print("当前分支:" + repo.active_branch.name, self.context.local_repo_commit)
 | 
			
		||||
            pass
 | 
			
		||||
        else:
 | 
			
		||||
            raise Exception(f"No commit to {self.context.repo_commit}")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@ import os
 | 
			
		|||
import hashlib
 | 
			
		||||
 | 
			
		||||
from utils import FileUtils
 | 
			
		||||
from utils.logger_utils import app_logger
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def string_to_md5(text):
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +66,9 @@ class ProjectInterface(Task):
 | 
			
		|||
                    f_out.write(line)
 | 
			
		||||
 | 
			
		||||
    def execute(self):
 | 
			
		||||
        if not self.context.update_code:
 | 
			
		||||
            app_logger().info("代码没有更新,不需要处理 unity 接口")
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        self.unity_proxy_api_file = os.path.join(self.context.temp_project_path,
 | 
			
		||||
                                                 "launcher-game/src/com/game/hachisdk/unity/UnityProxyApi.java".replace(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -276,6 +276,10 @@ class ProjectProguard(Task):
 | 
			
		|||
            pass
 | 
			
		||||
 | 
			
		||||
    def execute(self):
 | 
			
		||||
        if not self.context.update_code:
 | 
			
		||||
            app_logger().info("No update project proguard found")
 | 
			
		||||
            self.context.proguard_dict = self.context.get_map()
 | 
			
		||||
            return
 | 
			
		||||
        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")
 | 
			
		||||
| 
						 | 
				
			
			@ -324,6 +328,7 @@ class ProjectProguard(Task):
 | 
			
		|||
 | 
			
		||||
        encrypt_xml_resources(self.string_path, False, string_to_md5(self.context.package_name).upper())
 | 
			
		||||
 | 
			
		||||
        self.context.save_map(self.context.proguard_dict)
 | 
			
		||||
        app_logger().info(json.dumps(self.context.proguard_dict, indent=4))
 | 
			
		||||
 | 
			
		||||
# if __name__ == '__main__':
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,8 @@ import hashlib
 | 
			
		|||
from PIL import Image
 | 
			
		||||
import random
 | 
			
		||||
 | 
			
		||||
from utils.logger_utils import app_logger
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_md5(file_path):
 | 
			
		||||
    """计算文件的MD5值"""
 | 
			
		||||
| 
						 | 
				
			
			@ -64,6 +66,9 @@ def modify_one_pixel(input_path, output_path):
 | 
			
		|||
class ProjectResMd5(Task):
 | 
			
		||||
 | 
			
		||||
    def execute(self):
 | 
			
		||||
        if not self.context.update_code:
 | 
			
		||||
            app_logger().info("不需要更新res md5")
 | 
			
		||||
            return
 | 
			
		||||
        path = f"launcher-game/res/"
 | 
			
		||||
        root_path = os.path.join(self.context.temp_project_path, path)
 | 
			
		||||
        for i in os.listdir(root_path):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,6 +37,9 @@ class ProjectResString(Task):
 | 
			
		|||
        app_logger().debug("路径不存,不操作了,后续可以给他创建出来:" + string_path + "\t" + json.dumps(res, indent=4))
 | 
			
		||||
 | 
			
		||||
    def execute(self):
 | 
			
		||||
        if not self.context.update_code:
 | 
			
		||||
            app_logger().info("代码没有更新,不需要处理资源")
 | 
			
		||||
            return
 | 
			
		||||
        for key in self.context.string.keys():
 | 
			
		||||
            launcher = key.replace('base', '')
 | 
			
		||||
            if launcher:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,86 +1,11 @@
 | 
			
		|||
import os.path
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
import re
 | 
			
		||||
from xml.dom import minidom
 | 
			
		||||
 | 
			
		||||
import javaproperties
 | 
			
		||||
import requests
 | 
			
		||||
from lxml import etree
 | 
			
		||||
import xml.etree.ElementTree as ET
 | 
			
		||||
 | 
			
		||||
from scripts.context import Context
 | 
			
		||||
from scripts.task import Task
 | 
			
		||||
from utils import FileUtils
 | 
			
		||||
from utils.logger_utils import app_logger
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def process_manifest(input_path, output_path):
 | 
			
		||||
    # 解析XML文件
 | 
			
		||||
    tree = ET.parse(input_path)
 | 
			
		||||
    root = tree.getroot()
 | 
			
		||||
 | 
			
		||||
    # 定义命名空间映射
 | 
			
		||||
    android_namespace = 'http://schemas.android.com/apk/res/android'
 | 
			
		||||
    ET.register_namespace('android', android_namespace)
 | 
			
		||||
    namespaces = {'android': android_namespace}
 | 
			
		||||
 | 
			
		||||
    # 处理application标签,移除所有属性
 | 
			
		||||
    application = root.find('application')
 | 
			
		||||
    if application is not None:
 | 
			
		||||
        # 保存所有子节点
 | 
			
		||||
        children = list(application)
 | 
			
		||||
        # 清除application标签
 | 
			
		||||
        root.remove(application)
 | 
			
		||||
        # 创建新的application标签(无属性)
 | 
			
		||||
        new_application = ET.Element('application')
 | 
			
		||||
        # 添加回所有子节点
 | 
			
		||||
        for child in children:
 | 
			
		||||
            new_application.append(child)
 | 
			
		||||
        # 将新的application标签添加回root
 | 
			
		||||
        root.append(new_application)
 | 
			
		||||
 | 
			
		||||
        # 查找并删除指定的activity节点
 | 
			
		||||
        activity_to_remove = new_application.find(
 | 
			
		||||
            ".//activity[@android:name='com.unity3d.player.UnityPlayerActivity']",
 | 
			
		||||
            namespaces=namespaces
 | 
			
		||||
        )
 | 
			
		||||
        if activity_to_remove is not None:
 | 
			
		||||
            new_application.remove(activity_to_remove)
 | 
			
		||||
 | 
			
		||||
    # 保存处理后的XML,保留android命名空间前缀
 | 
			
		||||
    rough_string = ET.tostring(root, 'utf-8')
 | 
			
		||||
    reparsed = minidom.parseString(rough_string)
 | 
			
		||||
    pretty_xml = reparsed.toprettyxml(indent="  ", encoding='utf-8')
 | 
			
		||||
 | 
			
		||||
    # 去除空行
 | 
			
		||||
    lines = pretty_xml.decode('utf-8').split('\n')
 | 
			
		||||
    non_empty_lines = [line for line in lines if line.strip() != '']
 | 
			
		||||
    pretty_xml = '\n'.join(non_empty_lines).encode('utf-8')
 | 
			
		||||
 | 
			
		||||
    with open(output_path, 'wb') as f:
 | 
			
		||||
        f.write(pretty_xml)
 | 
			
		||||
 | 
			
		||||
    print(f"处理完成,结果已保存到 {output_path}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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 变量值
 | 
			
		||||
| 
						 | 
				
			
			@ -166,12 +91,8 @@ def update_gradle_property(content, key, new_value):
 | 
			
		|||
    return updated_content
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
LAUNCHER_CODE_PATH = f"LauncherCode/src/com/launchercode".replace("/", os.sep)
 | 
			
		||||
GAME_ACTIVITY_PATH = f"launcher-game/src/com/game/launcher/activity/GLGameActivity.kt".replace("/", os.sep)
 | 
			
		||||
GAME_ACTIVITY_PATH_2 = f"LauncherCode/src/com/launchercode/activity/GameActivity.kt".replace("/", os.sep)
 | 
			
		||||
ANDROID_MANIFEST_PATH = f"launcher-game/AndroidManifest.xml".replace("/", os.sep)
 | 
			
		||||
STRING_PATH = f"launcher-game/res/values/strings.xml".replace("/", os.sep)
 | 
			
		||||
LAUNCER_STRING_PATH = f"LauncherCode/src/com/launchercode/LauncherStringsValue.kt".replace("/", os.sep)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProjectUpdate(Task):
 | 
			
		||||
| 
						 | 
				
			
			@ -181,6 +102,9 @@ class ProjectUpdate(Task):
 | 
			
		|||
        self.build_gradle_path = None
 | 
			
		||||
 | 
			
		||||
    def update_package_name(self):
 | 
			
		||||
        if not self.context.update_config:
 | 
			
		||||
            app_logger().info("配置文件没有变动,package不需要更新")
 | 
			
		||||
            return
 | 
			
		||||
        """
 | 
			
		||||
        更新包名
 | 
			
		||||
        :return:
 | 
			
		||||
| 
						 | 
				
			
			@ -203,209 +127,6 @@ class ProjectUpdate(Task):
 | 
			
		|||
                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")
 | 
			
		||||
        if not os.path.exists(mainly_path):
 | 
			
		||||
            mainly_path = os.path.join(dst, "appConfig")
 | 
			
		||||
 | 
			
		||||
        google_services_json_path = os.path.join(dst, "google-services.json")
 | 
			
		||||
        if not os.path.exists(google_services_json_path):
 | 
			
		||||
            google_services_json_path = os.path.join(dst, "appConfig", "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"launcher-game{os.sep}assets")
 | 
			
		||||
 | 
			
		||||
        for file in list(filter(lambda f: not (f == "google_fonts.json" or f == "pag_gl_slide.pag"),
 | 
			
		||||
                                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(dst, "tkg_config_mainly.properties"), 'rb') as f:
 | 
			
		||||
            self.context.config = javaproperties.load(f)
 | 
			
		||||
 | 
			
		||||
        # 不打admob
 | 
			
		||||
        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, "launcher-game" + os.sep + "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)
 | 
			
		||||
 | 
			
		||||
            if os.path.exists(temp_dst):
 | 
			
		||||
                FileUtils.delete(temp_dst, True)
 | 
			
		||||
 | 
			
		||||
            FileUtils.decompress(res_path, temp_dst)
 | 
			
		||||
 | 
			
		||||
            build_path = os.path.join(temp_dst, "build")
 | 
			
		||||
            if os.path.exists(build_path):
 | 
			
		||||
                FileUtils.delete(build_path, True)
 | 
			
		||||
 | 
			
		||||
            if os.listdir(temp_dst).index("unityLibrary") >= 0:
 | 
			
		||||
                FileUtils.copy(os.path.join(temp_dst, "unityLibrary"), dst)
 | 
			
		||||
            else:
 | 
			
		||||
                FileUtils.copy(temp_dst, dst)
 | 
			
		||||
 | 
			
		||||
            android_manifest_xml_path = os.path.join(dst, "src", "main", "AndroidManifest.xml")
 | 
			
		||||
            process_manifest(android_manifest_xml_path, android_manifest_xml_path)
 | 
			
		||||
 | 
			
		||||
            text = open(os.path.join(dst, "build.gradle"), "r", encoding="utf-8").read()
 | 
			
		||||
            text = text.replace("implementation", "api")
 | 
			
		||||
            if not 'namespace "com.unity3d.player"' in text:
 | 
			
		||||
                text = text.replace("compileSdkVersion", """
 | 
			
		||||
        namespace "com.unity3d.player"
 | 
			
		||||
        compileSdkVersion""")
 | 
			
		||||
 | 
			
		||||
            text = text.replace("unityStreamingAssets.tokenize(', ')",
 | 
			
		||||
                                '[".unity3d", ".bundle", ".version", ".bytes", ".hash"]')
 | 
			
		||||
            text = text.replace("apply plugin: 'com.android.library'", """
 | 
			
		||||
plugins {
 | 
			
		||||
    id 'com.android.library'
 | 
			
		||||
}
 | 
			
		||||
            """)
 | 
			
		||||
            open(os.path.join(dst, "build.gradle"), "w", encoding="utf-8").write(text)
 | 
			
		||||
 | 
			
		||||
            lines = open(os.path.join(dst, "build.gradle"), "r", encoding="utf-8").readlines()
 | 
			
		||||
            new_lines = []
 | 
			
		||||
            for line in lines:
 | 
			
		||||
                if line.find("com.game:hachisdk") > 0:
 | 
			
		||||
                    continue
 | 
			
		||||
                new_lines.append(line)
 | 
			
		||||
            open(os.path.join(dst, "build.gradle"), "w", encoding="utf-8").writelines(new_lines)
 | 
			
		||||
 | 
			
		||||
            # 引用Unity项目
 | 
			
		||||
            text = open(os.path.join(self.context.temp_project_path, "ad.gradle"), "r", encoding="utf-8").read()
 | 
			
		||||
            text = uncomment_line(text, "implementation projects.unityLibrary")
 | 
			
		||||
            open(os.path.join(self.context.temp_project_path, "ad.gradle"), "w", encoding="utf-8").write(text)
 | 
			
		||||
 | 
			
		||||
            text = open(os.path.join(self.context.temp_project_path, "settings.gradle"), "r", encoding="utf-8").read()
 | 
			
		||||
            text = uncomment_line(text, "include ':unityLibrary'")
 | 
			
		||||
            open(os.path.join(self.context.temp_project_path, "settings.gradle"), "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("GLGameWebActivity", "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("@style/LauncherGameIntroTheme", "@style/UnityThemeSelector")
 | 
			
		||||
            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"launcher-game{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):
 | 
			
		||||
            if file == ".DS_Store":
 | 
			
		||||
                continue
 | 
			
		||||
            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里面的版本号
 | 
			
		||||
| 
						 | 
				
			
			@ -427,6 +148,9 @@ plugins {
 | 
			
		|||
        pass
 | 
			
		||||
 | 
			
		||||
    def update_string(self):
 | 
			
		||||
        if not self.context.update_config:
 | 
			
		||||
            app_logger().info("配置文件没有变动,string不需要更新")
 | 
			
		||||
            return
 | 
			
		||||
        privacy = self.context.get_config("TkA_Url_Privacy")
 | 
			
		||||
        if not privacy or privacy == "":
 | 
			
		||||
            raise Exception("配置文件中没有配置 TkA_Url_Privacy")
 | 
			
		||||
| 
						 | 
				
			
			@ -440,29 +164,11 @@ plugins {
 | 
			
		|||
        text = text.replace("https://doanvanquy.com/TermsOfUse.html",
 | 
			
		||||
                            privacy.replace("privacy.html", "TermsOfUse.html"))
 | 
			
		||||
        open(os.path.join(self.context.temp_project_path, STRING_PATH), "w", encoding="utf-8").write(text)
 | 
			
		||||
 | 
			
		||||
        # text = open(os.path.join(self.context.temp_project_path, LAUNCER_STRING_PATH), "r", encoding="utf-8").read()
 | 
			
		||||
        # text = text.replace("https://harmonitun.com/privacy.html", privacy)
 | 
			
		||||
        # text = text.replace("https://harmonitun.com/TermsOfUse.html",
 | 
			
		||||
        #                     privacy.replace("privacy.html", "TermsOfUse.html"))
 | 
			
		||||
        # text = text.replace("harmounitun@outlook.com", tkg_custom)
 | 
			
		||||
        # open(os.path.join(self.context.temp_project_path, LAUNCER_STRING_PATH), "w", encoding="utf-8").write(text)
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def execute(self):
 | 
			
		||||
        global GAME_ACTIVITY_PATH
 | 
			
		||||
        path = os.path.join(self.context.temp_project_path, GAME_ACTIVITY_PATH)
 | 
			
		||||
        if not os.path.exists(path):
 | 
			
		||||
            GAME_ACTIVITY_PATH = GAME_ACTIVITY_PATH_2
 | 
			
		||||
            # GAME_ACTIVITY_PATH = GAME_ACTIVITY_PATH_2
 | 
			
		||||
            pass
 | 
			
		||||
        self.build_gradle_path = os.path.join(self.context.temp_project_path, "build.gradle")
 | 
			
		||||
        self.update_package_name()
 | 
			
		||||
        self.update_keystore()
 | 
			
		||||
        self.update_config()
 | 
			
		||||
        self.update_icon()
 | 
			
		||||
        self.update_image()
 | 
			
		||||
        self.update_game_result()
 | 
			
		||||
        self.update_gradle_config()
 | 
			
		||||
        self.update_string()
 | 
			
		||||
        pass
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,71 @@
 | 
			
		|||
import os
 | 
			
		||||
import shutil
 | 
			
		||||
 | 
			
		||||
import javaproperties
 | 
			
		||||
 | 
			
		||||
from scripts.task import Task
 | 
			
		||||
from utils import FileUtils
 | 
			
		||||
from utils.logger_utils import app_logger
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProjectUpdateConfig(Task):
 | 
			
		||||
 | 
			
		||||
    def update_config(self):
 | 
			
		||||
        if not self.context.update_config:
 | 
			
		||||
            app_logger().info("配置文件没有更新")
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        更新配置文件
 | 
			
		||||
        :return:
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        target_path = self.context.config_path
 | 
			
		||||
 | 
			
		||||
        dst = os.path.join(self.context.temp_project_path, os.path.basename(target_path).replace(".zip", ""))
 | 
			
		||||
 | 
			
		||||
        if not self.context.update_config:
 | 
			
		||||
            app_logger().info("No update config found")
 | 
			
		||||
 | 
			
		||||
            with open(os.path.join(dst, "tkg_config_mainly.properties"), 'rb') as f:
 | 
			
		||||
                self.context.config = javaproperties.load(f)
 | 
			
		||||
 | 
			
		||||
            # 不打admob
 | 
			
		||||
            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")
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        if os.path.exists(dst):
 | 
			
		||||
            shutil.rmtree(dst)
 | 
			
		||||
 | 
			
		||||
        result = FileUtils.decompress(target_path, dst)
 | 
			
		||||
        app_logger().debug(f"{target_path} -> {dst} , 解压结果: {result}")
 | 
			
		||||
 | 
			
		||||
        mainly_path = os.path.join(dst, "mainly")
 | 
			
		||||
        if not os.path.exists(mainly_path):
 | 
			
		||||
            mainly_path = os.path.join(dst, "appConfig")
 | 
			
		||||
 | 
			
		||||
        google_services_json_path = os.path.join(dst, "google-services.json")
 | 
			
		||||
        if not os.path.exists(google_services_json_path):
 | 
			
		||||
            google_services_json_path = os.path.join(dst, "appConfig", "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"launcher-game{os.sep}assets")
 | 
			
		||||
 | 
			
		||||
        for file in list(filter(lambda f: not (f == "google_fonts.json" or f == "pag_gl_slide.pag"),
 | 
			
		||||
                                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), True)
 | 
			
		||||
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def execute(self):
 | 
			
		||||
        self.update_config()
 | 
			
		||||
        self.context.save_cache_config("config", self.context.config_config_md5)
 | 
			
		||||
        pass
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,170 @@
 | 
			
		|||
import os
 | 
			
		||||
import re
 | 
			
		||||
import xml.etree.ElementTree as ET
 | 
			
		||||
from xml.dom import minidom
 | 
			
		||||
 | 
			
		||||
from scripts.task import Task
 | 
			
		||||
from utils import FileUtils
 | 
			
		||||
from utils.logger_utils import app_logger
 | 
			
		||||
 | 
			
		||||
GAME_ACTIVITY_PATH = f"launcher-game/src/com/game/launcher/activity/GLGameActivity.kt".replace("/", os.sep)
 | 
			
		||||
ANDROID_MANIFEST_PATH = f"launcher-game/AndroidManifest.xml".replace("/", os.sep)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def process_manifest(input_path, output_path):
 | 
			
		||||
    # 解析XML文件
 | 
			
		||||
    tree = ET.parse(input_path)
 | 
			
		||||
    root = tree.getroot()
 | 
			
		||||
 | 
			
		||||
    # 定义命名空间映射
 | 
			
		||||
    android_namespace = 'http://schemas.android.com/apk/res/android'
 | 
			
		||||
    ET.register_namespace('android', android_namespace)
 | 
			
		||||
    namespaces = {'android': android_namespace}
 | 
			
		||||
 | 
			
		||||
    # 处理application标签,移除所有属性
 | 
			
		||||
    application = root.find('application')
 | 
			
		||||
    if application is not None:
 | 
			
		||||
        # 保存所有子节点
 | 
			
		||||
        children = list(application)
 | 
			
		||||
        # 清除application标签
 | 
			
		||||
        root.remove(application)
 | 
			
		||||
        # 创建新的application标签(无属性)
 | 
			
		||||
        new_application = ET.Element('application')
 | 
			
		||||
        # 添加回所有子节点
 | 
			
		||||
        for child in children:
 | 
			
		||||
            new_application.append(child)
 | 
			
		||||
        # 将新的application标签添加回root
 | 
			
		||||
        root.append(new_application)
 | 
			
		||||
 | 
			
		||||
        # 查找并删除指定的activity节点
 | 
			
		||||
        activity_to_remove = new_application.find(
 | 
			
		||||
            ".//activity[@android:name='com.unity3d.player.UnityPlayerActivity']",
 | 
			
		||||
            namespaces=namespaces
 | 
			
		||||
        )
 | 
			
		||||
        if activity_to_remove is not None:
 | 
			
		||||
            new_application.remove(activity_to_remove)
 | 
			
		||||
 | 
			
		||||
    # 保存处理后的XML,保留android命名空间前缀
 | 
			
		||||
    rough_string = ET.tostring(root, 'utf-8')
 | 
			
		||||
    reparsed = minidom.parseString(rough_string)
 | 
			
		||||
    pretty_xml = reparsed.toprettyxml(indent="  ", encoding='utf-8')
 | 
			
		||||
 | 
			
		||||
    # 去除空行
 | 
			
		||||
    lines = pretty_xml.decode('utf-8').split('\n')
 | 
			
		||||
    non_empty_lines = [line for line in lines if line.strip() != '']
 | 
			
		||||
    pretty_xml = '\n'.join(non_empty_lines).encode('utf-8')
 | 
			
		||||
 | 
			
		||||
    with open(output_path, 'wb') as f:
 | 
			
		||||
        f.write(pretty_xml)
 | 
			
		||||
 | 
			
		||||
    print(f"处理完成,结果已保存到 {output_path}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProjectUpdateGameRes(Task):
 | 
			
		||||
 | 
			
		||||
    def update_game_result(self):
 | 
			
		||||
        if not self.context.update_res_unity:
 | 
			
		||||
            app_logger().info("No update game res found")
 | 
			
		||||
            return
 | 
			
		||||
        """
 | 
			
		||||
        更新游戏资源
 | 
			
		||||
        :return:
 | 
			
		||||
        """
 | 
			
		||||
        if self.context.game_type == "unity_native":
 | 
			
		||||
            res_path = self.context.res_unity_path
 | 
			
		||||
            dst = os.path.join(self.context.temp_project_path, "unityLibrary")
 | 
			
		||||
            temp_dst = dst + "_res"
 | 
			
		||||
            if os.path.exists(dst):
 | 
			
		||||
                result = FileUtils.delete(dst, True)
 | 
			
		||||
                app_logger().info(f"删除unityLibrary结果 : {result}")
 | 
			
		||||
 | 
			
		||||
            if os.path.exists(temp_dst):
 | 
			
		||||
                result = FileUtils.delete(temp_dst, True)
 | 
			
		||||
                app_logger().info(f"删除temp unityLibrary结果 : {result}")
 | 
			
		||||
 | 
			
		||||
            FileUtils.decompress(res_path, temp_dst)
 | 
			
		||||
 | 
			
		||||
            build_path = os.path.join(temp_dst, "build")
 | 
			
		||||
            if os.path.exists(build_path):
 | 
			
		||||
                FileUtils.delete(build_path, True)
 | 
			
		||||
 | 
			
		||||
            if os.listdir(temp_dst).index("unityLibrary") >= 0:
 | 
			
		||||
                FileUtils.copy(os.path.join(temp_dst, "unityLibrary"), dst)
 | 
			
		||||
            else:
 | 
			
		||||
                FileUtils.copy(temp_dst, dst)
 | 
			
		||||
 | 
			
		||||
            android_manifest_xml_path = os.path.join(dst, "src", "main", "AndroidManifest.xml")
 | 
			
		||||
            process_manifest(android_manifest_xml_path, android_manifest_xml_path)
 | 
			
		||||
 | 
			
		||||
            text = open(os.path.join(dst, "build.gradle"), "r", encoding="utf-8").read()
 | 
			
		||||
            text = text.replace("implementation", "api")
 | 
			
		||||
            if not 'namespace "com.unity3d.player"' in text:
 | 
			
		||||
                text = text.replace("compileSdkVersion", """
 | 
			
		||||
            namespace "com.unity3d.player"
 | 
			
		||||
            compileSdkVersion""")
 | 
			
		||||
 | 
			
		||||
            text = text.replace("unityStreamingAssets.tokenize(', ')",
 | 
			
		||||
                                '[".unity3d", ".bundle", ".version", ".bytes", ".hash"]')
 | 
			
		||||
            text = text.replace("apply plugin: 'com.android.library'", """
 | 
			
		||||
    plugins {
 | 
			
		||||
        id 'com.android.library'
 | 
			
		||||
    }
 | 
			
		||||
                """)
 | 
			
		||||
            open(os.path.join(dst, "build.gradle"), "w", encoding="utf-8").write(text)
 | 
			
		||||
 | 
			
		||||
            lines = open(os.path.join(dst, "build.gradle"), "r", encoding="utf-8").readlines()
 | 
			
		||||
            new_lines = []
 | 
			
		||||
            for line in lines:
 | 
			
		||||
                if line.find("com.game:hachisdk") > 0:
 | 
			
		||||
                    continue
 | 
			
		||||
                new_lines.append(line)
 | 
			
		||||
            open(os.path.join(dst, "build.gradle"), "w", encoding="utf-8").writelines(new_lines)
 | 
			
		||||
 | 
			
		||||
            # 引用Unity项目
 | 
			
		||||
            text = open(os.path.join(self.context.temp_project_path, "ad.gradle"), "r", encoding="utf-8").read()
 | 
			
		||||
            text = uncomment_line(text, "implementation projects.unityLibrary")
 | 
			
		||||
            open(os.path.join(self.context.temp_project_path, "ad.gradle"), "w", encoding="utf-8").write(text)
 | 
			
		||||
 | 
			
		||||
            text = open(os.path.join(self.context.temp_project_path, "settings.gradle"), "r", encoding="utf-8").read()
 | 
			
		||||
            text = uncomment_line(text, "include ':unityLibrary'")
 | 
			
		||||
            open(os.path.join(self.context.temp_project_path, "settings.gradle"), "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("GLGameWebActivity", "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("@style/LauncherGameIntroTheme", "@style/UnityThemeSelector")
 | 
			
		||||
            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 execute(self):
 | 
			
		||||
        self.update_game_result()
 | 
			
		||||
 | 
			
		||||
        self.context.save_cache_config("res_unity", self.context.config_res_unity_md5)
 | 
			
		||||
        pass
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
import os
 | 
			
		||||
import shutil
 | 
			
		||||
 | 
			
		||||
from scripts.task import Task
 | 
			
		||||
from utils import FileUtils
 | 
			
		||||
from utils.logger_utils import app_logger
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProjectUpdateIcon(Task):
 | 
			
		||||
 | 
			
		||||
    def update_icon(self):
 | 
			
		||||
        """
 | 
			
		||||
        更新游戏Icon
 | 
			
		||||
        :return:
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if not self.context.update_res_icon:
 | 
			
		||||
            app_logger().info("No update res icon found")
 | 
			
		||||
            return
 | 
			
		||||
        tag = "res_icon_resources"
 | 
			
		||||
        dst = os.path.join(self.context.temp_project_path, tag)
 | 
			
		||||
        if os.path.exists(dst):
 | 
			
		||||
            shutil.rmtree(dst)
 | 
			
		||||
 | 
			
		||||
        FileUtils.decompress(self.context.res_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, "launcher-game" + os.sep + "res")
 | 
			
		||||
                app_logger().debug(f"copy icon = {temp_tart_path} -> {temp_dst}")
 | 
			
		||||
                FileUtils.copy(temp_tart_path, temp_dst, True)
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def execute(self):
 | 
			
		||||
        self.update_icon()
 | 
			
		||||
        self.context.save_cache_config("res_icon", self.context.config_res_icon_md5)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,52 @@
 | 
			
		|||
import os
 | 
			
		||||
import shutil
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
 | 
			
		||||
from scripts.task import Task
 | 
			
		||||
from utils import FileUtils
 | 
			
		||||
from utils.logger_utils import app_logger
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProjectUpdateImage(Task):
 | 
			
		||||
 | 
			
		||||
    def update_image(self):
 | 
			
		||||
        """
 | 
			
		||||
        更新游戏的资源
 | 
			
		||||
        :return:
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if not self.context.update_res_img:
 | 
			
		||||
            app_logger().info("No update image found")
 | 
			
		||||
            return
 | 
			
		||||
        dst = os.path.join(self.context.temp_project_path, "drawable_res")
 | 
			
		||||
        if os.path.exists(dst):
 | 
			
		||||
            shutil.rmtree(dst)
 | 
			
		||||
 | 
			
		||||
        FileUtils.decompress(self.context.res_img_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"launcher-game{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):
 | 
			
		||||
            if file == ".DS_Store":
 | 
			
		||||
                continue
 | 
			
		||||
            res_name = Path(file).stem
 | 
			
		||||
            file_name = self.context.get_map_from_key(res_name)
 | 
			
		||||
            temp_tar = os.path.join(dst, file)
 | 
			
		||||
            temp_dst = os.path.join(target_root_path, file).replace(file_name, self.context.get_map_from_key(res_name))
 | 
			
		||||
            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
 | 
			
		||||
            app_logger().info(f"copy {temp_tar} => {temp_dst}")
 | 
			
		||||
            FileUtils.copy(temp_tar, temp_dst, True)
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    def execute(self):
 | 
			
		||||
        self.update_image()
 | 
			
		||||
        self.context.save_cache_config("res_img", self.context.config_res_img_md5)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
import os.path
 | 
			
		||||
 | 
			
		||||
from scripts.task import Task
 | 
			
		||||
from utils import FileUtils
 | 
			
		||||
from utils.logger_utils import app_logger
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProjectUpdateKeystore(Task):
 | 
			
		||||
 | 
			
		||||
    def update_keystore(self):
 | 
			
		||||
        if not self.context.update_keystore:
 | 
			
		||||
            app_logger().info("No update_keystore")
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        name = os.path.basename(self.context.keystore_path).replace(".keystore", "")
 | 
			
		||||
        target_path = os.path.join(self.context.temp_project_path, name + ".keystore")
 | 
			
		||||
 | 
			
		||||
        if os.path.exists(target_path):
 | 
			
		||||
            os.remove(target_path)
 | 
			
		||||
 | 
			
		||||
        result = FileUtils.copy(self.context.keystore_path, target_path)
 | 
			
		||||
 | 
			
		||||
        app_logger().debug(f"copy keystore result {result}")
 | 
			
		||||
 | 
			
		||||
        target_path = os.path.join(self.context.temp_project_path, "keystore.properties")
 | 
			
		||||
        if os.path.exists(target_path):
 | 
			
		||||
            os.remove(target_path)
 | 
			
		||||
 | 
			
		||||
        open(target_path, "w", encoding="utf-8").write(
 | 
			
		||||
            f"""
 | 
			
		||||
keyAlias={name}
 | 
			
		||||
keyPassword=123456
 | 
			
		||||
storeFile=./{name}.keystore
 | 
			
		||||
storePassword=123456
 | 
			
		||||
"""
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def execute(self):
 | 
			
		||||
        self.update_keystore()
 | 
			
		||||
        self.context.save_cache_config("keystore", self.context.config_keystore_md5)
 | 
			
		||||
        pass
 | 
			
		||||
| 
						 | 
				
			
			@ -5,7 +5,6 @@ class Task:
 | 
			
		|||
 | 
			
		||||
    def __init__(self, context: Context):
 | 
			
		||||
        self.context = context
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def execute(self):
 | 
			
		||||
        pass
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue