# 安卓游戏打包脚本 # 1.用apktool解包 # 2.复制res资源、assets资源、jniLib资源 # 3.修改包名、app名、渠道文件 # 4.添加app icon、合并AndroidManifest文件 # 5.添加app启动图,修改AndroidManifest文件 # 6.生成新的R文件 # 7.将新的R文件编译,生成dex,再用baksmali生成smali,替换旧的R文件 # 8.将jar资源打包成dex,再用baksmali生成smali,拷贝到smali目录下 # 9.统计方法量,并分割dex(将smali文件拷贝到目录smali_classes2) # 10.用apktool回包 # 11.重新签名 import datetime import importlib import json import os import os.path import re import sys import uuid import zipfile import common_utils import config_utils import file_utils import game_utils import smali_utils import xml_utils ignoreLauncher = ['jm_ysdk', 'jm_yijie', 'jm_quick', 'jm_beiyu', 'jm_xq_jrtt', 'jm_zy_ysdk'] adaptApp = ['yjzx'] startTime = '' def pack(game, sdk, config): config['cache'] = uuid.uuid1() sub_channel = config['subChannel'] print('game = %s, sdk = %s, subChannel = %s, ...' % (game, sdk, sub_channel)) # 解包 print('解包') ret = decomplie(game, sdk, sub_channel, config) if ret: return ret if is_refactor_sdk(config): return pack_v2(game, sdk, sub_channel, config) else: return pack_v1(game, sdk, sub_channel, config) def pack_v1(game, sdk, sub_channel, config): # 删除旧代码 print('删除旧代码') ret = handle_jm_old_code(game, sdk, sub_channel, config) if ret: return ret # 删除一些不支持的属性 print('删除一些不支持的属性') ret = remove_no_support_attr(game, sdk, sub_channel, config) if ret: return ret # 删除一些不支持的配置 print('删除一些不支持的配置') ret = fix_un_support_config(game, sdk, sub_channel, config) if ret: return ret # 合并Drawable-v4目录 ret = merge_drawable_res(game, sdk, sub_channel, config) if ret: return ret # 移除相同的资源 ret = remove_same_values_res(game, sdk, sub_channel, config) if ret: return ret # 复制res资源 ret = copy_res(game, sdk, sub_channel, config) if ret: return ret # 合并主文件 ret = merge_manifest_res(game, sdk, sub_channel, config) if ret: return ret # 替换占位符 ret = change_placeholders(game, sdk, sub_channel, config) if ret: return ret # 添加meta-data ret = add_meta_data(game, sdk, sub_channel, config) if ret: return ret # 增加配置文件 ret = add_v1_config(game, sdk, sub_channel, config) if ret: return ret # 复制app res资源 ret = copy_app_res(game, sdk, sub_channel, config) if ret: return ret # 更改包名 if 'packageName' in config and config['packageName'] != '': ret = change_package_name(game, sdk, sub_channel, config) if ret: return ret # 更改app名 if 'name' in config and config['name'] != '': ret = change_app_name(game, sdk, sub_channel, config) if ret: return ret # 更改app icon if config['changeIcon']: ret = change_app_icon(game, sdk, sub_channel, config) if ret: return ret # 添加启动图操作 if config['addLauncher']: ret = add_launcher(game, sdk, sub_channel, config) if ret: return ret # 添加多图标 # ret = addMoreIcon(game, sdk, subChannel, config) # 复制icon图标到res copy_icon(game, sdk, sub_channel, config) # 打包lib依赖 ret = pack_jar(game, sdk, sub_channel, config) if ret: return ret # 修改继承Application print('修改继承Application') common_utils.change_application(game, sdk, sub_channel, config, 'com.jmhy.sdk.common.JMApplication') # sdk脚本处理 ret = do_sdk_post_script(game, sdk, config) if ret: return ret # 乐变sdk的特殊处理 ret = game_utils.handle_lebian_sdk_manifest(game, sdk, config) if ret: return ret # 游戏脚本处理 ret = do_game_post_script(game, sdk, config) if ret: return ret # 游戏独立处理 ret = do_game_script(game, sdk, config) if ret: return ret # log sdk if 'logSdk' in config: for log in config['logSdk']: ret = add_log_sdk(game, sdk, sub_channel, config, log) if ret: return ret # oaid sdk ret = addOaidSdk(game, sdk, sub_channel, config, "1.0.10") if ret: return ret # 生成R文件 '''ret = generateNewRFile(game, sdk, subChannel, config) if ret: return ret''' # 添加MultiDex支持 if config['splitDex']: ret = split_dex(game, sdk, sub_channel, config) if ret: return ret # 更改版本号 ret = changeVersion(game, sdk, sub_channel, config) if ret: return ret # 更改HardwareAccelerated属性 replace_hardware_accelerated(game, sdk, sub_channel, config) if ret: return ret # 回编译 ret = recomplie(game, sdk, sub_channel, config) if ret: return ret # 对齐apk ret = alignApk(game, sdk, sub_channel, config) if ret: return ret # 签名 ret = apk_signer_apk(game, sdk, sub_channel, config) if ret: return ret # 添加渠道信息 ret = add_channel(game, sdk, sub_channel, config) if ret: return ret copy_v1_apk_2_out_dir(game, sdk, sub_channel, config) if ret: return ret # 清理产生的中间文件 if config['clearCache']: clearTemp(game, sdk, sub_channel, config) endTime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') d1 = datetime.datetime.strptime(endTime, '%Y-%m-%d %H:%M:%S') d2 = datetime.datetime.strptime(startTime, '%Y-%m-%d %H:%M:%S') d = d1 - d2 print('开始时间:' + startTime) print('结束时间:' + endTime) print('用时:{}'.format(config_utils.getTime(d.seconds))) return 0 def pack_v2(game, sdk, sub_channel, config): # 删除旧代码 ret = handle_qingshi_old_code(game, sdk, sub_channel, config) if ret: return ret # 删除一些不支持的属性 ret = remove_no_support_attr(game, sdk, sub_channel, config) if ret: return ret # 删除一些不支持的配置 ret = fix_un_support_config(game, sdk, sub_channel, config) if ret: return ret # 合并Drawable-v4目录 ret = merge_drawable_res(game, sdk, sub_channel, config) if ret: return ret # 移除旧sdk资源 remove_v2_old_res(game, sdk, sub_channel, config) # 移除旧abi文件 remove_v2_old_abi(game, sdk, sub_channel, config) # 移除相同的资源 ret = remove_same_values_res(game, sdk, sub_channel, config) if ret: return ret # 复制res资源 ret = copy_res(game, sdk, sub_channel, config) if ret: return ret # 合并主文件 ret = merge_manifest_res(game, sdk, sub_channel, config) if ret: return ret # 替换占位符 ret = change_placeholders(game, sdk, sub_channel, config) if ret: return ret # 添加meta-data ret = add_meta_data(game, sdk, sub_channel, config) if ret: return ret # 增加配置文件 ret = add_v2_config(game, sdk, sub_channel, config) if ret: return ret # 复制app res资源 ret = copy_app_res(game, sdk, sub_channel, config) if ret: return ret # 更改包名 if 'packageName' in config and config['packageName'] != '': ret = change_package_name(game, sdk, sub_channel, config) if ret: return ret # 更改app名 if 'name' in config and config['name'] != '': ret = change_app_name(game, sdk, sub_channel, config) if ret: return ret # 更改app icon if config['changeIcon']: ret = change_app_icon(game, sdk, sub_channel, config) if ret: return ret # 添加启动图操作 if config['addLauncher']: ret = add_launcher(game, sdk, sub_channel, config) if ret: return ret copy_icon(game, sdk, sub_channel, config) # 打包lib依赖 ret = pack_jar(game, sdk, sub_channel, config) if ret: return ret # sdk脚本处理 ret = do_sdk_post_script(game, sdk, config) if ret: return ret # 乐变sdk的特殊处理 ret = game_utils.handle_lebian_sdk_manifest(game, sdk, config) if ret: return ret # 游戏脚本处理 ret = do_game_post_script(game, sdk, config) if ret: return ret # 游戏独立处理 ret = do_game_script(game, sdk, config) if ret: return ret # log sdk if 'logSdk' in config: for log in config['logSdk']: ret = add_log_sdk(game, sdk, sub_channel, config, log) if ret: return ret # 生成R文件 '''ret = generateNewRFile(game, sdk, subChannel, config) if ret: return ret''' # 添加MultiDex支持 if config['splitDex']: print('重构sdk split smali') decompile_path = file_utils.get_decompile_path(game, sdk, sub_channel, config['cache']) manifest = os.path.join(decompile_path, 'AndroidManifest.xml') application = xml_utils.get_application_attr(manifest, 'name') ret = split_smali(game, sdk, sub_channel, config, application) if ret: return ret # 更改版本号 ret = changeVersion(game, sdk, sub_channel, config) if ret: return ret # 更改HardwareAccelerated属性 replace_hardware_accelerated(game, sdk, sub_channel, config) if ret: return ret # 回编译 ret = recomplie(game, sdk, sub_channel, config) if ret: return ret # 对齐apk ret = alignApk(game, sdk, sub_channel, config) if ret: return ret # 签名 ret = apk_signer_apk(game, sdk, sub_channel, config) if ret: return ret copy_v2_apk_2_out_dir(game, sdk, sub_channel, config) if ret: return ret # 清理产生的中间文件 if config['clearCache']: clearTemp(game, sdk, sub_channel, config) endTime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') d1 = datetime.datetime.strptime(endTime, '%Y-%m-%d %H:%M:%S') d2 = datetime.datetime.strptime(startTime, '%Y-%m-%d %H:%M:%S') d = d1 - d2 print('开始时间:' + startTime) print('结束时间:' + endTime) print('用时:{}'.format(config_utils.getTime(d.seconds))) return 0 def decomplie(game, sdk, sub_channel, config): """ 解包 """ apktoolPath = file_utils.get_apktool_path() cacheGameApk = file_utils.get_cache_game_apk(game, config['random'], sdk) decompliePath = file_utils.get_decompile_path(game, sdk, sub_channel, config['cache']) if os.path.exists(decompliePath): print('delete decomplie folder...') file_utils.delete_folder(decompliePath) print('decomplie apk...') return file_utils.exec_jar_cmd(apktoolPath, 'd -f "%s" -o "%s"' % (cacheGameApk, decompliePath)) def remove_old_code(game, sdk, sub_channel, config): """ 删除旧代码 """ print(config) if config['refactorSdk']: return handle_qingshi_old_code(game, sdk, sub_channel, config) else: return handle_jm_old_code(game, sdk, sub_channel, config) def handle_jm_old_code(game, sdk, sub_channel, config): decompliePath = file_utils.get_decompile_path(game, sdk, sub_channel, config['cache']) codePath = os.path.join(decompliePath, 'smali', 'com', 'jmhy', 'sdk') # file_utils.deleteFolder(codePath) allFiles = [] allFiles = file_utils.list_files(codePath, allFiles) for f in allFiles: fpath, fname = os.path.split(f) # 分离文件名和路径 if fname == 'R.smali' or fname.startswith('R$'): continue os.remove(f) return 0 def handle_qingshi_old_code(game, sdk, sub_channel, config): print('handle_qingshi_old_code') decompile_path = file_utils.get_decompile_path(game, sdk, sub_channel, config['cache']) qingshi_path = os.path.join(decompile_path, 'smali', 'cn', 'qingshi') support_path = os.path.join(decompile_path, 'smali', 'cn', 'yyxx') xi_path = os.path.join(decompile_path, 'smali', 'XI') ali_path = os.path.join(decompile_path, 'smali', 'com', 'alibaba') asus_path = os.path.join(decompile_path, 'smali', 'com', 'asus') bun_path = os.path.join(decompile_path, 'smali', 'com', 'bun') cmic_path = os.path.join(decompile_path, 'smali', 'com', 'cmic') dolin_path = os.path.join(decompile_path, 'smali', 'com', 'dolin') huawei_path = os.path.join(decompile_path, 'smali', 'com', 'huawei') mobile_path = os.path.join(decompile_path, 'smali', 'com', 'mobile') netease_path = os.path.join(decompile_path, 'smali', 'com', 'netease') nirvana_path = os.path.join(decompile_path, 'smali', 'com', 'nirvana') samsung_path = os.path.join(decompile_path, 'smali', 'com', 'samsung') mmkv_path = os.path.join(decompile_path, 'smali', 'com', 'tencent', 'mmkv') zui_path = os.path.join(decompile_path, 'smali', 'com', 'zui') annotation_path = os.path.join(decompile_path, 'smali', 'android', 'support', 'annotation') v4_path = os.path.join(decompile_path, 'smali', 'android', 'support', 'v4') file_utils.delete_folder(qingshi_path) file_utils.delete_folder(support_path) file_utils.delete_folder(xi_path) file_utils.delete_folder(ali_path) file_utils.delete_folder(asus_path) file_utils.delete_folder(bun_path) file_utils.delete_folder(cmic_path) file_utils.delete_folder(dolin_path) file_utils.delete_folder(huawei_path) file_utils.delete_folder(mobile_path) file_utils.delete_folder(netease_path) file_utils.delete_folder(nirvana_path) file_utils.delete_folder(samsung_path) file_utils.delete_folder(mmkv_path) file_utils.delete_folder(zui_path) file_utils.delete_folder(qingshi_path) file_utils.delete_folder(annotation_path) file_utils.delete_folder(v4_path) return 0 def copy_res(game, sdk, sub_channel, config): """ 复制res资源 """ # 拷贝sdk资源 print('copy res...') decompliePath = file_utils.get_decompile_path(game, sdk, sub_channel, config['cache']) sdkPath = file_utils.get_full_sdk_path(sdk) resPath = os.path.join(sdkPath, 'res') decomplieResPath = file_utils.get_full_path(decompliePath, 'res') for d in os.listdir(resPath): copy_res_with_type(resPath, decomplieResPath, d) # 拷贝assets print('copy assets...') assetsPath = file_utils.get_full_path(sdkPath, 'assets') decomplieAssetsPath = file_utils.get_full_path(decompliePath, 'assets') if os.path.exists(assetsPath): ret = file_utils.copy_file_all_dir(assetsPath, decomplieAssetsPath) if ret: return ret # 拷贝jniLib print('copy jniLibs...') jniPath = file_utils.get_full_path(sdkPath, 'jniLibs') decomplieJniPath = file_utils.get_full_path(decompliePath, 'lib') abiFilters = [] if os.path.exists(decomplieJniPath): for abi in os.listdir(decomplieJniPath): # if abi == 'armeabi-v7a' or abi == 'armeabi' or abi == 'arm64-v8a': print('append : ' + abi) abiFilters.append(abi) else: abiFilters = ['armeabi-v7a'] if os.path.exists(jniPath): ret = file_utils.copy_file_all_dir( jniPath, decomplieJniPath, False, abiFilters) if ret: return ret return 0 def merge_drawable_res(game, sdk, sub_channel, config): """ 合并Drawable-v4目录 """ print('merge drawable path...') decompliePath = file_utils.get_decompile_path( game, sdk, sub_channel, config['cache']) resPath = os.path.join(decompliePath, 'res') for path in os.listdir(resPath): # print('res path %s' % path) if (path.startswith('drawable') or path.startswith('mipmap')) and path.endswith('-v4'): # print('merge path %s' % path) v4DrawablePath = os.path.join(resPath, path) drawablePath = os.path.join(resPath, path[:-3]) if os.path.exists(drawablePath): ret = file_utils.copy_file_all_dir( v4DrawablePath, drawablePath, True) if ret: return ret else: os.rename(v4DrawablePath, drawablePath) return 0 def remove_v2_old_res(game, sdk, sub_channel, config): print('remove v2 sdk old res...') decompile_path = file_utils.get_decompile_path(game, sdk, sub_channel, config['cache']) res_path = os.path.join(decompile_path, 'res') public_xml = os.path.join(res_path, 'values', 'public.xml') # 移除public.xml if os.path.exists(public_xml): print('remove public.xml') os.remove(public_xml) sub_folders = os.listdir(res_path) for folder in sub_folders: sub_folder_path = os.path.join(res_path, folder) qs_xml_docs = os.listdir(sub_folder_path) for xml in qs_xml_docs: if xml.startswith('qs_') or xml.startswith('authsdk_'): xml_path = os.path.join(sub_folder_path, xml) print(xml_path) os.remove(xml_path) if xml == 'widget_pns_action_bar.xml' or xml == 'widget_pns_optional_viewgroup.xml' or xml == 'widget_pns_protocol.xml': xml_path = os.path.join(sub_folder_path, xml) print(xml_path) os.remove(xml_path) def remove_same_values_res(game, sdk, sub_channel, config): """ 移除相同的资源 """ # 读取sdk的资源 print('remove same res...') decompile_path = file_utils.get_decompile_path(game, sdk, sub_channel, config['cache']) sdk_path = file_utils.get_full_sdk_path(sdk) sdk_res_path = os.path.join(sdk_path, 'res') print('sdk res path: %s' % sdk_res_path) res_list = [] for path in os.listdir(sdk_res_path): print('path: %s' % path) if not path.startswith('values'): continue abs_path = os.path.join(sdk_res_path, path) print('abs path: %s' % abs_path) for res_file in os.listdir(abs_path): if res_file.endswith('.DS_Store'): continue res_list = xml_utils.read_all_res(os.path.join(abs_path, res_file), res_list) if len(res_list) == 0: print('no same res found') return 0 # 移除相同的资源 resPath = os.path.join(decompile_path, 'res') for path in os.listdir(resPath): if not path.startswith('values'): continue abs_path = os.path.join(resPath, path) for res_file in os.listdir(abs_path): xml_utils.remove_same_values_res(os.path.join(abs_path, res_file), res_list) return 0 def merge_manifest_res(game, sdk, sub_channel, config): """ 合并主文件 """ print('merge AndroidManifest...') decompliePath = file_utils.get_decompile_path(game, sdk, sub_channel, config['cache']) manifest = os.path.join(decompliePath, 'AndroidManifest.xml') sdkPath = file_utils.get_full_sdk_path(sdk) libManifest = file_utils.get_full_path(sdkPath, 'manifest.xml') return xml_utils.merge_manifest_res(manifest, libManifest) def copy_app_res(game, sdk, sub_channel, config): """ 拷贝app的资源,比如app icon、启动图等 """ random = config['random'] channelPath = file_utils.getSubChannelPath(game, sdk, random, sub_channel) decompliePath = file_utils.get_decompile_path( game, sdk, sub_channel, config['cache']) # assets print('copy assets...') # 适配一剑斩仙 if game in adaptApp: print('适配一剑斩仙...') decomplieAssetsPath = file_utils.get_full_path(decompliePath, 'assets') skinZipPath = os.path.join(decomplieAssetsPath, 'skin.zip') skinPath = os.path.join(decomplieAssetsPath, 'skin') if os.path.exists(skinZipPath): with zipfile.ZipFile(skinZipPath) as zf: zf.extractall(decomplieAssetsPath) print('create unzip skin') assetsPath = file_utils.get_full_path(channelPath, 'assets') decomplieAssetsPath = file_utils.get_full_path( decompliePath, 'assets') if os.path.exists(assetsPath): ret = file_utils.copy_file_all_dir( assetsPath, decomplieAssetsPath) with zipfile.ZipFile(skinZipPath, 'w') as z: for root, dirs, files in os.walk(skinPath): for single_file in files: filepath = os.path.join(root, single_file) print('create zip ---> ' + filepath) temPath = 'skin/' + single_file z.write(filepath, temPath) z.close() else: print('normal copy assets...') assetsPath = file_utils.get_full_path(channelPath, 'assets') decomplieAssetsPath = file_utils.get_full_path( decompliePath, 'assets') if os.path.exists(assetsPath): ret = file_utils.copy_file_all_dir( assetsPath, decomplieAssetsPath) if ret: return ret else: print('normal copy assets...') assetsPath = file_utils.get_full_path(channelPath, 'assets') decomplieAssetsPath = file_utils.get_full_path(decompliePath, 'assets') if os.path.exists(assetsPath): ret = file_utils.copy_file_all_dir(assetsPath, decomplieAssetsPath) if ret: return ret # icon print('copy icon...') ret = copy_app_res_with_type(decompliePath, channelPath, 'icon') if ret: return ret # 启动图 print('copy splash...') ret = copy_app_res_with_type(decompliePath, channelPath, 'splash') if ret: return ret # 其他图片 print('copy image...') ret = copy_app_res_with_type(decompliePath, channelPath, 'image') if ret: return ret return 0 def copy_app_res_with_type(decompile_path, channel_path, type_name): decomplieResPath = os.path.join(decompile_path, 'res') iconPath = os.path.join(channel_path, type_name) if not os.path.exists(iconPath): print('dir "%s" not exists' % iconPath) return 0 for d in os.listdir(iconPath): ret = copy_res_with_type(iconPath, decomplieResPath, d) if ret: return ret return 0 def copy_res_with_type(res_path, decompile_res_path, type_name): # aapt的打包目录会带-v4后缀 resDir = os.path.join(res_path, type_name) target = os.path.join(decompile_res_path, type_name) targetV4 = os.path.join(decompile_res_path, type_name + '-v4') if not os.path.exists(target) and not os.path.exists(targetV4): os.makedirs(target) return file_utils.copy_file_all_dir(resDir, target, False) elif not os.path.exists(target): return file_utils.copy_file_all_dir(resDir, targetV4, False) else: return file_utils.copy_file_all_dir(resDir, target, False) def remove_no_support_attr(game, sdk, sub_channel, config): """ 删除一些不支持的属性 """ decompile_path = file_utils.get_decompile_path(game, sdk, sub_channel, config['cache']) manifest = os.path.join(decompile_path, 'AndroidManifest.xml') xml_utils.remove_root_attr(manifest, 'compileSdkVersion') xml_utils.remove_root_attr(manifest, 'compileSdkVersionCodename') return 0 def fix_un_support_config(game, sdk, sub_channel, config): """ 删除一些不支持的配置 """ # 检查minSdkVersion decompile_path = file_utils.get_decompile_path(game, sdk, sub_channel, config['cache']) yml = os.path.join(decompile_path, 'apktool.yml') min_sdk_version = 21 file_utils.change_min_sdk_version(yml, min_sdk_version) res_path = os.path.join(decompile_path, 'res') tag = '-v' for res in os.listdir(res_path): # print('res = ' + res) if res.startswith('values') and tag in res: start = res.index(tag) version = res[start + len(tag):] if not version.isdigit(): continue version = int(version) print('version = %d' % version) if version < min_sdk_version: un_support_path = os.path.join(res_path, res) print('un_support_path = ' + un_support_path) file_utils.delete_folder(un_support_path) print('deleteFolder = ' + un_support_path) return 0 def change_package_name(game, sdk, sub_channel, config): """ 更改包名 """ # 全局替换AndroidManifest里面的包名 newPackageName = config['packageName'] decompliePath = file_utils.get_decompile_path(game, sdk, sub_channel, config['cache']) manifest = os.path.join(decompliePath, 'AndroidManifest.xml') packageName = xml_utils.get_package_name(manifest) config['oldPackageName'] = packageName xml_utils.change_package_name(manifest, newPackageName) print('change package name %s --> %s' % (packageName, newPackageName)) return 0 def change_app_name(game, sdk, sub_channel, config): """ 更改app名 """ # 生成string.xml文件 name = config['name'] resName = 'common_sdk_name' if 'outName' in config: resName = resName + '_' + config['outName'] decompliePath = file_utils.get_decompile_path( game, sdk, sub_channel, config['cache']) stringFile = os.path.join(decompliePath, 'res', 'values', 'sdk_strings_temp.xml') content = '%s' % ( resName, name) file_utils.create_file(stringFile, content) # 修改主文件的app名的值 manifest = os.path.join(decompliePath, 'AndroidManifest.xml') xml_utils.change_app_name(manifest, '@string/%s' % resName) print('change app name %s' % name) return 0 def change_app_icon(game, sdk, subChannel, config): ''' 更改app icon ''' print('change app icon...') resName = 'common_sdk_icon' decompliePath = file_utils.get_decompile_path( game, sdk, subChannel, config['cache']) manifest = os.path.join(decompliePath, 'AndroidManifest.xml') xml_utils.change_app_icon(manifest, '@mipmap/%s' % resName) return 0 def add_meta_data(game, sdk, subChannel, config): ''' 添加meta-data ''' if 'metaData' not in config: return 0 print('add meta-data...') decompliePath = file_utils.get_decompile_path( game, sdk, subChannel, config['cache']) manifest = os.path.join(decompliePath, 'AndroidManifest.xml') xml_utils.addMetaData(manifest, config['metaData']) return 0 def add_v1_config(game, sdk, sub_channel, config): """ 添加config.json """ if 'configData' not in config: return 0 print('add config.json...') decompile_path = file_utils.get_decompile_path(game, sdk, sub_channel, config['cache']) configJson = os.path.join(decompile_path, 'assets', 'jmhy_config.json') jsonText = json.dumps(config['configData'], ensure_ascii=False) file_utils.create_file(configJson, jsonText) return 0 def add_v2_config(game, sdk, sub_channel, config): """ 添加config.json """ if 'configData' not in config: return 0 print('add config.json...') decompile_path = file_utils.get_decompile_path(game, sdk, sub_channel, config['cache']) config_json = os.path.join(decompile_path, 'assets', 'qs_game', 'qs_analytics.json') if os.path.exists(config_json): os.remove(config_json) json_text = json.dumps(config['configData']['channel_sdk_list'], ensure_ascii=False) file_utils.create_file(config_json, json_text) return 0 def change_placeholders(game, sdk, sub_channel, config): """ 处理掉占位符 """ if 'placeholders' not in config: return 0 decompliePath = file_utils.get_decompile_path(game, sdk, sub_channel, config['cache']) manifest = os.path.join(decompliePath, 'AndroidManifest.xml') placeholders = config['placeholders'] for placeholder in placeholders: oldText = '${%s}' % placeholder newText = placeholders[placeholder] print('change placeholder %s -> %s' % (oldText, newText)) file_utils.replace_content(manifest, oldText, newText) return 0 def add_launcher(game, sdk, subChannel, config): ''' 添加启动图 ''' # ysdk的特殊处理 if sdk in ignoreLauncher: return 0 random = config['random'] channelPath = file_utils.getSubChannelPath(game, sdk, random, subChannel) splashPath = os.path.join(channelPath, 'splash') if len(os.listdir(splashPath)) == 0: print('dir splash is empty') return 0 print('add launcher...') decompliePath = file_utils.get_decompile_path( game, sdk, subChannel, config['cache']) manifest = os.path.join(decompliePath, 'AndroidManifest.xml') activity = xml_utils.get_launcher_activity_name(manifest) if activity == 'com.jmhy.sdk.template.LauncherActivity': print('add launcher already exist...') return 1 # 添加关联资源 internalPath = file_utils.getFullInternalPath() ret = copy_app_res_with_type(decompliePath, internalPath, 'launcher_res') if ret: return ret # 拷贝代码 print('copy launcher code...') codePath = os.path.join(internalPath, 'launcher_code', 'smali') smaliPath = file_utils.get_full_path(decompliePath, 'smali') ret = file_utils.copy_file_all_dir(codePath, smaliPath) if ret: return ret # 修改主文件信息 print('change launcher config...') orientation = xml_utils.get_screen_orientation(manifest) activity = xml_utils.remove_launcher_activity(manifest) xml_utils.add_launcher_activity( manifest, orientation, 'com.jmhy.sdk.template.LauncherActivity') # 修改跳转的 launcherActivity = os.path.join( decompliePath, 'smali', 'com', 'jmhy', 'sdk', 'template', 'LauncherActivity.smali') file_utils.replace_content(launcherActivity, '{class}', activity) print('change launcher %s to %s' % (activity, 'com.jmhy.sdk.template.LauncherActivity')) # config['oldLauncher'] = activity if 'launcherTime' in config: timeHex = formatHex(config['launcherTime']) file_utils.replace_content(launcherActivity, '0x0BB8', timeHex) return 0 def addMoreIcon(game, sdk, subChannel, config): ''' 添加多个图标 ''' print('add more icon support...') decompliePath = file_utils.get_decompile_path( game, sdk, subChannel, config['cache']) icon = '@mipmap/common_sdk_icon' if not config['changeIcon']: manifest = os.path.join(decompliePath, 'AndroidManifest.xml') icon = xml_utils.get_application_attr(manifest, 'icon') switchIcon = icon if config['switchIcon']: switchIcon = '@mipmap/common_sdk_icon2' manifest = os.path.join(decompliePath, 'AndroidManifest.xml') return xml_utils.add_more_icon(manifest, icon, switchIcon) def copy_icon(game, sdk, subChannel, config): ''' 复制icon到res ,一键登录使用 ''' if config['changeIcon']: decompliePath = file_utils.get_decompile_path(game, sdk, subChannel, config['cache']) decomplieResPath = file_utils.get_full_path(decompliePath, 'res') iconPath = os.path.join(decomplieResPath, 'mipmap-xhdpi', 'common_sdk_icon.png') desPath = os.path.join( decomplieResPath, 'drawable-hdpi', 'jm_cmcc_icon.png') file_utils.copy_file(iconPath, desPath) else: decompliePath = file_utils.get_decompile_path(game, sdk, subChannel, config['cache']) decomplieResPath = file_utils.get_full_path(decompliePath, 'res') desPath = os.path.join(decomplieResPath, 'drawable-hdpi', 'jm_cmcc_icon.png') manifest = os.path.join(decompliePath, 'AndroidManifest.xml') icon = xml_utils.get_application_attr(manifest, 'icon') tag = icon[1:].split("/") if len(tag) < 1: return hdpi = ['-xhdpi', '-xxhdpi', '-xxxhdpi'] for h in hdpi: p1 = '%s%s' % (tag[0], h) png = tag[1] + ".png" iconPath = os.path.join(decomplieResPath, p1, png) if os.path.exists(iconPath): print('iconPath = ' + iconPath) file_utils.copy_file(iconPath, desPath) break def formatHex(millisecond): ''' 将毫秒转为16进制,4位格式 ''' timeHex = str(hex(millisecond)).upper() timeHex = timeHex[2:] formatHex = '' if len(timeHex) == 3: formatHex = '0x0' + timeHexaddLauncher elif len(timeHex) == 4: formatHex = '0x' + timeHex else: formatHex = '0x0BB8' return formatHex def do_sdk_post_script(game, sdk, config): """ 执行sdk相关特殊处理脚本 """ sdkPath = file_utils.get_full_sdk_path(sdk) scriptPath = os.path.join(sdkPath, 'script') targetScript = os.path.join(scriptPath, 'sdk_script.py') if not os.path.exists(targetScript): print('sdk_script no exists') return 0 print('do sdk post script...') sys.path.append(scriptPath) module = importlib.import_module('sdk_script') ret = module.execute(game, sdk, config) sys.path.remove(scriptPath) return ret def do_game_post_script(game, sdk, config): """ 执行游戏相关特殊处理脚本 """ channelPath = file_utils.get_full_game_path(game) scriptPath = os.path.join(channelPath, 'script') targetScript = os.path.join(scriptPath, 'game_script.py') if not os.path.exists(targetScript): print('game_script no exists') return 0 print('doGamePostScript...') sys.path.append(scriptPath) module = importlib.import_module('game_script') ret = module.execute(game, sdk, config) sys.path.remove(scriptPath) return ret def do_game_script(game, sdk, config): """ 执行游戏相关特殊处理脚本 """ channelPath = file_utils.get_full_path('game_script', game) targetScript = os.path.join(channelPath, 'game_script.py') print(targetScript + "--------") if not os.path.exists(targetScript): print('game_script no exists') return 0 print('doGameScript...') sys.path.append(channelPath) module = importlib.import_module('game_script') ret = module.execute(game, sdk, config) sys.path.remove(channelPath) return ret def add_log_sdk(game, sdk, sub_channel, config, logSdk): # 拷贝jniLibs decompliePath = file_utils.get_decompile_path(game, sdk, sub_channel, config['cache']) print('add log sdk, sdk:' + sdk) if sdk == 'jm_beta_sdk' or sdk == 'beta_sdk': sdk_path = file_utils.get_full_log_sdk_path(logSdk, True) elif sdk == 'qingshi': sdk_path = file_utils.get_full_log_sdk_v2_path(logSdk) else: sdk_path = file_utils.get_full_log_sdk_path(logSdk) print('copy log jniLibs...') jni_path = file_utils.get_full_path(sdk_path, 'jniLibs') decomplie_jni_path = file_utils.get_full_path(decompliePath, 'lib') abiFilters = [] if os.path.exists(decomplie_jni_path): for abi in os.listdir(decomplie_jni_path): if abi == 'armeabi-v7a' or abi == 'x86' or abi == 'x86_64' or abi == 'arm64-v8a': abiFilters.append(abi) else: abiFilters = ['armeabi-v7a'] if os.path.exists(jni_path): ret = file_utils.copy_file_all_dir(jni_path, decomplie_jni_path, False, abiFilters) if ret: return ret print('merge log AndroidManifest...') libManifest = file_utils.get_full_path(sdk_path, 'manifest.xml') if os.path.exists(libManifest): manifest = os.path.join(decompliePath, 'AndroidManifest.xml') ret = xml_utils.merge_manifest_res(manifest, libManifest) if ret: return ret change_placeholders(game, sdk, sub_channel, config) return pack_log_jar(game, sdk, sub_channel, config, logSdk) def addOaidSdk(game, sdk, subChannel, config, oaidVersion): sdkPath = file_utils.getFullOaidSDKPath(oaidVersion) decompliePath = file_utils.get_decompile_path( game, sdk, subChannel, config['cache']) # 拷贝assets print('copy oaid assets...') assetsPath = file_utils.get_full_path(sdkPath, 'assets') decomplieAssetsPath = file_utils.get_full_path(decompliePath, 'assets') if os.path.exists(assetsPath): ret = file_utils.copy_file_all_dir(assetsPath, decomplieAssetsPath) if ret: return ret # 拷贝jniLibs print('copy oaid jniLibs...') jniPath = file_utils.get_full_path(sdkPath, 'jniLibs') decomplieJniPath = file_utils.get_full_path(decompliePath, 'lib') abiFilters = [] if os.path.exists(decomplieJniPath): for abi in os.listdir(decomplieJniPath): if abi == 'armeabi-v7a' or abi == 'armeabi': abiFilters.append(abi) else: abiFilters = ['armeabi-v7a'] if os.path.exists(jniPath): ret = file_utils.copy_file_all_dir( jniPath, decomplieJniPath, False, abiFilters) if ret: return ret print('merge oaid AndroidManifest...') libManifest = file_utils.get_full_path(sdkPath, 'manifest.xml') if os.path.exists(libManifest): manifest = os.path.join(decompliePath, 'AndroidManifest.xml') ret = xml_utils.merge_manifest_res(manifest, libManifest) if ret: return ret return packOaidJar(game, sdk, subChannel, config, oaidVersion) def generateNewRFile(game, sdk, subChannel, config): """ 生成新的R文件 """ decompliePath = file_utils.get_decompile_path( game, sdk, subChannel, config['cache']) androidPlatforms = file_utils.get_android_compile_tool_path() manifest = os.path.join(decompliePath, 'AndroidManifest.xml') decomplieResPath = file_utils.get_full_path(decompliePath, 'res') compliePath = file_utils.get_full_path(decompliePath, 'gen') if not os.path.exists(compliePath): os.makedirs(compliePath) # 生成R文件 print('生成R文件 ...') if config['aapt2disable']: aapt = file_utils.get_aapt_path() ret = file_utils.get_exec_permission(aapt) if ret: return ret createRCmd = '"%s" p -f -m -J "%s" -S "%s" -I "%s" -M "%s"' % ( aapt, compliePath, decomplieResPath, androidPlatforms, manifest) ret = file_utils.exec_format_cmd(createRCmd) if ret: return ret else: # compile aapt = file_utils.get_aapt2_path() ret = file_utils.get_exec_permission(aapt) if ret: return ret print('compiled res ...') complieResPath = os.path.join(compliePath, 'compiled') complieResCmd = '"%s" compile --dir "%s" -o "%s"' % ( aapt, decomplieResPath, complieResPath) file_utils.exec_format_cmd(complieResCmd) # unzip print('unzip compiled res ...') unzipResPath = os.path.join(compliePath, 'aapt2_res') with zipfile.ZipFile(complieResPath) as zf: zf.extractall(unzipResPath) print('create unzip %s' % unzipResPath) # link print('link res ...') outApk = os.path.join(compliePath, 'res.apk') linkResCmd = '"%s" link -o "%s" -I "%s" --manifest "%s" --java "%s" --auto-add-overlay' % ( aapt, outApk, androidPlatforms, manifest, compliePath) for filename in os.listdir(unzipResPath): linkResCmd += ' %s' % filename print('link cmd len is %s' % len(linkResCmd)) ret = file_utils.exec_format_cmd(linkResCmd, unzipResPath) if ret: return ret # 编译R文件 print('complie R.java ...') packageName = xml_utils.get_package_name(manifest) packagePath = file_utils.get_package_path(compliePath, packageName) RSourceFile = os.path.join(packagePath, 'R.java') complieRCmd = 'javac -source 1.8 -target 1.8 -encoding UTF-8 "%s"' % RSourceFile ret = file_utils.exec_format_cmd(complieRCmd) if ret: return ret # 生成dex print('dex R.class ...') outDex = os.path.join(compliePath, 'classes.dex') if config['aapt2disable']: dx = file_utils.get_dx_path() dexCmd = '--dex --no-warning --output="%s" "%s"' % ( outDex, compliePath) else: dx = file_utils.get_d8_path() clazz = os.path.join(packagePath, '*.class') dexCmd = '--lib "%s" --output "%s" %s' % ( androidPlatforms, compliePath, clazz) ret = file_utils.exec_jar_cmd(dx, dexCmd) if ret: return ret # 反向dex生成smali # 存放在out目录 print('baksmali classes.dex ...') baksmaliPath = file_utils.get_baksmali_path() outPath = file_utils.get_full_path(decompliePath, 'out') ret = file_utils.exec_jar_cmd( baksmaliPath, 'd "%s" -o "%s"' % (outDex, outPath)) if ret: return ret # 将生成的文件拷贝到目标目录 print('copy R.smali ...') smaliPath = file_utils.get_full_path(decompliePath, 'smali') file_utils.copy_file_all_dir(outPath, smaliPath) return 0 def pack_jar(game, sdk, sub_channel, config): """ 打包所有的jar """ split_dex = config['splitDex'] decompile_path = file_utils.get_decompile_path( game, sdk, sub_channel, config['cache']) outPath = file_utils.get_full_path(decompile_path, 'gen') if not os.path.exists(outPath): os.makedirs(outPath) if config['aapt2disable']: dx = file_utils.get_dx_path() dex_cmd = '--dex --no-warning --output="%s"' % outPath else: dx = file_utils.get_d8_path() androidPlatforms = file_utils.get_android_compile_tool_path() dex_cmd = '--lib "%s" --output "%s"' % (androidPlatforms, outPath) # 找到所有lib依赖 sdk_path = file_utils.get_full_sdk_path(sdk) libs = os.path.join(sdk_path, 'libs') libConfig = os.path.join(libs, 'config.json') # 存在配置文件 if os.path.exists(libConfig): jsonText = file_utils.read_file(libConfig) libConf = json.loads(jsonText) if 'libConfig' in config and config['libConfig'] in libConf: conf = config['libConfig'] libList = libConf[conf] for jar in libList: dex_cmd += ' ' + os.path.join(libs, jar) elif 'default' in libConf: libList = libConf['default'] for jar in libList: dex_cmd += ' ' + os.path.join(libs, jar) else: for jar in os.listdir(libs): if not jar.endswith('.jar'): continue dex_cmd += ' ' + os.path.join(libs, jar) else: for jar in os.listdir(libs): if not jar.endswith('.jar'): continue dex_cmd += ' ' + os.path.join(libs, jar) if 'refactorSdk' in config and config['refactorSdk']: pass else: # multidex.jar if split_dex: print('getMultiDexPath ...') dex_cmd += ' ' + file_utils.get_multidex_path() # sdk实现类 dex_cmd += ' ' + os.path.join(sdk_path, '%s.jar' % sdk) print('packaging all jar ...') ret = file_utils.exec_jar_cmd(dx, dex_cmd) if ret: return ret # 反向dex生成smali # 存放在out目录 print('baksmali classes.dex ...') outDex = os.path.join(outPath, 'classes.dex') baksmaliPath = file_utils.get_baksmali_path() outPath = file_utils.get_full_path(decompile_path, 'out') ret = file_utils.exec_jar_cmd(baksmaliPath, 'd "%s" -o "%s"' % (outDex, outPath)) if ret: return ret # 将生成的文件拷贝到目标目录 print('copy all smali ...') smaliPath = file_utils.get_full_path(decompile_path, 'smali') ret = file_utils.copy_file_all_dir(outPath, smaliPath, True) if ret: return ret return 0 def pack_log_jar(game, sdk, sub_channel, config, log_sdk): """ 打包Log jar """ decompliePath = file_utils.get_decompile_path(game, sdk, sub_channel, config['cache']) outPath = file_utils.get_full_path(decompliePath, 'gen') if not os.path.exists(outPath): os.makedirs(outPath) if config['aapt2disable']: dx = file_utils.get_dx_path() dexCmd = '--dex --multi-dex --no-warning --output="%s"' % outPath else: dx = file_utils.get_d8_path() androidPlatforms = file_utils.get_android_compile_tool_path() dexCmd = '--lib "%s" --output "%s"' % (androidPlatforms, outPath) # 找到所有lib依赖 if sdk == 'jm_beta_sdk' or sdk == 'beta_sdk': sdk_path = file_utils.get_full_log_sdk_path(log_sdk, True) elif sdk == 'qingshi': sdk_path = file_utils.get_full_log_sdk_v2_path(log_sdk) else: sdk_path = file_utils.get_full_log_sdk_path(log_sdk) libs = os.path.join(sdk_path, 'libs') lib_config = os.path.join(libs, 'config.json') # 存在配置文件 if os.path.exists(lib_config): json_text = file_utils.read_file(lib_config) lib_list = json.loads(json_text) for jar in lib_list: if not jar.endswith('.jar'): continue dexCmd += ' ' + os.path.join(libs, jar) else: for jar in os.listdir(libs): if not jar.endswith('.jar'): continue dexCmd += ' ' + os.path.join(libs, jar) # sdk实现类 print('packaging all log jar ...') if sdk != 'qingshi': dexCmd += ' ' + os.path.join(sdk_path, '%s.jar' % log_sdk) ret = file_utils.exec_jar_cmd(dx, dexCmd) if ret: return ret # 反向dex生成smali # 存放在out目录 print('baksmali classes.dex ...') outDex = os.path.join(outPath, 'classes.dex') baksmaliPath = file_utils.get_baksmali_path() outPath = file_utils.get_full_path(decompliePath, 'out') ret = file_utils.exec_jar_cmd( baksmaliPath, 'd "%s" -o "%s"' % (outDex, outPath)) if ret: return ret # 将生成的文件拷贝到目标目录 print('copy all log smali ...') smaliPath = file_utils.get_full_path(decompliePath, 'smali') ret = file_utils.copy_file_all_dir(outPath, smaliPath, True) if ret: return ret return 0 def packOaidJar(game, sdk, subChannel, config, oaidVerion): ''' 打包oaid jar ''' decompliePath = file_utils.get_decompile_path( game, sdk, subChannel, config['cache']) outPath = file_utils.get_full_path(decompliePath, 'gen') if not os.path.exists(outPath): os.makedirs(outPath) if config['aapt2disable']: dx = file_utils.get_dx_path() dexCmd = '--dex --multi-dex --no-warning --output="%s"' % outPath else: dx = file_utils.get_d8_path() androidPlatforms = file_utils.get_android_compile_tool_path() dexCmd = '--lib "%s" --output "%s"' % (androidPlatforms, outPath) # 找到所有lib依赖 sdkPath = file_utils.getFullOaidSDKPath(oaidVerion) libs = os.path.join(sdkPath, 'libs') libConfig = os.path.join(libs, 'config.json') # 存在配置文件 # if os.path.exists(libConfig): # jsonText = file_utils.readFile(libConfig) # libList = json.loads(jsonText) # for jar in libList: # if not jar.endswith('.jar'): # continue # dexCmd += ' ' + os.path.join(libs, jar) # else: # for jar in os.listdir(libs): # if not jar.endswith('.jar'): # continue # dexCmd += ' ' + os.path.join(libs, jar) # sdk实现类 print('packageing oaid jar ...') dexCmd += ' ' + os.path.join(libs, 'miit_mdid_%s.jar' % oaidVerion) ret = file_utils.exec_jar_cmd(dx, dexCmd) if ret: return ret # 反向dex生成smali # 存放在out目录 print('baksmali classes.dex ...') outDex = os.path.join(outPath, 'classes.dex') baksmaliPath = file_utils.get_baksmali_path() outPath = file_utils.get_full_path(decompliePath, 'out') ret = file_utils.exec_jar_cmd( baksmaliPath, 'd "%s" -o "%s"' % (outDex, outPath)) if ret: return ret # 将生成的文件拷贝到目标目录 print('copy all log smali ...') smaliPath = file_utils.get_full_path(decompliePath, 'smali') ret = file_utils.copy_file_all_dir(outPath, smaliPath, True) if ret: return ret return 0 def split_dex(game, sdk, sub_channel, config): """ 分割dex """ # 判断是否已经存在application # 存在,则往原application添加内容 # 不存在,则拷贝一个默认的android.support.multidex.MultiDexApplication print('add MultiDex support...') decompile_path = file_utils.get_decompile_path(game, sdk, sub_channel, config['cache']) manifest = os.path.join(decompile_path, 'AndroidManifest.xml') application = xml_utils.get_application_attr(manifest, 'name') if application is None: ret = xml_utils.change_application_attr(manifest, 'name', 'android.support.multidex.MultiDexApplication') if ret: return ret else: smaliPath = os.path.join(decompile_path, 'smali') application_file = file_utils.get_package_path(smaliPath, application) application_file += '.smali' ret = change_application_dex(application_file) if ret: return ret return split_smali(game, sdk, sub_channel, config, application) def change_application_dex(file): """ 修改application的smali文件,增加MultiDex操作 """ index = file_utils.get_application_smali_index(file) file_utils.insert_application_smali(file, index) return 0 def split_smali(game, sdk, sub_channel, config, application): """ 如果函数上限超过限制,自动拆分smali,以便生成多个dex文件 """ print('splitSmali...') decompliePath = file_utils.get_decompile_path(game, sdk, sub_channel, config['cache']) smaliPath = os.path.join(decompliePath, 'smali') appPackage = None if application: appPackage = application[:application.rfind('.')] appPackage = appPackage.replace('.', '/') allFiles = [] allFiles = file_utils.list_files(smaliPath, allFiles) # print('file count is %d' % len(allFiles)) # maxFuncNum = 65535 # 留一点空间,防止计算误差 maxFuncNum = 64000 currFucNum = 0 totalFucNum = 0 currDexIndex = 1 allRefs = [] # 保证Application等类在第一个classex.dex文件中 for f in allFiles: f = f.replace('\\', '/') if (appPackage and appPackage in f) or '/android/support/multidex' in f: currFucNum += smali_utils.get_smali_method_count(f, allRefs) totalFucNum = currFucNum for f in allFiles: f = f.replace('\\', '/') if not f.endswith('.smali'): continue if (appPackage and appPackage in f) or '/android/support/multidex' in f: continue thisFucNum = smali_utils.get_smali_method_count(f, allRefs) totalFucNum += thisFucNum # print('%d # %d ==> %s' % (thisFucNum, currDexIndex, f)) # print('totalFucNum is %d' % totalFucNum) if currFucNum + thisFucNum >= maxFuncNum: currFucNum = thisFucNum currDexIndex += 1 newDexPath = os.path.join( decompliePath, 'smali_classes%d' % currDexIndex) os.makedirs(newDexPath) else: currFucNum += thisFucNum if currDexIndex > 1: newDexPath = os.path.join( decompliePath, 'smali_classes%d' % currDexIndex) targetFile = newDexPath + f[len(smaliPath):] file_utils.copy_file(f, targetFile, True) return 0 def changeVersion(game, sdk, sub_channel, config): """ 更改版本号 """ decompliePath = file_utils.get_decompile_path( game, sdk, sub_channel, config['cache']) yml = os.path.join(decompliePath, 'apktool.yml') versionCode = None versionName = None targetSdkVersion = None if 'versionCode' in config: versionCode = config['versionCode'] if 'versionName' in config: versionName = config['versionName'] if 'targetSdkVersion' in config: targetSdkVersion = config['targetSdkVersion'] else: targetSdkVersion = 26 print('changeVersion versionCode=%s,versionName=%s,targetSdkVersion=%s' % (versionCode, versionName, targetSdkVersion)) return file_utils.changeVersion(yml, versionCode, versionName, targetSdkVersion) def recomplie(game, sdk, subChannel, config): ''' 回编译 ''' print('recomplie apk...') apktoolPath = file_utils.get_apktool_path() decompliePath = file_utils.get_decompile_path( game, sdk, subChannel, config['cache']) outApk = file_utils.getOutApkPath(game, sdk, subChannel, config['cache']) useAppt2 = ' --use-aapt2' if config['aapt2disable']: useAppt2 = '' return file_utils.exec_jar_cmd(apktoolPath, 'b -f "%s" -o "%s"%s' % (decompliePath, outApk, useAppt2)) def alignApk(game, sdk, subChannel, config): ''' 对齐apk ''' print('align apk...') outApk = file_utils.getOutApkPath(game, sdk, subChannel, config['cache']) alignApk = file_utils.getAlignApkPath( game, sdk, subChannel, config['cache']) alignapkTool = file_utils.get_zipalign_path() if os.path.exists(alignApk): os.remove(alignApk) ret = file_utils.get_exec_permission(alignapkTool) if ret: return ret # zipalign.exe -v -p 4 input.apk output.apk return file_utils.exec_format_cmd('"%s" -f -p 4 "%s" "%s"' % (alignapkTool, outApk, alignApk)) def apk_signer_apk(game, sdk, sub_channel, config): """ 签名apk """ print('sign apk...') print('game = %s, sdk = %s, subChannel = %s, ...' % (game, sdk, sub_channel)) path = os.path.join(file_utils.get_current_path(), 'keystore', 'key.json') jsonText = file_utils.read_file(path) signConfig = json.loads(jsonText) keystore = {} for key in signConfig.keys(): print(key) if game.find(key) > -1 or game == key: if sdk in signConfig[key]: keystore = signConfig[key][sdk] break else: keystore = signConfig['default'] else: keystore = signConfig['default'] # if game in signConfig: # if sdk in signConfig[game]: # keystore = signConfig[game][sdk] # else: # keystore = signConfig['default'] # else: # keystore = signConfig['default'] print('storeFile is "%s"' % keystore['storeFile']) apksigner = file_utils.get_apksigner_path() alignApk = file_utils.getAlignApkPath( game, sdk, sub_channel, config['cache']) signedApk = file_utils.get_signed_apk_path( game, sdk, sub_channel, config['cache']) storeFile = os.path.join(file_utils.get_current_path(), 'keystore', keystore['storeFile']) # java -jar apksigner.jar sign --ks key.jks --ks-key-alias releasekey --ks-pass pass:pp123456 --key-pass pass:pp123456 --out output.apk input.apk v2disable = '' if 'v2disable' in config and config['v2disable']: v2disable = ' --v2-signing-enabled=false' return file_utils.exec_jar_cmd(apksigner, 'sign%s --ks "%s" --ks-key-alias %s --ks-pass pass:%s --key-pass pass:%s --out "%s" "%s"' % ( v2disable, storeFile, keystore['keyAlias'], keystore['storePassword'], keystore['keyPassword'], signedApk, alignApk)) def add_channel(game, sdk, sub_channel, config): """ 添加渠道信息 """ if 'v2disable' in config and config['v2disable']: return 0 walle = file_utils.getWallePath() signedApk = file_utils.get_signed_apk_path(game, sdk, sub_channel, config['cache']) walleApk = file_utils.getWalleApkPath(game, sdk, sub_channel, config['cache']) properties = config['properties'] appid = '' appkey = '' host = '' if 'appid' in properties: appid = properties['appid'] if 'appkey' in properties: appkey = properties['appkey'] if 'host' in properties: host = properties['host'] return file_utils.exec_jar_cmd(walle, 'put -e version=%s,agent=%s,appid=%s,appkey=%s,host=%s "%s" "%s"' % ( config_utils.getDate(), properties['agent'], appid, appkey, host, signedApk, walleApk)) def clearTemp(game, sdk, sub_channel, config): """ 清空中间产生的文件 """ print('clear temp...') targetApkPath = file_utils.getTargetApkPath(game, sdk, config['cache']) if os.path.exists(targetApkPath): file_utils.delete_folder(targetApkPath) decompliePath = file_utils.get_decompile_path( game, sdk, sub_channel, config['cache']) if os.path.exists(decompliePath): file_utils.delete_folder(decompliePath) random = config['random'] channelPath = file_utils.getChannelPath(game, random, sdk) if os.path.exists(channelPath): file_utils.delete_folder(channelPath) print('clear temp end') def packConsoleInput(): ''' 控制台打包 ''' if len(sys.argv) < 3: print('argument is missing') return 1 # 校验参数 game = sys.argv[1] sdk = sys.argv[2] # 可选参数,没有则默认打全部渠道 subChannel = None if len(sys.argv) > 3: subChannel = sys.argv[3] return packConsole(game, sdk, subChannel) def packConsole(game, sdk, config, subChannel): """ 控制台打包 """ cache_game_apk_path = file_utils.get_cache_game_apk(game, config['random'], sdk) print('cache game apk path %s' % cache_game_apk_path) if not os.path.exists(cache_game_apk_path): print('game "%s" not exists' % game) return 1 if not os.path.exists(file_utils.get_full_sdk_path(sdk)): print('sdk "%s" not exists' % sdk) return 1 global startTime startTime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') # 读取配置 random = config['random'] channelPath = file_utils.getChannelPath(game, random, sdk) configPath = os.path.join(channelPath, 'config.json') if not os.path.exists(configPath): print('%s not exists' % configPath) return 1 jsonText = file_utils.read_file(configPath) config = json.loads(jsonText) # 检查参数 if not config_utils.checkConfig(config): return 1 # 处理参数 config_utils.replaceArgs(config) successCount = 0 failureCount = 0 if type(config) == dict: if subChannel is None or config['subChannel'] == subChannel: ret = pack(game, sdk, config) if ret: failureCount += 1 else: successCount += 1 else: print('subChannel "%s" no found' % subChannel) return 1 elif type(config) == list: found = False for itemConfig in config: if subChannel is None or itemConfig['subChannel'] == subChannel: found = True ret = pack(game, sdk, itemConfig) if ret: failureCount += 1 else: successCount += 1 if not found: print('subChannel "%s" no found' % subChannel) return 1 print('success %d, failure %d' % (successCount, failureCount)) subChannelPath = os.path.join(channelPath, subChannel) print('------subChannelPath 目录清空:%s -------' % subChannelPath) file_utils.delete_folder(subChannelPath) return 0 def formatXml(game, sdk, subChannel, config): decompliePath = file_utils.get_decompile_path( game, sdk, subChannel, config['cache']) manifest = os.path.join(decompliePath, 'AndroidManifest.xml') return xml_utils.formatXml(manifest) def copy_v1_apk_2_out_dir(game, sdk, sub_channel, config): walleApk = file_utils.getWalleApkPath(game, sdk, sub_channel, config['cache']) releaseApkPath = "" if 'outName' in config and 'outPath' in config: if not os.path.exists(config['outPath']): os.makedirs(config['outPath']) releaseApkPath = os.path.join(config['outPath'], config['outName'] + '.apk') elif 'outName' in config: releaseApkPath = file_utils.getRenameApkPath(game, sdk, config['cache'], config['outName']) print('------生成正式包路径 %s -------' % releaseApkPath) file_utils.copy_file(walleApk, releaseApkPath) def copy_v2_apk_2_out_dir(game, sdk, sub_channel, config): signed_apk = file_utils.get_signed_apk_path(game, sdk, sub_channel, config['cache']) release_apk_path = "" if 'outName' in config and 'outPath' in config: if not os.path.exists(config['outPath']): os.makedirs(config['outPath']) release_apk_path = os.path.join(config['outPath'], config['outName'] + '.apk') elif 'outName' in config: release_apk_path = file_utils.getRenameApkPath(game, sdk, config['cache'], config['outName']) print('------生成正式包路径 %s -------' % release_apk_path) file_utils.copy_file(signed_apk, release_apk_path) def replace_hardware_accelerated(game, sdk, subChannel, config): decompliePath = file_utils.get_decompile_path(game, sdk, subChannel, config['cache']) manifest = os.path.join(decompliePath, 'AndroidManifest.xml') lines = open(manifest).readlines() fp = open(manifest, "w") isReplaced = False for l in lines: if isReplaced == False and l.find("android:hardwareAccelerated=") >= 0: isReplace = True l = re.sub("android:hardwareAccelerated=\"false\"", "android:hardwareAccelerated=\"true\"", l) fp.write(l) fp.close() pass def remove_v2_old_abi(game, sdk, sub_channel, config): print('remove v2 old abi...') decompile_path = file_utils.get_decompile_path(game, sdk, sub_channel, config['cache']) lib_path = os.path.join(decompile_path, 'lib') abi_folders = os.listdir(lib_path) for abi_folder in abi_folders: abi_folder_path = os.path.join(lib_path, abi_folder) abi_files = os.listdir(abi_folder_path) for abi in abi_files: if abi == 'libalicomphonenumberauthsdk_core.so': abi_path = os.path.join(abi_folder_path, abi) print(abi_path) os.remove(abi_path) if abi == 'libauth_number_product-2.12.1-log-online-standard-release_alijtca_plus.so': abi_path = os.path.join(abi_folder_path, abi) print(abi_path) os.remove(abi_path) if abi == 'libdolin-zap.so': abi_path = os.path.join(abi_folder_path, abi) print(abi_path) os.remove(abi_path) if abi == 'libmmkv.so': abi_path = os.path.join(abi_folder_path, abi) print(abi_path) os.remove(abi_path) if abi == 'libqsgamesdk.so': abi_path = os.path.join(abi_folder_path, abi) print(abi_path) os.remove(abi_path) if abi == 'libsecsdk.so': abi_path = os.path.join(abi_folder_path, abi) print(abi_path) os.remove(abi_path) def openFile(file, mode): return open(file, mode, encoding='UTF-8') def is_refactor_sdk(config) -> bool: return 'refactorSdk' in config and config['refactorSdk']