# -*- coding:utf-8 -*- import sys import os import xml.etree.ElementTree as ET from V1 import Contants, Exec_Cmd_Utils, HttpUtils, Replace_GameRes_Utils, PrintLog, Json_Util, File, Replace, \ apk_utils, Icon_Util from V1.PrintLog import printlog import random import shutil import yaml import json import traceback import time SUFFIX_BAT = "" DIR_SPLIT = "/" keystorepath = "/opt/packing_tool/keystore/yyrh.jks" storepass = "yyrh123456" keypass = "yyrh123456" alias = "yyrh" def startMerge(config_json_path): global ad_code_list, sp_code, apk_decompile_tmp_dir isSuccess = True errorMsg = "切包失败,请联系开发人员" time_start = time.time() try: sp_code = Json_Util.ReadJson(config_json_path, "sp_code") PrintLog.LOGFILE = "%s/%s.txt" % (Contants.SDK_LOG, sp_code) if os.path.exists(PrintLog.LOGFILE): os.remove(PrintLog.LOGFILE) printlog("[LOGFILE] : %s" % PrintLog.LOGFILE) printlog("[sp_code] : %s" % sp_code) origin_apk_full_path = Json_Util.ReadJson(config_json_path, "sub_apk_path") printlog("[origin_apk_full_path] : %s" % origin_apk_full_path) origin_apk_dir = os.path.dirname(origin_apk_full_path) origin_apk_full_name = os.path.basename(origin_apk_full_path) printlog("[origin_apk_dir] %s" % origin_apk_dir) randomNumber = getRamdomNumber(6) origin_apk_name = os.path.splitext(origin_apk_full_name)[0] apk_decompile_tmp_dir = origin_apk_dir + DIR_SPLIT + randomNumber + DIR_SPLIT + origin_apk_name ad_sdk_type = Json_Util.ReadJson(config_json_path, "ad_sdk_type") printlog("[ad_sdk_type] : %s" % ad_sdk_type) ad_allow_pt = Json_Util.ReadJson(config_json_path, "ad_allow_pt") printlog("[ad_allow_pt] : %s" % ad_allow_pt) ad_code_list = json.dumps(Json_Util.ReadJson(config_json_path, "ad_code_list")) printlog("[ad_code_list] : %s" % ad_code_list) sdk_code = Json_Util.ReadJson(config_json_path, "sdk_code") printlog("[sdk_code] : %s" % sdk_code) channel_map_id = Json_Util.ReadJson(config_json_path, "channel_map_id") printlog("[channel_map_id] : %s" % channel_map_id) # 包名 package_name = Json_Util.ReadJson(config_json_path, "package_name") # sys.argv[3] printlog("[package_name] : %s" % package_name) # 版本号 version = Json_Util.ReadJson(config_json_path, "version") vCode = Json_Util.ReadJson(config_json_path, "v_code") # targetSdkVersion targetSdkVersion = Json_Util.ReadJson(config_json_path, "targetSdkVersion") # icon路径 icon_path = Json_Util.ReadJson(config_json_path, "icon_path") printlog("[icon_path]: %s" % icon_path) unsignedApkPath = "%s_unsigned.apk" % apk_decompile_tmp_dir printlog("unsignedApkPath: %s" % unsignedApkPath) signedApkPath = "%s_signed.apk" % apk_decompile_tmp_dir printlog("signedApkPath: %s" % signedApkPath) zipalignApkPath = "%s_zipaligned.apk" % apk_decompile_tmp_dir printlog("zipalignApkPath: %s" % zipalignApkPath) HttpUtils.notify_sp_cut_state(sp_code, "10", "正在反编译文件", ad_code_list) printlog("----- begin decompile -----") ret = decompile(origin_apk_full_path, apk_decompile_tmp_dir) if ret == 1: errorMsg = "找不到游戏母包,请检查" isSuccess = False printlog(errorMsg) HttpUtils.notify_sp_cut_state(sp_code, "99", errorMsg, ad_code_list) return printlog("----- begin replace params -----") temp_xml_path = apk_decompile_tmp_dir + DIR_SPLIT + "AndroidManifest.xml" printlog("temp_xml_path %s" % temp_xml_path) HttpUtils.notify_sp_cut_state(sp_code, "30", "正在修改SP_CODE", ad_code_list) if ad_allow_pt != 0: add_andriod_node(temp_xml_path, "yyrh_sp_code", sp_code) HttpUtils.notify_sp_cut_state(sp_code, "40", "正在修改合并渠道", ad_code_list) if ad_sdk_type == 2: sdk_name = Json_Util.ReadJson(config_json_path, "sdk_code") printlog("[sdk_name] : %s" % sdk_name) sdk_root_dir = apk_utils.getCutAdSdkRootHome() mergeManifest(sdk_root_dir + DIR_SPLIT + sdk_name + DIR_SPLIT + sdk_name + "_sdk_config.xml", temp_xml_path) mergeResources(sdk_root_dir + DIR_SPLIT + sdk_name + DIR_SPLIT + sdk_name, apk_decompile_tmp_dir) sdkParam = Json_Util.ReadJson(config_json_path, "sdk_param_conf") printlog("channel sdk_param_conf: %s" % str(sdkParam)) # 替换Androidmanifest.xml中的 package_name值为包名 Replace.replaceAM_package_name(temp_xml_path) if sdkParam != None and sdkParam != "": keys = Json_Util.getJsonKeys(sdkParam) for key in keys: printlog(key) # 替换AndroidManifest.xml中的meta-data字段 Replace.replaceAM_Meta_data(temp_xml_path, key, sdkParam[key]) # 替换assets/YyrhAdParam.cnf中的关键字 Replace.replaceAssets_Param( apk_decompile_tmp_dir + DIR_SPLIT + "assets" + DIR_SPLIT + "YyrhAdParam.cnf", key, sdkParam[key]) # 特殊渠道需要特殊处理 # Replace.special_replace(sdk_name, apk_decompile_tmp_dir, DIR_SPLIT, key, sdkParam[key]) pass # 修改包Apk文件名 # 修改包名 if package_name: oldPkgName = getOldPkgName(temp_xml_path) origin_apk_name = origin_apk_name.replace(oldPkgName, package_name) modifyPkgName(temp_xml_path, package_name) pass # 替换Icon printlog("----- begin replace resource -----") HttpUtils.notify_sp_cut_state(sp_code, "50", "正在替换资源", ad_code_list) if icon_path: ret = Icon_Util.replaceIcon(icon_path, apk_decompile_tmp_dir) if not ret: errorMsg = "找不到icon,请检查" printlog(errorMsg) HttpUtils.notify_cut_state(sp_code, "99", errorMsg) # 替换启动页 splash_path = Json_Util.ReadJson(config_json_path, "splash_path") printlog("splash_path:%s" % splash_path) if splash_path is not None: if os.path.exists(splash_path): shutil.copy(splash_path, "%s/assets/yyrh_start_image.jpg" % apk_decompile_tmp_dir) else: errorMsg = "找不到闪屏文件,请检查" isSuccess = False printlog(errorMsg) HttpUtils.notify_sp_cut_state(sp_code, "99", errorMsg, ad_code_list) return # 获取游戏名,版本号,版本名,targetSdkVersion,屏幕方向关键字 gameParam = Json_Util.ReadJson(config_json_path, "meta_config_value") printlog("gameParam:%s" % str(gameParam)) if gameParam: keys = Json_Util.getJsonKeys(gameParam) for key in keys: if key == "APP_NAME": Replace.replaceString_Appname(apk_decompile_tmp_dir, key, gameParam[key]) elif key == "version": version = gameParam[key] elif key == "v_code": vCode = gameParam[key] elif key == "targetSdkVersion": targetSdkVersion = gameParam[key] # 替换方向 elif key == "SDK_ORIENTATION": replaceOrientation = '/bin/sed -i "1,\\$s/sdk_orientation/%s/g" %s' % ( gameParam[key], temp_xml_path) printlog("replaceOrientation: %s" % replaceOrientation) os.system(replaceOrientation) Replace.replaceAssets_Param( apk_decompile_tmp_dir + DIR_SPLIT + "assets" + DIR_SPLIT + "YyrhParam.cnf", "SDK_ORIENTATION", gameParam[key]) pass # 修改版本号 targetSdkVersion printlog("version: %s vCode : %s" % (version, vCode)) printlog("targetSdkVersion: %s" % targetSdkVersion) yml = "%s/apktool.yml" % apk_decompile_tmp_dir if version != None and version != "": Replace.replaceAM_VersionName(yml, version) if vCode != None and vCode != "": Replace.replaceAM_VersionCode(yml, vCode) if targetSdkVersion != None and targetSdkVersion != "": Replace.replaceYml_targetSdkVersion(yml, targetSdkVersion) HttpUtils.notify_sp_cut_state(sp_code, "70", "正在替换游戏资源", ad_code_list) # 替换游戏资源 resourcepaths = Json_Util.ReadJson(config_json_path, "game_resource_replace_path") printlog(str(resourcepaths)) resourceconfigs = Json_Util.ReadJson(config_json_path, "game_resource_config") if resourcepaths is not None: keys = Json_Util.getJsonKeys(resourcepaths) for key in keys: path = resourcepaths[key] printlog(path) if not os.path.exists(path) and path != "": errorMsg = "游戏资源不存在:%s" % path printlog(errorMsg) isSuccess = False HttpUtils.notify_sp_cut_state(sp_code, "99", errorMsg, ad_code_list) return printlog("replace game resource.") Replace_GameRes_Utils.replaceAdGameResource(apk_decompile_tmp_dir, resourcepaths, resourceconfigs) printlog("----- begin recompile apk -----") HttpUtils.notify_sp_cut_state(sp_code, "80", "正在回编译APK", ad_code_list) recompile(apk_decompile_tmp_dir, unsignedApkPath) printlog("unsignedApkPath: %s" % unsignedApkPath) printlog("----- begin resign apk -----") HttpUtils.notify_sp_cut_state(sp_code, "90", "正在重签名,对齐APK", ad_code_list) resignApk(unsignedApkPath, keystorepath, storepass, alias, keypass, signedApkPath) printlog("signedApkPath %s" % signedApkPath) printlog("----- begin zipalign apk -----") zipalignApk(signedApkPath, zipalignApkPath) printlog("zipalignApkPath %s" % zipalignApkPath) dst_dir = "%s%s" % (apk_utils.getSpOutPutDir(), DIR_SPLIT) apkDstPath = "%s%s_%s.apk" % (dst_dir, origin_apk_name, sp_code) printlog("origin_apk_name %s" % origin_apk_name) if ad_allow_pt == 0 and ad_sdk_type == 2: apkDstPath = "%s%s_%s_%s.apk" % (dst_dir, origin_apk_name, sdk_code, channel_map_id) if ad_allow_pt == 0 and ad_sdk_type == 1: apkDstPath = "%s%s_%s_%s.apk" % (dst_dir, origin_apk_name, sdk_code, channel_map_id) if not os.path.exists(dst_dir): os.makedirs(dst_dir) printlog("apkDstPath %s" % apkDstPath) shutil.copyfile(zipalignApkPath, apkDstPath) time_end = time.time() time_c = time_end - time_start printlog("切包总用时:%s秒" % time_c) HttpUtils.notify_sp_cut_state(sp_code, "100", "切包成功(点击复制链接)", ad_code_list) except Exception as err: HttpUtils.notify_sp_cut_state(sp_code, "99", errorMsg, ad_code_list) printlog("cut error occur:%s" % err) isSuccess = False printlog(traceback.format_exc()) finally: if apk_decompile_tmp_dir: File.safeFileDelete(os.path.dirname(apk_decompile_tmp_dir)) return isSuccess def decompile(apkPath, desPath): printlog("start to apkPath %s" % apkPath) global SUFFIX_BAT if os.path.exists(apkPath) == False: printlog("no such apk:%s" % apkPath) return 1 home = apk_utils.getComplieToolsHome() decompileCmd = "%s/apktool%s d -f %s -o %s" % (home, SUFFIX_BAT, apkPath, desPath) printlog("decompileCmd:%s" % decompileCmd) ret = os.system(decompileCmd) printlog("finished decompiling -------------------------") return ret # recompile fileset to apk def recompile(apkPath, outputApkPath): global SUFFIX_BAT printlog("------------------------------ start to recompile ------------------------------ ") global SUFFIX_BAT recompileCmd = "%s/apktool%s -q b %s -o %s" % (apk_utils.getComplieToolsHome(), SUFFIX_BAT, apkPath, outputApkPath) status, output = Exec_Cmd_Utils.exeCommonCmd(recompileCmd) printlog(output) if status != 0: return 1 printlog("------------------------------ finished recompiling ------------------------------ ") return 0 def zipalignApk(unZipalignApk, outputPath=""): printlog("start to zipalign apk ----------------------------") if outputPath == "": point = unZipalignApk.rfind(".") outputPath = unZipalignApk[0:point] + "_zipaligned" + unZipalignApk[point:] printlog("outputPath:%s" % outputPath) zipalignCmd = "%s/zipalign -f 4 %s %s" % (apk_utils.getSdkToolsPath(), unZipalignApk, outputPath) printlog("zipalignCmd:%s" % zipalignCmd) status, output = Exec_Cmd_Utils.exeCommonCmd(zipalignCmd) printlog(output) if status != 0: return 1 printlog("finished zipaligning apk -----------------------------") return 0 def add_andriod_node(xmlpath, key, value): global child ET.register_namespace('android', 'http://schemas.android.com/apk/res/android') tree = ET.parse(xmlpath) root = tree.getroot() node = ET.Element("meta-data") node.set("android:name", key) node.set("android:value", value) node.tail = "\n\t" for i in root.iter("application"): for child in i: pass child.tail = '\n\t\t' for a in root.iter("application"): a.append(node) break tree.write(xmlpath, encoding="utf-8", xml_declaration=True) def getRamdomNumber(size): str = "" for i in range(size): ch = chr(random.randrange(ord('0'), ord('9') + 1)) str += ch return str def resignApk(unsignedApkPath, keystorepath, storepass, alias, keypass, outputPath=""): printlog("start to resign apk -----------------------") if outputPath == "": point = unsignedApkPath.rfind(".") outputPath = unsignedApkPath[0:point] + "_signed" + unsignedApkPath[point:] printlog("signedName:%s" % outputPath) # 获取签名算法 getKeystoreAlgorithmShell = "%s/keytool -list -v -keystore %s -alias %s -storepass %s | /bin/sed -n \"13p\" | /usr/bin/awk -F ': ' '{print $2}'" % ( apk_utils.getJavaBinPath(), keystorepath, alias, storepass) printlog("getKeystoreAlgorithmShell: %s" % getKeystoreAlgorithmShell) ret, algorithm = Exec_Cmd_Utils.exeCommonCmd(getKeystoreAlgorithmShell) algorithmMethod = algorithm.strip() printlog("algorithmMethod: %s" % algorithmMethod) resignCmd = "%s/jarsigner -sigalg %s -digestalg SHA1 -storepass %s -keypass %s -keystore %s -signedjar %s %s %s" % ( apk_utils.getJavaBinPath(), algorithmMethod, storepass, keypass, keystorepath, outputPath, unsignedApkPath, alias) printlog("resignCmd:%s" % resignCmd) status, output = Exec_Cmd_Utils.exeCommonCmd(resignCmd) printlog(output) if status != 0: return 1 printlog("finished resigning apk --------------------------") return 0 def modifyPkgName(temp_xml_path, pkgName): # 修改包名 manifestRoot = ET.parse(temp_xml_path).getroot() if manifestRoot == None: return 1 if pkgName == "": pkgName = manifestRoot.attrib["package"] printlog("get pkg name:%s" % pkgName) else: oldPkgName = manifestRoot.attrib["package"] content = open(temp_xml_path, "r").read() content = content.replace(oldPkgName, pkgName) open(temp_xml_path, "w").write(content) printlog("old pkg name:%s" % oldPkgName) printlog("new pkg name:%s" % pkgName) def getOldPkgName(temp_xml_path): # 修改包名 manifestRoot = ET.parse(temp_xml_path).getroot() if manifestRoot is None: return "" else: oldPkgName = manifestRoot.attrib["package"] return oldPkgName def mergeLibs(sdkPath, apkPath): printlog("start to merge lib --------------------------------") apkLib = "%s/lib" % apkPath sdkLib = "%s/lib" % sdkPath ret = 0 if not os.path.exists(apkLib) and os.path.exists(sdkLib): shutil.copytree(sdkLib, apkLib) printlog("finished merging lib --------------------------------") return ret apkFiles = os.listdir(apkLib) sdkFiles = os.listdir(sdkLib) for cpFile in sdkFiles: if os.path.isfile(cpFile) == False and cpFile not in apkFiles: continue ret = ret | File.copyFiles("%s/lib/%s" % (sdkPath, cpFile), "%s/lib/%s" % (apkPath, cpFile)) printlog("finished merging lib --------------------------------") return ret # sdk resources + apk resources def mergeResources(sdkPath, apkPath): printlog("mergeResources apkPath : %s" % apkPath) printlog("start to merge resources ---------------------------") ret = 0 # merge res printlog("start to merge copyFiles ---------------------------") ret = ret | File.copyFiles("%s/res" % sdkPath, "%s/res" % apkPath) printlog("start to merge mergeLibs ---------------------------") # copy lib and assets ret = ret | mergeLibs(sdkPath, apkPath) printlog("start to merge assets ---------------------------") ret = ret | File.copyFiles("%s/assets" % sdkPath, "%s/assets" % apkPath) printlog("start to merge smali ---------------------------") ret = ret | File.mergeDir("%s/smali" % sdkPath, "%s/smali" % apkPath) printlog("start to merge unknown ---------------------------") ret = ret | File.mergeDir("%s/unknown" % sdkPath, "%s/unknown" % apkPath) # 暂时的方案是用sdk的apktool.yml覆盖原包apktool.yml 后期需要做apktool.yml合并 if os.path.exists("%s/apktool.yml" % sdkPath): printlog("start to merge apktool.yml ---------------------------") mergeYml("%s/apktool.yml" % sdkPath, "%s/apktool.yml" % apkPath) # shutil.copy("%s/apktool.yml"%sdkPath, "%s/apktool.yml"%apkPath) # 修改微信支付 # ret = ret | File.copyFiles("%s/smali"%sdkPath,"%s/smali"%apkPath) printlog("finished merging resources ---------------------------") return ret def mergeManifest(sdkConfigPath, temp_xml_path): printlog("start to merge AndroidManifest.xml -------------------------") ret = 0 ret = ret | File.mergeManifest(temp_xml_path, sdkConfigPath) # 根据配置修改游戏名,包名等 printlog("finished merging AndroidManifest.xml -------------------------") return ret def compileResources(apkPath, tempDir, pkgName=""): printlog("[start to compile resources] -----------------------------------") global SUFFIX_EXE global SUFFIX_BAT if File.copyFiles("%s/res" % apkPath, "%s/res" % tempDir) != 0: printlog("no such files") return 1 if not os.path.exists("%s/gen" % tempDir): os.mkdir("%s/gen" % tempDir) # 修改包名 manifestRoot = ET.parse("%s/AndroidManifest.xml" % apkPath).getroot() if manifestRoot is None: return 1 if pkgName == "": pkgName = manifestRoot.attrib["package"] printlog("get pkg name:%s ----------------------------" % pkgName) else: oldPkgName = manifestRoot.attrib["package"] content = open("%s/AndroidManifest.xml" % apkPath, "r").read() content = content.replace(oldPkgName, pkgName) printlog("content ---------------------------------") printlog(content) open("%s/AndroidManifest.xml" % apkPath, "w").write(content) printlog("old pkg name:%s ----------------------------" % oldPkgName) printlog("new pkg name:%s ----------------------------" % pkgName) # open("tmp/AndroidManifest.xml","w").write(content) # manifestRoot.attrib["package"] = pkgName createRFileCmd = "%s/aapt%s p -m -J %s/gen -M %s/AndroidManifest.xml -I %s/android.jar -S %s/res " % ( apk_utils.getComplieToolsHome(), SUFFIX_EXE, tempDir, apkPath, apk_utils.getToolsJarHome(), tempDir) printlog("createRFileCmd:%s" % createRFileCmd) if os.system(createRFileCmd) != 0: return 1 sourcePath = pkgName.replace(".", "/") createRClassCmd = "%s/javac -encoding utf-8 %s/gen/%s/R.java" % (apk_utils.getJavaBinPath(), tempDir, sourcePath) printlog("createRClassCmd:%s" % createRClassCmd) if os.system(createRClassCmd) != 0: return 1 createDexCmd = "%s/dx%s --dex --output %s/classes.dex %s/gen" % ( apk_utils.getSdkBuildToolPath(), SUFFIX_BAT, tempDir, tempDir) printlog("createDexCmd:%s" % createDexCmd) if os.system(createDexCmd) != 0: return 1 dex2smaliCmd = "%s/java -jar %s/baksmali-2.1.0.jar -o %s/smali %s/classes.dex" % ( apk_utils.getJavaBinPath(), apk_utils.getToolsJarHome(), apkPath, tempDir) printlog("dex2smaliCmd:%s" % dex2smaliCmd) if os.system(dex2smaliCmd) != 0: return 1 printlog("finished compiling resources -----------------------------------") return 0 def mergeYml(fromfile, tofile): if not os.path.exists(fromfile): return False, "origin yaml file is not exists" if not os.path.exists(tofile): return False, "destination yaml file is not exists" header = "!!brut.androlib.meta.MetaInfo\n" fromcontent = open(fromfile, "r").read() fromcontent = fromcontent.replace(header, "") tocontent = open(tofile, "r").read() tocontent = tocontent.replace(header, "") fromroot = yaml.safe_load(fromcontent) toroot = yaml.safe_load(tocontent) if fromroot == None: printlog("from root is empty") return False, "orgin yaml file cant not be parsed or empty" if toroot == None: printlog("to root is empty") return False, "destination yaml file cant not be parsed or is empty" donotcompresskey = "doNotCompress" if donotcompresskey in fromroot: arr = fromroot[donotcompresskey] if not donotcompresskey in toroot: toroot[donotcompresskey] = arr else: toarr = toroot[donotcompresskey] toroot[donotcompresskey] = list(set(toarr + arr)) unknownfileskey = "unknownFiles" if unknownfileskey in fromroot: fromunknownfilesarr = fromroot[unknownfileskey] else: fromunknownfilesarr = {} tounknownfilesarr = toroot[unknownfileskey] toroot[unknownfileskey] = dict(fromunknownfilesarr.items()).update(tounknownfilesarr.items()) desfile = open(tofile, "w") desfile.writelines(header) yaml.safe_dump(toroot, desfile, default_flow_style=False) return True, "" pass