1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246 |
- # 安卓游戏打包脚本
- # 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 file_utils
- import xml_utils
- import smali_utils
- import config_utils_record
- import game_utils
- import os
- import os.path
- import json
- import sys
- import importlib
- import uuid
- import zipfile
- from xml.etree import ElementTree as ET
- from xml.etree.ElementTree import SubElement
- def pack(game, sdk, config):
- config['cache'] = uuid.uuid1()
- subChannel = config['subChannel']
- # 解包
- ret = decomplie(game, sdk, subChannel, config)
- if ret:
- return ret
- # 删除旧代码
- ret = removeOldCode(game, sdk, subChannel, config)
- if ret:
- return ret
- if 'deleteList' in config:
- # 删除旧资源
- ret = removeOldRes(game, sdk, subChannel, config)
- if ret:
- return ret
- # 删除一些不支持的属性
- ret = removeNoSupportAttr(game, sdk, subChannel, config)
- if ret:
- return ret
- # 删除一些不支持的配置
- ret = fixUnSupportConfig(game, sdk, subChannel, config)
- if ret:
- return ret
- # 合并Drawable-v4目录
- ret = mergeDrawableRes(game, sdk, subChannel, config)
- if ret:
- return ret
- # 移除相同的资源
- ret = removeSameRes(game, sdk, subChannel, config)
- if ret:
- return ret
- # 复制res资源
- ret = copyRes(game, sdk, subChannel, config)
- if ret:
- return ret
- # 合并主文件
- ret = mergeManifestRes(game, sdk, subChannel, config)
- if ret:
- return ret
- # 替换占位符
- ret = changePlaceholders(game, sdk, subChannel, config)
- if ret:
- return ret
- # 添加meta-data
- ret = addMetaData(game, sdk, subChannel, config)
- if ret:
- return ret
- # 复制app res资源
- ret = copyAppRes(game, sdk, subChannel, config)
- if ret:
- return ret
- # 合并语言文件
- ret = mergeLanguage(game, sdk, subChannel, config)
- if ret:
- return ret
- # 增加配置文件
- ret = addConfig(game, sdk, subChannel, config)
- if ret:
- return ret
- #保存旧包名
- config['oldPackageName'] = getPackageName(game, sdk, subChannel, config)
- # 更改包名
- if 'packageName' in config and config['packageName'] != '':
- ret = changePackageName(game, sdk, subChannel, config)
- if ret:
- return ret
- else:
- config['packageName'] = getPackageName(game, sdk, subChannel, config)
- # 更改app名
- if 'name' in config and config['name'] != '':
- ret = changeAppName(game, sdk, subChannel, config)
- if ret:
- return ret
- # 更改app icon
- if config['changeIcon']:
- ret = changeAppIcon(game, sdk, subChannel, config)
- if ret:
- return ret
- # 添加启动图操作
- if config['addLauncher']:
- ret = addLauncher(game, sdk, subChannel, config)
- if ret:
- return ret
- # 打包lib依赖
- ret = packJar(game, sdk, subChannel, config)
- if ret:
- return ret
- # sdk脚本处理
- ret = doSDKPostScript(game, sdk, config)
- if ret:
- return ret
- # 乐变sdk的特殊处理
- ret = game_utils.sdkLebianChange(game, sdk, config)
- if ret:
- return ret
- # 复制EntryActivity文件
- ret = copyEntryActivityCode(game, sdk, subChannel, config)
- if ret:
- return ret
- # 游戏脚本处理
- ret = doGamePostScript(game, sdk, config)
- if ret:
- return ret
- # 生成R文件
- '''ret = generateNewRFile(game, sdk, subChannel, config)
- if ret:
- return ret'''
- # 添加MultiDex支持
- if config['splitDex']:
- ret = splitDex(game, sdk, subChannel, config)
- if ret:
- return ret
- # 更改版本号
- ret = changeVersion(game, sdk, subChannel, config)
- if ret:
- return ret
- # 回编译
- ret = recomplie(game, sdk, subChannel, config)
- if ret:
- return ret
- # 对齐apk
- ret = alignApk(game, sdk, subChannel, config)
- if ret:
- return ret
- # 签名
- ret = apksignerApk(game, sdk, subChannel, config)
- if ret:
- return ret
- # 清理产生的中间文件
- if config['clearCache']:
- clearTemp(game, sdk, subChannel, config)
- return 0
- def decomplie(game, sdk, subChannel, config):
- '''
- 解包
- '''
- apktoolPath = file_utils.getApkToolPath()
- gamePath = file_utils.getFullGameApk(config,game)
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- if os.path.exists(decompliePath):
- print('delete decomplie folder...')
- file_utils.deleteFolder(decompliePath)
- print('decomplie apk...')
- return file_utils.execJarCmd(apktoolPath, 'd -f "%s" -o "%s"' % (gamePath, decompliePath))
- def removeOldRes(game, sdk, subChannel, config):
- '''
- 删除旧资源
- '''
- print('delete res ...')
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- for subPath in config['deleteList']:
- resPath = os.path.join(decompliePath, subPath)
- if os.path.exists(resPath):
- os.remove(resPath)
- print('delete ' + resPath)
- return 0
- def removeOldCode(game, sdk, subChannel, config):
- '''
- 删除旧代码
- '''
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- codePath = os.path.join(decompliePath, 'smali', 'com', 'jmhy', 'lib', 'record')
- #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)
- print('remove %s' % f)
- return 0
- def copyRes(game, sdk, subChannel, config):
- '''
- 复制res资源
- '''
- # 拷贝sdk资源
- print('copy res...')
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- sdkPath = file_utils.getFullSDKPath(sdk)
- resPath = os.path.join(sdkPath, 'res')
- decomplieResPath = file_utils.getFullPath(decompliePath, 'res')
- for d in os.listdir(resPath):
- copyResWithType(resPath, decomplieResPath, d)
- # 拷贝assets
- print('copy assets...')
- assetsPath = file_utils.getFullPath(sdkPath, 'assets')
- decomplieAssetsPath = file_utils.getFullPath(decompliePath, 'assets')
- if os.path.exists(assetsPath):
- ret = file_utils.copyFileAllDir(assetsPath, decomplieAssetsPath)
- if ret:
- return ret
- # 拷贝jniLib
- print('copy jniLibs...')
- jniPath = file_utils.getFullPath(sdkPath, 'jniLibs')
- decomplieJniPath = file_utils.getFullPath(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.copyFileAllDir(jniPath, decomplieJniPath, False, abiFilters)
- if ret:
- return ret
- return 0
- def mergeDrawableRes(game, sdk, subChannel, config):
- '''
- 合并Drawable-v4目录
- '''
- print('merge drawable path...')
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, 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.copyFileAllDir(v4DrawablePath, drawablePath, True)
- if ret:
- return ret
- else:
- os.rename(v4DrawablePath, drawablePath)
- return 0
- def removeSameRes(game, sdk, subChannel, config):
- '''
- 移除相同的资源
- '''
- # 读取sdk的资源
- print('remove same res...')
- sdkPath = file_utils.getFullSDKPath(sdk)
- sdkResPath = os.path.join(sdkPath, 'res')
- resList = []
- for path in os.listdir(sdkResPath):
- if not path.startswith('values'):
- continue
- absPath = os.path.join(sdkResPath, path)
- for resFile in os.listdir(absPath):
- '''if not resFile.startswith('jm_'):
- continue'''
- resList = xml_utils.readAllRes(os.path.join(absPath, resFile), resList)
- if len(resList) == 0:
- print('no same res found')
- return 0
- removeList = []
- # 移除相同的资源
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- resPath = os.path.join(decompliePath, 'res')
- for path in os.listdir(resPath):
- if not path.startswith('values'):
- continue
- absPath = os.path.join(resPath, path)
- for resFile in os.listdir(absPath):
- print('resFile ==> ' + os.path.join(absPath, resFile))
- #xml_utils.removeSameRes(os.path.join(absPath, resFile), resList)
- removeList = xml_utils.removeSameRes2(os.path.join(absPath, resFile), resList, removeList)
- print('--------------')
- print(removeList)
- if len(removeList) == 0:
- print('no same res remove')
- return 0
- publicResPath = os.path.join(decompliePath, 'res/values/public.xml')
- print('publicResPath ---->' + publicResPath)
- xml_utils.removeIdFromPublic(publicResPath,removeList)
- return 0
- def mergeManifestRes(game, sdk, subChannel, config):
- '''
- 合并主文件
- '''
- print('merge AndroidManifest...')
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
- sdkPath = file_utils.getFullSDKPath(sdk)
- libManifest = file_utils.getFullPath(sdkPath, 'manifest.xml')
- return xml_utils.mergeManifestRes(manifest, libManifest)
- def copyAppRes(game, sdk, subChannel, config):
- '''
- 拷贝app的资源,比如app icon、启动图等
- '''
- channelPath = file_utils.getSubChannelPath(game, sdk, subChannel)
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- # assets
- print('copy assets...')
- assetsPath = file_utils.getFullPath(channelPath, 'assets')
- decomplieAssetsPath = file_utils.getFullPath(decompliePath, 'assets')
- if os.path.exists(assetsPath):
- ret = file_utils.copyFileAllDir(assetsPath, decomplieAssetsPath)
- if ret:
- return ret
- # icon
- print('copy icon...')
- ret = copyAppResWithType(decompliePath, channelPath, 'icon')
- if ret:
- return ret
- # 启动图
- print('copy splash...')
- ret = copyAppResWithType(decompliePath, channelPath, 'splash')
- if ret:
- return ret
- # 其他图片
- print('copy image...')
- ret = copyAppResWithType(decompliePath, channelPath, 'image')
- if ret:
- return ret
- return 0
- def mergeLanguage(game, sdk, subChannel, config):
- '''
- 合并语言文件
- '''
- print('merge language...')
- channelPath = file_utils.getSubChannelPath(game, sdk, subChannel)
- mergePath = file_utils.getFullPath(channelPath, 'merge')
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- if not os.path.exists(mergePath):
- print('%s not exists!' % mergePath)
- return 0
- if not os.path.isdir(mergePath):
- print('%s not a dir!' % mergePath)
- return 0
- for fileName in os.listdir(mergePath):
- mergeJson(mergePath, decompliePath, fileName)
- return 0
- def mergeJson(srcDir, changeDir, fileName):
- srcFile = os.path.join(srcDir, fileName)
- if os.path.isdir(srcFile):
- for fileName2 in os.listdir(srcFile):
- changeDir2 = os.path.join(changeDir, fileName)
- mergeJson(srcFile, changeDir2, fileName2)
- else:
- changeFile = os.path.join(changeDir, fileName)
- if not os.path.exists(changeFile):
- print('%s not exists!' % changeFile)
- return 0
- jsonText1 = file_utils.readFile(srcFile)
- jsonText2 = file_utils.readFile(changeFile)
- print('*************src config*************')
- print(jsonText1)
- print('************************************')
- print('*************target config*************')
- print(jsonText2)
- print('************************************')
- json1 = json.loads(jsonText1)
- json2 = json.loads(jsonText2)
- for item in json1:
- if item in json2:
- json2[item] = json1[item]
- jsonStr = json.dumps(json2, ensure_ascii=False)
- print('*************merge config*************')
- print(jsonStr)
- print('************************************')
- print('>> %s' % changeFile)
- file_utils.createFile(changeFile, jsonStr)
- return 0
-
- def copyAppResWithType(decompliePath, channelPath, typeName):
- decomplieResPath = os.path.join(decompliePath, 'res')
- iconPath = os.path.join(channelPath, typeName)
- if not os.path.exists(iconPath):
- print('dir "%s" not exists' % iconPath)
- return 0
- for d in os.listdir(iconPath):
- ret = copyResWithType(iconPath, decomplieResPath, d)
- if ret:
- return ret
- return 0
- def copyResWithType(resPath, decomplieResPath, typeName):
- # appt的打包目录会带-v4后缀
- resDir = os.path.join(resPath, typeName)
- target = os.path.join(decomplieResPath, typeName)
- targetV4 = os.path.join(decomplieResPath, typeName + '-v4')
- if not os.path.exists(target) and not os.path.exists(targetV4):
- os.makedirs(target)
- return file_utils.copyFileAllDir(resDir, target, False)
- elif not os.path.exists(target):
- return file_utils.copyFileAllDir(resDir, targetV4, False)
- else:
- return file_utils.copyFileAllDir(resDir, target, False)
- def removeNoSupportAttr(game, sdk, subChannel, config):
- '''
- 删除一些不支持的属性
- '''
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
-
- xml_utils.removeRootAttr(manifest, 'compileSdkVersion')
- xml_utils.removeRootAttr(manifest, 'compileSdkVersionCodename')
-
- return 0
- def fixUnSupportConfig(game, sdk, subChannel, config):
- '''
- 删除一些不支持的配置
- '''
- # 检查minSdkVersion
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- yml = os.path.join(decompliePath, 'apktool.yml')
- minSdkVersion = 15
- file_utils.changeMinSdkVersion(yml, minSdkVersion)
- resPath = os.path.join(decompliePath, 'res')
- tag = '-v'
- for res in os.listdir(resPath):
- 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 < minSdkVersion:
- unSopportPath = os.path.join(resPath, res)
- print('unSopportPath = ' + unSopportPath)
- file_utils.deleteFolder(unSopportPath)
- print('deleteFolder = ' + unSopportPath)
-
- return 0
- def changePackageName(game, sdk, subChannel, config):
- '''
- 更改包名
- '''
- # 全局替换AndroidManifest里面的包名
- newPackageName = config['packageName']
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
- packageName = xml_utils.getPackageName(manifest)
-
- xml_utils.changePackageName(manifest, newPackageName)
- print('change package name %s --> %s' % (packageName, newPackageName))
- return 0
- def getPackageName(game, sdk, subChannel, config):
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
- packageName = xml_utils.getPackageName(manifest)
- return packageName
- def changeAppName(game, sdk, subChannel, config):
- '''
- 更改app名
- '''
- # 生成string.xml文件
- name = config['name']
- resName = 'shanshen_sdk_name'
- if 'outName' in config:
- resName = resName + '_' + config['outName']
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- stringFile = os.path.join(decompliePath, 'res', 'values', 'sdk_strings_temp.xml')
- content = '<?xml version="1.0" encoding="utf-8"?><resources><string name="%s">%s</string></resources>' % (resName, name)
- file_utils.createFile(stringFile, content)
- # 修改主文件的app名的值
- manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
- xml_utils.changeAppName(manifest, '@string/%s' % resName)
- print('change app name %s' % name)
- return 0
- def changeAppIcon(game, sdk, subChannel, config):
- '''
- 更改app icon
- '''
- print('change app icon...')
- resName = 'record_sdk_icon'
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
- xml_utils.changeAppIcon(manifest, '@mipmap/%s' % resName)
- return 0
- def addMetaData(game, sdk, subChannel, config):
- '''
- 添加meta-data
- '''
- if 'metaData' not in config:
- return 0
- print('add meta-data...')
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
- xml_utils.addMetaData(manifest, config['metaData'])
- return 0
- def addConfig(game, sdk, subChannel, config):
- '''
- 添加config.json
- '''
- if 'configData' not in config:
- print('configData is null')
- return 0
- print('add config.json...')
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- configJson = os.path.join(decompliePath, 'assets', 'tool_config.json')
- jsonText = json.dumps(config['configData'], ensure_ascii=False)
- file_utils.createFile(configJson, jsonText)
- return 0
- def changePlaceholders(game, sdk, subChannel, config):
- '''
- 处理掉占位符
- '''
- if 'placeholders' not in config:
- return 0
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, 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.replaceContent(manifest, oldText, newText)
- return 0
- def addLauncher(game, sdk, subChannel, config):
- '''
- 添加启动图
- '''
- channelPath = file_utils.getSubChannelPath(game, sdk, 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.getDecompliePath(game, sdk, subChannel, config['cache'])
- manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
- activity = xml_utils.getLauncherActivityName(manifest)
- if activity == 'com.shanshen.sdk.template.LauncherActivity':
- print('add launcher already exist...')
- return 1
- # 添加关联资源
- internalPath = os.path.join(file_utils.getCurrentPath(), 'internal_shanshen')
- ret = copyAppResWithType(decompliePath, internalPath, 'launcher_res')
- if ret:
- return ret
- # 拷贝代码
- print('copy launcher code...')
- codePath = os.path.join(internalPath, 'launcher_code', 'smali')
- smaliPath = file_utils.getFullPath(decompliePath, 'smali')
- ret = file_utils.copyFileAllDir(codePath, smaliPath)
- if ret:
- return ret
- # 修改主文件信息
- print('change launcher config...')
- orientation = xml_utils.getScreenOrientation(manifest)
- activity = xml_utils.removeLauncherActivity(manifest)
- xml_utils.addLauncherActivity(manifest, orientation, 'com.shanshen.sdk.template.LauncherActivity')
- # 修改跳转的
- launcherActivity = os.path.join(decompliePath, 'smali', 'com', 'shanshen', 'sdk', 'template', 'LauncherActivity.smali')
- file_utils.replaceContent(launcherActivity, '{class}', activity)
- print('change launcher %s to %s' % (activity, 'com.shanshen.sdk.template.LauncherActivity'))
- # config['oldLauncher'] = activity
- if 'launcherTime' in config:
- timeHex = formatHex(config['launcherTime'])
- file_utils.replaceContent(launcherActivity, '0x0BB8', timeHex)
- return 0
- def addMoreIcon(game, sdk, subChannel, config):
- '''
- 添加多个图标
- '''
- print('add more icon support...')
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- icon = '@mipmap/common_sdk_icon'
- if not config['changeIcon']:
- manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
- icon = xml_utils.getApplicationAttr(manifest, 'icon')
- switchIcon = icon
- if config['switchIcon']:
- switchIcon = '@mipmap/common_sdk_icon2'
- manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
- return xml_utils.addMoreIcon(manifest, icon, switchIcon)
- def formatHex(millisecond):
- '''
- 将毫秒转为16进制,4位格式
- '''
- timeHex = str(hex(millisecond)).upper()
- timeHex = timeHex[2:]
- formatHex = ''
- if len(timeHex) == 3:
- formatHex = '0x0' + timeHex
- elif len(timeHex) == 4:
- formatHex = '0x' + timeHex
- else:
- formatHex = '0x0BB8'
- return formatHex
- def doSDKPostScript(game, sdk, config):
- '''
- 执行sdk相关特殊处理脚本
- '''
- sdkPath = file_utils.getFullSDKPath(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('doSDKPostScript...')
- sys.path.append(scriptPath)
- module = importlib.import_module('sdk_script')
- ret = module.execute(game, sdk, config)
- sys.path.remove(scriptPath)
- return ret
- def doGamePostScript(game, sdk, config):
- '''
- 执行游戏相关特殊处理脚本
- '''
- channelPath = file_utils.getFullGamePath(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 generateNewRFile(game, sdk, subChannel, config):
- '''
- 生成新的R文件
- '''
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- androidPlatforms = file_utils.getAndroidCompileToolPath()
- manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
- decomplieResPath = file_utils.getFullPath(decompliePath, 'res')
- compliePath = file_utils.getFullPath(decompliePath, 'gen')
- if not os.path.exists(compliePath):
- os.makedirs(compliePath)
- # 生成R文件
- print('create R.java ...')
- if config['aapt2disable']:
- aapt = file_utils.getAAPTPath()
- ret = file_utils.getExecPermission(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.execFormatCmd(createRCmd)
- if ret:
- return ret
- else:
- # compile
- aapt = file_utils.getAAPT2Path()
- ret = file_utils.getExecPermission(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.execFormatCmd(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.execFormatCmd(linkResCmd, unzipResPath)
- if ret:
- return ret
- # 编译R文件
- print('complie R.java ...')
- packageName = xml_utils.getPackageName(manifest)
- packagePath = file_utils.getPackagePath(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.execFormatCmd(complieRCmd)
- if ret:
- return ret
- # 生成dex
- print('dex R.class ...')
- dx = file_utils.getDxPath()
- outDex = os.path.join(compliePath, 'classes.dex')
- ret = file_utils.execJarCmd(dx, '--dex --output="%s" "%s"' % (outDex, compliePath))
- if ret:
- return ret
- # 反向dex生成smali
- # 存放在out目录
- print('baksmali classes.dex ...')
- baksmaliPath = file_utils.getBaksmaliPath()
- outPath = file_utils.getFullPath(decompliePath, 'out')
- ret = file_utils.execJarCmd(baksmaliPath, 'd "%s" -o "%s"' % (outDex, outPath))
- if ret:
- return ret
- # 将生成的文件拷贝到目标目录
- print('copy R.smali ...')
- smaliPath = file_utils.getFullPath(decompliePath, 'smali')
- file_utils.copyFileAllDir(outPath, smaliPath)
- return 0
- def packJar(game, sdk, subChannel, config):
- '''
- 打包所有的jar
- '''
- splitDex = config['splitDex']
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- outPath = file_utils.getFullPath(decompliePath, 'gen')
- if not os.path.exists(outPath):
- os.makedirs(outPath)
- if config['aapt2disable']:
- dx = file_utils.getDxPath()
- dexCmd = '--dex --multi-dex --no-warning --output="%s"' % outPath
- else:
- dx = file_utils.getD8Path()
- androidPlatforms = file_utils.getAndroidCompileToolPath()
- dexCmd = '--lib "%s" --output "%s"' % (androidPlatforms, outPath)
- # 找到所有lib依赖
- sdkPath = file_utils.getFullSDKPath(sdk)
- libs = os.path.join(sdkPath, 'libs')
- libConfig = os.path.join(libs, 'config.json')
- # 存在配置文件
- if os.path.exists(libConfig):
- jsonText = file_utils.readFile(libConfig)
- libConf = json.loads(jsonText)
- if 'libConfig' in config and config['libConfig'] in libConf:
- conf = config['libConfig']
- libList = libConf[conf]
- for jar in libList:
- dexCmd += ' ' + os.path.join(libs, jar)
- elif 'default' in libConf:
- libList = libConf['default']
- for jar in libList:
- 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)
- else:
- for jar in os.listdir(libs):
- if not jar.endswith('.jar'):
- continue
- dexCmd += ' ' + os.path.join(libs, jar)
- # multidex.jar
- if splitDex:
- dexCmd += ' ' + file_utils.getMultiDexPath()
- # sdk实现类
- print('packageing all jar ...')
- ret = file_utils.execJarCmd(dx, dexCmd)
- if ret:
- return ret
- # 反向dex生成smali
- # 存放在out目录
- print('baksmali classes.dex ...')
- outDex = os.path.join(outPath, 'classes.dex')
- baksmaliPath = file_utils.getBaksmaliPath()
- outPath = file_utils.getFullPath(decompliePath, 'out')
-
- ret = file_utils.execJarCmd(baksmaliPath, 'd "%s" -o "%s"' % (outDex, outPath))
- if ret:
- return ret
- # 将生成的文件拷贝到目标目录
- print('copy all smali ...')
- smaliPath = file_utils.getFullPath(decompliePath, 'smali')
- ret = file_utils.copyFileAllDir(outPath, smaliPath, True)
- if ret:
- return ret
- return 0
- def splitDex(game, sdk, subChannel, config):
- '''
- 分割dex
- '''
- # 判断是否已经存在application
- # 存在,则往原application添加内容
- # 不存在,则拷贝一个默认的android.support.multidex.MultiDexApplication
- print('add MultiDex support...')
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
- application = xml_utils.getApplicationAttr(manifest, 'name')
- if application is None:
- ret = xml_utils.changeApplicationAttr(manifest, 'name', 'android.support.multidex.MultiDexApplication')
- if ret:
- return ret
- else:
- smaliPath = os.path.join(decompliePath, 'smali')
- applicationFile = file_utils.getPackagePath(smaliPath, application)
- applicationFile += '.smali'
- ret = changeApplicationDex(applicationFile)
- if ret:
- return ret
- return splitSmali(game, sdk, subChannel, config, application)
- def changeApplicationDex(file):
- '''
- 修改application的smali文件,增加MultiDex操作
- '''
- index = file_utils.getApplicationSmaliIndex(file)
- file_utils.insertApplicationSmali(file, index)
- return 0
- def splitSmali(game, sdk, subChannel, config, application):
- '''
- 如果函数上限超过限制,自动拆分smali,以便生成多个dex文件
- '''
- print('splitSmali...')
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, 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.copyFile(f, targetFile, True)
- return 0
- def changeVersion(game, sdk, subChannel, config):
- '''
- 更改版本号
- '''
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, 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']
- return file_utils.changeVersion(yml, versionCode, versionName, targetSdkVersion)
- def recomplie(game, sdk, subChannel, config):
- '''
- 回编译
- '''
- print('recomplie apk...')
- apktoolPath = file_utils.getApkToolPath()
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- outApk = file_utils.getOutApkPath(game, sdk, subChannel, config['cache'])
- useAppt2 = ' --use-aapt2'
- if config['aapt2disable']:
- useAppt2 = ''
- return file_utils.execJarCmd(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.getAlignPath()
- if os.path.exists(alignApk):
- os.remove(alignApk)
- ret = file_utils.getExecPermission(alignapkTool)
- if ret:
- return ret
- # zipalign.exe -v -p 4 input.apk output.apk
- return file_utils.execFormatCmd('"%s" -f -p 4 "%s" "%s"' % (alignapkTool, outApk, alignApk))
- def apksignerApk(game, sdk, subChannel, config):
- '''
- 签名apk
- '''
- print('sign apk...')
- path = os.path.join(file_utils.getCurrentPath(), 'keystore', 'key.json')
- jsonText = file_utils.readFile(path)
- signConfig = json.loads(jsonText)
- keystore = {}
- if game in signConfig:
- if sdk in signConfig[game] and subChannel in signConfig[game][sdk]:
- keystore = signConfig[game][sdk][subChannel]
- else:
- keystore = signConfig['default']
- else:
- keystore = signConfig['default']
- print('storeFile is "%s"' % keystore['storeFile'])
-
- apksigner = file_utils.getApksignerPath()
- alignApk = file_utils.getAlignApkPath(game, sdk, subChannel, config['cache'])
- signedApk = file_utils.getSignApkPath(game, sdk, subChannel, config['cache'])
- storeFile = os.path.join(file_utils.getCurrentPath(), 'keystore', keystore['storeFile'])
- if 'outName' in config and 'outPath' in config:
- if not os.path.exists(config['outPath']):
- os.makedirs(config['outPath'])
- signedApk = os.path.join(config['outPath'], config['outName'] + '.apk')
- print('signedApk = ' + signedApk)
- elif 'outName' in config:
- signedApk = file_utils.getRenameApkPath(game, sdk, config['cache'], config['outName'])
- print('signedApk = ' + signedApk)
- # 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.execJarCmd(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 addChannel(game, sdk, subChannel, config):
- '''
- 添加渠道信息
- '''
- if 'v2disable' in config and config['v2disable']:
- return 0
-
- walle = file_utils.getWallePath()
- signedApk = file_utils.getSignApkPath(game, sdk, subChannel, config['cache'])
- if 'outName' in config and 'outPath' in config:
- signedApk = os.path.join(config['outPath'], config['outName'] + '.apk')
- elif 'outName' in config:
- signedApk = file_utils.getRenameApkPath(game, sdk, config['cache'], config['outName'])
- properties = config['properties']
- appid = ''
- appkey = ''
- if 'appid' in properties:
- appid = properties['appid']
- if 'appkey' in properties:
- appkey = properties['appkey']
- return file_utils.execJarCmd(walle, 'put -e version=%s,agent=%s,appid=%s,appkey=%s "%s" "%s"' % (config_utils_record.getDate(), properties['agent'], appid, appkey, signedApk, signedApk))
- def clearTemp(game, sdk, subChannel, config):
- '''
- 清空中间产生的文件
- '''
- print('clear temp...')
- alignApk = file_utils.getAlignApkPath(game, sdk, subChannel, config['cache'])
- outApk = file_utils.getOutApkPath(game, sdk, subChannel, config['cache'])
- if os.path.exists(alignApk):
- os.remove(alignApk)
- if os.path.exists(outApk):
- os.remove(outApk)
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- file_utils.deleteFolder(decompliePath)
- 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, subChannel):
- '''
- 控制台打包
- '''
- if not os.path.exists(file_utils.getFullGameApk(game)):
- print('game "%s" not exists' % game)
- return 1
- if not os.path.exists(file_utils.getFullSDKPath(sdk)):
- print('sdk "%s" not exists' % sdk)
- return 1
- # 读取配置
- channelPath = file_utils.getChannelPath(game, sdk)
- configPath = os.path.join(channelPath, 'config.json')
- if not os.path.exists(configPath):
- print('%s not exists' % configPath)
- return 1
-
- jsonText = file_utils.readFile(configPath)
- config = json.loads(jsonText)
- # 检查参数
- if not config_utils_record.checkConfig(config):
- return 1
- # 处理参数
- config_utils_record.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))
-
- return 0
- def copyEntryActivityCode(game, sdk, subChannel, config):
- '''
- 拷贝代码
- '''
- print('copy EntryActivity.smali')
- sdkPath = file_utils.getFullSDKPath(sdk)
- EntryActivity = 'EntryActivity.smali'
- entryFile = os.path.join(sdkPath, 'smali', EntryActivity)
- decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
- smaliPath = os.path.join(decompliePath, 'smali')
- targetPath = file_utils.getPackagePath(smaliPath, config['packageName'])
- targetFile = os.path.join(targetPath, EntryActivity)
- ret = file_utils.copyFile(entryFile, targetFile)
- if ret:
- return ret
- writeActivityToManifest(os.path.join(decompliePath, 'AndroidManifest.xml'),config)
- oldText = 'com/jmhy/floatsdk/sample/EntryActivity'
- newText = config['packageName'].replace('.', '/') + "/EntryActivity"
- print("EntryActivity.smali change '{}' to '{}' ...".format(oldText,newText))
- file_utils.replaceContent(targetFile, oldText, newText)
- return 0
- def writeActivityToManifest(targetManifest, config):
- androidNS = 'http://schemas.android.com/apk/res/android'
- ET.register_namespace('android', androidNS)
- targetTree = ET.parse(targetManifest)
- targetRoot = targetTree.getroot()
- appNode = targetRoot.find('application')
- activitys = appNode.findall('activity')
- keyName = "{0}{1}{2}name".format("{",androidNS,"}")
- theme = "{0}{1}{2}theme".format("{",androidNS,"}")
- for activity in activitys:
- activityName = activity.get(keyName)
- if activityName.find('.EntryActivity')>=0:
- oldName = activityName;
- newName = config['packageName'] + ".EntryActivity"
- activity.set(keyName,newName)
- print("EntryActivity change '{}' to '{}' ...".format(oldName, newName))
- if activityName.find('com.tool.floatsdk.ui.WebActivity')>=0:
- if activity.get(theme) is not None or activity.get(theme) != "":
- del activity.attrib[theme]
- perNode = targetRoot.find('permissions')
- #permissions = perNode.findall('uses-permission')
- print(perNode)
- print("------------")
- targetTree.write(targetManifest, 'UTF-8')
|