├── README.md ├── addNativeCode.py ├── autoBornCode.py ├── renameNative.py └── word_list.json /README.md: -------------------------------------------------------------------------------- 1 | # iosMixTools 2 | ios混淆脚本工具,主要用于游戏类。顺便安利一波: [IOS马甲包混淆](https://blog.csdn.net/lyzz0612/article/details/80390362) 3 | 有任何问题和建议欢迎联系: email: lyzz0612@qq.com 4 | 5 | ## Updated 6 | 这些工具用完目前还是2.1被拒, 4.3的可以尝试下;第三方加固工具试用版也被拒,目前在打算先用小游戏过审。 7 | 8 | 资源混淆感觉应该够了,但还是被4.3。这边用的游戏引擎代码基本没动,准备对它动刀了 9 | 10 | * 定义宏加到代码不同地方,根据行号不同插入不同代码,可引入第三方库 11 | ``` 12 | #define MIX_FUNC_1 {SOME CODE} 13 | #define MIX_FUNC(__LINE__) MIX_FUNC_##__LINE__ 14 | ``` 15 | 16 | * 改写原有宏实现,如cocos引擎的CCASSERT,CCLOG 17 | * 替换字符串,改为解密加密字符串 18 | 19 | ### Updated 2019/01/17 20 | 刚用小游戏提审过了,主要改地方是 21 | 1. 没加垃圾资源,但是把所有资源打成加密zip包+xor加密,启动时再解压执行。这样苹果机审应该不可能扫出重复资源来 22 | 2. 换iOS类名、函数名。原来只是加了垃圾函数,类名也只是加前缀,这次全换了完全不同的名字 23 | 3. 换机器打包、提审。本来公司就几台打包机器,提了很多次估计被标记了,一提就封号……这次是自己的电脑打包和提审的 24 | 4. 去掉热更联网检查,改为固定时间判断。原来是启动会检查更新,虽然提示是改成正在加载资源了,但有可能检测到网络数据了。这次直接固定提审1个月开放热更 25 | 26 | ### Updated 27 | 发现新的混淆工具[https://github.com/zfjsyqk/ZFJObsLib](https://github.com/zfjsyqk/ZFJObsLib),看描述功能很齐全,待试用。 28 | 29 | ## 使用说明 30 | 31 | ### 1. addNative.py 生成oc垃圾代码工具 32 | 此脚本会扫描指定目录,给OC文件添加垃圾函数,同时创建垃圾文件到/trash目录。 33 | #### 参数说明 34 | 35 | * ` --oc_folder OC_FOLDER` OC_FOLDER为OC代码所在目录 36 | * `--replace`替换OC_FOLDER下的原文件,同时原代码会备份到脚本目录下的backup_ios目录。不指定此项垃圾代码只会放到脚本目录下的target_ios/ 37 | 38 | addNative.py里还有一些配置可以看需求手动修改,如生成垃圾文件的数量,垃圾函数的数量,忽略文件列表等,具体请查看代码顶部相关注释 39 | 40 | ### 2. renameNative.py 修改类名前缀工具 41 | 类名是引用可能较为复杂的东西,工具批量替换的限制要求会比较多。如果你的项目满足以下条件,那么这个工具会比较适合你: 42 | 43 | * 大部分类名都带相同的前缀,也只准备替换前缀; 44 | * 大部分类都只在一个大文件夹下,它们之间相互引用,外部调用的情况较少并且你能很有把握的排除或手动替换它们; 45 | * 类名和文件名一致; 46 | 47 | 本工具的流程是扫描指定文件夹,找到名称(或者说类名,工具假设两者是一致的)以指定前缀开始的文件;修改替换文件名前缀;并再次遍历此文件夹所有文件,将文件内容中的所有该名称也替换掉;替换xx.xcodeproj/project.pbxproj下的路径,省去在Xcode中手动添加文件(因为文件名修改了,不替换的话Xcode上还保持原来的名称会提示找不到文件);同时为了防止文件夹名称跟文件名相同而导致替换project.pbxproj时将目录名也替换掉的情况,对文件夹名称也进行相同的流程。 48 | 49 | #### 参数说明: 50 | 51 | * `--add_prefix PREFIX` 添加类名前缀,有此项old_prefix和new_prefix将不起作用,此项请提前在renameNative.py文件中ignore_path_text添加不需要前缀的文件或路径 52 | * `--old_prefix OLD_PREFIX` 替换前的类名前缀 53 | * `--new_prefix NEW_PREFIX` 替换后的类名前缀 54 | * `--ios_path IOS_PATH` OC文件目录 55 | * `--proj_path PROJ_PATH ` xx.xcodeproj路径 56 | 57 | 运行示例: 58 | `python renameNative.py --add_prefix ANDROID --ios_path xx/xx/xx/ --proj_path xx/xx/xx.xcodeproj` 59 | `python renameNative.py --old_prefix ANDROID --new_prefix IOS --ios_path xx/xx/xx/ --proj_path xx/xx/xx.xcodeproj` 60 | 61 | 62 | ### 3. autoBornCode.py 添加lua和png,修改资源文件MD5 63 | 此脚本会扫描指定文件夹,在路径包含/res的目录创建png, 其他地方创建lua。根据目录下的文件和文件夹数随机添加文件和子文件夹,创建数量是根据目录下原有文件和文件夹的数量随机。然后会给大部分类型资源文件添加一些无效内容来改变其MD5。 64 | 65 | #### 参数说明 66 | 67 | * `--res RESOURCE_DIR` 资源目录 68 | * `--target TARGET_FOLDER` 可选参数,修改后的资源存放目录。不设置为脚本目录下的target_resource 69 | 70 | 在代码的最前面有个匹配规则, path_exclude表示必须是不包含该字符串的路径才能创建该类型文件,path_include表示必须是包含该字符串的路径才要创建该类型文件 71 | ``` 72 | match_rule = { 73 | ".png": { 74 | "path_include": os.path.sep + "res", 75 | }, 76 | ".lua": { 77 | # "path_include": "src", 78 | "path_exclude": os.path.sep + "res", 79 | }, 80 | } 81 | ``` 82 | 83 | **TODO**音效文件修改MD5值 84 | 85 | 86 | ### 4. iOS加固插件 87 | [五款iOS加固产品测试与点评](http://telecom.chinabyte.com/300/14570300.shtml) 88 | 89 | [加固产品比较](https://www.codercto.com/a/28193.html) 90 | 91 | 1. Obfuscator-LLVM 92 | 93 | [Obfuscator-LLVM在iOS中的实践](https://www.jianshu.com/p/a631b5584de6) 94 | [obfuscator-llvm Github Installation](https://github.com/obfuscator-llvm/obfuscator/wiki/Installation) 95 | 配置了一下,编译了好长时间发现支持的clang版本只到4.0,很多issue都没人管了,而且不支持字符串混淆,优点是开源,有空可以自己折腾一下 96 | 97 | 2. 网易的试用需要审核 98 | 99 | [网易易盾](http://dun.163.com/product/ios-reinforce) 100 | 101 | 3. 顶象的试用插件有大概半个月的期限,~~只支持30%的加固,不过对我们提审的来说应该够了~~ 经试验试用版提审会被2.3.1拒绝,提示用了代码混淆 102 | 103 | [顶象](https://www.dingxiang-inc.com/business/ios) 104 | [顶象文档](https://cdn.dingxiang-inc.com/public-service/docs/compiler-ios/) 105 | 106 | 107 | 4. 几维安全、360都是在线加固的 108 | 109 | [几维安全静态库](https://www.kiwisec.com/product/app-encrypt.html) 110 | [加固保iOS](http://jiagu.360.cn/#/app/android) 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /addNativeCode.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | # 添加OC垃圾代码 4 | import os,sys 5 | import random 6 | import string 7 | import re 8 | import md5 9 | import time 10 | import json 11 | import shutil 12 | import hashlib 13 | import time 14 | import argparse 15 | 16 | import sys 17 | reload(sys) 18 | sys.setdefaultencoding("utf-8") 19 | 20 | script_path = os.path.split(os.path.realpath(sys.argv[0]))[0] 21 | #垃圾代码临时存放目录 22 | target_ios_folder = os.path.join(script_path, "./target_ios") 23 | #备份目录 24 | backup_ios_folder = os.path.join(script_path, "./backup_ios") 25 | 26 | #忽略文件列表,不给这些文件添加垃圾函数 27 | ignore_file_list = ["main.m", "GNative.mm", "MobClickCpp.mm"] 28 | #忽略文件夹列表,不处理这些文件夹下的文件 29 | ignore_folder_list = ["thirdparty","Support",".svn","paycenter","UMMobClick.framework","UMessage_Sdk_1.5.0a","TencentOpenApi_IOS_Bundle.bundle", 30 | "TencentOpenAPI.framework","Bugly.framework","AlipaySDK.framework","AlipaySDK.bundle"] 31 | 32 | #创建函数数量范围 33 | create_func_min = 2 34 | create_func_max = 10 35 | 36 | #创建垃圾文件数量范围 37 | create_file_min = 10 38 | create_file_max = 20 39 | 40 | #oc代码目录 41 | ios_src_path = "" 42 | 43 | #确保添加的函数不重名 44 | funcname_set = set() 45 | 46 | #单词列表,用以随机名称 47 | with open(os.path.join(script_path, "./word_list.json"), "r") as fileObj: 48 | word_name_list = json.load(fileObj) 49 | 50 | #获取一个随机名称 51 | def getOneName(): 52 | global word_name_list 53 | return random.choice(word_name_list) 54 | 55 | #oc代码头文件函数声明 56 | def getOCHeaderFuncText(): 57 | funcName = getOneName() 58 | text = "\n- (void)%s" %(funcName) 59 | return text 60 | 61 | #oc代码函数实现模板 62 | def getOCFuncText(header_text): 63 | arg1 = getOneName() 64 | text = [ 65 | header_text + "\n", 66 | "{\n", 67 | "\tNSLog(@\"%s\");\n" %(arg1), 68 | "}\n" 69 | ] 70 | return "".join(text) 71 | 72 | #oc代码以@end结尾,在其前面添加text 73 | def appendTextToOCFile(file_path, text): 74 | with open(file_path, "r") as fileObj: 75 | old_text = fileObj.read() 76 | fileObj.close() 77 | end_mark_index = old_text.rfind("@end") 78 | if end_mark_index == -1: 79 | print "\t非法的结尾格式: " + file_path 80 | return 81 | new_text = old_text[:end_mark_index] 82 | new_text = new_text + text + "\n" 83 | new_text = new_text + old_text[end_mark_index:] 84 | 85 | with open(file_path, "w") as fileObj: 86 | fileObj.write(new_text) 87 | 88 | #处理单个OC文件,添加垃圾函数。确保其对应头文件存在于相同目录 89 | def dealWithOCFile(filename, file_path): 90 | global target_ios_folder,create_func_min,create_func_max,funcname_set 91 | funcname_set.clear() 92 | end_index = file_path.rfind(".") 93 | pre_name = file_path[:end_index] 94 | header_path = pre_name + ".h" 95 | if not os.path.exists(header_path): 96 | print "\t相应头文件不存在:" + file_path 97 | return 98 | 99 | new_func_num = random.randint(create_func_min, create_func_max) 100 | print "\t给%s添加%d个方法" %(filename, new_func_num) 101 | for i in range(new_func_num): 102 | header_text = getOCHeaderFuncText() 103 | # print "add %s to %s" %(header_text, header_path.replace(target_ios_folder, "")) 104 | appendTextToOCFile(header_path, header_text + ";\n") 105 | funcText = getOCFuncText(header_text) 106 | appendTextToOCFile(file_path, funcText) 107 | 108 | #扫描parent_folder,添加垃圾函数,处理了忽略列表 109 | def addOCFunctions(parent_folder): 110 | global ignore_file_list 111 | for parent, folders, files in os.walk(parent_folder): 112 | need_ignore = None 113 | for ignore_folder in ignore_folder_list: 114 | if parent.find("/" + ignore_folder) != -1: 115 | need_ignore = ignore_folder 116 | break 117 | if need_ignore: 118 | print "\t忽略文件夹" + ignore_folder 119 | continue 120 | 121 | for file in files: 122 | if file.endswith(".m") or file.endswith(".mm"): 123 | if file in ignore_file_list: 124 | continue 125 | dealWithOCFile(file, os.path.join(parent, file)) 126 | 127 | #新创建的垃圾文件header模板 128 | def getOCHeaderFileText(class_name): 129 | global funcname_set 130 | new_func_name = getOneName() 131 | while new_func_name in funcname_set: 132 | new_func_name = getOneName() 133 | funcname_set.add(new_func_name) 134 | 135 | text = [ 136 | "#import \n\n", 137 | "@interface %s : NSObject {\n" %(class_name), 138 | "\tint %s;\n" %(new_func_name), 139 | "\tfloat %s;\n" %(getOneName()), 140 | "}\n\n@end" 141 | ] 142 | return string.join(text) 143 | 144 | #新创建的垃圾文件mm模板 145 | def getOCMMFileText(class_name): 146 | text = [ 147 | '#import "%s.h"\n\n' %(class_name), 148 | "@implementation %s\n" %(class_name), 149 | "\n\n@end" 150 | ] 151 | return string.join(text) 152 | 153 | #添加垃圾文件到parent_folder/trash/ 154 | def addOCFile(parent_folder): 155 | global create_file_min, create_file_max 156 | file_list = [] 157 | target_folder = os.path.join(parent_folder, "trash") 158 | if os.path.exists(target_folder): 159 | shutil.rmtree(target_folder) 160 | os.mkdir(target_folder) 161 | file_num = random.randint(create_file_min, create_file_max) 162 | for i in range(file_num): 163 | file_name = getOneName() 164 | file_list.append("#import \"" + file_name + ".h\"") 165 | print "\t创建OC文件 trash/" + file_name 166 | header_text = getOCHeaderFileText(file_name) 167 | full_path = os.path.join(target_folder, file_name + ".h") 168 | with open(full_path, "w") as fileObj: 169 | fileObj.write(header_text) 170 | fileObj.close() 171 | 172 | mm_text = getOCMMFileText(file_name) 173 | full_path = os.path.join(target_folder, file_name + ".mm") 174 | with open(full_path, "w") as fileObj: 175 | fileObj.write(mm_text) 176 | all_header_text = "\n".join(file_list) 177 | 178 | with open(os.path.join(parent_folder, "Trash.h"), "w") as fileObj: 179 | fileObj.write(all_header_text) 180 | fileObj.close() 181 | 182 | #解析参数 183 | def parse_args(): 184 | parser = argparse.ArgumentParser(description='oc垃圾代码生成工具.') 185 | parser.add_argument('--oc_folder', dest='oc_folder', type=str, required=True, help='OC代码所在目录') 186 | parser.add_argument('--replace', dest='replace_ios', required=False, help='直接替换oc源代码', action="store_true") 187 | 188 | args = parser.parse_args() 189 | return args 190 | 191 | def main(): 192 | app_args = parse_args() 193 | global ios_src_path, backup_ios_folder, target_ios_folder 194 | ios_src_path = app_args.oc_folder 195 | if not os.path.exists(ios_src_path): 196 | print "oc_folder path not exist." 197 | exit(0) 198 | 199 | print "拷贝OC代码到target_ios" 200 | if os.path.exists(target_ios_folder): 201 | shutil.rmtree(target_ios_folder) 202 | shutil.copytree(ios_src_path, target_ios_folder) 203 | 204 | print "开始创建oc文件到trash目录" 205 | addOCFile(target_ios_folder) 206 | print "\n开始添加oc方法" 207 | addOCFunctions(target_ios_folder) 208 | 209 | if app_args.replace_ios: 210 | print "\n用target_ios替换原目录" 211 | print "\t备份OC代码到" + os.path.abspath(backup_ios_folder) 212 | if os.path.exists(backup_ios_folder): 213 | shutil.rmtree(backup_ios_folder) 214 | shutil.copytree(ios_src_path, backup_ios_folder) 215 | 216 | print "\t开始替换" 217 | trash_folder = os.path.join(ios_src_path, "trash") 218 | if os.path.exists(trash_folder): 219 | shutil.rmtree(trash_folder) 220 | os.mkdir(trash_folder) 221 | 222 | for parent, folders, files in os.walk(target_ios_folder): 223 | for file in files: 224 | if file.endswith(".h") or file.endswith(".m") or file.endswith(".mm"): 225 | full_path = os.path.join(parent, file) 226 | target_path = full_path.replace(target_ios_folder, ios_src_path) 227 | shutil.copy(full_path, target_path) 228 | print "替换成功\n需要在Xcode上重新添加trash文件下的oc文件" 229 | else: 230 | print "垃圾代码生成完毕,垃圾代码目录:" + os.path.abspath(target_ios_folder) 231 | 232 | print "\nfinished" 233 | 234 | if __name__ == "__main__": 235 | main() 236 | -------------------------------------------------------------------------------- /autoBornCode.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | import os,sys 4 | import random 5 | import string 6 | import re 7 | import md5 8 | import time 9 | import json 10 | import shutil 11 | import hashlib 12 | import time 13 | import argparse 14 | 15 | script_path = os.path.split(os.path.realpath(sys.argv[0]))[0] 16 | 17 | resource_path = "" 18 | target_path = os.path.join(script_path, "target_resource") 19 | 20 | #匹配规则,路径包含path_include且不包含path_exclude的才会创建对应类型的文件 21 | match_rule = { 22 | ".png": { 23 | "path_include": os.path.sep + "res", 24 | }, 25 | ".lua": { 26 | # "path_include": "src", 27 | "path_exclude": os.path.sep + "res", 28 | }, 29 | } 30 | #确保添加的函数不重名 31 | funcname_set = set() 32 | 33 | #单词列表,用以随机名称 34 | with open(os.path.join(script_path, "./word_list.json"), "r") as fileObj: 35 | word_name_list = json.load(fileObj) 36 | 37 | #获取一个随机名称 38 | def getOneName(): 39 | global word_name_list 40 | return random.choice(word_name_list) 41 | 42 | # 获取lua垃圾方法 43 | def getLuaFuncText(): 44 | global funcname_set 45 | new_func_name = getOneName() 46 | while new_func_name in funcname_set: 47 | new_func_name = getOneName() 48 | funcname_set.add(new_func_name) 49 | 50 | argv_name = getOneName() 51 | text = [ 52 | '\nlocal function '+new_func_name+'()\n', 53 | '\tlocal %s = %d + %d\n' %(argv_name, random.randint(1, 1000), random.randint(1, 1000)), 54 | '\treturn %s\n' %(argv_name), 55 | 'end\n' 56 | ] 57 | return string.join(text) 58 | 59 | #获取png内容 60 | def getPngText(): 61 | text = str(random.randint(1, 100)) * random.randint(1024, 10240) 62 | text = text + "0000000049454e44ae426082".decode('hex') 63 | return text 64 | 65 | #添加单个文件 66 | def addSingleFile(file_path): 67 | global target_path 68 | print "add file " + file_path.replace(target_path, "") 69 | _, file_type = os.path.splitext(file_path) 70 | if file_type == ".lua": 71 | with open(file_path, "w") as fileObj: 72 | func_num = random.randint(2, 5) 73 | for j in range(0, func_num): 74 | func_text = getLuaFuncText() 75 | fileObj.write(func_text) 76 | fileObj.close() 77 | elif file_type == ".png": 78 | with open(file_path, "wb") as fileObj: 79 | fileObj.write(getPngText()) 80 | fileObj.close() 81 | 82 | def addFileTo(parent_folder, level, min_file_num = 0): 83 | global match_rule, target_path 84 | create_folder_list = [] 85 | for parent, folders, files in os.walk(parent_folder): 86 | target_file_type = "" 87 | relative_path = parent.replace(target_path, "") 88 | for file_type, match_config in match_rule.items(): 89 | if match_config.has_key("path_exclude") and relative_path.find(match_config["path_exclude"]) != -1: 90 | continue 91 | if not match_config.has_key("path_include") or relative_path.find(match_config["path_include"]) != -1: 92 | target_file_type = file_type 93 | break 94 | if target_file_type == "": 95 | continue 96 | 97 | #创建文件数量 98 | new_file_num = random.randint(len(files) / 2, len(files)) + min_file_num 99 | for i in range(0, new_file_num): 100 | file_path = os.path.join(parent, getOneName() + target_file_type) 101 | addSingleFile(file_path) 102 | 103 | #防止创建太多层的文件夹 104 | if level > 2: 105 | continue 106 | #创建文件夹数量 107 | new_fold_num = random.randint(len(folders) / 2, len(folders)) 108 | for i in range(0, new_fold_num): 109 | target_folder = os.path.join(parent, getOneName()) 110 | #为了不阻断os.walk,延后创建文件夹 111 | create_folder_list.append(target_folder) 112 | 113 | for folder_path in create_folder_list: 114 | try: 115 | print "create folder " + folder_path.replace(target_path, "") 116 | os.mkdir(folder_path) 117 | addFileTo(folder_path, level + 1, random.randint(2, 5)) 118 | except Exception as e: 119 | print e 120 | #----------------------------------------ended add file---------------------- 121 | def changeSingleFileMD5(file_path): 122 | _, file_type = os.path.splitext(file_path) 123 | with open(file_path, "ab") as fileObj: 124 | if file_type == ".png": 125 | text = "".join(random.sample(string.ascii_letters, 11)) 126 | elif file_type == ".jpg": 127 | text = "".join(random.sample(string.ascii_letters, 20)) 128 | elif file_type == ".lua": 129 | text = "\n--#*" + "".join(random.sample(string.ascii_letters, 10)) + "*#--" 130 | else: 131 | text = " "*random.randint(1, 100) 132 | fileObj.write(text) 133 | fileObj.close() 134 | 135 | #改变文件md5 136 | def changeFolderMD5(target_path): 137 | type_filter = set([".png", ".jpg", ".lua", ".json", ".plist", ".fnt"]) 138 | for parent, folders, files in os.walk(target_path): 139 | for file in files: 140 | full_path = os.path.join(parent, file) 141 | _, file_type = os.path.splitext(full_path) 142 | if file_type in type_filter: 143 | changeSingleFileMD5(full_path) 144 | 145 | #----------------------------------------------------main------------------------------------------------ 146 | 147 | def parse_args(): 148 | global res_path 149 | parser = argparse.ArgumentParser(description='资源变异工具') 150 | parser.add_argument('--res', dest='res_dir', type=str, required=True, help='资源目录') 151 | parser.add_argument('--target', dest='target_dir', type=str, required=False, default=target_path, help='资源导出目录') 152 | 153 | args = parser.parse_args() 154 | return args 155 | 156 | def main(): 157 | global resource_path, target_path 158 | app_args = parse_args() 159 | resource_path = app_args.res_dir 160 | target_path = app_args.target_dir 161 | 162 | if not os.path.exists(resource_path): 163 | print "res path not exists: " + resource_path 164 | exit(0) 165 | if target_path != resource_path: 166 | if os.path.exists(target_path): 167 | shutil.rmtree(target_path) 168 | shutil.copytree(resource_path, target_path) 169 | 170 | addFileTo(target_path, 0) 171 | print "\n\nstart modify file md5" 172 | changeFolderMD5(target_path) 173 | print "finish!" 174 | 175 | if __name__ == "__main__": 176 | main() -------------------------------------------------------------------------------- /renameNative.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | import os,sys 4 | import random 5 | import string 6 | import re 7 | import md5 8 | import time 9 | import json 10 | import shutil 11 | import hashlib 12 | import time 13 | import argparse 14 | 15 | import sys 16 | reload(sys) 17 | sys.setdefaultencoding("utf-8") 18 | 19 | script_path = os.path.split(os.path.realpath(sys.argv[0]))[0] 20 | add_prefix = "" 21 | old_prefix = "xxx" 22 | new_prefix = "zzz" 23 | ios_src_path = "" 24 | project_file_path = "" 25 | 26 | ignore_path_text = [".a", ".png", ".plist", ".storyboard", ".pch"] 27 | 28 | #首字母大写 29 | def isNeedIgnore(file_path): 30 | global ignore_path_text 31 | for ignore_text in ignore_path_text: 32 | if file_path.find(ignore_text) != -1: 33 | return True 34 | return False 35 | 36 | def replaceStringInFile(full_path, old_text, new_text): 37 | with open(full_path, "r") as fileObj: 38 | all_text = fileObj.read() 39 | fileObj.close() 40 | 41 | all_text = all_text.replace(old_text, new_text) 42 | with open(full_path, "w") as fileObj: 43 | fileObj.write(all_text) 44 | fileObj.close() 45 | 46 | def renameFileInXcodeProj(old_file_name, new_file_name): 47 | global project_file_path 48 | if os.path.exists(project_file_path): 49 | replaceStringInFile(project_file_path, old_file_name, new_file_name) 50 | 51 | def renameInAllFile(old_text, new_text): 52 | global ios_src_path 53 | for parent, folders, files in os.walk(ios_src_path): 54 | for file in files: 55 | full_path = os.path.join(parent, file) 56 | replaceStringInFile(full_path, old_text, new_text) 57 | 58 | def dealWithIos(): 59 | print "开始重命名类名" 60 | global old_prefix, new_prefix, ios_src_path 61 | for parent, folders, files in os.walk(ios_src_path): 62 | for file in files: 63 | old_full_path = os.path.join(parent, file) 64 | if file.startswith(old_prefix) and not isNeedIgnore(old_full_path): 65 | new_file_name = file.replace(old_prefix, new_prefix) 66 | print "\t重命名文件: %s -> %s" %(file, new_file_name) 67 | 68 | new_full_path = os.path.join(parent, new_file_name) 69 | os.rename(old_full_path, new_full_path) 70 | #在项目工程中改名 71 | renameFileInXcodeProj(file, new_file_name) 72 | 73 | #在可能引用的地方替换 74 | old_file_base_name = os.path.splitext(file)[0] 75 | new_file_base_name = old_file_base_name.replace(old_prefix, new_prefix) 76 | renameInAllFile(old_file_base_name, new_file_base_name) 77 | 78 | for parent, folders, files in os.walk(ios_src_path): 79 | for folder in folders: 80 | old_full_path = os.path.join(parent, folder) 81 | if folder.startswith(old_prefix) and not isNeedIgnore(old_full_path): 82 | new_folder_name = folder.replace(old_prefix, new_prefix) 83 | print "\t重命名文件夹: %s -> %s" %(folder, new_folder_name) 84 | new_full_path = os.path.join(parent, new_folder_name) 85 | os.rename(old_full_path, new_full_path) 86 | #在项目工程中改名 87 | renameFileInXcodeProj(folder, new_folder_name) 88 | print "finish\n" 89 | 90 | def addPreFix(): 91 | print "开始添加前缀" 92 | global add_prefix, ios_src_path 93 | for parent, folders, files in os.walk(ios_src_path): 94 | for file in files: 95 | old_full_path = os.path.join(parent, file) 96 | if not isNeedIgnore(old_full_path): 97 | new_file_name = add_prefix + file 98 | print "\t重命名文件: %s -> %s" %(file, new_file_name) 99 | 100 | new_full_path = os.path.join(parent, new_file_name) 101 | os.rename(old_full_path, new_full_path) 102 | #在项目工程中改名 103 | renameFileInXcodeProj(file, new_file_name) 104 | 105 | #在可能引用的地方替换 106 | old_file_base_name = os.path.splitext(file)[0] 107 | new_file_base_name = os.path.splitext(new_file_name)[0] 108 | renameInAllFile(old_file_base_name, new_file_base_name) 109 | renameInAllFile(add_prefix+add_prefix, add_prefix) 110 | 111 | for parent, folders, files in os.walk(ios_src_path): 112 | for folder in folders: 113 | old_full_path = os.path.join(parent, folder) 114 | if not isNeedIgnore(old_full_path): 115 | new_folder_name = add_prefix + folder 116 | print "\t重命名文件夹: %s -> %s" %(folder, new_folder_name) 117 | new_full_path = os.path.join(parent, new_folder_name) 118 | os.rename(old_full_path, new_full_path) 119 | #在项目工程中改名 120 | renameFileInXcodeProj(folder, new_folder_name) 121 | print "finish\n" 122 | 123 | 124 | #----------------------------------------------------main------------------------------------------------ 125 | def parse_args(): 126 | global script_path, proj_ios_path 127 | parser = argparse.ArgumentParser(description='修改类名前缀工具.\n') 128 | parser.add_argument('--add_prefix', dest='add_prefix', type=str, required=False, default="", help='添加类名前缀') 129 | parser.add_argument('--old_prefix', dest='old_prefix', type=str, required=False, help='原类名前缀') 130 | parser.add_argument('--new_prefix', dest='new_prefix', type=str, required=False, help='替换后类名前缀') 131 | parser.add_argument('--ios_path', dest='ios_path', type=str, required=True, help='OC文件目录') 132 | parser.add_argument('--proj_path', dest='proj_path', type=str, required=False, default="", help='xx.xcodeproj路径') 133 | args = parser.parse_args() 134 | return args 135 | 136 | def main(): 137 | global old_prefix, new_prefix, ios_src_path, project_file_path, add_prefix 138 | app_args = parse_args() 139 | 140 | add_prefix = app_args.add_prefix 141 | old_prefix = app_args.old_prefix 142 | new_prefix = app_args.new_prefix 143 | ios_src_path = app_args.ios_path 144 | project_file_path = os.path.join(app_args.proj_path, "project.pbxproj") 145 | if not os.path.exists(ios_src_path): 146 | print "ios_path not exists: " + ios_src_path 147 | exit(0) 148 | if not os.path.exists(project_file_path): 149 | print "proj_path not exists: " + project_file_path 150 | 151 | print "请提前备份文件夹或确认在版本管理软件中" 152 | raw_input("回车继续执行") 153 | if add_prefix and add_prefix != "": 154 | addPreFix() 155 | exit(0) 156 | dealWithIos() 157 | 158 | 159 | if __name__ == "__main__": 160 | main() 161 | --------------------------------------------------------------------------------