某文件格式

文件头

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);
			}
		}
	}
}