package_utils_record.py 38 KB

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