第1行: | 第1行: | ||
某文件格式 | 某文件格式 | ||
+ | == 文件头 == | ||
+ | Endian: Little Endian | ||
+ | <source lang="asm"> | ||
+ | 0x0000 Signature: AFS\0 | ||
+ | 0x0004 31 01 00 00 -> 0x131 = 305: record count | ||
+ | 0x0008 00 10 00 00 -> 1st start: 0x1000 | ||
+ | 0x000C 7F 13 00 00 -> 1st size: 0x137F | ||
+ | 0x0010 00 28 00 00 -> 2nd start: 0x2800 | ||
+ | 0x0014 B8 3D 00 00 -> 2nd size: 0x3DB8 | ||
+ | .... | ||
+ | </source> | ||
+ | 最后一组之后:可能有文件索引的start & size,也可能没有…… | ||
+ | |||
+ | 替代方法:从最后一个末尾往后找第一个非0的地方…… | ||
+ | |||
+ | == 文件索引 == | ||
+ | 在文件末尾,索引项大小: 0x30 = 48字节 | ||
+ | <source lang="asm"> | ||
+ | 1st record: | ||
+ | 0x2C6000 BAD.BIP 00..00 | ||
+ | 0x2C6020 D9 07 08 00 -> 不明,包内相同,解包无关 | ||
+ | 0x2C6024 0D 00 0A 00 -> 不明,包内相同,解包无关,0D 0A好像不是那个意思…… | ||
+ | 0x2C6028 06 00 20 00 -> 不明,多数递增00 00 02 00,也有不变/突变的情况,解包无关 | ||
+ | 0x2C602C 31 01 00 00 305 -> file count 貌似是垃圾字段,或者是文件头重复…… | ||
+ | 2nd record: | ||
+ | 0x2C6030 KA01.BIP 00..00 | ||
+ | 0x2C6050 D9 07 08 00 | ||
+ | 0x2C6054 0D 00 0A 00 | ||
+ | 0x2C6058 06 00 22 00 | ||
+ | 0x2C605C 00 10 00 00 4096 -> 1st start | ||
+ | 3rd record: | ||
+ | 0x2C6060 KA02.BIP 00..00 | ||
+ | 0x2C6080 D9 07 08 00 | ||
+ | 0x2C6080 0D 00 0A 00 | ||
+ | 0x2C6080 06 00 24 00 | ||
+ | 0x2C6080 7F 13 00 00 4991 -> 1st size | ||
+ | |||
+ | Last record @ 0x2C9900 Total: 305 = 0x131 records | ||
+ | </source> | ||
+ | |||
+ | == 文件块 == | ||
+ | 貌似先存的是索引里的偶数文件,跳过了第一个…… 反正一样解 | ||
+ | <source lang="asm"> | ||
+ | Last file start @ 2C5800 | ||
+ | -> Guess: Min block size: 0x200 = 512 bytes | ||
+ | 1st / 2nd start @ 0x1000 -? 0x237F size: 0x137F -> KA02.BIP | ||
+ | next start @ 0x2800 diff: 0x4000 -? 0x65B8 size: 0x3DB8 -> KA04.BIP | ||
+ | next start @ 0x6800 diff: 0x4000 size: 0xC63C -> KA06.BIP | ||
+ | next start @ 0xA800 size: 0x379C -> KA08.BIP | ||
+ | next start @ 0xE000 -? 0x1335A size: 0x535A diff: 0x5800 -> KAO.BIP | ||
+ | next start @ 0x13800 | ||
+ | </source> | ||
+ | 貌似是用了[[LZSS]]压缩的,块前四字节是原大小,之后是[[LZSS]]压缩结果。大概是0x200对齐,末尾0填充。 | ||
+ | |||
+ | == 分析过程 == | ||
<pre> | <pre> | ||
mac.afs: | mac.afs: | ||
第84行: | 第139行: | ||
BIP有些是脚本,格式还不清楚,反正最后有字符串库,前面不知道是啥…… | BIP有些是脚本,格式还不清楚,反正最后有字符串库,前面不知道是啥…… | ||
− | + | == 解包代码 == | |
+ | unlzss.cpp | ||
<source lang="cpp"> | <source lang="cpp"> |
2012年1月10日 (二) 21:19的最新版本
某文件格式
文件头
Endian: Little Endian
0x0000 Signature: AFS\0
0x0004 31 01 00 00 -> 0x131 = 305: record count
0x0008 00 10 00 00 -> 1st start: 0x1000
0x000C 7F 13 00 00 -> 1st size: 0x137F
0x0010 00 28 00 00 -> 2nd start: 0x2800
0x0014 B8 3D 00 00 -> 2nd size: 0x3DB8
....
最后一组之后:可能有文件索引的start & size,也可能没有……
替代方法:从最后一个末尾往后找第一个非0的地方……
文件索引
在文件末尾,索引项大小: 0x30 = 48字节
1st record:
0x2C6000 BAD.BIP 00..00
0x2C6020 D9 07 08 00 -> 不明,包内相同,解包无关
0x2C6024 0D 00 0A 00 -> 不明,包内相同,解包无关,0D 0A好像不是那个意思……
0x2C6028 06 00 20 00 -> 不明,多数递增00 00 02 00,也有不变/突变的情况,解包无关
0x2C602C 31 01 00 00 305 -> file count 貌似是垃圾字段,或者是文件头重复……
2nd record:
0x2C6030 KA01.BIP 00..00
0x2C6050 D9 07 08 00
0x2C6054 0D 00 0A 00
0x2C6058 06 00 22 00
0x2C605C 00 10 00 00 4096 -> 1st start
3rd record:
0x2C6060 KA02.BIP 00..00
0x2C6080 D9 07 08 00
0x2C6080 0D 00 0A 00
0x2C6080 06 00 24 00
0x2C6080 7F 13 00 00 4991 -> 1st size
Last record @ 0x2C9900 Total: 305 = 0x131 records
文件块
貌似先存的是索引里的偶数文件,跳过了第一个…… 反正一样解
Last file start @ 2C5800
-> Guess: Min block size: 0x200 = 512 bytes
1st / 2nd start @ 0x1000 -? 0x237F size: 0x137F -> KA02.BIP
next start @ 0x2800 diff: 0x4000 -? 0x65B8 size: 0x3DB8 -> KA04.BIP
next start @ 0x6800 diff: 0x4000 size: 0xC63C -> KA06.BIP
next start @ 0xA800 size: 0x379C -> KA08.BIP
next start @ 0xE000 -? 0x1335A size: 0x535A diff: 0x5800 -> KAO.BIP
next start @ 0x13800
貌似是用了LZSS压缩的,块前四字节是原大小,之后是LZSS压缩结果。大概是0x200对齐,末尾0填充。
分析过程
mac.afs: found records @ tail 1st filename @ 2C6000 2nd filename @ 2C6030 3rd filename @ 2C6060 -> file record: 0x30 = 48 bytes 1st record: 0x2C6000 BAD.BIP 00..00 0x2C6020 D9 07 08 00 0x2C6024 0D 00 0A 00 0x2C6028 06 00 20 00 -> + 00 00 02 00 0x2C602C 31 01 00 00 305 -> file count? 2nd record: 0x2C6030 KA01.BIP 00..00 0x2C6050 D9 07 08 00 -> static 0x2C6054 0D 00 0A 00 -> static 0x2C6058 06 00 22 00 -> increasing ? 0x2C605C 00 10 00 00 4096 -> 1st start 3rd record: 0x2C6060 KA02.BIP 00..00 0x2C6080 D9 07 08 00 0x2C6080 0D 00 0A 00 0x2C6080 06 00 24 00 0x2C6080 7F 13 00 00 4991 -> 1st size Last record @ 0x2C9900 Total: 305 = 0x131 records Last file start @ 2C5800 -> Min block size: 0x200 = 512 bytes 1st / 2nd start @ 0x1000 -? 0x237F size: 0x137F -> KA02.BIP next start @ 0x2800 diff: 0x4000 -? 0x65B8 size: 0x3DB8 -> KA04.BIP next start @ 0x6800 diff: 0x4000 size: 0xC63C -> KA06.BIP next start @ 0xA800 size: 0x379C -> KA08.BIP next start @ 0xE000 -? 0x1335A size: 0x535A diff: 0x5800 -> KAO.BIP next start @ 0x13800 diff: Header: 0x00 AFS\0 0x0004 31 01 00 00 -> 0x131 = 305: record count 0x0008 00 10 00 00 -> 1st start 0x000C 7F 13 00 00 -> 1st size 0001000: 0823 0000 ff09 0f06 0000 6008 005f 0900 .#........`.._.. 0001010: 0860 fff5 f009 f9f0 ff4c 008c 0012 0319 .`.......L...... 0001020: 00ff 1a00 6580 0100 65a0 ff01 0033 0100 ....e...e....3.. 0001030: 01ff 10bb 3510 e8f3 6400 02eb f0c0 8fff ....5...d....... 0001040: 1e00 0f2b 0231 0fea f165 ff02 5a00 6520 ...+.1...e..Z.e 0x0010 00 28 00 00 -> 2nd start 0x0014 B8 3D 00 00 -> 2nd size 0002800: 0c6d 0000 ff09 0f06 0000 6008 00ff 1203 .m........`..... 0002810: 1900 1a00 6580 ff1e 0065 a001 0033 01bf ....e....e...3.. 0002820: 0001 ff10 0010 e8f3 65ff 0201 0065 2001 ........e....e . 0002830: 0009 ef00 0860 0a19 0009 6014 b619 0001 .....`....`..... 0002840: a0ec f000 0229 0203 be29 021a 6000 004c .....)...)..`..L 0x0018 00 68 00 00 -> 3rd start 0x001C C6 3C 00 00 -> 3rd size 0006800: 6367 0000 ff09 0f06 0000 6008 00df 0900 cg........`..... 0006810: 0860 0af5 f009 60ff 1900 4c00 4f00 5300 .`....`...L.O.S. 0006820: ff74 1065 8001 0065 a0ff 0100 3301 0001 .t.e...e....3... 0006830: ff10 bb4f 10e8 f364 0002 ebf0 c08f ff2d ...O...d.......- 0006840: 000f 2902 2f0f eaf1 41ff 043f 0340 003e ..)./...A..?.@.> 0x0020 00 A8 00 00 -> 4th start 0x0024 9C 37 00 00 -> 4th size 000a800: b163 0000 ff09 0f06 0000 6008 00df 0900 .c........`..... 000a810: 0860 0af5 f009 60ff 1b00 4c00 5200 5300 .`....`...L.R.S. 000a820: ff70 1065 8001 0065 a0ff 0100 3301 0001 .p.e...e....3... 000a830: ff10 fb0a 10e8 f341 043f 0340 ff00 3e00 .......A.?.@..>. 000a840: 3b80 3c80 3bff 8039 033a 0038 000f ff00 ;.<.;..9.:.8.... 0x0028 00 E0 00 00 -> 5th start 0x002C 5A 53 00 00 -> 5th size 0x0030 00 38 01 00 -> 6th start
于是可以解出来 但是貌似多数文件都压缩了……
- ADX = Ogg
- BIP: Many possible formats...
- T2P: Image, ...
Update: 其实都是标准的LZSS压缩,只是我原来不知道还有那么些恶心的地方,于是搞了好久……
图片都是TIM2格式,据说是PS2的?里面包着奇怪的东西,多数是原封不动的PNG
BIP有些是脚本,格式还不清楚,反正最后有字符串库,前面不知道是啥……
解包代码
unlzss.cpp
#include <iostream>
#include <fcntl.h>
#include <vector>
#include <string>
#include <sys/endian.h>
#include <errno.h>
#include <sys/stat.h>
#include <zlib.h>
using namespace std;
typedef unsigned char uchar;
string dump(unsigned char *data)
{
char buf[20];
snprintf(buf, 20, "%02x %02x %02x %02x", data[0], data[1], data[2], data[3]);
return string(buf);
}
void toh(uint32_t &x)
{
x = le32toh(x);
}
class File {
int size, offset;
uchar sig1[4], sig2[4], mark[4], xx[4];
string name;
public:
File(int _offset, int _size) : size(_size), offset(_offset) {}
int get_size() { return size; }
int get_offset() { return offset; }
const string & get_name() { return name; }
void set_info(const string& _name, uchar *_sig1, uchar *_sig2, uchar *_mark, uchar *_xx)
{
name = _name;
memcpy(sig1, _sig1, 4);
memcpy(sig2, _sig1, 4);
memcpy(mark, _mark, 4);
memcpy(xx, _xx, 4);
}
};
int main(int argc, char **argv)
{
if (argc < 2)
{
cout << "usage: " << argv[0] << " <afs file> [<output dir>]" << endl;
return -1;
}
char head[4];
int f = open(argv[1], O_RDONLY);
if (f < 0)
{
cout << "fail to open file: " << errno << endl;
return -2;
}
read(f, head, 4);
if (memcmp(head, "AFS\0", 4) != 0)
{
cout << "signature mismatch!\n" << endl;
close(f);
return 1;
}
vector<File> files;
uint32_t filecount;
read(f, &filecount, 4);
filecount = le32toh(filecount);
cout << "file count: " << filecount << endl;
// last file: directory
for (int i=0; i<filecount+1; i++)
{
uint32_t offset, size;
read(f, &offset, 4);
read(f, &size, 4);
toh(offset);
toh(size);
if ((size == 0) && (i == filecount))
{ // ugly state: no last record
int x = files[i-1].get_offset() + files[i-1].get_size() + 1;
lseek(f, x, SEEK_SET);
char ch;
read(f, &ch, 1);
while (ch == 0)
{
read(f, &ch, 1);
x++;
}
offset = x;
size = 0x30 * filecount;
}
files.push_back(File(offset, size));
// cout << "File " << i << ": offset=" << hex << files[i].get_offset() << ",size=" << size << "#" << dec << size << dec << endl;
}
for (int i=0; i<filecount; i++)
{
lseek(f, files[filecount].get_offset() + 0x30 * i, SEEK_SET);
char name[33];
name[32] = '\0';
read(f, name, 32);
string sname(name);
unsigned char sig1[4];
unsigned char sig2[4];
read(f, sig1, 4);
read(f, sig2, 4);
unsigned char mark[4];
read(f, mark, 4);
unsigned char xx[4];
read(f, xx, 4);
files[i].set_info(sname, sig1, sig2, mark, xx);
cout << "File " << i << ": name=" << sname << ",offset=" << hex << files[i].get_offset() << ",size=" << files[i].get_size() << dec << endl;
cout << "sig1=" << dump(sig1) << ",sig2=" << dump(sig2) << ",mark=" << dump(mark) << ",mock=" << dump(xx) << endl;
}
if (argc > 2)
{
string destdir = argv[2];
mkdir(destdir.c_str(), 0755);
int bufsize = 0;
char *buf = NULL;
char *unbuf = NULL;
int unbufsize = 0;
for (int i=0; i<filecount; i++)
{
File &frec = files[i];
lseek(f, frec.get_offset(), SEEK_SET);
if (frec.get_size() > bufsize)
{
if (buf)
delete []buf;
buf = new char[frec.get_size()];
}
int cnt = read(f, buf, frec.get_size());
if (cnt != frec.get_size())
{
cerr << "warning: partial read for " << frec.get_name() << " " << cnt << " instead of " << frec.get_size() << " errno " << errno << endl;
}
string destpath = destdir + "/" + frec.get_name();
int outf = open(destpath.c_str(), O_WRONLY | O_CREAT, 0644);
if (outf < 0)
{
cerr << "failed to open output file for " << destpath << " : " << errno << endl;
} else {
cout << "writing to " << destpath << " size: " << cnt << endl;
write(outf, buf, cnt);
close(outf);
}
}
}
}