Windows8 64bitで魔法使いの夜をプレイ

魔法使いの夜というノベルゲーム(詳細はググって)がありますが
Windows8 64bit(のConsumer Preview)では動作しません。
2013/01/14追記:すでに公式で修正パッチが出ています、単純に魔法使いの夜をプレイしたいだけの人は公式のサポートページへGO。

動作しない理由は簡単、不正行為防止用のプロテクトが不正環境だと誤認しているからです。
このページではWindows8 64bitで魔法使いの夜をプレイする方法と、その原理を説明します。

説明とかいいからうごかし方だけ教えろという方向けの完成品。
Win8WOH.zip
使い方はzipの中のversion.dllを魔法使いの夜.exeと同じディレクトリに置く、それだけ。

なぜWindows8では動かないのか

plugin/魔法使いの夜.tpmがプロテクトなどなどを行っているDLLで
(拡張子がdllじゃないですが、中身は普通のdllです。 KIRIKIRIプラグインなので拡張子がtpmなだけ)
起動時に読み込まれているKernel32.dllが正規のシステムディレクトリ以下に配置されているものかどうかのチェックをしています。

ですが、この確認方法に問題があって
確認用パス取得に使用しているGetModuleFileNameAは
Windows7 64bitであれば%windir%SysWOW64以下と返しますが、Windows8 64bitであれば%windir%System32以下と返します。

魔法使いの夜.tpmは必ずSysWOW64の法のパスを返す前提で動作しているため、Windows8では不正な環境であるとみなされ起動しなくなるのです。

ちなみに、MSDN上ではどちらの挙動になるとも言及されていません。
つまり未規定の動作に頼っていることになるので、あまり行儀のいいこととは言えません。

どうやって動くようにしたのか

動くようにするのは簡単で、GetModuleFileNameAが%windir%SysWOW64以下のパスを返すようになればいいわけです。

起動時に読み込むDLLのダミーをアプリケーションと同じディレクトリに配置して読み込ませ
IATテーブルという外部DLL呼び出しに使う部分のGetModuleFileNameA部分を自作関数へ向くように書き換え
そこでKernel32.dllを対象としているときのみ、SysWOW64以下のパスを返すようにしました。

終わりに

実は魔法使いの夜はexe本体やtpmなどにパッチをあてるとプロテクトに引っかかって起動できなくなります。
今回の目的はプロテクトを解除することではないので、プロテクト対象外のdllのダミーを使うことで実現しています。

dllを署名するなど仕組みが分かっていても破るのが難しいプロテクトを入れるのは素晴らしいのですが
今回のような、MSDNに記述されていない細部の挙動に頼った幼稚なプロテクトはやめていただきたいところ。

ほんと、署名部分は素晴らしいのになぁ。

2013/01/14追記:
いろいろなところからリンクされててびっくりしました。
どうやら魔法使いの夜に限らず、ワムソフト版と呼ばれる吉里吉里エンジンを使用したゲーム全般で発生する問題だったようで、
なまじ汎用的に作っちゃったのでどのゲームもこれで動くようになった、ということらしいです。

ワムソフトさんではすでに対応版を出しているらしく、動きの速いところでは公式で修正パッチが出ているそうなので、出ているものはそちらを使いましょう。
それでもだめなら、だめもとで使ってみるといいかも?
動作確認していないのでちゃんと動くかは保証しかねますが。

VC++11製バイナリをXPで動かす

2012年4月1日現在、VisualC++11 Betaで作成したバイナリはXPで動作しません。
今回は、それを強引にXPで動くようにしてしまう方法を紹介します。

なぜXPで動かないのか?

理由は簡単で

  • 動作対象OSがVista以上
  • ランタイムでVista以降のAPIを使用

だから。

動作対象OSをXPに変更

exeファイルには動作対象のOSを指定するサブシステムバージョンというものが存在し、それに満たない環境では動作しないようになっています。
本来ならリンカオプションのsubsystemで変更できるのですが、VC++11からはXP(5.1)以下を選択できません。
なので、強引にexe冒頭で指定されているサブシステムバージョンを5.1に書き換えることで回避します。

具体的には
ここを05 00 01 00と書き換えるとXP以上という意味になります。

書き換えている場所について詳しく知りたい人は、IMAGE_NT_HEADERSのOptionalHeader.MajorSubsystemVersionとOptionalHeader.MinorSubsystemVersionでググってください。

Windowsのバージョン番号はwikipediaの「Windows のタイムライン」を参照

Vista以降のAPI呼び出しを止める

動作対象のOSをXP以上にしたとしても、XPに存在しないAPIを呼んでいては起動すらできません。
これを回避する方法はいくつかあるのですが、ここではダミーのKernel32.dllを作成し、そこに必要なAPIを独自実装することで回避することにします。

exeファイルには当然リンクするべきdll名が記述されています。
そのうちKERNEL32.dllを書き換え、ダミーのDLL名(ここではKernelXP.dllとします)に変更し、そのDLL越しにKERNEL32.dllを呼ぶように細工します。
これは単にexeファイル内からKERNEL32.dllという文字列を探し、書き換えるだけで可能です。

つぎにダミーのDLLの作り方ですが、まず完成品がこちら。
KernelXP.zip

作り方としては、XPのKERNEL32.dllでエクスポートされている関数の一覧を作り
※エクスポート関数一覧の取得方法はDllFuncList.zipなどを参照
それらをすべてKERNEL32.dllへ受け流すだけのDLLを作成。
その後exeファイルをXPで実行してみて、APIが見つからないエラーが出るたびに、そのダミー実装を作るを繰り返すだけ。
詳しくは、DLLインジェクションやダミーDLLなどでググると見つかるかと思います。

終わりに

今回はダミーのDLLを経由させる方法で実現しましたが、一般的にはIATを書き換える方が多いようです。
いつかそちらについても言及してみたいなと思いつつ、今回はここまで。

th123_aiVer0.95リリース

変更点の大部分をすでに覚えていないのだが、何とか書き出してみる。

Ver0.95

  • get_obj_dataで返るHPがおかしかった不具合の修正
  • 環境変数act_blockの追加
  • コンパイル環境の変更
  • get_special_dataの13と14が動作するように
  • PANIC後の再読み込みが機能していなかったのを修正
  • 非想天則1.10aに対応

get_obj_dataはたぶん何か計算ミスってたんだと思います。

act_blockは従来までの*_frame系がリセットされて判別できない問題に対応するため
frameがリセットされるたびにインクリメントされている値を入れました。
ちなみに、自力計算ではなくth123.exeの中から取得しています。

コンパイル環境は何度か変えました。
最新はVC++11Beta Ultimate。
VC++11ではXP向けバイナリは作れないんですが、無理やり力技で何とかしてます。

get_special_dataはどうやったかは覚えてないけど動くようになったらしいです、たぶん。

PANIC後の再読み込みがなぜか動いてなかったので修正。
なぜかファイル名のリセットがかかっていて、再読み込みしようとして失敗して落ちるというアホかましてました。

1.10a対応はちゃんとアドレスサーチして入れました。
が、結果は有志の方が作ってくれたiniとほぼ一致していたので無駄でしたね。

get_hitareaにバグあるらしいので、近いうちに直した版をリリース予定。
一週間以内には出るんじゃないかな。
たぶん、たぶんね。