Welcome to Yumao′s Blog.
看這章之前希望大家可以先看完前面四章拆包記錄
這章可以當做是拆包記錄的一個補充說明
按照SqPack的規定 我們是按照索引來讀取文件
那麼那些沒有索引的文件該如何解包呢
總結以前的解包經驗 我們可以實現以下暴力解包方案
我們可以直接拋開index文件
解析dat文件
進行文件的提取解包工作
很多外國友人也是這麼工作的
像xivdb的作者等
廢話不多說
直接上代碼
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); } } }
轉載請說明出處
有問題可以找我探討喔
输出文件放在循环外面嘛,循环内是压缩分卷
多谢楼主代码 研究下
求大神直接出个软件,代码实在看不懂啊