トップ 差分 一覧 ソース ヘルプ ログイン

Running Ninomy 3D のフレームレート変更

最終更新時間:2016年07月08日 01時33分59秒
このエントリーをはてなブックマークに追加

[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を呼んで停止しているはずである。