def edit_yml(channel, decompile_dir): LogUtils.info('-----> EditYML <------') path = decompile_dir + '/apktool.yml' yml_file = open(path, encoding='utf-8') content = '' while True: line = yml_file.readline() if line.find('- assets/') == 0: yml_file.tell() elif line.find('targetSdkVersion') != -1: config = Utils.get_local_config() if config is not None and config['TARGET_SDK_VERSION'] != '0': content += ' targetSdkVersion: \'' + config[ 'TARGET_SDK_VERSION'] + '\'\n' else: content += line elif line.find('versionCode') != -1 and len( channel['gameVersionCode']) > 0: content += ' versionCode: \'' + channel['gameVersionCode'] + '\'\n' elif line.find('versionName') != -1 and len( channel['gameVersionName']) > 0: content += ' versionName: ' + channel['gameVersionName'] + '\n' else: if not line: break line.replace('\r', '') if line.strip() != '': if line.find('doNotCompress:') == 0: yml_file.tell() content += 'doNotCompress:\n- assets/*\n' else: content += line LogUtils.info("apktool.yml:\n%s", content) with open(path, 'w', encoding='utf-8') as f: f.write(content)
def merge_manifest(decompile_dir, sdk_dir): target_manifest = os.path.join(decompile_dir, 'AndroidManifest.xml') sdk_manifest = os.path.join(sdk_dir, 'SDKManifest.xml') if not os.path.exists(target_manifest) or not os.path.exists(sdk_manifest): LogUtils.error( 'the manifest file is not exists.\n targetManifest: %s\n sdkManifest: %s', target_manifest, sdk_manifest) return 1 ET.register_namespace('android', androidNS) target_tree = ET.parse(target_manifest) target_root = target_tree.getroot() # 去除manifest标签中compileSdkVersion属性 attrs = target_root.attrib target_root.attrib = {} for s in attrs: if s.find('compileSdkVersion') < 0: target_root.set(s, attrs[s]) ET.register_namespace('android', androidNS) sdk_tree = ET.parse(sdk_manifest) sdk_root = sdk_tree.getroot() f = open(target_manifest, 'r', encoding='utf-8') target_content = f.read() f.close() permission_config_node = sdk_root.find('permissionConfig') if permission_config_node is not None and len(permission_config_node) > 0: for child in list(permission_config_node): key = '{' + androidNS + '}name' val = child.get(key) if val is not None and len(val) > 0: if -1 == target_content.find(val): target_root.append(child) app_config_node = sdk_root.find('applicationConfig') app_node = target_root.find('application') if app_config_node is not None: proxy_application_name = app_config_node.get('proxyApplication') if proxy_application_name is not None and len( proxy_application_name) > 0: meta_node = SubElement(app_node, 'meta-data') key = '{' + androidNS + '}name' val = '{' + androidNS + '}value' meta_node.set(key, 'SS_APPLICATION_PROXY_NAME') meta_node.set(val, proxy_application_name) for child in list(app_config_node): target_root.find('application').append(child) target_tree.write(target_manifest, xml_declaration=True, encoding='utf-8', method='xml') LogUtils.info('merge manifest file success.') return 0
def generate_r_file(package_name, decompile_dir): # check_value_resources(decompile_dir) temp_path = os.path.dirname(decompile_dir) + '/temp' LogUtils.debug('generate R:the temp path is %s', temp_path) if os.path.exists(temp_path): Utils.del_file(temp_path) os.makedirs(temp_path) res_path = decompile_dir + '/res' temp_res_path = temp_path + '/res' Utils.copy_file(res_path, temp_res_path) gen_path = temp_path + '/gen' os.makedirs(gen_path) aapt_path = Utils.get_full_path('tools/aapt2.exe') android_path = Utils.get_full_path('tools/android.jar') java = Utils.get_full_path('tools/jdk/bin/java') javac = Utils.get_full_path('tools/jdk/bin/javac') manifest_path = decompile_dir + '/AndroidManifest.xml' res_flat_path = temp_path + "/res_flat.zip" cmd = '%s compile -o %s --dir %s' % (aapt_path, res_flat_path, temp_res_path) ret = Utils.exec_cmd(cmd) if ret: return 1 cmd = '%s link -o %s --manifest %s -I %s --java %s %s' % ( aapt_path, temp_path + '/res.apk', manifest_path, android_path, gen_path, res_flat_path) ret = Utils.exec_cmd(cmd) if ret: return 1 LogUtils.info('package_name:%s', package_name) r_path = package_name.replace('.', '/') r_path = gen_path + '/' + r_path + '/R.java' cmd = '%s -source 1.8 -target 1.8 -encoding UTF-8 %s' % (javac, r_path) ret = Utils.exec_cmd(cmd) if ret: return 1 dex_path = temp_path + '/classes.dex' dex_tool_path = Utils.get_full_path('tools/dx.jar') cmd = '%s -jar %s --dex --output %s %s' % (java, dex_tool_path, dex_path, gen_path) ret = Utils.exec_cmd(cmd) if ret: return 1 smali_path = decompile_dir + '/smali' ret = dex2smali(dex_path, smali_path, '') if ret: return 1 return 0
def sign_apk(game, apk_file): key_path = Utils.get_full_path('games/' + game['id'] + '/keystore/' + game['keystore']) if not os.path.exists(key_path): LogUtils.info('the keystore file not exists: %s', key_path) return 1 LogUtils.info('the keystore file is %s', key_path) aapt = Utils.get_full_path('tools/aapt.exe') lcmd = "%s list %s" % (aapt, apk_file) out = Utils.exec_cmd2(lcmd) if out is not None and len(out) > 0: for filename in out.split('\n'): if filename.find('META-INF') == 0: rmcmd = "%s remove %s %s" % (aapt, apk_file, filename) Utils.exec_cmd(rmcmd) jar_signer = Utils.get_full_path('tools/jdk/bin/jarsigner') sign_cmd = "%s -keystore %s -storepass %s -keypass %s %s %s -sigalg SHA1withRSA -digestalg SHA1" % ( jar_signer, key_path, game['keypwd'], game['aliaspwd'], apk_file, game['alias']) return Utils.exec_cmd(sign_cmd)
def write_developer_properties(game, channel, target_file_path): config = get_local_config() if config is None: return 1 pro_str = 'YINHU_SDK_VERSION_CODE=' + channel['sdkVersionName'] + '\n' pro_str = pro_str + 'YINHU_APPID=' + game['id'] + '\n' pro_str = pro_str + 'YINHU_APPKEY=' + game['key'] + '\n' pro_str = pro_str + 'YINHU_Channel=' + channel['channelId'] + '\n' pro_str = pro_str + 'YINHU_AUTH_URL=' + config['YINHU_AUTH_URL'] + '\n' pro_str = pro_str + 'DEBUG_MODES=' + channel['debug'] + '\n' if channel['sdkParams'] is not None and len(channel['sdkParams']) > 0: for param in channel['sdkParams']: if param['writeIn'] == '2': pro_str = pro_str + param['name'] + '=' + param['value'] + '\n' LogUtils.info('the develop info is:\n%s', pro_str) target_file = open(target_file_path, 'w', encoding='utf-8') target_file.write(pro_str) target_file.close() return 0
def set_channel_params(channel, sdk_params): config_file = get_full_path('channelsdk/' + channel['sdk'] + '/config.xml') if not os.path.exists(config_file): LogUtils.error(' => the %s config.xml is not exists path: %s', channel['name'], config_file) return False try: tree = ET.parse(config_file) root = tree.getroot() channel['sdkParams'] = [] param_nodes = root.find('params') for param_node in param_nodes: param = {} param['name'] = param_node.get('name') key = param_node.get('name') if key in sdk_params and sdk_params[key] is not None: param['value'] = sdk_params[key] else: LogUtils.info(' => the sdk %s have a new parameter: %s', channel['name'], key) param['value'] = "" param['showName'] = param_node.get('showName') param['writeIn'] = param_node.get('writeIn') channel['sdkParams'].append(param) channel['plugins'] = [] plugin_nodes = root.find('plugins') for p_node in plugin_nodes: p = {} p['name'] = p_node.get('name') p['type'] = p_node.get('type') channel['plugins'].append(p) version_node = root.find('version') version_update_time = version_node.find('updateTime') channel['sdkUpdateTime'] = version_update_time.text version_name_node = version_node.find('versionName') channel['sdkVersionName'] = version_name_node.text return True except Exception as e: LogUtils.error(' => can not parse config.xml: %s', config_file) LogUtils.error('parse xml exception!\n%s', e.__str__()) return False
def get_app_icon_name(decompile_dir): try: ET.register_namespace('android', androidNS) tree = ET.parse(decompile_dir + '/AndroidManifest.xml') root = tree.getroot() application = root.find('application') key = '{' + androidNS + '}icon' icon_name = application.get(key) LogUtils.info('=>AndroidManifest key iconName: %s', icon_name) if icon_name is None: name = 'ic_launcher' else: name = icon_name.split('/')[-1] application.set(key, '@mipmap/' + name) tree.write(decompile_dir + '/AndroidManifest.xml', xml_declaration=True, encoding='utf-8', method='xml') return name except Exception as e: LogUtils.error("get_app_icon_name exception") LogUtils.error('parse xml exception!\n%s', e.__str__()) return None
def exec_cmd(cmd): try: LogUtils.info('*********************cmd start***********************') cmd = cmd.replace('\\', '/') cmd = re.sub('/+', '/', cmd) st = subprocess.STARTUPINFO st.dwFlags = subprocess.STARTF_USESHOWWINDOW st.wShowWindow = subprocess.SW_HIDE LogUtils.info('cmd: %s', cmd) sub = subprocess.run(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) code, std, err = sub.returncode, sub.stdout, sub.stderr if code: LogUtils.error('===>exec Fail<===') LogUtils.error("\n" + err.decode('gbk')) else: # LogUtils.info(std.decode('gbk')) LogUtils.info('===>exec success<===') return code except Exception as e: LogUtils.error('===>exec Fail<===') LogUtils.error('Exception:' + e.__str__()) return 1 finally: LogUtils.info('*********************cmd end***********************')
def run(self): # 开启任务,发送打包信号 self.signal.signal.emit(self.channel['channelId'], 0, "正在打包......", 0) # 清空已有的workspace work_dir = Utils.get_full_path('workspace/' + self.game['id'] + '/' + self.channel['channelId']) Utils.del_file(work_dir) os.makedirs(work_dir) # 生成当前打包任务的logger,添加到字典 LogUtils.add_logger(work_dir) LogUtils.info('Current Selected Game ID is : %s, with SDK is : %s', self.game['id'], self.channel['sdk']) LogUtils.info('game:\n%s', self.game) LogUtils.info('channel:\n%s', self.channel) src_apk_path = work_dir + '/songshu.apk' # 复制上传母包到workspace result = Utils.copy_file(self.apk, src_apk_path) if self.flag(result, "打包失败:复制母包文件失败,详情查看log.log", 5): return # 反编译母包 decompile_dir = work_dir + '/decompile' frame_work_dir = work_dir + '/framework' result = ApkUtils.decompile_apk(src_apk_path, decompile_dir, frame_work_dir) if self.flag(result, "打包失败:反编译母包异常,详情查看log.log", 15): return # 复制sdk资源到工作目录 sdk_source_dir = Utils.get_full_path('channelsdk/' + self.channel['sdk']) sdk_dest_dir = work_dir + '/sdk' result = Utils.copy_file(sdk_source_dir, sdk_dest_dir) if self.flag(result, "打包失败:复制SDK文件夹失败,详情查看log.log", 18): return # 将插件里的jar资源转dex result = ApkUtils.jar2dex(sdk_source_dir, sdk_dest_dir + '/classes.dex') if self.flag(result, "打包失败:渠道jar转dex异常,详情查看log.log", 25): return # 将插件里的dex资源转smali,合并到母包反编译目录中 result = ApkUtils.dex2smali(sdk_dest_dir + '/classes.dex', decompile_dir + '/smali', '') if self.flag(result, "打包失败:渠道dex转smali异常,详情查看log.log", 28): return # 合并manifest文件 result = ApkUtils.merge_manifest(decompile_dir, sdk_dest_dir) if self.flag(result, "打包失败:合并manifest文件失败,详情查看log.log", 30): return # 复制插件libs里的so库 ApkUtils.copy_libs(decompile_dir, sdk_dest_dir) if self.flag(0, "", 33): return # 复制插件assets文件夹 result = Utils.copy_file(sdk_dest_dir + '/assets', decompile_dir + '/assets') if self.flag(result, "打包失败:复制assets文件夹失败,详情查看log.log", 35): return # 复制插件res文件夹 result = Utils.copy_file(sdk_dest_dir + '/res', decompile_dir + '/res') if self.flag(result, "打包失败:复制res文件夹失败,详情查看log.log", 38): return # 复制渠道特殊配置资源,比如,针对个别渠道设置的loading页或logo ApkUtils.copy_ext_res(self.game, decompile_dir) if self.flag(0, "", 40): return # 将游戏原来的包名替换成渠道里面的包名,四大组件也会按照相关规则替换包名 package_name = ApkUtils.rename_package_name(decompile_dir, self.channel['package']) if self.flag(0, "", 45): return # 给对应的icon添加角标 ApkUtils.append_channel_mark(self.game, sdk_dest_dir, decompile_dir) if self.flag(0, "", 50): return # 配置参数写入 result = ApkUtils.write_develop_info(self.game, self.channel, decompile_dir) if self.flag(result, "打包失败:写入配置参数失败,详情查看log.log", 52): return # 如果主sdk有特殊的逻辑。执行特殊的逻辑脚本。 result = ApkUtils.do_sdk_script(self.channel, decompile_dir, package_name, sdk_dest_dir) if self.flag(result, "打包失败:执行渠道脚本异常,详情查看log.log", 55): return # 修改游戏名称,并将meta-data写入manifest文件 ApkUtils.modify_manifest(self.channel, decompile_dir, package_name) if self.flag(0, "", 60): return # 重新生成R文件,并导入到包名下 result = ApkUtils.generate_r_file(package_name, decompile_dir) if self.flag(result, "打包失败:重新生成R文件异常,详情查看log.log", 75): return # 防止方法数超65535,判断是否分dex result = ApkUtils.classes_split(decompile_dir, sdk_dest_dir) if self.flag(result, "打包失败:分dex出现异常,详情查看log.log", 78): return # 修改apktool.yml里的压缩配置,防止包体变大 ApkUtils.edit_yml(self.channel, decompile_dir) if self.flag(0, "", 80): return # 回编译生成apk target_apk = work_dir + '/output.apk' result = ApkUtils.recompile_apk(decompile_dir, target_apk, frame_work_dir) if self.flag(result, "打包失败:回编译APK异常,详情查看log.log", 90): return # 复制添加资源到apk result = ApkUtils.copy_root_ext_files(target_apk, decompile_dir) if self.flag(result, "打包失败:母包其余资源导入异常,详情查看log.log", 92): return # apk签名(v1签名) result = ApkUtils.sign_apk(self.game, target_apk) if self.flag(result, "打包失败:渠道包签名异常,详情查看log.log", 98): return # apk对齐 time_str = QDateTime.currentDateTime().toString("yyyyMMddhhmm") dest_apk_name = self.game['name'] + '-' + self.channel[ 'name'] + '-' + time_str + '.apk' dest_apk_dir = Utils.get_full_path('output/' + self.game['id'] + '/' + self.channel['channelId']) if not os.path.exists(dest_apk_dir): os.makedirs(dest_apk_dir) dest_apk = dest_apk_dir + '/' + dest_apk_name result = ApkUtils.align_apk(target_apk, dest_apk) if self.is_close: pass else: if result == 0: self.signal.signal.emit(self.channel['channelId'], 1, "打包成功:双击打开出包目录", 100) else: self.signal.signal.emit(self.channel['channelId'], result, "打包失败:apk包体4k对齐异常,详情查看log.log", 100) LogUtils.close()
def append_channel_mark(game, sdk_dest_dir, decompile_dir): game_icon_path = 'games/' + game['id'] + '/icon/icon.png' game_icon_path = Utils.get_full_path(game_icon_path) if not os.path.exists(game_icon_path): LogUtils.info('The game %s icon is not exists : %s', game['id'], game_icon_path) return LogUtils.info('The game %s icon path : %s', game['id'], game_icon_path) game_icon_name = get_app_icon_name(decompile_dir) if game_icon_name is None: return game_icon_name = game_icon_name + '.png' LogUtils.info('The game icon name: %s', game_icon_name) icon_img = Image.open(game_icon_path) mark_path = sdk_dest_dir + '/mark.png' if not os.path.exists(mark_path): LogUtils.info('The mark path is not exists : %s', mark_path) else: LogUtils.info('The mark path : %s', mark_path) mark_img = Image.open(mark_path) icon_img = merge_icon_mark(icon_img, mark_img, (0, 0)) ldpi_icon = icon_img.resize((36, 36), Image.ANTIALIAS) mdpi_icon = icon_img.resize((48, 48), Image.ANTIALIAS) hdpi_icon = icon_img.resize((72, 72), Image.ANTIALIAS) xhdpi_icon = icon_img.resize((96, 96), Image.ANTIALIAS) xxhdpi_icon = icon_img.resize((144, 144), Image.ANTIALIAS) xxxhdpi_icon = icon_img.resize((192, 192), Image.ANTIALIAS) icons = (ldpi_icon, mdpi_icon, hdpi_icon, xhdpi_icon, xxhdpi_icon, xxxhdpi_icon) drawables = ('drawable-ldpi', 'drawable-mdpi', 'drawable-hdpi', 'drawable-xhdpi', 'drawable-xxhdpi', 'drawable-xxxhdpi') mipmaps = ('mipmap-ldpi', 'mipmap-mdpi', 'mipmap-hdpi', 'mipmap-xhdpi', 'mipmap-xxhdpi', 'mipmap-xxxhdpi') for drawable in drawables: icon_dir = decompile_dir + '/res/' + drawable Utils.del_file(icon_dir + '/' + game_icon_name) if os.path.exists(icon_dir) and len(os.listdir(icon_dir)) <= 0: os.rmdir(icon_dir) icon_dir = decompile_dir + '/res/' + drawable + '-v4' Utils.del_file(icon_dir + '/' + game_icon_name) if os.path.exists(icon_dir) and len(os.listdir(icon_dir)) <= 0: os.rmdir(icon_dir) if not os.path.exists(decompile_dir + '/res/drawable'): os.mkdir(decompile_dir + '/res/drawable') xxhdpi_icon.save(decompile_dir + '/res/drawable/' + game_icon_name, 'PNG') for i in range(len(mipmaps)): icon_dir = decompile_dir + '/res/' + mipmaps[i] + '-v4' Utils.del_file(icon_dir + '/' + game_icon_name) if os.path.exists(icon_dir) and len(os.listdir(icon_dir)) <= 0: os.rmdir(icon_dir) icon_dir = decompile_dir + '/res/' + mipmaps[i] if not os.path.exists(icon_dir): os.makedirs(icon_dir) icons[i].save(os.path.join(icon_dir, game_icon_name), 'PNG')
def modify_manifest(channel, decompile_dir, package_name): manifest_file = decompile_dir + '/AndroidManifest.xml' ET.register_namespace('android', androidNS) tree = ET.parse(manifest_file) root = tree.getroot() app_node = root.find('application') # 修改游戏名称 if len(channel['gameName']) > 0: label = app_node.get('{' + androidNS + '}label') if label.find('@string/') == 0: app_name = label[8:len(label)] else: app_name = 'app_name' app_node.set('{' + androidNS + '}label', '@string/' + app_name) activity_node_lst = app_node.findall('activity') for activity_node in activity_node_lst: if activity_node.get('{' + androidNS + '}label') is not None: activity_node.set('{' + androidNS + '}label', '@string/' + app_name) string_file = decompile_dir + '/res/values/strings.xml' string_tree = ET.parse(string_file) strings = string_tree.getroot().findall('string') for string in strings: if string.get('name') == app_name: string_tree.getroot().remove(string) break string_node = SubElement(string_tree.getroot(), 'string', {'name': app_name}) string_node.text = channel['gameName'] string_tree.write(string_file, xml_declaration=True, encoding='utf-8', method='xml') LogUtils.info('modify game name: %s', channel['gameName']) # 写入meta-data key = '{' + androidNS + '}name' val = '{' + androidNS + '}value' meta_data_list = app_node.findall('meta-data') if meta_data_list is not None: for metaDataNode in meta_data_list: key_name = metaDataNode.attrib[key] for child in channel['sdkParams']: if key_name == child['name'] and child['writeIn'] == '1': LogUtils.warning( 'the meta-data node %s repeated. remove it .', key_name) app_node.remove(metaDataNode) for child in channel['sdkParams']: if child['writeIn'] is not None and child['writeIn'] == '1': meta_node = SubElement(app_node, 'meta-data') meta_node.set(key, child['name']) meta_node.set(val, child['value']) LogUtils.info('writeIn meta-data: %s=%s', child['name'], child['value']) # 修改替换包名占位符 xml_str = ET.tostring(root, 'utf-8').decode('utf-8') if xml_str.find('NEW_PACKAGE_NAME') != -1: LogUtils.info('modify package name: %s', package_name) xml_str = xml_str.replace('NEW_PACKAGE_NAME', package_name) root = ET.fromstring(xml_str) Utils.indent(root) tree = ET.ElementTree(root) tree.write(manifest_file, xml_declaration=True, encoding='utf-8', method='xml') LogUtils.info('The manifestFile modify successfully')
def rename_package_name(decompile_dir, package_name): manifest_file = decompile_dir + '/AndroidManifest.xml' ET.register_namespace('android', androidNS) tree = ET.parse(manifest_file) root = tree.getroot() old_package_name = root.attrib.get('package') if package_name is None or len(package_name) <= 0: return old_package_name if package_name[0:1] == '.': package_name = old_package_name + package_name LogUtils.info('renamePackageName ------------the new package name is %s', package_name) app_node = root.find('application') activity_list = app_node.findall('activity') key = '{' + androidNS + '}name' if activity_list is not None and len(activity_list) > 0: for aNode in activity_list: activity_name = aNode.attrib[key] if activity_name[0:1] == '.': activity_name = old_package_name + activity_name elif activity_name.find('.') == -1: activity_name = old_package_name + '.' + activity_name aNode.attrib[key] = activity_name service_list = app_node.findall('service') if service_list is not None and len(service_list) > 0: for sNode in service_list: service_name = sNode.attrib[key] if service_name[0:1] == '.': service_name = old_package_name + service_name elif service_name.find('.') == -1: service_name = old_package_name + '.' + service_name sNode.attrib[key] = service_name receiver_list = app_node.findall('receiver') if receiver_list is not None and len(receiver_list) > 0: for sNode in receiver_list: receiver_name = sNode.attrib[key] if receiver_name[0:1] == '.': receiver_name = old_package_name + receiver_name elif receiver_name.find('.') == -1: receiver_name = old_package_name + '.' + receiver_name sNode.attrib[key] = receiver_name provider_list = app_node.findall('provider') if provider_list is not None and len(provider_list) > 0: for sNode in provider_list: provider_name = sNode.attrib[key] if provider_name[0:1] == '.': provider_name = old_package_name + provider_name elif provider_name.find('.') == -1: provider_name = old_package_name + '.' + provider_name sNode.attrib[key] = provider_name provider_authorities = sNode.attrib['{' + androidNS + '}authorities'] if provider_authorities.find(old_package_name) != -1: provider_authorities = provider_authorities.replace( old_package_name, package_name) sNode.attrib['{' + androidNS + '}authorities'] = provider_authorities root.attrib['package'] = package_name tree.write(manifest_file, xml_declaration=True, encoding='utf-8', method='xml') return package_name