[PC, ゲーム]
「Running Ninomy 3D」はとても古いWindows用フリーゲームだ。
Windows10でも動作はするものの、今のPCではゲームスピードが速くなりすぎて非常に難しい。
それでもクリアしている人がいるのがこの世の恐ろしいところである。
とりあえず調整する方法
バイナリエディタを用意して
- 0000DA99
- 0000E175
の2カ所の値を書き換える。
ここが元ファイルでは"1E"になっていて
これによって1フレームが最短30ms(最大33.33333FPS)に設定されている。
この値を変更すればフレームレートが変わる。
例えば"32"にすれば、最短50ms(最大20FPS)になる。
"50"にすれば、最短80ms(最大12.5FPS)になる。
当時のPCではこの程度の速度で動いていた記憶がある(適当)
やってること(メモ)
この値は"SetTimer"というWin32 API関数の引数の1つである。
SetTimerを使うと、一定時間ごとにプログラム内の関数を呼び出すことができる。
このゲームではSetTimerで最短待ち時間を設定した上で、時間経過時にそのフレームの処理が済んでいたら、次のフレームの処理を開始し、また、処理が終わっていない場合は終わるまで待つように実装していると思われる。
Visual Studio付属の逆アセンブラことdumpbin.exeに /import オプションを与えて実行すると、こんな感じのテキストが出てくる。
USER32.dll 48FD5C Import Address Table 48F6B4 Import Name Table 0 time date stamp 0 Index of first forwarder reference 1A3 MoveWindow 36 ClientToScreen 235 ShowWindow 173 KillTimer 1B7 PostMessageA 21E SetTimer D7 GetAsyncKeyState 231 ShowCursor 259 UpdateWindow B2 EnableWindow 1E2 SendMessageA 1 AdjustWindowRect 1D3 ReleaseDC E4 GetClientRect 210 SetRect EE GetDC
Import Address Table には、各関数のアドレスが入っている。
各関数のアドレスは32bitで保持されており、例えばSetTimerは
48FD5C + 4 * 5 = 48FD70
にアドレスが格納されている。
逆アセンブルしたファイルから"48FD70"を検索すると
0040E693: 8B 46 20 mov eax,dword ptr [esi+20h] 0040E696: 6A 00 push 0 0040E698: 6A 1E push 1Eh 0040E69A: 6A 01 push 1 0040E69C: 50 push eax 0040E69D: FF 15 70 FD 48 00 call dword ptr ds:[0048FD70h]
いかにもそれっぽいものがある。
スタックに引数を積んでSetTimerを呼び出している。
……実際には"1Eh"で検索したらこいつが出てきて、SetTimerでフレームレートを制限していることが推定できたというのが正しい。
SetTimerの引数は以下の通り。
https://msdn.microsoft.com/ja-jp/library/cc411200.aspx
UINT_PTR SetTimer( HWND hWnd, // ウィンドウのハンドル UINT_PTR nIDEvent, // タイマの識別子 UINT uElapse, // タイムアウト値 TIMERPROC lpTimerFunc // タイマのプロシージャ );
引数をスタックに積む処理は後ろから順番に行うので
0040E698: 6A 1E push 1Eh
これがuElapseの値であり、確かに30msごとに関数が呼び出されるタイマーを作成している。
もちろんこのままだと30msごとに延々と呼び出され続けるので、適切にKillTimerを呼んで停止しているはずである。