ネットゲームをウインドウモード化するには?


ボールペン  2004-10-17 09:56:30  No: 54775  IP: [192.*.*.*]

DirectXを使用しているネットゲーム(BomberMan Online)は、フルスクリーン
モードでしか起動することができないようになっているので、ウインドウモード化する方法についてです。
調べた挙句、DirectXを描画する前のウインドウ作成時にフックをかけ、SetWindowPosまたはMoveWindowを使用して解像度及び初期位置を書き換える
方法にたどり着いたのですが、どうも他のソフトではサイズと初期位置はうまく変更されるのですが、BomberMan Onlineをフックしながら実行すると、
固まってしまいます(フルスクリーン状態)。
以下にコードを記述します(抜粋)。

hHook = SetWindowsHookEx(WH_CALLWNDPROC, ....

LRESULT CALLBACK MyHookProc(int nCode, WPARAM wp, LPARAM lp){
    CWPSTRUCT *cwp = (CWPSTRUCT *)lp;
    HWND hBomber; DWORD dwStyle;

if(nCode < 0 )
   CallNextHookEx(hHook,....
if( nCode == HC_ACTION ){
     switch( cwp->message ){
       case WM_CREATE:
         hBomber = FindWindow(NULL, "OnlineBomberMan");
      if( hBomber ){
        // タイトルバーを取り除く
         dwStyle = GetWindowLong(hBomber, GWL_STYLE);
         if( dwStyle & WS_CAPTION )
              dwStyle ^= WS_CAPTION;
         SetWindowLong(hBomber, GWL_STYLE, dwStyle);
         // ウインドウ解像度と初期位置を指定する
         MoveWindow(hBomber, 0, 0, 800, 600, TRUE);
        SetWindowLong(hBomber, NULL, 0, 0, 800, 600, SWP_SHOWWINDOW);
        }}}

とこのようになりました。  
もし何か分かる事等があればレスお願いします。

編集    削除
Ban  2004-10-17 11:56:01  No: 54776  IP: [192.*.*.*]

不正改造対策でなんらかのチェックが入ってるとか。

内部のプログラムが全画面をロックしている前提で
書かれていて、かってに設定を変えてもそこで処理に
失敗するとか。
# プログラマの立場からしたら、そんな不正操作の結果まで
# 保証なんてしてられないわけで.....。

編集    削除
ボールペン  2004-10-18 00:22:42  No: 54777  IP: [192.*.*.*]

Banさん
レスありがとうございます。
>>不正改造対策でなんらかのチェックが入ってるとか。
はい、確かにそのような仕様になっている可能性もあります。
しかし、DirectXを使用しているゲームをウインドウ化するツールというもの
は多々存在していて、それを使用すればウインドウ化は成功するのです。
ということは、フックをかける方法が違うのかあるいは他に方法があるのか
ということになるかと・・・・。
上記の点もふまえ、何か分かる事があればレスお願いします。

ウインドウ化する方法 http://homepage1.nifty.com/meru/prog/rag_c2.html
DirectX窓化ツール    http://saw.minidns.net/software/#soft09

編集    削除
Ban  2004-10-18 20:05:41  No: 54778  IP: [192.*.*.*]

申し訳ありませんがボンバーマンをやったことがないので
固有の処理についてはまったく分かりません。

また、ボンバーマンの利用規約「第7条(禁止事項等)」に
違反せずにできるとは思いませんので試す気もありません。

解像度が合わないのであれば正規に状況を報告して対応を
期待されるべきかと思います。


一般的には"お行儀のいい"アプリなら先の処理だけで動くと
思いますので、意図的か故意かは分かりませんが、当該ソフトは
先のようになっているのでは?という疑念を提示したのみです。
# 上記に示されたツールも、ドキュメントを読む限りでは
# ボンバーマンで動作確認がとれているという記載は
# 見当たりませんでしたけど。

編集    削除
Ban  2004-10-18 21:16:21  No: 54779  IP: [192.*.*.*]

> 意図的か故意かは

一緒だし...>自分

編集    削除
III  2004-10-19 00:59:22  No: 54780  IP: [192.*.*.*]

WH_CALLWNDPROCRETやWH_CBTやWH_GETMESSAGEでは無理なんですかね?
全く試したことないですけど。

少し話はずれますが
> SetWindowLong(hBomber, NULL, 0, 0, 800, 600, SWP_SHOWWINDOW);
最後のこれは SetWindowPosの間違えですよね。
MoveWindow(hBomber, 0, 0, 800, 600, TRUE);
SetWindowPos(hBomber, NULL, 0, 0, 800, 600, SWP_SHOWWINDOW);
って同じ処理をしているのでは?

編集    削除
Werck  2004-10-19 03:36:33  No: 54781  IP: [192.*.*.*]

DirectXはフルスクリーンモードとウィンドウモードとで
初期化や描画方法が根本的に異なるため、
ウィンドウ位置とサイズを変更するだけではだめで、
DirectXの主要APIもフックする必要があります。
DirectX8以降は初期化時のフラグを1つ変更するだけで済むんですが、
BomberMan Onlineは確かDirectX7で、いろいろと面倒です。

編集    削除
ボールペン  2004-10-19 05:44:30  No: 54782  IP: [192.*.*.*]

たくさんのレスありがとうございます。

BANさん
>># ボンバーマンで動作確認がとれているという記載は
>># 見当たりませんでしたけど
動作確認はとれているのですが、どうも他のユーザーの方はウインドウ化できるのですが、私の環境だとブルースクリーンエラーが発生するみたいなので、実際に自分でソフトを作ってみて勉強しようと思い、スレを立てた所存です。
それとウインドウ化する理由はもう一つあって、そのネットゲームは最小化すらできない仕様になってしまっており、不便すぎると思ったのもあります。ウインドウ化すると最小化解除もかかるので(原因不明)、ウインドウ化
というより最小化のフィルタリングを外す事ができても"解決"になります。

Ⅲさん
>>WH_CALLWNDPROCRETやWH_CBTやWH_GETMESSAGEでは無理なんですかね?
色々やったのですが、反応しなかったり動かなくなったりしました。
フックのタイミング自体は調べたので合ってると思います。
>>最後のこれは SetWindowPosの間違えですよね。
はい、SetWindowPosの間違いでした・・・・  すいません。
MoveWindowとSetWindowPosの処理は二通りあるという意味です。

Werckさん
なるほど・・・・  ということはDirectX7の場合の主要APIをフックすればいいということですか?  DirectXについては調べまわっていたんですが、それらしい関数等は見つからなかったのですが・・・。
フックのフラグもみましたが、DirectXについてはなかったように思います。
具体的にどのようにすればよいのでしょうか?  
後、最小化のフィルターはBomberManOnline自体がフックをかけていると思うので、こちらからフックをかけてフックを外す事はできないでしょうか?

編集    削除
Werck  2004-10-19 14:44:40  No: 54783  IP: [192.*.*.*]

DirectXに限らず、WindowsAPIをフックする正規の手段はないので、
ddraw.dllのラッパーを作るとか、プログラムのヘッダを書き換えるなど、
多少イリーガルな方法を取るしかありません。
上記窓化ツールにソースがついているので、それを参照してみるとよいかと。

最小化に関しては、おそらく最小化された時に送られる、
WM_NCACTIVATEやWM_SIZEメッセージを見ていると考えられます。
ウィンドウプロシージャをフックして、
これらのメッセージをカットすればいけるかもしれません。
他の方法で検出しているとダメですが…
ウィンドウプロシージャのフックはSetWindowLong関数を使います。
大まかな流れはこんな感じです。

WNDPROC pOrgWndProc;
LRESULT CALLBACK HookWndProc(HWND, UINT, WPARAM, LPARAM);
    …
    pOrgWndProc = (WNDPROC)SetWindowLong(hBomber, GWL_WNDPROC, (LONG)HookWndProc);
    …
LRESULT CALLBACK HookWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    switch(Msg)
    case WM_ACTIVATE:
    case WM_NCACTIVATE:
    case WM_SIZE:
        DefWindowProc(hWnd, Msg, wParam, lParam);
        return false;  //さすがに根こそぎカットはまずいかも
    }
    return CallWindowProc(pOrgWndProc, hWnd, Msg, wParam, lParam);
}

実際にBomberMan Onlineを解析してみたわけではないのでわかりませんが…

編集    削除
ボールペン  2004-10-19 20:14:47  No: 54784  IP: [192.*.*.*]

Werckさん
ウインドウ化については、ソースを見て少しずつ理解していくことにします。内容が難解ですぐには理解不能と判断しました。

最小化の事なんですが、BomberManOnlineのウインドウをサブクラス化し、WM_ACTIVATE,WM_NCACTIVATE,WM_SIZEの箇所をDefWindowProcで返したのですが・・・  なんかそれ以前の問題で、BomberManOnlineを起動してサブクラス化すると、ネットワークに接続のダイアログが出てからボンバーマンが起動しなくなってしまいました。
WM_ACTIVATEだけでやったり、WM_SIZEだけでやっても同じでした。
また、プロージャのswitch(msg)ロジックを記述せずに、CallWindowProcで
返した場合も同じ結果でした。
ウインドウハンドルを取得するタイミングとサブクラス化するタイミングは、BomberManOnlineのウインドウ作成時(WM_CREATEにフックした際)ですが、これでよかったのでしょうか?

編集    削除
Werck  2004-10-20 07:05:54  No: 54785  IP: [192.*.*.*]

SetWindowLong()によるサブクラス化が、
2回以上実行されているということはありませんか?
これをやると無限再帰呼び出しになり、
スタックオーバーフローで強制終了します。
ただ多くのオンラインゲームでは例外をハンドルしていて、
一見何事もなかったかのように終了しますが…

編集    削除
ボールペン  2004-10-20 09:25:35  No: 54786  IP: [192.*.*.*]

確認してみましたが、一度しか実行していませんでした。
色々実験した結果、CallWindowProcで本来のプロージャに処理を返す記述を
すると、何事もなかったように終了してしまいます。
逆に、サブクラス化したプロージャのreturn CallWindowProcの箇所を
return TRUEやFALSEにすると、起動はできるのですが、やはりフルスクリーン状態のまま固まってしまいます。
また、WH_KEYBOARDに設定し、F2キーにBomberMan Onlineのウインドウハンドル取得と、サブクラス化実行処理を割り当てて、その時のみサブクラス化してみても、何事もなかったように強制終了してしまいました。
一応間違っている可能性もあるので、自分が書いたコードを抜粋します。

// BomberManOnline自体のWM_CREATEにフックした際の処理
 static HWND hBomber; // グローバル
 WNDPROC BomWndProc;  // グローバル
hHook = SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)HookProc, hInst, NULL);
LRESULT CALLBACK HookProc(int nCode, WPARAM wp, LPARAM lp)
   CWPSTRUCT *cwp = (CWPSTRUCT*)lp; // message=BomberManOnlineのWM_XXXXXに対するフック
   switch(cwp->message)
       case WM_CREATE:
          hBomber = FindWindow(NULL, "Online BomberMan"); // タイトルは確認済み
          if(hBomber)
             BomWndProc = (WNDPROC)SetWindowLong(hBomber, GWL_WNDPROC, (LONG)BomSubClassProc);
            break;
       default:
            return CallNextHookEx(hHook, nCode, wp, lp);
   return CallNextHookEx(hHook, nCode, wp, lp);

LRESULT CALLBACK BomSubClassProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
   switch(msg)
      case WM_SIZE:
      case WM_ACTIVATE:
      case WM_NCACTIVATE:
         DefWindowProc(hWnd, msg, wp, lp);
         return FALSE;
      
    return CallWindowProc(BomWndProc, hWnd, msg, wp, lp);

// F2キーにBomberManOnlineのウインドウハンドル取得とサブクラス化を割り当てた場合
   hHook = SetWindowsHookEx(WH_KEYBOARD, .......
   LRESULT CALLBACK HookProc(....
       if(wp==VK_F2)
          hBomber = FindWindow(NULL, "Online BomberMan");
         if(hBomber)  // ウインドウが存在するならサブクラス化実行
          BomWndProc = (WNDPROC)SetWindowLong(hBomber, GWL_WNDPROC, (LONG)BomSubClassProc);
       else
          return CallNextHookEx(......
  return CallNextHookEx(..............

このようになりました。  
もしかしたら、BomberManOnlineのプロージャの処理を変更すると強制終了とかいう関数やフラグが存在するのかもしれません。

編集    削除
Werck  2004-10-20 14:34:07  No: 54787  IP: [192.*.*.*]

てっきり「最小化すると終了してしまう」のだと思ってたんですが、
そもそも最小化、というかAlt+Tabによる切り替え自体できなくなってますね。
これだと、ウィンドウプロシージャをいくらフックしても無駄です。

Microsoftによると「Alt+Tabを無効にしてはいけない」とのことですが(^^;、
いくつか方法がありまして、
・Win9x系の場合、SystemParametersInfo()でスクリーンセーバモードにする
・WinNT系の場合、RegisterHotKey()でAlt+Tabの機能をオーバーライドする
というのが有名です(具体的な方法は「Alt Tab 無効」で検索すればすぐわかります)。
前者の解除は通常モードに設定しなおせばOKですが、
ホットキーの削除はIDがわからないといけません(片端から試すのもありかも)。
またBOがこの方法を採用しているかも不明です。

余談ですが、WinNT系においてCtrl+Alt+Delを無効にするのは、
システムファイルをいじらない限り不可能だと思っていたんですが、
実際にはしっかり無効になっているゲームが存在しますね。
どうやってるんでしょう。

編集    削除
ボールペン  2004-10-20 17:20:19  No: 54788  IP: [192.*.*.*]

以前、BomberManOnlineのSYSWIN.DLLをディスアセンブルした結果が残っていたので見てみると、SystemParametersInfo()とRegisterHotKey()のAPI関数
がアドレスにありました。  ということはそれらを使用して無効化しているのは間違いないようです。  どいういうわけか、BomberManJ.exeは、VC6.0
の混合モードならコードを吐き出したのですが、ディスアセンブルはできませんでした。  なんらかのプロテクトでしょうか?
SystemParametersInfo()による無効化は、WINDOWS XP(NT)なので効果はありませんでした。  実行したのは、WM_CREATEにフックをかけた際です。
SystemParametersInfo (SPI_SCREENSAVERRUNNING, FALSE, &spiID, 0);

残るはRegisterHotKey()なのですが、IDC_HOTKEYに割り当てられている数値
は、+0〜500000までforループ走査したのですが・・・・  なんかすごく起動に時間がかかったりして、徐々に走査したのです。  -の場合はないと思うので・・・  500000から先に定義されている可能性もあるのでしょうか?
for(int i=0; i <= 500000; i++)
  UnRegisterHotKey(hBomber, i);

また、これは方法がズレますが、RegisterHotKeyのあるアドレス周辺の、call命令による呼び出しと思われるほとんどの箇所を、9090(nop)に書き換えてみたり、SetWindowsHookExの箇所を9090に書き換えたりしたのですけど、
RegisterHotKeyの呼び出しの箇所を書き換えた際と、他の適当な箇所を書き換えた際での反応が違いました。  RegisterHotKeyの場合は、問題が発生
しましたというMicrosoft仕様のダイアログがでて強制終了、適当な箇所の
場合は、起動してログインする際にDLLのバージョンが違いますというエラーが出ました。  アセンブラは多少できる程度なので、検討はずれなことを言っているかもしれません。

よろしくお願いします。
なんかすごくプロテクトが堅くなっているに間違いありません。

編集    削除
Werck  2004-10-22 13:59:26  No: 54789  IP: [192.*.*.*]

ホットキーのIDの範囲は0x0000〜0xFFFFです。
ただフックプロシージャ中でIDが共通かはまだ調べてません。
syswin.dll中のSystemParametersInfo()とRegisterHotKey()は
潰してみても変化がなかったので、本体のほうで行っているか、
あるいは別の方法を使っている可能性が高いです。
EXEファイルには暗号化がかけられていて、
逆アセンブルが通らないため、解析は困難かと思います。
ちなみにNOP命令でCALLを潰す場合、直前の引数渡しのPUSHも潰さないと、
スタックに不整合が発生して暴走してしまいます。

編集    削除
ボールペン  2004-10-23 16:06:32  No: 54790  IP: [192.*.*.*]

今まで試してみた方法を一度まとめてみます。

・WM_ACTIVATE, WM_NCACTIVATE, WM_SIZEにグローバルフックをかけ、return FALSEを返して機能を無効化。
・BomberManOnlineのウインドウハンドルをサブクラス化し、WM_ACTIVATE, WM_NCACTIVATE, WM_SIZEのメッセージを横取りし、return FALSEを返す。(CallWindowProcを返すと何事もなかったかのように強制終了)
・WM_CREATEにグローバルフックをかけ、UnRegisterHotKey()によるホットキー削除。(Forループにより+500000まで走査)
・WM_CREATEにグローバルフックをかけ、SystemParametersInfo()によるAlt + Tab無効化解除。

バイナリエディタでどこかを書き換えると、必ず発見されてしまい、強制終了してしまうようで、書き換えられているかBO自体が調べているように思います。  (DLLの場合はログインする際にエラー)
Alt + Tabを無効化しているというのは、少し違うかもしれません。  WINDOWSキーや、Ctrl + Escにより一度デスクトップを表示してから、もう一度Alt + Tabを押すと反応するようになるようです。
ウインドウモードで起動できるようになると、最小化も一緒に解除されます。  これは何を意味しているのでしょうか?
あと、DirectX窓化ツールを使う際は、OnlineBomb.exeとBomberManJ.exeを両方リストビューにドラッグしてからフックしないといけないようで、片方だけでは窓化されませんでした。  この辺りもよく分かりません。  両方フックをかけるとはどういうことなのですか?

編集    削除