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

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

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

关键字:, , , , ,