古いPCゲームソフトとMIDI ファイルの読み込み - narukoの開発メモ
上記の続き.
このプログラムはゲーム起動中に CPU core 1 つを使用率100%にしつづけてしまう. 原因は GDI を利用しており、Windows の API の PeekMessage() を利用した無限ループになっている. これの直し方について最近試して使っている ChatGPT に聞きながら進めてみた.
ChatGPT が教えてくれた OllyDbg 経由でこのゲームを起動して、使用率があがる場面でとりあえず pause すると PeekMessageA() 付近で停まる. 命令を見る分には下記のようになっており、これは基本的な使い方だと思われる.
while(PeekMessageA()){ GetMessageA(); ... }
これを下記に書き換える. コンパイラでは while(cond){...} は if(cond) と do{...}while(cond); に分離されているので、コンパイル後の while(cond) を書き換えることになる.
while(wrap_peek_message_a()){ GetMessageA(); }
wrap_peek_message_a() を作る.
int __attribute__((stdcall, dllexport)) wrap_peek_message_a(LPMSG m, HWND h, UINT m0, UINT m1, UINT op) { const int r = PeekMessageA(m, h, m0, m1, op); if(r == 0){ Sleep(1); } return r; }
このゲームプログラムは Sleep() を使用してない. 本来は WindowsAPI を呼ぶための import table に Sleep() を追加して80386アセンブル命令を埋め込む形を使いたかった. そのやり方がよくわからなかったので、自前で dll を作り(32 bit コンパイラなんて久しぶりに使った)、 exe ファイルを改変するソフト(ChatGPT が教えてくれたもの. よくしらない)を利用して その dll を読むようにして、wrap_peek_message_a() を呼ぶようにする. これらの過程はちょっと変だし、GUIを毎回触る必要があり改めたい.
とりあえずこれで OllyDbg を読み込み問題の CALL PeekMessageA を CALL wrap_peek_message_a にすることで CPU の使用率が大幅に下がることを確認. EXE ファイルも更新して応急措置としての作業は終わりになる.
ABI と Visual C++ 4
筆者はいままで Windows のソフトをバイナリレベルでいじることがなかったので今回はじめて知ったこと. 80386 系の C で作られた関数の呼び出し規約(ABI)は Windows 独自の __stdcall と __fastcall がある. Windows API は __stdcall であり、問題ない. __fastcall は引数にレジスタ ECX と EDX 、残りは stack に push し、 call 後に呼び出した関数側が stack pointer を戻して呼び出し元に戻る (RET xxx).
それでこのバイナリは解析ソフトによると Visual C++ 4 で作られていて、 __fastcall に似ているがレジスタ渡しは ECX のみである.
先述のように CALL をまるまる書き換えてこっちで作った C の関数を実行させる方法が __stdcall の Windows API ではできるが static 関数ではこれが使えなくて困っている. 自作関数を呼び出す場合はやりかたはあるが、自作関数から元のプログラムの関数を呼ぶ場合には最善策が思いつかない.
この件、詳しい方がこれを読んでいたら対策方法を教えてほしい.
応急処置からのちゃんとした処置方法
今回の問題の根本的な理由は PeekMessage を利用した無限ループ以外に GDI ではマシな方法がなかったことである. Windows のソフトの作り方には詳しくないので最悪はいまもマシな方法がない可能性もある. Sleep() をいれるのは悪くはないのだが、最適な部分で再開させているわけではない. このゲームではさほど問題にならないが、スクロールがあるエンディングシーンでは「処理が異様に早くて読めない」から「処理がやや早くティアリングが目立つ」になったのでやはりあまりよくない.
根本的には適切なループ再開条件を解析して適切なタイミングで動作を再開させることにしたい. その他小さい要改善点を直すとなると、先述の ABI の問題なども障害になっている.