Welcome to Yumao′s Blog.
看這章之前希望大家可以先看完前面四章拆包記錄
這章可以當做是拆包記錄的一個補充說明
按照SqPack的規定 我們是按照索引來讀取文件
那麼那些沒有索引的文件該如何解包呢
總結以前的解包經驗 我們可以實現以下暴力解包方案
我們可以直接拋開index文件
解析dat文件
進行文件的提取解包工作
很多外國友人也是這麼工作的
像xivdb的作者等
廢話不多說
直接上代碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | import java.io.File; import java.io.FileOutputStream; import java.io.RandomAccessFile; import java.util.Arrays; import com.jcraft.jzlib.JZlib; import com.jcraft.jzlib.ZStream; public class broUnpack { // 按照索引文件的不稳定性 使用爆破方法读取dat文件 private static String filePath = "0a0000.win32.dat0" ; public static void main(String[] args) throws Exception { // 数据量比较大 使用raf容易定位操作 RandomAccessFile randomAccessFile = new RandomAccessFile(filePath, "r" ); // 使用raf进行每16字节为断进行读取 byte [] tmpBytes = new byte [ 16 ]; for ( long i = 0 ; i < randomAccessFile.length() / 16 ; i++) { randomAccessFile.seek(i * 16 ); for ( int j = 0 ; j < 16 ; j++) { tmpBytes[j] = randomAccessFile.readByte(); } if ((tmpBytes[ 0 ] & 0xFF ) == 0x80 && (tmpBytes[ 2 ] & 0xFF ) == 0x00 && (tmpBytes[ 3 ] & 0xFF ) == 0x00 && (tmpBytes[ 4 ] & 0xFF ) == 0x02 ) { // 遍历获取有效文件头 直接提取文件 Long seek = i * 16 ; System.out.println( "文件头地址: " + Long.toHexString(seek)); System.out.println( "文件头内容: " + HexUtils.Bytes2HexString(tmpBytes)); getFileContent(seek, randomAccessFile); } } randomAccessFile.close(); } private static void getFileContent( long addr, RandomAccessFile randomAccessFile) throws Exception { randomAccessFile.seek(addr); // 进行标准数据头格式判断 if (addr < randomAccessFile.length() && randomAccessFile.readByte() == ( byte ) 0x80 ) { // 数据头长度获取 long headerLength = getHeaderLength(randomAccessFile, addr); // 数据段内文件个数获取 long fileCount = getFileCount(randomAccessFile, addr); System.out.println( "该数据块内文件个数为:" + fileCount + " " ); // 多文件读取 先将指针偏移到说明头 获取文件长度 randomAccessFile.seek(addr + headerLength); // 循环干的事情 for ( int i = 0 ; i < fileCount; i++) { int count = i + 1 ; System.out.print( "正在处理第 " + count + "个文件 " ); while (randomAccessFile.readByte() != ( byte ) 0x10 ) { // 什么都不做 就是偏移指针 直到跳到下一段数据的开始 } // 获取指针 然后处理文件输出 long nowAddr = randomAccessFile.getFilePointer() - 1 ; // 搞定文件即可 System.out.println( "地址为:" + Long.toHexString(nowAddr) + " " ); outputFile(randomAccessFile, nowAddr); } } else { System.out.println(); } } private static int [] getFileLength(RandomAccessFile randomAccessFile, long addr) throws Exception { randomAccessFile.seek(addr + 0x8 ); byte eSize[] = new byte [ 4 ]; byte oSize[] = new byte [ 4 ]; for ( int i = 3 ; i >= 0 ; i--) { eSize[i] = randomAccessFile.readByte(); } for ( int i = 3 ; i >= 0 ; i--) { oSize[i] = randomAccessFile.readByte(); } int size[] = new int [ 2 ]; size[ 0 ] = Integer.parseInt( HexUtils.Bytes2HexString(eSize).replace( " " , "" ), 16 ); size[ 1 ] = Integer.parseInt( HexUtils.Bytes2HexString(oSize).replace( " " , "" ), 16 ); return size; } private static void outputFile(RandomAccessFile randomAccessFile, long addr) throws Exception { // 进行压缩前后文件长度获取 int [] size = getFileLength(randomAccessFile, addr); System.out.println( "文件大小为:" + Arrays.toString(size)); // TODO之后再写有问题的处理块方法 先跳过 if (size[ 0 ] / size[ 1 ] < 250 ) { // 将指针偏移到数据开始部分 randomAccessFile.seek(addr + 0x10 ); // 开始读取到一个临时数组 byte [] tmp = new byte [size[ 0 ]]; for ( int i = 0 ; i < ( int ) size[ 0 ]; i++) { tmp[i] = randomAccessFile.readByte(); } // 准备好头和尾 byte header[] = { ( byte ) 0x58 , ( byte ) 0x85 }; byte footer[] = HexUtils.HexString2Bytes(Long.toHexString(adler32( tmp, ( int ) size[ 0 ]))); // 组合数组 byte [] hexBytes = new byte [header.length + tmp.length + footer.length]; System.arraycopy(header, 0 , hexBytes, 0 , header.length); System.arraycopy(tmp, 0 , hexBytes, header.length, tmp.length); System.arraycopy(footer, 0 , hexBytes, header.length + tmp.length, footer.length); // 解压 byte [] uncompr = uncompress(hexBytes, ( int ) size[ 1 ]); // 提取解压之后的文件头说明 设置为文件的扩展名 byte [] extension = new byte [ 8 ]; System.arraycopy(uncompr, 0 , extension, 0 , 8 ); String exName = ( new String(extension, "UTF-8" )).toLowerCase(); // tmp String exTmp = "" ; for ( int i = 0 ; i < exName.length(); i++) { if (isEng(exName.charAt(i))) { exTmp = exTmp + exName.charAt(i); } } if (exTmp.equals( "" )) exTmp = "dat" ; // 输出文件 File fileOut = new File( "0a0000/" + Long.toHexString(addr) + "." + exTmp); FileOutputStream fos = new FileOutputStream(fileOut); fos.write(uncompr); fos.flush(); fos.close(); } } private static boolean isEng( char c) { String tmp = c + "" ; String abc = "qwertyuiopasdfghjklzxcvbnm" ; if (abc.contains(tmp)) { return true ; } return false ; } private static long getFileCount(RandomAccessFile randomAccessFile, long addr) throws Exception { randomAccessFile.seek(addr + 0x14 ); byte tmp[] = new byte [ 4 ]; for ( int i = 3 ; i >= 0 ; i--) { tmp[i] = randomAccessFile.readByte(); } return Long.parseLong(HexUtils.Bytes2HexString(tmp).replace( " " , "" ), 16 ); } private static int getHeaderLength(RandomAccessFile randomAccessFile, long addr) throws Exception { randomAccessFile.seek(addr); byte [] tmp = new byte [ 4 ]; for ( int i = 3 ; i > 0 ; i--) { tmp[i] = randomAccessFile.readByte(); } return Integer.parseInt(HexUtils.Bytes2HexString(tmp).replace( " " , "" ), 16 ); } // Mark工具 private static int adler32( byte [] inB, int size) { final int a32mod = 65521 ; int s1 = 1 , s2 = 0 ; for ( int i = 0 ; i < size; i++) { int b = inB[i]; s1 = (s1 + b) % a32mod; s2 = (s2 + s1) % a32mod; } return ( int ) ((s2 << 16 ) + s1); } // Zlib解压工具 private static byte [] uncompress( byte [] data, int length) { int err; int uncomprLen = length; byte [] uncompr = new byte [uncomprLen]; ZStream d_stream = new ZStream(); err = d_stream.inflateInit(); CHECK_ERR(d_stream, err, "inflateInit" ); d_stream.next_in = data; d_stream.next_in_index = 0 ; d_stream.next_out = uncompr; d_stream.next_out_index = 0 ; while (d_stream.total_out < uncomprLen && d_stream.total_in < uncomprLen) { d_stream.avail_in = d_stream.avail_out = 1 ; err = d_stream.inflate(JZlib.Z_NO_FLUSH); if (err == JZlib.Z_STREAM_END) { break ; } CHECK_ERR(d_stream, err, "inflate" ); } err = d_stream.inflateEnd(); CHECK_ERR(d_stream, err, "inflateEnd" ); byte [] unzipfile = new byte [( int ) d_stream.total_out]; System.arraycopy(uncompr, 0 , unzipfile, 0 , unzipfile.length); return unzipfile; } // Zlib解压检错工具 static void CHECK_ERR(ZStream z, int err, String msg) { if (err != JZlib.Z_OK) { if (z.msg != null ) System.out.print(z.msg + " " ); System.out.println(msg + " error: " + err); System.exit( 1 ); } } } |
轉載請說明出處
有問題可以找我探討喔
输出文件放在循环外面嘛,循环内是压缩分卷
多谢楼主代码 研究下
求大神直接出个软件,代码实在看不懂啊