package_utils_shanshen.py 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063
  1. # 安卓游戏打包脚本
  2. # 1.用apktool解包
  3. # 2.复制res资源、assets资源、jniLib资源
  4. # 3.修改包名、app名、渠道文件
  5. # 4.添加app icon、合并AndroidManifest文件
  6. # 5.添加app启动图,修改AndroidManifest文件
  7. # 6.生成新的R文件
  8. # 7.将新的R文件编译,生成dex,再用baksmali生成smali,替换旧的R文件
  9. # 8.将jar资源打包成dex,再用baksmali生成smali,拷贝到smali目录下
  10. # 9.统计方法量,并分割dex(将smali文件拷贝到目录smali_classes2)
  11. # 10.用apktool回包
  12. # 11.重新签名
  13. import file_utils
  14. import xml_utils
  15. import smali_utils
  16. import config_utils_shanshen
  17. import game_utils
  18. import os
  19. import os.path
  20. import json
  21. import sys
  22. import importlib
  23. import uuid
  24. import zipfile
  25. def pack(game, sdk, config):
  26. config['cache'] = uuid.uuid1()
  27. subChannel = config['subChannel']
  28. # 解包
  29. ret = decomplie(game, sdk, subChannel, config)
  30. if ret:
  31. return ret
  32. # 删除旧代码
  33. ret = removeOldCode(game, sdk, subChannel, config)
  34. if ret:
  35. return ret
  36. # 删除一些不支持的属性
  37. ret = removeNoSupportAttr(game, sdk, subChannel, config)
  38. if ret:
  39. return ret
  40. # 删除一些不支持的配置
  41. ret = fixUnSupportConfig(game, sdk, subChannel, config)
  42. if ret:
  43. return ret
  44. # 合并Drawable-v4目录
  45. ret = mergeDrawableRes(game, sdk, subChannel, config)
  46. if ret:
  47. return ret
  48. # 移除相同的资源
  49. ret = removeSameRes(game, sdk, subChannel, config)
  50. if ret:
  51. return ret
  52. # 复制res资源
  53. ret = copyRes(game, sdk, subChannel, config)
  54. if ret:
  55. return ret
  56. # 合并主文件
  57. ret = mergeManifestRes(game, sdk, subChannel, config)
  58. if ret:
  59. return ret
  60. # 替换占位符
  61. ret = changePlaceholders(game, sdk, subChannel, config)
  62. if ret:
  63. return ret
  64. # 添加meta-data
  65. ret = addMetaData(game, sdk, subChannel, config)
  66. if ret:
  67. return ret
  68. # 复制app res资源
  69. ret = copyAppRes(game, sdk, subChannel, config)
  70. if ret:
  71. return ret
  72. # 更改包名
  73. if 'packageName' in config and config['packageName'] != '':
  74. ret = changePackageName(game, sdk, subChannel, config)
  75. if ret:
  76. return ret
  77. else:
  78. config['packageName'] = getPackageName(game, sdk, subChannel, config)
  79. # 更改app名
  80. if 'name' in config and config['name'] != '':
  81. ret = changeAppName(game, sdk, subChannel, config)
  82. if ret:
  83. return ret
  84. # 更改app icon
  85. if config['changeIcon']:
  86. ret = changeAppIcon(game, sdk, subChannel, config)
  87. if ret:
  88. return ret
  89. # 添加启动图操作
  90. if config['addLauncher']:
  91. ret = addLauncher(game, sdk, subChannel, config)
  92. if ret:
  93. return ret
  94. # 打包lib依赖
  95. ret = packJar(game, sdk, subChannel, config)
  96. if ret:
  97. return ret
  98. # sdk脚本处理
  99. ret = doSDKPostScript(game, sdk, config)
  100. if ret:
  101. return ret
  102. # 乐变sdk的特殊处理
  103. ret = game_utils.sdkLebianChange(game, sdk, config)
  104. if ret:
  105. return ret
  106. # 游戏脚本处理
  107. ret = doGamePostScript(game, sdk, config)
  108. if ret:
  109. return ret
  110. # 生成R文件
  111. ret = generateNewRFile(game, sdk, subChannel, config)
  112. if ret:
  113. return ret
  114. # 添加MultiDex支持
  115. if config['splitDex']:
  116. ret = splitDex(game, sdk, subChannel, config)
  117. if ret:
  118. return ret
  119. # 更改版本号
  120. ret = changeVersion(game, sdk, subChannel, config)
  121. if ret:
  122. return ret
  123. # 回编译
  124. ret = recomplie(game, sdk, subChannel, config)
  125. if ret:
  126. return ret
  127. # 对齐apk
  128. ret = alignApk(game, sdk, subChannel, config)
  129. if ret:
  130. return ret
  131. # 签名
  132. ret = apksignerApk(game, sdk, subChannel, config)
  133. if ret:
  134. return ret
  135. # 添加渠道信息
  136. ret = addChannel(game, sdk, subChannel, config)
  137. if ret:
  138. return ret
  139. # 清理产生的中间文件
  140. if config['clearCache']:
  141. clearTemp(game, sdk, subChannel, config)
  142. return 0
  143. def decomplie(game, sdk, subChannel, config):
  144. '''
  145. 解包
  146. '''
  147. apktoolPath = file_utils.getApkToolPath()
  148. gamePath = file_utils.getFullGameApk(game)
  149. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  150. if os.path.exists(decompliePath):
  151. print('delete decomplie folder...')
  152. file_utils.deleteFolder(decompliePath)
  153. print('decomplie apk...')
  154. return file_utils.execJarCmd(apktoolPath, 'd -f "%s" -o "%s"' % (gamePath, decompliePath))
  155. def removeOldCode(game, sdk, subChannel, config):
  156. '''
  157. 删除旧代码
  158. '''
  159. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  160. codePath = os.path.join(decompliePath, 'smali', 'com', 'shanshen', 'sdk')
  161. file_utils.deleteFolder(codePath)
  162. return 0
  163. def copyRes(game, sdk, subChannel, config):
  164. '''
  165. 复制res资源
  166. '''
  167. # 拷贝sdk资源
  168. print('copy res...')
  169. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  170. sdkPath = file_utils.getFullSDKPath(sdk)
  171. resPath = os.path.join(sdkPath, 'res')
  172. decomplieResPath = file_utils.getFullPath(decompliePath, 'res')
  173. for d in os.listdir(resPath):
  174. copyResWithType(resPath, decomplieResPath, d)
  175. # 拷贝assets
  176. print('copy assets...')
  177. assetsPath = file_utils.getFullPath(sdkPath, 'assets')
  178. decomplieAssetsPath = file_utils.getFullPath(decompliePath, 'assets')
  179. if os.path.exists(assetsPath):
  180. ret = file_utils.copyFileAllDir(assetsPath, decomplieAssetsPath)
  181. if ret:
  182. return ret
  183. # 拷贝jniLib
  184. print('copy jniLibs...')
  185. jniPath = file_utils.getFullPath(sdkPath, 'jniLibs')
  186. decomplieJniPath = file_utils.getFullPath(decompliePath, 'lib')
  187. abiFilters = []
  188. if os.path.exists(decomplieJniPath):
  189. for abi in os.listdir(decomplieJniPath):
  190. if abi == 'armeabi-v7a' or abi == 'armeabi':
  191. abiFilters.append(abi)
  192. else:
  193. abiFilters = ['armeabi-v7a']
  194. if os.path.exists(jniPath):
  195. ret = file_utils.copyFileAllDir(jniPath, decomplieJniPath, False, abiFilters)
  196. if ret:
  197. return ret
  198. return 0
  199. def mergeDrawableRes(game, sdk, subChannel, config):
  200. '''
  201. 合并Drawable-v4目录
  202. '''
  203. print('merge drawable path...')
  204. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  205. resPath = os.path.join(decompliePath, 'res')
  206. for path in os.listdir(resPath):
  207. print('res path %s' % path)
  208. if (path.startswith('drawable') or path.startswith('mipmap')) and path.endswith('-v4'):
  209. print('merge path %s' % path)
  210. v4DrawablePath = os.path.join(resPath, path)
  211. drawablePath = os.path.join(resPath, path[:-3])
  212. if os.path.exists(drawablePath):
  213. ret = file_utils.copyFileAllDir(v4DrawablePath, drawablePath, True)
  214. if ret:
  215. return ret
  216. else:
  217. os.rename(v4DrawablePath, drawablePath)
  218. return 0
  219. def removeSameRes(game, sdk, subChannel, config):
  220. '''
  221. 移除相同的资源
  222. '''
  223. # 读取sdk的资源
  224. print('remove same res...')
  225. sdkPath = file_utils.getFullSDKPath(sdk)
  226. sdkResPath = os.path.join(sdkPath, 'res')
  227. resList = []
  228. for path in os.listdir(sdkResPath):
  229. if not path.startswith('values'):
  230. continue
  231. absPath = os.path.join(sdkResPath, path)
  232. for resFile in os.listdir(absPath):
  233. '''if not resFile.startswith('jm_'):
  234. continue'''
  235. resList = xml_utils.readAllRes(os.path.join(absPath, resFile), resList)
  236. if len(resList) == 0:
  237. print('no same res found')
  238. return 0
  239. # 移除相同的资源
  240. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  241. resPath = os.path.join(decompliePath, 'res')
  242. for path in os.listdir(resPath):
  243. if not path.startswith('values'):
  244. continue
  245. absPath = os.path.join(resPath, path)
  246. for resFile in os.listdir(absPath):
  247. xml_utils.removeSameRes(os.path.join(absPath, resFile), resList)
  248. return 0
  249. def mergeManifestRes(game, sdk, subChannel, config):
  250. '''
  251. 合并主文件
  252. '''
  253. print('merge AndroidManifest...')
  254. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  255. manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
  256. sdkPath = file_utils.getFullSDKPath(sdk)
  257. libManifest = file_utils.getFullPath(sdkPath, 'manifest.xml')
  258. return xml_utils.mergeManifestRes(manifest, libManifest)
  259. def copyAppRes(game, sdk, subChannel, config):
  260. '''
  261. 拷贝app的资源,比如app icon、启动图等
  262. '''
  263. channelPath = file_utils.getSubChannelPath(game, sdk, subChannel)
  264. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  265. # assets
  266. print('copy assets...')
  267. assetsPath = file_utils.getFullPath(channelPath, 'assets')
  268. decomplieAssetsPath = file_utils.getFullPath(decompliePath, 'assets')
  269. if os.path.exists(assetsPath):
  270. ret = file_utils.copyFileAllDir(assetsPath, decomplieAssetsPath)
  271. if ret:
  272. return ret
  273. # icon
  274. print('copy icon...')
  275. ret = copyAppResWithType(decompliePath, channelPath, 'icon')
  276. if ret:
  277. return ret
  278. # 启动图
  279. print('copy splash...')
  280. ret = copyAppResWithType(decompliePath, channelPath, 'splash')
  281. if ret:
  282. return ret
  283. # 其他图片
  284. print('copy image...')
  285. ret = copyAppResWithType(decompliePath, channelPath, 'image')
  286. if ret:
  287. return ret
  288. return 0
  289. def copyAppResWithType(decompliePath, channelPath, typeName):
  290. decomplieResPath = os.path.join(decompliePath, 'res')
  291. iconPath = os.path.join(channelPath, typeName)
  292. if not os.path.exists(iconPath):
  293. print('dir "%s" not exists' % iconPath)
  294. return 0
  295. for d in os.listdir(iconPath):
  296. ret = copyResWithType(iconPath, decomplieResPath, d)
  297. if ret:
  298. return ret
  299. return 0
  300. def copyResWithType(resPath, decomplieResPath, typeName):
  301. # appt的打包目录会带-v4后缀
  302. resDir = os.path.join(resPath, typeName)
  303. target = os.path.join(decomplieResPath, typeName)
  304. targetV4 = os.path.join(decomplieResPath, typeName + '-v4')
  305. if not os.path.exists(target) and not os.path.exists(targetV4):
  306. os.makedirs(target)
  307. return file_utils.copyFileAllDir(resDir, target, False)
  308. elif not os.path.exists(target):
  309. return file_utils.copyFileAllDir(resDir, targetV4, False)
  310. else:
  311. return file_utils.copyFileAllDir(resDir, target, False)
  312. def removeNoSupportAttr(game, sdk, subChannel, config):
  313. '''
  314. 删除一些不支持的属性
  315. '''
  316. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  317. manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
  318. xml_utils.removeRootAttr(manifest, 'compileSdkVersion')
  319. xml_utils.removeRootAttr(manifest, 'compileSdkVersionCodename')
  320. return 0
  321. def fixUnSupportConfig(game, sdk, subChannel, config):
  322. '''
  323. 删除一些不支持的配置
  324. '''
  325. # 检查minSdkVersion
  326. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  327. yml = os.path.join(decompliePath, 'apktool.yml')
  328. minSdkVersion = 15
  329. file_utils.changeMinSdkVersion(yml, minSdkVersion)
  330. resPath = os.path.join(decompliePath, 'res')
  331. tag = '-v'
  332. for res in os.listdir(resPath):
  333. print('res = ' + res)
  334. if res.startswith('values') and tag in res:
  335. start = res.index(tag)
  336. version = res[start+len(tag):]
  337. if not version.isdigit():
  338. continue
  339. version = int(version)
  340. print('version = %d' % version)
  341. if version < minSdkVersion:
  342. unSopportPath = os.path.join(resPath, res)
  343. print('unSopportPath = ' + unSopportPath)
  344. file_utils.deleteFolder(unSopportPath)
  345. print('deleteFolder = ' + unSopportPath)
  346. return 0
  347. def changePackageName(game, sdk, subChannel, config):
  348. '''
  349. 更改包名
  350. '''
  351. # 全局替换AndroidManifest里面的包名
  352. newPackageName = config['packageName']
  353. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  354. manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
  355. packageName = xml_utils.getPackageName(manifest)
  356. xml_utils.changePackageName(manifest, newPackageName)
  357. print('change package name %s --> %s' % (packageName, newPackageName))
  358. return 0
  359. def getPackageName(game, sdk, subChannel, config):
  360. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  361. manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
  362. packageName = xml_utils.getPackageName(manifest)
  363. return packageName
  364. def changeAppName(game, sdk, subChannel, config):
  365. '''
  366. 更改app名
  367. '''
  368. # 生成string.xml文件
  369. name = config['name']
  370. resName = 'shanshen_sdk_name'
  371. if 'outName' in config:
  372. resName = resName + '_' + config['outName']
  373. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  374. stringFile = os.path.join(decompliePath, 'res', 'values', 'sdk_strings_temp.xml')
  375. content = '<?xml version="1.0" encoding="utf-8"?><resources><string name="%s">%s</string></resources>' % (resName, name)
  376. file_utils.createFile(stringFile, content)
  377. # 修改主文件的app名的值
  378. manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
  379. xml_utils.changeAppName(manifest, '@string/%s' % resName)
  380. print('change app name %s' % name)
  381. return 0
  382. def changeAppIcon(game, sdk, subChannel, config):
  383. '''
  384. 更改app icon
  385. '''
  386. print('change app icon...')
  387. resName = 'shanshen_sdk_icon'
  388. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  389. manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
  390. xml_utils.changeAppIcon(manifest, '@mipmap/%s' % resName)
  391. return 0
  392. def addMetaData(game, sdk, subChannel, config):
  393. '''
  394. 添加meta-data
  395. '''
  396. if 'metaData' not in config:
  397. return 0
  398. print('add meta-data...')
  399. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  400. manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
  401. xml_utils.addMetaData(manifest, config['metaData'])
  402. return 0
  403. def changePlaceholders(game, sdk, subChannel, config):
  404. '''
  405. 处理掉占位符
  406. '''
  407. if 'placeholders' not in config:
  408. return 0
  409. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  410. manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
  411. placeholders = config['placeholders']
  412. for placeholder in placeholders:
  413. oldText = '${%s}' % placeholder
  414. newText = placeholders[placeholder]
  415. print('change placeholder %s -> %s' % (oldText, newText))
  416. file_utils.replaceContent(manifest, oldText, newText)
  417. return 0
  418. def addLauncher(game, sdk, subChannel, config):
  419. '''
  420. 添加启动图
  421. '''
  422. channelPath = file_utils.getSubChannelPath(game, sdk, subChannel)
  423. splashPath = os.path.join(channelPath, 'splash')
  424. if len(os.listdir(splashPath)) == 0:
  425. print('dir splash is empty')
  426. return 0
  427. print('add launcher...')
  428. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  429. manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
  430. activity = xml_utils.getLauncherActivityName(manifest)
  431. if activity == 'com.shanshen.sdk.template.LauncherActivity':
  432. print('add launcher already exist...')
  433. return 1
  434. # 添加关联资源
  435. internalPath = os.path.join(file_utils.getCurrentPath(), 'internal_shanshen')
  436. ret = copyAppResWithType(decompliePath, internalPath, 'launcher_res')
  437. if ret:
  438. return ret
  439. # 拷贝代码
  440. print('copy launcher code...')
  441. codePath = os.path.join(internalPath, 'launcher_code', 'smali')
  442. smaliPath = file_utils.getFullPath(decompliePath, 'smali')
  443. ret = file_utils.copyFileAllDir(codePath, smaliPath)
  444. if ret:
  445. return ret
  446. # 修改主文件信息
  447. print('change launcher config...')
  448. orientation = xml_utils.getScreenOrientation(manifest)
  449. activity = xml_utils.removeLauncherActivity(manifest)
  450. xml_utils.addLauncherActivity(manifest, orientation, 'com.shanshen.sdk.template.LauncherActivity')
  451. # 修改跳转的
  452. launcherActivity = os.path.join(decompliePath, 'smali', 'com', 'shanshen', 'sdk', 'template', 'LauncherActivity.smali')
  453. file_utils.replaceContent(launcherActivity, '{class}', activity)
  454. print('change launcher %s to %s' % (activity, 'com.shanshen.sdk.template.LauncherActivity'))
  455. # config['oldLauncher'] = activity
  456. if 'launcherTime' in config:
  457. timeHex = formatHex(config['launcherTime'])
  458. file_utils.replaceContent(launcherActivity, '0x0BB8', timeHex)
  459. return 0
  460. def addMoreIcon(game, sdk, subChannel, config):
  461. '''
  462. 添加多个图标
  463. '''
  464. print('add more icon support...')
  465. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  466. icon = '@mipmap/common_sdk_icon'
  467. if not config['changeIcon']:
  468. manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
  469. icon = xml_utils.getApplicationAttr(manifest, 'icon')
  470. switchIcon = icon
  471. if config['switchIcon']:
  472. switchIcon = '@mipmap/common_sdk_icon2'
  473. manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
  474. return xml_utils.addMoreIcon(manifest, icon, switchIcon)
  475. def formatHex(millisecond):
  476. '''
  477. 将毫秒转为16进制,4位格式
  478. '''
  479. timeHex = str(hex(millisecond)).upper()
  480. timeHex = timeHex[2:]
  481. formatHex = ''
  482. if len(timeHex) == 3:
  483. formatHex = '0x0' + timeHex
  484. elif len(timeHex) == 4:
  485. formatHex = '0x' + timeHex
  486. else:
  487. formatHex = '0x0BB8'
  488. return formatHex
  489. def doSDKPostScript(game, sdk, config):
  490. '''
  491. 执行sdk相关特殊处理脚本
  492. '''
  493. sdkPath = file_utils.getFullSDKPath(sdk)
  494. scriptPath = os.path.join(sdkPath, 'script')
  495. targetScript = os.path.join(scriptPath, 'sdk_script.py')
  496. if not os.path.exists(targetScript):
  497. print('sdk_script no exists')
  498. return 0
  499. print('doSDKPostScript...')
  500. sys.path.append(scriptPath)
  501. module = importlib.import_module('sdk_script')
  502. ret = module.execute(game, sdk, config)
  503. sys.path.remove(scriptPath)
  504. return ret
  505. def doGamePostScript(game, sdk, config):
  506. '''
  507. 执行游戏相关特殊处理脚本
  508. '''
  509. channelPath = file_utils.getFullGamePath(game)
  510. scriptPath = os.path.join(channelPath, 'script')
  511. targetScript = os.path.join(scriptPath, 'game_script.py')
  512. if not os.path.exists(targetScript):
  513. print('game_script no exists')
  514. return 0
  515. print('doGamePostScript...')
  516. sys.path.append(scriptPath)
  517. module = importlib.import_module('game_script')
  518. ret = module.execute(game, sdk, config)
  519. sys.path.remove(scriptPath)
  520. return ret
  521. def generateNewRFile(game, sdk, subChannel, config):
  522. '''
  523. 生成新的R文件
  524. '''
  525. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  526. androidPlatforms = file_utils.getAndroidCompileToolPath()
  527. manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
  528. decomplieResPath = file_utils.getFullPath(decompliePath, 'res')
  529. compliePath = file_utils.getFullPath(decompliePath, 'gen')
  530. if not os.path.exists(compliePath):
  531. os.makedirs(compliePath)
  532. # 生成R文件
  533. print('create R.java ...')
  534. if config['aapt2disable']:
  535. aapt = file_utils.getAAPTPath()
  536. ret = file_utils.getExecPermission(aapt)
  537. if ret:
  538. return ret
  539. createRCmd = '"%s" p -f -m -J "%s" -S "%s" -I "%s" -M "%s"' % (aapt, compliePath, decomplieResPath, androidPlatforms, manifest)
  540. ret = file_utils.execFormatCmd(createRCmd)
  541. if ret:
  542. return ret
  543. else:
  544. # compile
  545. aapt = file_utils.getAAPT2Path()
  546. ret = file_utils.getExecPermission(aapt)
  547. if ret:
  548. return ret
  549. print('compiled res ...')
  550. complieResPath = os.path.join(compliePath, 'compiled')
  551. complieResCmd = '"%s" compile --dir "%s" -o "%s"' % (aapt, decomplieResPath, complieResPath)
  552. file_utils.execFormatCmd(complieResCmd)
  553. # unzip
  554. print('unzip compiled res ...')
  555. unzipResPath = os.path.join(compliePath, 'aapt2_res')
  556. with zipfile.ZipFile(complieResPath) as zf:
  557. zf.extractall(unzipResPath)
  558. print('create unzip %s' % unzipResPath)
  559. # link
  560. print('link res ...')
  561. outApk = os.path.join(compliePath, 'res.apk')
  562. linkResCmd = '"%s" link -o "%s" -I "%s" --manifest "%s" --java "%s" --auto-add-overlay' % (aapt, outApk, androidPlatforms, manifest, compliePath)
  563. for filename in os.listdir(unzipResPath):
  564. linkResCmd += ' %s' % filename
  565. print('link cmd len is %s' % len(linkResCmd))
  566. ret = file_utils.execFormatCmd(linkResCmd, unzipResPath)
  567. if ret:
  568. return ret
  569. # 编译R文件
  570. print('complie R.java ...')
  571. packageName = xml_utils.getPackageName(manifest)
  572. packagePath = file_utils.getPackagePath(compliePath, packageName)
  573. RSourceFile = os.path.join(packagePath, 'R.java')
  574. complieRCmd = 'javac -source 1.8 -target 1.8 -encoding UTF-8 "%s"' % RSourceFile
  575. ret = file_utils.execFormatCmd(complieRCmd)
  576. if ret:
  577. return ret
  578. # 生成dex
  579. print('dex R.class ...')
  580. dx = file_utils.getDxPath()
  581. outDex = os.path.join(compliePath, 'classes.dex')
  582. ret = file_utils.execJarCmd(dx, '--dex --output="%s" "%s"' % (outDex, compliePath))
  583. if ret:
  584. return ret
  585. # 反向dex生成smali
  586. # 存放在out目录
  587. print('baksmali classes.dex ...')
  588. baksmaliPath = file_utils.getBaksmaliPath()
  589. outPath = file_utils.getFullPath(decompliePath, 'out')
  590. ret = file_utils.execJarCmd(baksmaliPath, 'd "%s" -o "%s"' % (outDex, outPath))
  591. if ret:
  592. return ret
  593. # 将生成的文件拷贝到目标目录
  594. print('copy R.smali ...')
  595. smaliPath = file_utils.getFullPath(decompliePath, 'smali')
  596. file_utils.copyFileAllDir(outPath, smaliPath)
  597. return 0
  598. def packJar(game, sdk, subChannel, config):
  599. '''
  600. 打包所有的jar
  601. '''
  602. splitDex = config['splitDex']
  603. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  604. outPath = file_utils.getFullPath(decompliePath, 'gen')
  605. if not os.path.exists(outPath):
  606. os.makedirs(outPath)
  607. if config['aapt2disable']:
  608. dx = file_utils.getDxPath()
  609. dexCmd = '--dex --multi-dex --no-warning --output="%s"' % outPath
  610. else:
  611. dx = file_utils.getD8Path()
  612. androidPlatforms = file_utils.getAndroidCompileToolPath()
  613. dexCmd = '--lib "%s" --output "%s"' % (androidPlatforms, outPath)
  614. # 找到所有lib依赖
  615. sdkPath = file_utils.getFullSDKPath(sdk)
  616. libs = os.path.join(sdkPath, 'libs')
  617. libConfig = os.path.join(libs, 'config.json')
  618. # 存在配置文件
  619. if os.path.exists(libConfig):
  620. jsonText = file_utils.readFile(libConfig)
  621. libConf = json.loads(jsonText)
  622. if 'libConfig' in config and config['libConfig'] in libConf:
  623. conf = config['libConfig']
  624. libList = libConf[conf]
  625. for jar in libList:
  626. dexCmd += ' ' + os.path.join(libs, jar)
  627. else:
  628. for jar in os.listdir(libs):
  629. if not jar.endswith('.jar'):
  630. continue
  631. dexCmd += ' ' + os.path.join(libs, jar)
  632. else:
  633. for jar in os.listdir(libs):
  634. if not jar.endswith('.jar'):
  635. continue
  636. dexCmd += ' ' + os.path.join(libs, jar)
  637. # multidex.jar
  638. if splitDex:
  639. dexCmd += ' ' + file_utils.getMultiDexPath()
  640. # sdk实现类
  641. print('packageing all jar ...')
  642. ret = file_utils.execJarCmd(dx, dexCmd)
  643. if ret:
  644. return ret
  645. # 反向dex生成smali
  646. # 存放在out目录
  647. print('baksmali classes.dex ...')
  648. outDex = os.path.join(outPath, 'classes.dex')
  649. baksmaliPath = file_utils.getBaksmaliPath()
  650. outPath = file_utils.getFullPath(decompliePath, 'out')
  651. ret = file_utils.execJarCmd(baksmaliPath, 'd "%s" -o "%s"' % (outDex, outPath))
  652. if ret:
  653. return ret
  654. # 将生成的文件拷贝到目标目录
  655. print('copy all smali ...')
  656. smaliPath = file_utils.getFullPath(decompliePath, 'smali')
  657. ret = file_utils.copyFileAllDir(outPath, smaliPath, True)
  658. if ret:
  659. return ret
  660. return 0
  661. def splitDex(game, sdk, subChannel, config):
  662. '''
  663. 分割dex
  664. '''
  665. # 判断是否已经存在application
  666. # 存在,则往原application添加内容
  667. # 不存在,则拷贝一个默认的android.support.multidex.MultiDexApplication
  668. print('add MultiDex support...')
  669. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  670. manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
  671. application = xml_utils.getApplicationAttr(manifest, 'name')
  672. if application is None:
  673. ret = xml_utils.changeApplicationAttr(manifest, 'name', 'android.support.multidex.MultiDexApplication')
  674. if ret:
  675. return ret
  676. else:
  677. smaliPath = os.path.join(decompliePath, 'smali')
  678. applicationFile = file_utils.getPackagePath(smaliPath, application)
  679. applicationFile += '.smali'
  680. ret = changeApplicationDex(applicationFile)
  681. if ret:
  682. return ret
  683. return splitSmali(game, sdk, subChannel, config, application)
  684. def changeApplicationDex(file):
  685. '''
  686. 修改application的smali文件,增加MultiDex操作
  687. '''
  688. index = file_utils.getApplicationSmaliIndex(file)
  689. file_utils.insertApplicationSmali(file, index)
  690. return 0
  691. def splitSmali(game, sdk, subChannel, config, application):
  692. '''
  693. 如果函数上限超过限制,自动拆分smali,以便生成多个dex文件
  694. '''
  695. print('splitSmali...')
  696. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  697. smaliPath = os.path.join(decompliePath, 'smali')
  698. appPackage = None
  699. if application:
  700. appPackage = application[:application.rfind('.')]
  701. appPackage = appPackage.replace('.', '/')
  702. allFiles = []
  703. allFiles = file_utils.list_files(smaliPath, allFiles)
  704. #print('file count is %d' % len(allFiles))
  705. #maxFuncNum = 65535
  706. # 留一点空间,防止计算误差
  707. maxFuncNum = 64000
  708. currFucNum = 0
  709. totalFucNum = 0
  710. currDexIndex = 1
  711. allRefs = []
  712. #保证Application等类在第一个classex.dex文件中
  713. for f in allFiles:
  714. f = f.replace('\\', '/')
  715. if (appPackage and appPackage in f) or '/android/support/multidex' in f:
  716. currFucNum += smali_utils.get_smali_method_count(f, allRefs)
  717. totalFucNum = currFucNum
  718. for f in allFiles:
  719. f = f.replace('\\', '/')
  720. if not f.endswith('.smali'):
  721. continue
  722. if (appPackage and appPackage in f) or '/android/support/multidex' in f:
  723. continue
  724. thisFucNum = smali_utils.get_smali_method_count(f, allRefs)
  725. totalFucNum += thisFucNum
  726. #print('%d # %d ==> %s' % (thisFucNum, currDexIndex, f))
  727. #print('totalFucNum is %d' % totalFucNum)
  728. if currFucNum + thisFucNum >= maxFuncNum:
  729. currFucNum = thisFucNum
  730. currDexIndex += 1
  731. newDexPath = os.path.join(decompliePath, 'smali_classes%d' % currDexIndex)
  732. os.makedirs(newDexPath)
  733. else:
  734. currFucNum += thisFucNum
  735. if currDexIndex > 1:
  736. newDexPath = os.path.join(decompliePath, 'smali_classes%d' % currDexIndex)
  737. targetFile = newDexPath + f[len(smaliPath):]
  738. file_utils.copyFile(f, targetFile, True)
  739. return 0
  740. def changeVersion(game, sdk, subChannel, config):
  741. '''
  742. 更改版本号
  743. '''
  744. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  745. yml = os.path.join(decompliePath, 'apktool.yml')
  746. versionCode = None
  747. versionName = None
  748. targetSdkVersion = None
  749. if 'versionCode' in config:
  750. versionCode = config['versionCode']
  751. if 'versionName' in config:
  752. versionName = config['versionName']
  753. if 'targetSdkVersion' in config:
  754. targetSdkVersion = config['targetSdkVersion']
  755. return file_utils.changeVersion(yml, versionCode, versionName, targetSdkVersion)
  756. def recomplie(game, sdk, subChannel, config):
  757. '''
  758. 回编译
  759. '''
  760. print('recomplie apk...')
  761. apktoolPath = file_utils.getApkToolPath()
  762. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  763. outApk = file_utils.getOutApkPath(game, sdk, subChannel, config['cache'])
  764. useAppt2 = ' --use-aapt2'
  765. if config['aapt2disable']:
  766. useAppt2 = ''
  767. return file_utils.execJarCmd(apktoolPath, 'b -f "%s" -o "%s" %s' % (decompliePath, outApk, useAppt2))
  768. def alignApk(game, sdk, subChannel, config):
  769. '''
  770. 对齐apk
  771. '''
  772. print('align apk...')
  773. outApk = file_utils.getOutApkPath(game, sdk, subChannel, config['cache'])
  774. alignApk = file_utils.getAlignApkPath(game, sdk, subChannel, config['cache'])
  775. alignapkTool = file_utils.getAlignPath()
  776. if os.path.exists(alignApk):
  777. os.remove(alignApk)
  778. ret = file_utils.getExecPermission(alignapkTool)
  779. if ret:
  780. return ret
  781. # zipalign.exe -v -p 4 input.apk output.apk
  782. return file_utils.execFormatCmd('"%s" -f -p 4 "%s" "%s"' % (alignapkTool, outApk, alignApk))
  783. def apksignerApk(game, sdk, subChannel, config):
  784. '''
  785. 签名apk
  786. '''
  787. print('sign apk...')
  788. path = os.path.join(file_utils.getCurrentPath(), 'keystore', 'key.json')
  789. jsonText = file_utils.readFile(path)
  790. signConfig = json.loads(jsonText)
  791. keystore = {}
  792. if game in signConfig:
  793. if sdk in signConfig[game] and subChannel in signConfig[game][sdk]:
  794. keystore = signConfig[game][sdk][subChannel]
  795. else:
  796. keystore = signConfig['default']
  797. else:
  798. keystore = signConfig['default']
  799. print('storeFile is "%s"' % keystore['storeFile'])
  800. apksigner = file_utils.getApksignerPath()
  801. alignApk = file_utils.getAlignApkPath(game, sdk, subChannel, config['cache'])
  802. signedApk = file_utils.getSignApkPath(game, sdk, subChannel, config['cache'])
  803. storeFile = os.path.join(file_utils.getCurrentPath(), 'keystore', keystore['storeFile'])
  804. if 'outName' in config and 'outPath' in config:
  805. if not os.path.exists(config['outPath']):
  806. os.makedirs(config['outPath'])
  807. signedApk = os.path.join(config['outPath'], config['outName'] + '.apk')
  808. elif 'outName' in config:
  809. signedApk = file_utils.getRenameApkPath(game, sdk, config['cache'], config['outName'])
  810. # 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
  811. v2disable = ''
  812. if 'v2disable' in config and config['v2disable']:
  813. v2disable = ' --v2-signing-enabled=false'
  814. 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))
  815. def addChannel(game, sdk, subChannel, config):
  816. '''
  817. 添加渠道信息
  818. '''
  819. if 'v2disable' in config and config['v2disable']:
  820. return 0
  821. walle = file_utils.getWallePath()
  822. signedApk = file_utils.getSignApkPath(game, sdk, subChannel, config['cache'])
  823. if 'outName' in config and 'outPath' in config:
  824. signedApk = os.path.join(config['outPath'], config['outName'] + '.apk')
  825. elif 'outName' in config:
  826. signedApk = file_utils.getRenameApkPath(game, sdk, config['cache'], config['outName'])
  827. properties = config['properties']
  828. appid = ''
  829. appkey = ''
  830. if 'appid' in properties:
  831. appid = properties['appid']
  832. if 'appkey' in properties:
  833. appkey = properties['appkey']
  834. return file_utils.execJarCmd(walle, 'put -e version=%s,agent=%s,appid=%s,appkey=%s "%s" "%s"' % (config_utils_shanshen.getDate(), properties['agent'], appid, appkey, signedApk, signedApk))
  835. def clearTemp(game, sdk, subChannel, config):
  836. '''
  837. 清空中间产生的文件
  838. '''
  839. print('clear temp...')
  840. alignApk = file_utils.getAlignApkPath(game, sdk, subChannel, config['cache'])
  841. outApk = file_utils.getOutApkPath(game, sdk, subChannel, config['cache'])
  842. if os.path.exists(alignApk):
  843. os.remove(alignApk)
  844. if os.path.exists(outApk):
  845. os.remove(outApk)
  846. decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
  847. file_utils.deleteFolder(decompliePath)
  848. print('clear temp end')
  849. def packConsoleInput():
  850. '''
  851. 控制台打包
  852. '''
  853. if len(sys.argv) < 3:
  854. print('argument is missing')
  855. return 1
  856. # 校验参数
  857. game = sys.argv[1]
  858. sdk = sys.argv[2]
  859. # 可选参数,没有则默认打全部渠道
  860. subChannel = None
  861. if len(sys.argv) > 3:
  862. subChannel = sys.argv[3]
  863. return packConsole(game, sdk, subChannel)
  864. def packConsole(game, sdk, subChannel):
  865. '''
  866. 控制台打包
  867. '''
  868. if not os.path.exists(file_utils.getFullGameApk(game)):
  869. print('game "%s" not exists' % game)
  870. return 1
  871. if not os.path.exists(file_utils.getFullSDKPath(sdk)):
  872. print('sdk "%s" not exists' % sdk)
  873. return 1
  874. # 读取配置
  875. channelPath = file_utils.getChannelPath(game, sdk)
  876. configPath = os.path.join(channelPath, 'config.json')
  877. if not os.path.exists(configPath):
  878. print('%s not exists' % configPath)
  879. return 1
  880. jsonText = file_utils.readFile(configPath)
  881. config = json.loads(jsonText)
  882. # 检查参数
  883. if not config_utils_shanshen.checkConfig(config):
  884. return 1
  885. # 处理参数
  886. config_utils_shanshen.replaceArgs(config)
  887. successCount = 0
  888. failureCount = 0
  889. if type(config) == dict:
  890. if subChannel is None or config['subChannel'] == subChannel:
  891. ret = pack(game, sdk, config)
  892. if ret:
  893. failureCount += 1
  894. else:
  895. successCount += 1
  896. else:
  897. print('subChannel "%s" no found' % subChannel)
  898. return 1
  899. elif type(config) == list:
  900. found = False
  901. for itemConfig in config:
  902. if subChannel is None or itemConfig['subChannel'] == subChannel:
  903. found = True
  904. ret = pack(game, sdk, itemConfig)
  905. if ret:
  906. failureCount += 1
  907. else:
  908. successCount += 1
  909. if not found:
  910. print('subChannel "%s" no found' % subChannel)
  911. return 1
  912. print('success %d, failure %d' % (successCount, failureCount))
  913. return 0