file_utils.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. # -*- coding:utf-8 -*-
  2. import channel_special_replace
  3. from print_log import printlog
  4. import os, xml.etree.ElementTree as ET, shutil, re
  5. androidNS = 'http://schemas.android.com/apk/res/android'
  6. namespace = '{' + androidNS + '}'
  7. def read_file(file):
  8. """
  9. 读取文件内容
  10. """
  11. content = ''
  12. with open_file(file, 'r') as f:
  13. content = f.read()
  14. return content
  15. # 文件修改时间比较
  16. # file1 > file2 return 1
  17. # file1 == file2 return 0
  18. # file1 < file2 return -1
  19. # 任意一个文件不存在则返回None
  20. def compareFileModifyTime(file1, file2):
  21. result = 0
  22. if not os.path.exists(file1):
  23. err = "file %s is not exists" % file1
  24. printlog(err)
  25. return None
  26. if not os.path.exists(file2):
  27. err = "file %s is not exists" % file2
  28. printlog(err)
  29. return None
  30. file1MTime = os.stat(file1).st_mtime
  31. file2MTime = os.stat(file2).st_mtime
  32. printlog("[file1MTime]: %s" % file1MTime)
  33. printlog("[file2MTime]: %s" % file2MTime)
  34. if file1MTime > file2MTime:
  35. return 1
  36. elif file1MTime < file2MTime:
  37. return -1
  38. else:
  39. return 0
  40. pass
  41. # 修改包名
  42. def modifyPkgName(apkPath, pkgName):
  43. manifestRoot = ET.parse("%s/AndroidManifest.xml" % apkPath).getroot()
  44. if manifestRoot is None:
  45. printlog("AndroidManifest.xml 文件解析失败")
  46. return 1
  47. if pkgName == "":
  48. printlog("包名未配置,保持母包包名")
  49. else:
  50. oldPkgName = manifestRoot.attrib["package"]
  51. content = open("%s/AndroidManifest.xml" % apkPath, "r").read()
  52. content = content.replace(oldPkgName, pkgName)
  53. open("%s/AndroidManifest.xml" % apkPath, "w").write(content)
  54. printlog("[old pkg name] : %s ----------------------------" % oldPkgName)
  55. printlog("[new pkg name] : %s ----------------------------" % pkgName)
  56. # sdkConfig.xml + AndroidManifest.xml
  57. # sdkConfigPath: 渠道配置路径
  58. # apkPath:解包后AndroidManifest.xml路径
  59. def mergeManifest(sdkConfigPath, apkManifestPath):
  60. printlog("------------------------- start to merge AndroidManifest.xml -------------------------")
  61. ret = 0
  62. # 注册命名空间
  63. ET.register_namespace('android', androidNS)
  64. printlog("[apkManifestPath] : %s" % apkManifestPath)
  65. printlog("[sdkConfig] : %s" % sdkConfigPath)
  66. if not os.path.exists(apkManifestPath) or not os.path.exists(sdkConfigPath):
  67. printlog("targetManifest or sdkConfigPath is not exists!")
  68. return 1
  69. sdkRoot = ET.parse(sdkConfigPath).getroot()
  70. if sdkRoot is None:
  71. printlog("cant parse sdkConfig!")
  72. return 1
  73. targetTree = ET.parse(apkManifestPath)
  74. targetRoot = targetTree.getroot()
  75. if targetRoot is None:
  76. printlog("cant parse targetTree")
  77. return 1
  78. targetApplicationRoot = targetRoot.find("application")
  79. # 合并activity和service
  80. sdkActivities = sdkRoot.find("sdk_activity").iter("activity")
  81. sdkActivities_alicas = sdkRoot.find("sdk_activity").iter("activity-alias")
  82. sdkServices = sdkRoot.find("sdk_activity").iter("service")
  83. sdkReceiver = sdkRoot.find("sdk_activity").iter("receiver")
  84. sdkProvider = sdkRoot.find("sdk_activity").iter("provider")
  85. for activity in sdkActivities:
  86. targetApplicationRoot.append(activity)
  87. for service in sdkServices:
  88. targetApplicationRoot.append(service)
  89. for receiver in sdkReceiver:
  90. targetApplicationRoot.append(receiver)
  91. for provider in sdkProvider:
  92. targetApplicationRoot.append(provider)
  93. for alicas in sdkActivities_alicas:
  94. targetApplicationRoot.append(alicas)
  95. printlog("merging activity and service completed!")
  96. # 合并meta-data
  97. sdkMetas = sdkRoot.find("sdk_meta").iter("meta-data")
  98. for meta in sdkMetas:
  99. targetApplicationRoot.append(meta)
  100. printlog("merging meta-data completed!")
  101. # 合并permission
  102. sdkPermissions = []
  103. for per in sdkRoot.find("sdk_permission").findall("uses-permission"):
  104. sdkPermissions.append(list(per.attrib.values())[0])
  105. targetPermissions = []
  106. for per in targetRoot.findall("uses-permission"):
  107. targetPermissions.append(list(per.attrib.values())[0])
  108. targetRoot.remove(per)
  109. # 利用set排重
  110. mergePermissions = list(set(sdkPermissions) | set(targetPermissions))
  111. for per in mergePermissions:
  112. element = ET.Element("uses-permission")
  113. element.attrib = {"android:name": per}
  114. element.tail = "\n\t"
  115. targetRoot.append(element)
  116. printlog("merging permission completed!")
  117. targetTree.write("%s" % apkManifestPath, "utf-8", xml_declaration=True)
  118. # 根据配置修改游戏名,包名等
  119. printlog("------------------------- finished merging AndroidManifest.xml -------------------------")
  120. return ret
  121. # 文件安全删除
  122. def safeFileDelete(filePath):
  123. if os.path.exists(filePath):
  124. if os.path.isdir(filePath):
  125. shutil.rmtree(filePath)
  126. printlog("[delete isdir filePath] : %s" % filePath)
  127. elif os.path.isfile(filePath):
  128. printlog("[delete isfile filePath] : %s" % filePath)
  129. os.remove(filePath)
  130. return
  131. # 删除文件但不删除文件夹
  132. def del_file(path_data):
  133. for i in os.listdir(path_data): # os.listdir(path_data)#返回一个列表,里面是当前目录下面的所有东西的相对路径
  134. file_data = path_data + "/" + i # 当前文件夹的下面的所有东西的绝对路径
  135. if os.path.isfile(file_data): # os.path.isfile判断是否为文件,如果是文件,就删除.如果是文件夹.递归给del_file.
  136. os.remove(file_data)
  137. else:
  138. del_file(file_data)
  139. # 复制并覆盖文件
  140. def copyAllFile(fromFile, toFile):
  141. for parent, dirName, fileNames in os.walk(fromFile):
  142. oriParent = parent.replace(fromFile, toFile)
  143. for fileName in fileNames:
  144. fromDir = "%s/%s" % (parent, fileName)
  145. # printlog("fromDir :%s"%fromDir)
  146. toDir = "%s/%s" % (oriParent, fileName)
  147. # printlog("toDir: %s"%toDir)
  148. copyFileForCompareFile(fromDir, toDir)
  149. pass
  150. def copy_file_all_dir(from_dir, to_dir, delete=False, support=None):
  151. """
  152. 拷贝目录下所有文件文件
  153. """
  154. ret = copy_dir(from_dir, to_dir, delete, support)
  155. if ret:
  156. return ret
  157. if delete:
  158. delete_folder(from_dir)
  159. return 0
  160. def copy_dir(from_dir, to_dir, delete=False, support=None):
  161. """
  162. 拷贝目录下所有文件文件
  163. """
  164. # print('copy all file %s --> %s' % (fromDir, toDir))
  165. if not os.path.exists(from_dir):
  166. print('%s not exists!' % from_dir)
  167. return 1
  168. if not os.path.isdir(from_dir):
  169. print('%s not a dir!' % from_dir)
  170. return 1
  171. for fromFile in os.listdir(from_dir):
  172. from_file_path = os.path.join(from_dir, fromFile)
  173. to_file_path = os.path.join(to_dir, fromFile)
  174. if os.path.isdir(from_file_path):
  175. # 不支持的类型
  176. if support is not None and fromFile not in support:
  177. continue
  178. ret = copy_dir(from_file_path, to_file_path, delete, support)
  179. if ret:
  180. return ret
  181. else:
  182. ret = copy_file(from_file_path, to_file_path, delete)
  183. if ret:
  184. return ret
  185. return 0
  186. def copy_file(from_file, to_file, delete=False):
  187. """
  188. 拷贝文件
  189. """
  190. if not os.path.isfile(from_file):
  191. print('----> %s not a file!' % from_file)
  192. return 1
  193. # 分离文件名和路径
  194. file_path, file_name = os.path.split(to_file)
  195. if not os.path.exists(file_path):
  196. # 创建路径
  197. os.makedirs(file_path)
  198. if delete:
  199. # 移动文件
  200. shutil.move(from_file, to_file)
  201. else:
  202. # 复制文件
  203. if not from_file.endswith('.DS_Store'):
  204. shutil.copyfile(from_file, to_file)
  205. return 0
  206. def copyFileForCompareFile(fromFile, toFile):
  207. if not os.path.exists(fromFile):
  208. printlog("file %s is not exists" % fromFile)
  209. return
  210. filePattern = re.compile(r".*\..*")
  211. toDir = toFile
  212. if filePattern.match(toFile):
  213. toDir = os.path.dirname(toFile)
  214. if not os.path.exists(toDir):
  215. os.makedirs(toDir)
  216. shutil.copy(fromFile, toFile)
  217. pass
  218. def change_min_sdk_version(yml, min_sdk_version):
  219. with open_file(yml, 'r+') as f:
  220. content = ''
  221. line = f.readline()
  222. while line:
  223. if 'minSdkVersion' in line:
  224. start = line.index("'")
  225. version = int(line[start + 1:-2])
  226. if version < min_sdk_version:
  227. content += ' minSdkVersion: \'%s\'\n' % min_sdk_version
  228. else:
  229. return 0
  230. else:
  231. content += line
  232. line = f.readline()
  233. f.seek(0, 0)
  234. f.truncate()
  235. f.write(content)
  236. return 0
  237. def open_file(file, mode):
  238. return open(file, mode, encoding='UTF-8')
  239. def delete_folder(folder):
  240. """
  241. 删除目录以及目录下的文件
  242. """
  243. if not os.path.exists(folder):
  244. return
  245. shutil.rmtree(folder)
  246. def iterate_dir_path(dir):
  247. path_list = []
  248. for path in os.listdir(dir):
  249. abs_path = os.path.join(dir, path)
  250. path_list.append(abs_path)
  251. return path_list
  252. def replace_content(file, old_txt, new_txt):
  253. """
  254. 全局替换
  255. """
  256. with open_file(file, 'r+') as f:
  257. t = f.read()
  258. t = t.replace(old_txt, new_txt)
  259. # 读写偏移位置移到最开始处
  260. f.seek(0, 0)
  261. # 清空内容
  262. f.truncate()
  263. f.write(t)
  264. def replace_manifest_meta_data(am_path, key, value):
  265. lines = open(am_path).readlines()
  266. fp = open(am_path, 'w')
  267. for s in lines:
  268. if s.find('android:name=\"' + key + '\"') != -1:
  269. printlog("Before replaceAM: %s" % s)
  270. s = re.sub('android:name=\"' + key + '\" android:value=\".*\"',
  271. 'android:name=\"' + key + '\" android:value=\"' + value + '\"', s)
  272. printlog("After replaceAM: %s" % s)
  273. fp.write(s)
  274. fp.close()
  275. pass
  276. def replace_assets_param(assets_param_path, key, value):
  277. lines = open(assets_param_path).readlines()
  278. fp = open(assets_param_path, 'w')
  279. for s in lines:
  280. s = re.sub(key + '=.*', key + '=' + value, s)
  281. fp.write(s)
  282. fp.close()
  283. pass
  284. def special_replace(sdk_name, prj_path, key, value):
  285. # 获取特殊替换
  286. printlog(" 开始进行渠道特殊处理 :%s" % sdk_name)
  287. replaceFun = channel_special_replace.get_special_replace(sdk_name)
  288. if replaceFun:
  289. replaceFun(prj_path, key, value)
  290. else:
  291. printlog(" 渠道无需特殊处理 :%s" % sdk_name)
  292. pass
  293. def replace_string_app_name(apk_decompile_tmp_dir, value):
  294. if not value:
  295. return
  296. # 命名空间,用于获取对应的值
  297. amTree = ET.parse(os.path.join(apk_decompile_tmp_dir, 'AndroidManifest.xml'))
  298. if amTree is None:
  299. return
  300. amRoot = amTree.getroot()
  301. applicationNode = amRoot.find("application")
  302. nameLabelAttrib = applicationNode.attrib.get("{%s}label" % androidNS)[1:]
  303. nameLabelArr = nameLabelAttrib.split("/")
  304. nameLabelFile = nameLabelArr[0]
  305. nameLabel = nameLabelArr[1]
  306. modifyFileTree = ET.parse("%s/res/values/%ss.xml" % (apk_decompile_tmp_dir, nameLabelFile))
  307. if modifyFileTree is None:
  308. return False
  309. modifyFileRoot = modifyFileTree.getroot()
  310. for child in list(modifyFileRoot):
  311. if child.attrib["name"] == nameLabel:
  312. child.text = value
  313. modifyFileTree.write("%s/res/values/%ss.xml" % (apk_decompile_tmp_dir, nameLabelFile), "utf-8",
  314. xml_declaration=True)
  315. return True
  316. pass
  317. def replace_version_name(yml_path, value):
  318. if not os.path.exists(yml_path):
  319. return False
  320. replaceVersionNameShell = "/bin/sed -i -E \"s/(versionName:).*/\\1 %s/\" %s" % (value, yml_path)
  321. printlog("[replaceVersionNameShell] : %s" % replaceVersionNameShell)
  322. ret = os.system(replaceVersionNameShell)
  323. return ret
  324. pass
  325. def replace_version_code(yml_path, value):
  326. if not os.path.exists(yml_path):
  327. return False
  328. replaceVersionCodeShell = "/bin/sed -i -E \"s/(versionCode:).*/\\1 %s/\" %s" % (value, yml_path)
  329. printlog("[replaceVersionCodeShell] : %s" % replaceVersionCodeShell)
  330. ret = os.system(replaceVersionCodeShell)
  331. return ret
  332. pass
  333. def replace_yml_target_sdk_version(apkYmlPath, code):
  334. lines = open(apkYmlPath).readlines()
  335. fp = open(apkYmlPath, 'w')
  336. for line in lines:
  337. if 'targetSdkVersion' in line:
  338. newTargetVersion = " targetSdkVersion: '%s'" % code + '\n'
  339. printlog("oldTargetVersion:%s --------newTargetVersion:%s" % (line, newTargetVersion))
  340. line = re.sub(line, newTargetVersion, line)
  341. fp.write(line)
  342. fp.close()
  343. pass
  344. def changeVersion(yml, versionCode=None, versionName=None, targetSdkVersion=None):
  345. tag = ['versionCode:', 'versionName:', 'targetSdkVersion:']
  346. printlog('versionCode:%s versionName:%s targetSdkVersion:%s' % (versionCode, versionName, targetSdkVersion))
  347. with open_file(yml, 'r+') as f:
  348. content = ''
  349. line = f.readline()
  350. while line:
  351. if versionCode and tag[0] in line:
  352. content += ' versionCode: \'%s\'\n' % versionCode
  353. elif versionName and tag[1] in line:
  354. content += ' versionName: \'%s\'\n' % versionName
  355. elif targetSdkVersion and tag[2] in line:
  356. content += ' targetSdkVersion: \'%s\'\n' % targetSdkVersion
  357. else:
  358. content += line
  359. line = f.readline()
  360. f.seek(0, 0)
  361. f.truncate()
  362. f.write(content)
  363. def replace_file_keyword(file, keyword, value):
  364. if not os.path.exists(file):
  365. return False
  366. replaceVersionCodeShell = "/bin/sed -i -E \"s/(%s).*/\\1 %s/\" %s" % (keyword, value, file)
  367. printlog("[replaceVersionCodeShell] : %s" % replaceVersionCodeShell)
  368. ret = os.system(replaceVersionCodeShell)
  369. return ret
  370. pass
  371. # 修改微信回调页面
  372. def merge_wx_page(package_name, apk_decompile_tmp_dir):
  373. manifest_path = os.path.join(apk_decompile_tmp_dir, 'AndroidManifest.xml')
  374. manifest_root = ET.parse(manifest_path).getroot()
  375. for child in manifest_root.findall('application/activity'):
  376. if 'wxapi' in child.attrib[namespace + "name"]:
  377. old_package_name_path = child.attrib[namespace + "name"].split('.wxapi.')[0].replace(".", "/")
  378. old_wx_api_code_path = os.path.join(apk_decompile_tmp_dir, 'smali_classes2', old_package_name_path, 'wxapi')
  379. printlog('[old_wx_api_code_path]:%s' % old_wx_api_code_path)
  380. new_package_name_path = package_name.replace(".", "/")
  381. new_wx_api_code_path = os.path.join(apk_decompile_tmp_dir, 'smali_classes2', new_package_name_path, 'wxapi')
  382. printlog('[new_wx_api_code_path]:%s' % new_wx_api_code_path)
  383. if old_wx_api_code_path == new_wx_api_code_path:
  384. printlog('已经在包名相同的微信回调代码,无需修改')
  385. return 0
  386. if not os.path.exists(old_wx_api_code_path):
  387. old_wx_api_code_path = os.path.join(apk_decompile_tmp_dir, 'smali', old_package_name_path,
  388. 'wxapi')
  389. if not os.path.exists(old_wx_api_code_path):
  390. printlog('微信回调代码不存在,不执行合并操作')
  391. return 0
  392. copy_file_all_dir(old_wx_api_code_path, new_wx_api_code_path, False)
  393. for parent, dirnames, filenames in os.walk(new_wx_api_code_path):
  394. for filename in filenames:
  395. abs_filename = os.path.join(new_wx_api_code_path, filename)
  396. printlog("[start to replace package_name]:%s" % abs_filename)
  397. replace_content(abs_filename, old_package_name_path, new_package_name_path)
  398. replace_content(manifest_path, old_package_name_path.replace('/', '.'), package_name)
  399. return
  400. if __name__ == "__main__":
  401. merge_wx_page('com.uxmfe.fgkg', '/Users/kaiweicai/Documents/Project/PackKit/YYXXPackKit/game/104082/dcm_tmphuawei')