Welcome to Yumao′s Blog.
FFxivARR SqPack 暴力拆包
, 2014年05月06日 , Java Language , 评论 3 ,

看這章之前希望大家可以先看完前面四章拆包記錄
這章可以當做是拆包記錄的一個補充說明
按照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);
		}
	}
}

轉載請說明出處
有問題可以找我探討喔

关键字:, , , , ,