ゲーム日本語化 Advent Calendar 2012
実際に解析してみる。
The Real Texasを例にする。
(以下数値は16進数)
フォルダを見るとglobというサイズの大きいファイルがある。
他にゲームデータらしきファイルは無い。globがアーカイブと思われる。
軽くダンプしてみる
0000010に 78 9c が見える。たぶんzlibヘッダ。
2つのアーカイブを見比べる。
共通のシグネチャやバージョン番号らしきヘッダはないことがわかる。
サイズの一番小さいworld.globを調べる
先頭付近と末尾付近をざっと眺める
どこにもファイル名らしきものは見当たらない。
先頭の
e5 f9 2b 00
be 00 00 00
はよくわからない。後者をファイル数と仮定する。
zlibヘッダの直前に
4c 00 00 00
2c 4e 00 00
が見える。前者がファイルサイズ、後者が展開後サイズとあたりをつける。
ここからバイナリエディタでまじめに見る
int size
int origsize
char data[size]
と仮定して、dataの先頭にsizeを足して飛んでみる
5c にきた。
なんかズレてるので、sizeはデータサイズではなく、origsizeの4byte分を含めたサイズとわかる
int size
int origsize
char data[size-4] // zlib compressed
とりあえずこの仮定でunpackerを作ってみる。
ファイル名は適当に連番をつける。
$fp = fopen($file,'r'); $unk = fgetint($fp); $num = fgetint($fp); for($i=0;$i<$num;$i++) { $size = fgetint($fp); $origsize = fgetint($fp); $data = fread($fp,$size-4); $data = gzuncompress($data); $outfile = $dir.sprintf("%04d.dat",$i); file_put_contents($outfile,$data); printf("%d %x %d %d %x\n",$i,$pos,$size,$origsize,ftell($fp)); } fclose($fp);
実行してみる。
うまく展開されているようだ。
アーカイブ名がworld.globなのでマップデータか何かだろう。
ただし、最終位置がアーカイブのサイズと会わない。ヘッダの値をファイル数と仮定したのは間違いだったようだ。
最後まで処理するようにする。
for($i=0;true/*$i<$num*/;$i++) { $pos = ftell($fp); if ($pos>=$filesize) break;
最後のファイルだけなんか大きいので覗いてみる。
どう見てもファイル名っぽい。
先頭の4a 1e 00 00はファイル数ぽい。
次の20 00 00 00は、ファイル名の長さっぽい。
解説してなかったが、文字列はたいてい
長さ 文字....
文字.....0
のいずれか。
長さ 文字...0
の場合もある。
あるいは、長さちょうどのサイズではなく、4byte等でalignされるように末尾にpaddingが付くこともある。
20 00 00 00 の直後の world... 文字列に20を加えてみると、うまく文字列の末尾になった。
次の文字列開始30 までの間に、
e3 0d 10 00
cc 00 00 00
30からの文字列末尾の次にも、
19 0a 10 00
24 00 00 00
が見える。
ためしに 100de3に飛んでみる
size origsize zlibヘッダ
と圧縮されたファイルの先頭ぽいので、これはオフセットとわかった。
もう一つの値 cc 00 00 00 はよくわからん
int num struct entry[num] int len char name[len] int ofs // offset to file int unk
改めてファイルの先頭を見てみると
E5 F9 2B 00
BE 00 00 00
と、ofs,unkと同じ構造をしていることがわかる。
2BF9E5に飛んでみると、これは先ほど解析した最後のファイルである、エントリ情報を指しているとわかる。
2BF9E5 はファイルサイズより小さいので、アーカイブ内の何かを指すオフセットだ、と 最初に気づいていれば、先にファイルエントリ情報を見つけられたはずだが、まあこういうこともある。
ここまでの情報をもとに、アーカイブを先頭から読んで連番ファイルに出力するのではなく、ファイルエントリ情報を読んでファイル名を付けて出力するように変更する。
$fp = fopen($file,'r'); $entpos = fgetint($fp); $unk0 = fgetint($fp); $data = read_data($fp,$entpos); $st = new BinStream($data); $num = $st->getint(); echo $num,"\n"; for($i=0;$i<$num;$i++) { $namelen = $st->getint(); $name = $st->read($namelen); $ofs = $st->getint(); $unk = $st->getint(); printf("%x %x %s\n",$ofs,$unk,$name); $data = read_data($fp,$ofs); savefile($dir.$name,$data); } fclose($fp); function read_data($fp,$pos) { fseek($fp,$pos,SEEK_SET); $size = fgetint($fp); $origsize = fgetint($fp); $data = fread($fp,$size-4); $data = gzuncompress($data); return $data; }
うまくいった。
data.globを展開してみる
なんかエラーだって
エラー部を見てみると
sizeが0の場合はorigsize,dataが続かず、すぐ次のファイルになっていることがわかる。
int size // if 0 , no origsize,data
int origsize
char data[size-4] // zlib compressed
size == 0 の特殊処理を入れる。
function read_data($fp,$pos) { fseek($fp,$pos,SEEK_SET); $size = fgetint($fp); if ($size==0) return '';
もう一度展開してみる。
うまくいった。完成。
The Real Texas unpacker
0 件のコメント:
コメントを投稿