寒い眠いだるい

夜10時ごろ眠くなり寝てしまったところ、朝2時ごろ起床。レポートを書いたり非想天則のdatロード部を解析してたりしたところ、朝6時ごろになって猛烈なだるさと眠気が。1時間ほど寝て風呂入ってしてたら1時間目(プログラミング第3)に45分くらい遅刻しました。今日はプリプロセッサをやっていました。マクロ関数でのコード構築をかっこいいだのプロっぽいだの植えつけるのはやめてください。死ね。死んでしまえ。*1


うさみみハリケーンだと逆アセンブルブレークポイント・ステップ実行が微妙くさかったのでOllyDbgに乗り換えました。最初の解析に少し時間がかかるものの、サブルーチン・ループのカタマリを表示してくれたり、スタックフレームの動きが並行して見やすかったり、文字列先頭へのポインタっぽいものを文字列表示してくれたりしてうれしいです。
"data/common/spellcard.csv"あたりの文字列から、ファイル名を受け取りdatからファイルを読みだす関数を探したのですが(存在はSWRSToysのBGMChangerのソースより推測)、引数1がファイル名の関数に行きつき、その前後の呼び出しの中で本当にこれなのか迷っていたところ、結局このブログにコメントしてくれたSWRSToysの作者さんの提供アドレスにぴったり一致しました。
BGMChangerでのこの関数の形式はこうなっています。

bool __fastcall f(IFileReader **pp, int, const char *lpEntryName);

返り値(eax)は0x201や0x301が返っていましたが、直後に

test al, al

で1バイトしか使っておらず、*2BGMChangerの通りboolだろうとしました。多分trueで成功。
問題はファイル名を受け取って結果をどう返しているかということなんですが、これがよくわかりませんでした。関数call直前で文字列先頭ポインタをpushしていて、その少し前にecxをいじっているかもしれない感じでした。VCのfastcallは関数の引数前から2つをecx,edxに入れ、他は右からスタックに積むということだったので、edxが触られていないのはおかしいじゃないかと訳がわからなくなりました。で、結局朝気付いたことには、thiscallはthisポインタをecxに入れ、引数をスタックに積むということでした。つまりこれはC++のオブジェクト生成コードくさいということです。BGMChangerはfastcallでIFileReader **ppがecxに、int(ダミー)がedxに割り当てられるのを利用し、ecxを得ているのだと解釈しました。この関数にわざわざ__fastcallを指定する理由なんて考えられないからね。これの元のC++コードってどんな感じなんだ…?C++コンパイル後のアセンブラの形はほとんど知らないからなあ…。
ちなみにIFileReader(仮称)インターフェースはSWRSToysソースより以下の通りです。readmeに仮想関数テーブルの解析法が書いてあったけどここまでできる気しない…。

struct __declspec(novtable) IFileReader {
	virtual ~IFileReader() {}
	virtual bool Read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) = 0;
	virtual DWORD GetReadLength() = 0;
	virtual LONG Seek(LONG lDistanceToMove, DWORD dwMoveMethod) = 0;
	virtual DWORD GetLength() = 0;
};

結論を言っちゃうと、LoadPackageFile(仮)(thiscall?)(const char *filename)[418F60]の先頭をjmp [自作LoadPackageFile]に書き換えるか、10箇所程度あるcall 418F60の飛び先を自作関数に書き換えるかでdatロード関数をフックできそうです。ほとんどBGMChangerのまねでできちゃいそうだね…。これをファイルが存在したらそのファイルデータを返し、なかったらdatから抽出するようにすれば改造が楽になるわけですね。よかったね!!


nkmrさんがリバサ*3北斗がばれていたって言ってました。まあ一択ですよね。全員にばれてましたよね。

*1:ルールとマナーを守って楽しく暴言を吐こう

*2:1byte al, ah、合わせて2byte ax、4byte拡張して eax

*3:起き上がり。起床。