一、主题:
用 java 实现一个自动生成 TexturePacker 工程文件的工具,以提高开发过程中自动化的程度。
二、环境:
Mac OS X 1.7.2
TexturePacker 2.4.3
三、测试得到的数据:
---------------------------- FUCK_YOU-hd 0x26 0x22 fuck_you-hd 0x26 0x22 HATE_YOU-hd 0x26 0x22 FUCK-hd 0x1E 0x1A ---------------------------- f-hd 0x18 0x14 z-hd 0x18 0x14 AA-hd 0x1A 0x16 z2-hd 0x1A 0x16 abc-hd 0x1C 0x18 #@de-hd 0x1E 0x1A def3%-hd 0x20 0x1C Q运气+耐性-hd 0x22 0x1E
四、规律:
1.(过期)如果有:
a = textureFileName 前缀符号的 ascii 码值;
b = dataFileName 前缀符号的 ascii 码值;
则:a – b = 4;
2.(过期)textureFileName 前缀符号的遴选规则(中英文符号均适用):
ascii 值 = 0×10 + name.length * 2;
3.前面总结出来的规律全部推翻!
作者惯用的伎俩就是在字符串的前面用一个两位的十六进制数记录字符串的长度信息。
举例:
[0x18]f-hd.pvr.ccz "f-hd.pvr.ccz".length() = 12 0x18 = 16 * 1 + 8 = 24 [0x1C]abc-hd.pvr.ccz "abc-hd.pvr.ccz".length() = 14 0x1C = 16 * 1 + 12 = 28 [0x14]f-hd.plist "f-hd.plist".length() = 10 0x14 = 16 * 1 + 4 = 20 [0x22]gen/#3@fe_223.png "gen/#3@fe_223.png".length = 17 0x22 = 16 * 2 + 2 = 34
综上可以做出推论:两位十六进制数的值为字符串长度的两倍(大量测试均支持该项推论)。
4.tps 所使用到的图片文件的数目也被记录下来了:
在 fileList 的后面,特征序列如下:
0x00 0x00 0x00 0x0B 0x00 0x00 0x00 0x00 [0x04]
中括号里面的数值记录着所用到的图片文件的数目信息。
5.测试过程中发现一些要注意的地方:
I.图片文件的名称中不能包含中文字符,推荐采用 “数字、字母、下划线” 的命名组合;
II.图片文件的名称中使用特殊字符(‘@’、‘#’、‘!’等)没有发现什么问题;
III.发现 TexturePacker 一个很有意思的小细节:
数据完全相同但名称不同的图片,tp 能自动识别并优化(表现为在纹理集中剔除雷同纹理)。
IV.无关紧要的一些数据:
andengine-java = imgp_perlude.java settingsAbsPath = /Users/user/Desktop/images
五、说点儿什么:
昨天从下午一直弄到凌晨一点半,今天也花了 6+ 小时的功夫~
Over! 此次顺利破解规则全耐运气和耐性了。
我深深感叹,哪怕 TexturePacker 的作者再做多一些防护措施,我也有是没法去应对的。
如果作者从一开始就用某种加密算法对工程文件做加密,而不是采用字符间插入不可见控制字符的手段,
那么我应该从一开始就会选择放弃了 —— 我对密文破解并不擅长,而且觉得这种事情很麻烦。
偏偏作者用的是半明文,不是怂恿我去干点儿“坏事儿” 么?
当然,不得不承认,作者在明文间加一些不可见控制符这一招确实比较阴。
从最终结果来看,虽然我也耗费了比较大的心力,但是我成为了这场角逐的胜利者,
对于这一点,我感到非常高兴!
从技术的角度来看:
1.此次我温习巩固了一下字符编码方面的知识;
2.java 在字符编码范畴操作文件的时候需要注意的地方:
gbk 编码的文本文件,需以 gbk 编码将字节数组组装成 Unicode 字符串(实际为 utf-8)。
回写的时候写字节数组,这样会比较直观一点(在此之前先将字符串按特定编码转换为字节数组)。
@Override protected void handle(File f) { // TODO Auto-generated method stub System.out.println(f.getAbsolutePath()); String rawStr = StringFileBridge.file2String(f, "MacRoman"); try { byte[] bytes = rawStr.getBytes("MacRoman"); ByteArrayFileBridge.byteArray2File(bytes, f); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } }
3.ascii 码表实际上是非常有用的,不要忽视了这一点。
4.最后,提一下 ImageHandler1.8 的更新细节:
完成处理的时候,弹出的对话框仅有确定一个按钮,去掉无用的取消按钮。
提高了打包图片集的自动化程度,不用再手动去剔除-hd、-ipadhd了,
不用手动去新建相关的 *-hd.tps、*-ipadhd.tps 文件了。
还有另外两个比较实用的功能可以考虑一下:
1.遍历某文件夹下面的所有 tps 文件,完成批量输出 pvr.ccz、plist 的操作。
2.将某文件夹下面的所有 pvr.ccz、plist 文件抓出来放到一个文件夹的子目录下面,方便使用。
相关核心源码:
/** * 作用:返回加过 “料” 的字符串~ * @param raw * @return */ private String getSaltedString(String raw) { String[] arr = raw.split("|"); StringBuffer sb = new StringBuffer(); for(int i = 1; i < arr.length; ++ i) { sb.append(arr[i]).append('\0'); } sb.deleteCharAt(sb.length() - 1); return sb.toString(); } /** * 获取文件数组中 png 格式文件的数目~ * @param files * @return */ private int getPngCount(File[] files) { int counter = 0; for(File item : files) { if(item.isFile() && item.getName().endsWith(".png")) { counter ++; } } return counter; } /** 生成 -hd 的 tps 文件~ */ public void genHdTps(String tpsName, File f) { // 1.加载 -hd.tps 模板~ String template = TemplateLoader.loadTemplateByName("template_hd"); // 2.替换 textureFileName、dataFileName~ String name = tpsName + "-hd"; int prefix = (name.length() + ".pvr.ccz".length()) * 2; String texFileNameSalted = getSaltedString((char)prefix + name); String handled = template.replace("TEXTURE_FILE_NAME", texFileNameSalted); prefix = (name.length() + ".plist".length()) * 2; String dataFileNameSalted = getSaltedString((char)prefix + name); handled = handled.replace("DATA_FILE_NAME", dataFileNameSalted); // 3.组装图片列表数据~ StringBuffer sbFiles = new StringBuffer(); String strBase = "gen/"; File[] files = f.listFiles(); sbFiles.append((char)getPngCount(files)); // 这个字符数据记录了所用到图片的数目~ for (int i = 0; i < files.length; ++ i) { File item = files[i]; // 非目录、非 .DS_Store 文件、必须是 png 格式的图片文件(-ipadhd 的时候会将 -hd.tps 罗列进来)~ if (!item.isDirectory() && !item.getName().startsWith(".DS_Store") && item.getName().endsWith(".png")) { int length = (strBase.length() + files[i].getName().length()) * 2; sbFiles.append("\0\0\0").append((char)length).append("\0"); sbFiles.append(getSaltedString(strBase + files[i].getName())); } } // 4.替换图片列表数据~ System.out.println("-hd: " + sbFiles.toString()); handled = handled.replace("FILE_LIST", sbFiles.toString()); // 5.生成 -hd.tps 文件~ try { byte[] bytes = handled.getBytes("MacRoman"); File newFile = new File(f.getAbsolutePath() + File.separator + name + ".tps"); ByteArrayFileBridge.byteArray2File(bytes, newFile); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } /** 生成 -ipadhd 的 tps 文件~ */ public void genIpadhdTps(String tpsName, File f) { // 1.加载 -hd.tps 模板~ String template = TemplateLoader.loadTemplateByName("template_ipadhd"); // 2.替换 textureFileName、dataFileName~ String name = tpsName + "-ipadhd"; int prefix = (name.length() + ".pvr.ccz".length()) * 2; String hdTexFileNameSalted = getSaltedString((char)prefix + name); String hdHandled = template.replace("TEXTURE_FILE_NAME", hdTexFileNameSalted); prefix = (name.length() + ".plist".length()) * 2; String hdDataFileNameSalted = getSaltedString((char)prefix + name); hdHandled = hdHandled.replace("DATA_FILE_NAME", hdDataFileNameSalted); // 3.组装图片列表数据~ StringBuffer sbFiles = new StringBuffer(); String strBase = "gen/iPadHD/"; File[] files = f.listFiles(); sbFiles.append((char)getPngCount(files)); // 这个字符数据记录了所用到图片的数目~ for (int i = 0; i < files.length; ++ i) { File item = files[i]; // 非目录、非 .DS_Store 文件、必须是 png 格式的图片文件(-ipadhd 的时候会将 -hd.tps 罗列进来)~ if (!item.isDirectory() && !item.getName().startsWith(".DS_Store") && item.getName().endsWith(".png")) { int length = (strBase.length() + files[i].getName().length()) * 2; sbFiles.append("\0\0\0").append((char)length).append("\0"); sbFiles.append(getSaltedString(strBase + files[i].getName())); } } // 4.替换图片列表数据~ hdHandled = hdHandled.replace("FILE_LIST", sbFiles.toString()); // 5.生成 -hd.tps 文件~ try { byte[] bytes = hdHandled.getBytes("MacRoman"); File newFile = new File(f.getAbsolutePath() + File.separator + name + ".tps"); ByteArrayFileBridge.byteArray2File(bytes, newFile); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } }
模版加载:
public static final String CLASS_PATH = "/org/bruce/image/handler/resources/"; /** * 按模板文件名称加载~ * @param tName * @return */ public static String loadTemplateByName(String tName) { String tFileClassPath = CLASS_PATH + tName + ".txt"; InputStream is = TemplateLoader.class.getResourceAsStream(tFileClassPath); byte[] bytes = inputStream2byteArray(is); String tContents = null; try { tContents = new String(bytes, "MacRoman"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return tContents; } /** * InputStream 转 byte[]~ * @param is * @return */ public static byte[] inputStream2byteArray(InputStream is) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); int i; try { while((i = is.read()) != -1) { baos.write(i); } baos.close(); } catch (IOException e) { e.printStackTrace(); } byte[] bytes = baos.toByteArray(); return bytes; }
模板文件(因为包含特殊字符,所以我用 Base64 做了加密处理):
template_hd.txt
AAAAGgBUAGUAeAB0AHUAcgBlAFAAYQBjAGsAZQByAAAACgAyAC4ANAAuADMA AAABAAAAAAAAAAAAAAAsAAAAFABkAGEAdABhAEYAbwByAG0AYQB0AAAACgAA AAAOAGMAbwBjAG8AcwAyAGQAAAAcAHQAZQB4AHQAdQByAGUAUwB1AGIAUABh AHQAaAAAAAoAAAAAAAAAABoAaABlAHUAcgBpAHMAdABpAGMATQBhAHMAawAA AAEAAAAAABoAYgBvAHIAZABlAHIAUABhAGQAZABpAG4AZwAAAAIAAAAAAAAA AAgAdAByAGkAbQAAAAEAAQAAACAAbABpAGIARwBkAHgARgBpAGwAdABlAHIA aQBuAGcAWQAAAAoAAAAADABMAGkAbgBlAGEAcgAAABIAcwBjAGEAbABlAE0A bwBkAGUAAAAKAAAAAAwAcwBtAG8AbwB0AGgAAAAYAGkAbgBuAGUAcgBQAGEA ZABkAGkAbgBnAAAAAgAAAAAAAAAAIABsAGkAYgBHAGQAeABGAGkAbAB0AGUA cgBpAG4AZwBYAAAACgAAAAAMAEwAaQBuAGUAYQByAAAAGgBhAGwAbABvAHcA UgBvAHQAYQB0AGkAbwBuAAAAAQABAAAAGgB0AGUAeAB0AHUAcgBlAEYAbwBy AG0AYQB0AAAACgAAAAAOAHAAdgByAC4AYwBjAHoAAAAOAGUAeAB0AHIAdQBk AGUAAAACAAAAAAAAAAAeAHQAZQB4AHQAdQByAGUARgBpAGwAZQBOAGEAbQBl AAAACgAAAABURVhUVVJFX0ZJTEVfTkFNRQAuAHAAdgByAC4AYwBjAHoAAAAM AGEAdQB0AG8AUwBEAAAAAQABAAAACgBxAHUAaQBlAHQAAAABAAAAAAAcAGEA bgBkAGUAbgBnAGkAbgBlAC0AagBhAHYAYQAAAAoAAAAAIgBpAG0AZwBwAF8A cABlAHIAbAB1AGQAZQAuAGoAYQB2AGEAAAAUAHMAaABhAHAAZQBEAGUAYgB1 AGcAAAABAAAAAAAeAHQAcgBpAG0AUwBwAHIAaQB0AGUATgBhAG0AZQBzAAAA AQAAAAAAJgBhAG4AZABlAG4AZwBpAG4AZQAtAG0AYQBnAGYAaQBsAHQAZQBy AAAACgAAAAAMAGwAaQBuAGUAYQByAAAAHgBhAG4AZABlAG4AZwBpAG4AZQAt AHcAcgBhAHAAdAAAAAoAAAAACgBjAGwAYQBtAHAAAAAeAGEAbgBkAGUAbgBn AGkAbgBlAC0AdwByAGEAcABzAAAACgAAAAAKAGMAbABhAG0AcAAAACYAYQBu AGQAZQBuAGcAaQBuAGUALQBtAGkAbgBmAGkAbAB0AGUAcgAAAAoAAAAADABs AGkAbgBlAGEAcgAAABoAdAByAGkAbQBUAGgAcgBlAHMAaABvAGwAZAAAAAIA AAAAAQAAABgAZABhAHQAYQBGAGkAbABlAE4AYQBtAGUAAAAKAAAAAERBVEFf RklMRV9OQU1FAC4AcABsAGkAcwB0AAAAGABvAHUAdABwAHUAdABGAG8AcgBt AGEAdAAAAAoAAAAAEABSAEcAQgBBADQANAA0ADQAAAAQAGYAaQBsAGUATABp AHMAdAAAAAsAAAAARklMRV9MSVNUAAAAKgByAGUAZAB1AGMAZQBCAG8AcgBk AGUAcgBBAHIAdABpAGYAYQBjAHQAcwAAAAEAAAAAAAYAZABwAGkAAAACAAAA AEgAAAAKAHMAYwBhAGwAZQAAAIcAP/AAAAAAAAAAAAAOAGYAbABpAHAAUABW AFIAAAABAAAAAAAEAGMAcgAAAAoAAAAAAgBwAAAAFABkAGkAdABoAGUAcgBU AHkAcABlAAAACgAAAAAoAEYAbABvAHkAZABTAHQAZQBpAG4AYgBlAHIAZwAr AEEAbABwAGgAYQAAACAAcAByAGUAbQB1AGwAdABpAHAAbAB5AEEAbABwAGgA YQAAAAEAAQAAAB4AcwBlAHQAdABpAG4AZwBzAEEAYgBzAFAAYQB0AGgAAAAK AAAAADQALwBVAHMAZQByAHMALwB1AHMAZQByAC8ARABlAHMAawB0AG8AcAAv AGkAbQBhAGcAZQBzAAAAHgBiAGEAYwBrAGcAcgBvAHUAbgBkAEMAbwBsAG8A cgAAAAMAAAAAAAAAABoAYQBsAGwAbwB3AEYAcgBlAGUAUwBpAHoAZQAAAAEA AAAAACIAZgBvAHIAYwBlAGQAVABlAHgAdAB1AHIAZQBTAGkAegBlAAAAFQD/ /////////wAAACAAYQB1AHQAbwBBAGwAaQBhAHMARQBuAGEAYgBsAGUAZAAA AAEAAQAAABIAYQBsAGcAbwByAGkAdABoAG0AAAAKAAAAABAATQBhAHgAUgBl AGMAdABzAAAAFABqAHAAZwBRAHUAYQBsAGkAdAB5AAAAAgAAAABQAAAACABj AHIAbwBwAAAAAQAAAAAAGABzAGgAYQBwAGUAUABhAGQAZABpAG4AZwAAAAIA AAAAAAAAABwAbQBhAHgAVABlAHgAdAB1AHIAZQBTAGkAegBlAAAAFQAAAAgA AAAIAAAAACoAYQBuAGQAZQBuAGcAaQBuAGUALQBwAGEAYwBrAGEAZwBlAG4A YQBtAGUAAAAKAAAAAA4AVABlAHgAdAB1AHIAZQ==
template_ipadhd.txt
AAAAGgBUAGUAeAB0AHUAcgBlAFAAYQBjAGsAZQByAAAACgAyAC4ANAAuADMA AAABAAAAAAAAAAAAAAAsAAAAFABkAGEAdABhAEYAbwByAG0AYQB0AAAACgAA AAAOAGMAbwBjAG8AcwAyAGQAAAAcAHQAZQB4AHQAdQByAGUAUwB1AGIAUABh AHQAaAAAAAoAAAAAAAAAABoAaABlAHUAcgBpAHMAdABpAGMATQBhAHMAawAA AAEAAAAAABoAYgBvAHIAZABlAHIAUABhAGQAZABpAG4AZwAAAAIAAAAAAAAA AAgAdAByAGkAbQAAAAEAAQAAACAAbABpAGIARwBkAHgARgBpAGwAdABlAHIA aQBuAGcAWQAAAAoAAAAADABMAGkAbgBlAGEAcgAAABIAcwBjAGEAbABlAE0A bwBkAGUAAAAKAAAAAAwAcwBtAG8AbwB0AGgAAAAYAGkAbgBuAGUAcgBQAGEA ZABkAGkAbgBnAAAAAgAAAAAAAAAAIABsAGkAYgBHAGQAeABGAGkAbAB0AGUA cgBpAG4AZwBYAAAACgAAAAAMAEwAaQBuAGUAYQByAAAAGgBhAGwAbABvAHcA UgBvAHQAYQB0AGkAbwBuAAAAAQABAAAAGgB0AGUAeAB0AHUAcgBlAEYAbwBy AG0AYQB0AAAACgAAAAAOAHAAdgByAC4AYwBjAHoAAAAOAGUAeAB0AHIAdQBk AGUAAAACAAAAAAAAAAAeAHQAZQB4AHQAdQByAGUARgBpAGwAZQBOAGEAbQBl AAAACgAAAABURVhUVVJFX0ZJTEVfTkFNRQAuAHAAdgByAC4AYwBjAHoAAAAM AGEAdQB0AG8AUwBEAAAAAQAAAAAACgBxAHUAaQBlAHQAAAABAAAAAAAcAGEA bgBkAGUAbgBnAGkAbgBlAC0AagBhAHYAYQAAAAoAAAAAIgBpAG0AZwBwAF8A cABlAHIAbAB1AGQAZQAuAGoAYQB2AGEAAAAUAHMAaABhAHAAZQBEAGUAYgB1 AGcAAAABAAAAAAAeAHQAcgBpAG0AUwBwAHIAaQB0AGUATgBhAG0AZQBzAAAA AQAAAAAAJgBhAG4AZABlAG4AZwBpAG4AZQAtAG0AYQBnAGYAaQBsAHQAZQBy AAAACgAAAAAMAGwAaQBuAGUAYQByAAAAHgBhAG4AZABlAG4AZwBpAG4AZQAt AHcAcgBhAHAAdAAAAAoAAAAACgBjAGwAYQBtAHAAAAAeAGEAbgBkAGUAbgBn AGkAbgBlAC0AdwByAGEAcABzAAAACgAAAAAKAGMAbABhAG0AcAAAACYAYQBu AGQAZQBuAGcAaQBuAGUALQBtAGkAbgBmAGkAbAB0AGUAcgAAAAoAAAAADABs AGkAbgBlAGEAcgAAABoAdAByAGkAbQBUAGgAcgBlAHMAaABvAGwAZAAAAAIA AAAAAQAAABgAZABhAHQAYQBGAGkAbABlAE4AYQBtAGUAAAAKAAAAAERBVEFf RklMRV9OQU1FAC4AcABsAGkAcwB0AAAAGABvAHUAdABwAHUAdABGAG8AcgBt AGEAdAAAAAoAAAAAEABSAEcAQgBBADQANAA0ADQAAAAQAGYAaQBsAGUATABp AHMAdAAAAAsAAAAARklMRV9MSVNUAAAAKgByAGUAZAB1AGMAZQBCAG8AcgBk AGUAcgBBAHIAdABpAGYAYQBjAHQAcwAAAAEAAAAAAAYAZABwAGkAAAACAAAA AEgAAAAKAHMAYwBhAGwAZQAAAIcAP/AAAAAAAAAAAAAOAGYAbABpAHAAUABW AFIAAAABAAAAAAAEAGMAcgAAAAoAAAAAAgBwAAAAFABkAGkAdABoAGUAcgBU AHkAcABlAAAACgAAAAAoAEYAbABvAHkAZABTAHQAZQBpAG4AYgBlAHIAZwAr AEEAbABwAGgAYQAAACAAcAByAGUAbQB1AGwAdABpAHAAbAB5AEEAbABwAGgA YQAAAAEAAQAAAB4AcwBlAHQAdABpAG4AZwBzAEEAYgBzAFAAYQB0AGgAAAAK AAAAADQALwBVAHMAZQByAHMALwB1AHMAZQByAC8ARABlAHMAawB0AG8AcAAv AGkAbQBhAGcAZQBzAAAAHgBiAGEAYwBrAGcAcgBvAHUAbgBkAEMAbwBsAG8A cgAAAAMAAAAAAAAAABoAYQBsAGwAbwB3AEYAcgBlAGUAUwBpAHoAZQAAAAEA AAAAACIAZgBvAHIAYwBlAGQAVABlAHgAdAB1AHIAZQBTAGkAegBlAAAAFQD/ /////////wAAACAAYQB1AHQAbwBBAGwAaQBhAHMARQBuAGEAYgBsAGUAZAAA AAEAAQAAABIAYQBsAGcAbwByAGkAdABoAG0AAAAKAAAAABAATQBhAHgAUgBl AGMAdABzAAAAFABqAHAAZwBRAHUAYQBsAGkAdAB5AAAAAgAAAABQAAAACABj AHIAbwBwAAAAAQAAAAAAGABzAGgAYQBwAGUAUABhAGQAZABpAG4AZwAAAAIA AAAAAAAAABwAbQBhAHgAVABlAHgAdAB1AHIAZQBTAGkAegBlAAAAFQAAAAgA AAAIAAAAACoAYQBuAGQAZQBuAGcAaQBuAGUALQBwAGEAYwBrAGEAZwBlAG4A YQBtAGUAAAAKAAAAAA4AVABlAHgAdAB1AHIAZQ==