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);
}
}
}
轉載請說明出處
有問題可以找我探討喔
输出文件放在循环外面嘛,循环内是压缩分卷
多谢楼主代码 研究下
求大神直接出个软件,代码实在看不懂啊