from print_log import printlog import os import file_utils, apk_tool, path_utils, contants, xml_utils, merge_share_sdk from xml.etree import ElementTree as ET androidNS = 'http://schemas.android.com/apk/res/android' def pack(apk_decompile_tmp_dir, channel_path, package_name, config): printlog('删剔除母包融合SDK代码(cn/yyxx)') ret = remove_yyxx_old_code(apk_decompile_tmp_dir) if ret: return ret printlog('删除一些不支持的属性') ret = remove_no_support_attr(apk_decompile_tmp_dir) if ret: return ret printlog('修改minSdkVersion为21,删除res里仅支持21以下的资源目录') change_un_support_config(apk_decompile_tmp_dir) printlog('修改关于反编译后不符合要求的资源目录 eg:drawable-hdpi-v4') ret = merge_darwable_res_end_with_v4(apk_decompile_tmp_dir) if ret: return ret printlog('移除母包SDK的res资源') ret = remove_old_res(apk_decompile_tmp_dir) if ret: return ret printlog('移除母包SDK的.so文件') remove_old_abi(apk_decompile_tmp_dir) if ret: return ret printlog('移除打包目录中values的重复资源') ret = remove_same_values_res(apk_decompile_tmp_dir, channel_path) if ret: return ret printlog('复制渠道SDK资源') ret = copy_res(apk_decompile_tmp_dir, channel_path) if ret: return ret # 合并主文件 printlog('合并Androidmanifest.xml 文件') sdk_base_xml = os.path.join(channel_path, "SDKManifest.xml") apk_decom_dir_manifest_xml = os.path.join(apk_decompile_tmp_dir, 'AndroidManifest.xml') ret = mergeManifest(apk_decom_dir_manifest_xml, sdk_base_xml) if ret: return ret if 'WEIBO_SHARE' in config or 'QQ_SHARE' in config or 'WECHAT_SHARE' in config: merge_share_sdk.start_merge(apk_decompile_tmp_dir, config) # 修改${applicationId} printlog('修改替换AndroidManifest.xml中${applicationId}关键字') temp_manifest_path = os.path.join(apk_decompile_tmp_dir, 'AndroidManifest.xml') file_utils.replace_content(temp_manifest_path, '${applicationId}', package_name) # 生成R文件 printlog('生成R文件') ret = apk_tool.create_R_file(apk_decompile_tmp_dir, package_name) if ret: return ret def remove_yyxx_old_code(apk_decompile_tmp_dir): yyxx_code_path = os.path.join(apk_decompile_tmp_dir, 'smali', 'cn', 'yyxx') file_utils.safeFileDelete(yyxx_code_path) return 0 def remove_no_support_attr(apk_decompile_tmp_dir): """ 删除一些不支持的属性 """ manifest = os.path.join(apk_decompile_tmp_dir, 'AndroidManifest.xml') xml_utils.remove_root_attr(manifest, 'compileSdkVersion') xml_utils.remove_root_attr(manifest, 'compileSdkVersionCodename') return 0 def change_un_support_config(apk_decompile_tmp_dir): """ 默认最低版本为21 1.修改 minSdkVersion 为21 2.删除res里仅支持21以下的资源目录 :param apk_decompile_tmp_dir: :return: """ min_sdk_version = 21 yml = os.path.join(apk_decompile_tmp_dir, 'apktool.yml') file_utils.change_min_sdk_version(yml, min_sdk_version) res_path = os.path.join(apk_decompile_tmp_dir, '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) printlog('version = %d' % version) if version < min_sdk_version: un_support_path = os.path.join(res_path, res) file_utils.delete_folder(un_support_path) printlog('[un_support_path] : %s \n has been deleted ' % un_support_path) def merge_darwable_res_end_with_v4(apk_decompile_tmp_dir): """ 修改关于反编译后不符合要求的资源目录 eg:drawable-hdpi-v4 :param apk_decompile_tmp_dir: :return: """ res_path = os.path.join(apk_decompile_tmp_dir, 'res') for path in os.listdir(res_path): # 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(res_path, path) drawablePath = os.path.join(res_path, 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_old_res(apk_decompile_tmp_dir): """ 该步骤未完善,考虑如果覆盖res资源,即可不用执行 :param apk_decompile_tmp_dir: :return: """ res_path = os.path.join(apk_decompile_tmp_dir, 'res') temp_public_xml = os.path.join(res_path, 'values', 'public.xml') if os.path.exists(temp_public_xml): printlog('remove sdk node in public.xml ') temp_public_xml_tree = ET.parse(temp_public_xml) root = temp_public_xml_tree.getroot() for node in list(root): name = node.attrib.get('name') if name.startswith('yyxx_ui_') or name.startswith('yyxx_comm'): root.remove(node) temp_public_xml_tree.write(temp_public_xml, 'UTF-8') sub_folders = os.listdir(res_path) for folder in sub_folders: sub_folder_path = os.path.join(res_path, folder) res_xml_docs = os.listdir(sub_folder_path) for xml in res_xml_docs: if xml.startswith('yyxx_ui_') or xml.startswith('yyxx_comm'): xml_path = os.path.join(sub_folder_path, xml) print(xml_path) os.remove(xml_path) return 0 def remove_old_abi(apk_decompile_tmp_dir): libs_name = ['libdolin-zap.so', 'libmmkv.so', 'libsecsdk.so', 'libyyxxgame.so'] lib_path = os.path.join(apk_decompile_tmp_dir, 'lib') if not os.path.exists(lib_path): return 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 in libs_name: abi_path = os.path.join(abi_folder_path, abi) os.remove(abi_path) printlog("remove [abi_path]:%s" % abi_path) def remove_same_values_res(apk_decompile_tmp_dir, channel_path): channel_res_path = os.path.join(channel_path, 'res') temp_res_path = os.path.join(apk_decompile_tmp_dir, 'res') res_list = [] for value_dir in os.listdir(channel_res_path): if not value_dir.startswith('values'): continue value_path = os.path.join(channel_res_path, value_dir) for res_file in os.listdir(value_path): if res_file.endswith('.DS_Store'): continue # if res_file.startswith('hnyy_') or res_file.startswith('xy_') or res_file.startswith( # 'xy_') or res_file.startswith('qj_') \ # or res_file.startswith('xinrui_') or res_file.startswith('yyxx_comm_'): res_list = xml_utils.read_all_res(os.path.join(value_path, res_file), res_list) if len(res_list) == 0: printlog('no same res found') return 0 # 移除相同的资源 for path in os.listdir(temp_res_path): if not path.startswith('values'): continue abs_path = os.path.join(temp_res_path, 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 copy_res(apk_decompile_tmp_dir, channel_path): sdk_res_path = os.path.join(channel_path, 'res') printlog('[sdk_res_path]: %s ' % sdk_res_path) temp_res_path = os.path.join(apk_decompile_tmp_dir, 'res') printlog('[temp_res_path]: %s ' % temp_res_path) printlog('begin copy sdk res.') for d in os.listdir(sdk_res_path): copy_res_with_type(sdk_res_path, temp_res_path, d) # 拷贝assets printlog('copy assets...') sdk_assets_path = os.path.join(channel_path, 'assets') temp_assets_path = os.path.join(apk_decompile_tmp_dir, 'assets') # 拷贝smali_classes2 printlog('copy smali_classes2...') sdk_smali_path = os.path.join(channel_path, 'smali_classes2') temp_smali_path = os.path.join(apk_decompile_tmp_dir, 'smali_classes2') if os.path.exists(sdk_smali_path): ret = file_utils.copy_file_all_dir(sdk_smali_path, temp_smali_path) if ret: return ret if os.path.exists(sdk_assets_path): ret = file_utils.copy_file_all_dir(sdk_assets_path, temp_assets_path) if ret: return ret # 拷贝jniLib printlog('copy lib...') temp_jni_path = os.path.join(apk_decompile_tmp_dir, 'lib') sdk_jni_path = os.path.join(channel_path, 'jniLibs') if not os.path.exists(temp_jni_path): printlog('There is no need to merge lib .[temp_jni_path:]:%s' % temp_jni_path) return 0 abiFilters = [] if os.path.exists(temp_jni_path): for abi in os.listdir(temp_jni_path): printlog('append : ' + abi) abiFilters.append(abi) else: abiFilters = ['armeabi-v7a'] if os.path.exists(sdk_jni_path): ret = file_utils.copy_file_all_dir( sdk_jni_path, temp_jni_path, False, abiFilters) 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.copyAllFile(resDir, target) elif not os.path.exists(target): return file_utils.copyAllFile(resDir, targetV4) else: return file_utils.copyAllFile(resDir, target) def merge_manifest_aar_to_sdk(manifest_from, manifest_to): """ Merge aars AndroidManifest.xml to the sdk SdkManifest.xml """ if not os.path.exists(manifest_to) or not os.path.exists(manifest_from): print("the manifest file is not exists.manifestTo:%s;manifestFrom:%s", manifest_to, manifest_from) return False ET.register_namespace('android', androidNS) targetTree = ET.parse(manifest_to) # 获取xml根节点 targetRoot = targetTree.getroot() ET.register_namespace('android', androidNS) aarTree = ET.parse(manifest_from) aarRoot = aarTree.getroot() f = open(manifest_to) targetContent = f.read() f.close() permissionConfigNode = targetRoot.find('permissionConfig') for child in list(aarRoot): # 子节点对应的values,uses-feature,uses-permission,permission key = '{' + androidNS + '}name' val = child.get(key) if val != None and len(val) > 0: attrIndex = targetContent.find(val) # values不存在添加 if -1 == attrIndex: permissionConfigNode.append(child) aarAppNode = aarRoot.find('application') sdkAppConfigNode = targetRoot.find('applicationConfig') if aarAppNode is not None: for aarChild in list(aarAppNode): key = '{' + androidNS + '}name' val = aarChild.get(key) if val != None and len(val) > 0: attrIndex = targetContent.find(val) if -1 == attrIndex: sdkAppConfigNode.append(aarChild) targetTree.write(manifest_to, 'UTF-8') return True def merge_manifest_res(apk_decompile_tmp_dir, channel_path, sdk_name, platform): """ 合并AndroidManifest.xml文件 """ sdk_manifest_path = os.path.join(channel_path, sdk_name + '_sdk_config.xml') temp_manifest_path = os.path.join(apk_decompile_tmp_dir, 'AndroidManifest.xml') platform_manifest_path = os.path.join(platform, 'platform_sdk_config.xml') ret = file_utils.mergeManifest(sdk_manifest_path, temp_manifest_path) if ret: return ret ret = file_utils.mergeManifest(platform_manifest_path, temp_manifest_path) if ret: return ret def pack_jar(apk_decompile_tmp_dir, channel_path, platform): """ 打包所有的jar :param platform: :param apk_decompile_tmp_dir: :param channel_path: :param sdk_name: :return: """ temp_gen_path = os.path.join(apk_decompile_tmp_dir, 'gen') print('[temp_gen_path]: %s ' % temp_gen_path) if not os.path.exists(temp_gen_path): os.makedirs(temp_gen_path) if contants.is_use_aapt2: dx = path_utils.get_dx_path() dex_cmd = '--dex --no-warning --output="%s"' % temp_gen_path else: dx = path_utils.get_d8_path() android_jar = path_utils.get_android_compile_tool_path() dex_cmd = '--lib "%s" --output "%s"' % (android_jar, temp_gen_path) # 找到所有lib依赖,编译成class.dex sdk_lib_path = os.path.join(channel_path, 'lib') platform_lib_path = os.path.join(platform, 'lib') lib_list = '' for sdk_lib in file_utils.iterate_dir_path(sdk_lib_path): if sdk_lib.endswith('.DS_Store'): continue lib_list += ' ' + sdk_lib for platform_lib in file_utils.iterate_dir_path(platform_lib_path): if platform_lib.endswith('.DS_Store'): continue lib_list += ' ' + platform_lib dex_cmd = dex_cmd + lib_list printlog('packaging all jar ...') ret = apk_tool.exec_jar_cmd(dx, dex_cmd) if ret: return ret printlog('baksmali classes.dex ...') out_dex = os.path.join(temp_gen_path, 'classes.dex') bak_smali_path = path_utils.get_baksmali_path() out_smali_path = os.path.join(temp_gen_path, 'out') ret = apk_tool.exec_jar_cmd(bak_smali_path, 'd "%s" -o "%s"' % (out_dex, out_smali_path)) if ret: return ret # 将生成的文件拷贝到目标目录 print('copy all smali ...') temp_smali_path = os.path.join(apk_decompile_tmp_dir, 'smali') ret = file_utils.copy_file_all_dir(out_smali_path, temp_smali_path, True) if ret: return ret def mergeManifest(targetManifest, sdkManifest): """ Merge sdk SdkManifest.xml to the apk AndroidManifest.xml """ if not os.path.exists(targetManifest) or not os.path.exists(sdkManifest): printlog("the manifest file is not exists.targetManifest:%s;sdkManifest:%s" % (targetManifest, sdkManifest)) return 1 ET.register_namespace('android', androidNS) targetTree = ET.parse(targetManifest) targetRoot = targetTree.getroot() ET.register_namespace('android', androidNS) sdkTree = ET.parse(sdkManifest) sdkRoot = sdkTree.getroot() f = open(targetManifest) targetContent = f.read() f.close() permissionConfigNode = sdkRoot.find('permissionConfig') if permissionConfigNode is not None and len(permissionConfigNode) > 0: for child in list(permissionConfigNode): key = '{' + androidNS + '}name' val = child.get(key) if val != None and len(val) > 0: attrIndex = targetContent.find(val) if -1 == attrIndex: targetRoot.append(child) appConfigNode = sdkRoot.find('applicationConfig') appNode = targetRoot.find('application') if appConfigNode is not None: for child in list(appConfigNode): appNode.append(child) targetTree.write(targetManifest, 'UTF-8') return 0 def remove_public_tag_node(public_path): if os.path.exists(public_path): printlog('remove sdk node in public.xml ') temp_public_xml_tree = ET.parse(public_path) root = temp_public_xml_tree.getroot() for node in list(root): name = node.attrib.get('name') if name.startswith('yyxx_ui_') or name.startswith('yyxx_comm'): root.remove(node) temp_public_xml_tree.write(public_path, 'UTF-8') if __name__ == "__main__": remove_public_tag_node("/Users/kaiweicai/Desktop/apk/public.xml")