投稿記事

プログラムの記事 (10)

Heliodor 2018/11/16 18:12

プログラム開発環境について

何を使って開発してるんですか?という質問をたまに頂くので、開発環境について少しお話しします。

まず、プログラム的な環境でいえば、有料ツールは一切使っていません。全て無料で手に入るモノです。
ヴィータ大脱出や滅びの国の王女のプログラムはC++を使って開発しました。統合開発環境(IDE)は無料で入手できる Visual Studio Express で、これにDirectX 9.0 SDK October 2004 や Ogg vorbis, TinyXml, Lua などのライブラリを組み合わせています。

ちなみにDirectX9.0のSDKがなぜ最新版ではなく古い October 2004 なのかというと、実はこのSDKは、D3DXを使うのにDLLを必要としない最後のSDKなんです。つまり、これ以降のSDKでD3DXの関数を使おうとすると、d3dx9_43.dllとかのDLLが必要になります。そしてこれらのDLLは結構な確率でユーザーのPCにインストールされておらず、トラブルの元になります。
なので、プログラム的な利便性(新しく追加されたライブラリ関数が使えないとか)を多少犠牲にして、古いSDKで作っていました。

プロジェクトファイルは最初は手動で設定していましたが、いろいろオプション設定などを変えているうちに、クリーンな状態のプロジェクトファイルが欲しくなる場合が何回かあったため、そういうことが簡単にできるように cmake を使うようになりました。ちなみにプロジェクトは普通複数のビルド構成を含んでいますが、ヴィータ大脱出の場合は基本的に通常版と体験版があり、それぞれについて開発デバッグ用、友人テストプレイヤー配布用、製品版(デバッグ用の機能を完全に削除したもの)等があります。さらに、そのそれぞれについてステップ実行やランタイム情報の取得などが可能なDebug版と、最適化コンパイルするためのRelease版があるため、最終的には10以上のプログラム構成を含んでいます。

マップエディタなど、内製GUIツールの作成にはC#を使いました(もちろん Visual Studio Express で)。C#はちょっとしたツールを作るのにもかなり便利で使いやすく、ついでにゲームもC#で作れば楽だったのかもしれませんが、ヴィータ大脱出を作り始めた当時、まだ使っている人の多かったWindowsXPをサポートするという条件を考えた時に .net frameworkがインストールされていない可能性があったため、C++のままで行く事にしました。
(さすがにもうXPはいいよね……?という気もするので今後は変わるかもしれませんが)

GUIを伴わないツールやバッチファイル的なモノを作る時には Python です。例えばデータとexeファイル、readmeなどのテキストファイルをまとめて zip に固め、公開可能なファイルを作る作業などです。以前はゲームのスクリプトとして Python を使っていた時期もあったのですが、C側のコードがかなり巨大になってしまうことと、Pyhton のクラスをC側で定義するのが結構面倒だったので、ゲームへの組み込みはやめてしまいました。その代わりに使うようになったのが Lua です。ゲームのシーン進行や、会話スクリプトは全て Lua でやっています。Luaはとにかく構造がシンプルで、C側の実装が楽なんです。ただ、配列のインデックスが1起算なのはいまだに慣れません……。

キャラクタアニメの定義やデータテーブルなどには XML を使いました。YAML を使っていた時期もありましたが、YAMLは文法があまりにすっきりしすぎていて(冗長な情報がほとんどない)なんとなく不安に感じるという点と、タブではなくスペースでのインデントを強○されるのが個人的に嫌だったので、やめてしまいました。C側では TinyXml を使って読み込んでいます。

その他のツールとしては、、テキスト編集には Sakura Editor、バイナリエディタは Starling、zipの作成や中身の確認には7-zip portable、テキスト比較は WinMerge、バージョン管理は TortoiseHg です(そうです私はMercurial派です)。
ちなみにバージョン管理ツールは複数人で使うもの、と思っている方も多いと思いますが、これは個人で使うにも非常に便利なものです。私はバックアップと編集差分の確認、複数のPCでソースコードを同期する目的で使っています。

それと、CG関係のツールは語るとまた長くなるのでそのうち別にまとめるかも?
(ツールの変遷とか部分的に使っているものとか、まあ色々と……)。

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

Heliodor 2018/10/27 22:31

続・ゲームデータの自動ビルドについて

前回の続きです。

生ファイルからゲームファイルを作るとき、生ファイルのタイムスタンプを調べて、異なっている場合だけ再ビルドする、という話でした。
しかし、その方法で再ビルドしてもうまくいかない場合があります。

それはファイルが「変更」ではなく「削除」されてしまった場合です。


例えば、ある生ファイルが不要になって削除された場合、そこから生成されたゲームファイルも削除しておかないといけないという事です。逆に、なんらかの手違いでゲームファイルの方を削除してしまった場合、ファイルが削除された場合、たとえ生ファイルに何の変更も無かった場合でも再ビルドしてゲームファイルをら作り直さないといけません。

結局、処理しないといけないのは次の場合についてです


A. 新しい生ファイルが追加されている(前回のビルド情報が存在しない)→ビルド
B. 生ファイルが変更されている(前回ビルド時とタイムスタンプが異なる)→再ビルド
C. 生ファイルが削除されている(前回ビルド時には存在したのに、今は無い)→対応するゲームファイルを削除
D. ゲームファイルが消えてしまった(前回のビルド時には存在したのに、今は無い)→対応する生ファイルを再ビルド


ここで気をつけないといけないのはDのパターンです。1つの生ファイルから1つのゲームファイルが出来る場合は話が簡単なのですが、1つの生ファイルから複数のゲームファイルができる場合(例: psdからレイヤごとに分解したpngを生成)、あるいは複数の生ファイルから1つのゲームファイルができる場合(例: 複数のpngを結合して一枚の大きなpngを生成)は少々ややこしいことになります。

例えば生ファイルAからゲームファイルaが、生ファイルBからゲームファイルbができる場合は、ゲームファイルaが消えているなら生ファイルAを再ビルド、ゲームファイルbが消えているなら生ファイルBを再ビルドすればいいだけです。

しかし、2つの生ファイルAとBから1つのゲームファイルcができる場合、ゲームファイルcが消えていたら生ファイルAとBの両方を再ビルドしないといけません。

逆に、1つの生ファイルAから2つのゲームファイルbとcができる場合、ゲームファイルbとcのどちらか一方でも消えていたら生ファイルAを再ビルドしないといけません。



このようなややこしい状態を解決するためには、生成関係を保存しておくようにします。例えば生ファイルAからゲームファイルaが生成される場合は、
A→a
と記録し、生ファイルBからゲームファイルbが生成されるなら
B→b
と記録します。

もし1つの生ファイルCから2つのゲームファイルdとeが生成されるなら
C→d
C→e
と記録し、逆に2つの生ファイルFとGから1つのゲームファイルhが生成されるなら
F→h
G→h
と記録します。

このようにして、生ファイルとゲームファイルの生成関係ペアを全て記録しておきます。これは生成関係の記録であると同時に、どんなゲームファイルを出力したかのリストにもなりす。このリストにあるゲームファイルが実際に存在するか確認し、消えていたら生成関係を調べ、関係する生ファイルを再ビルドすればゲームファイルを再生成できます。

例えば出力ファイルaが消えていた場合、aを含むペアを検索すると
A→a

が見つかるので、生ファイルAを再ビルドすればゲームファイルaが得られる事が分かります。同様に、出力ファイルhが消えていたならhを含むペアを検索すれば
F→h
G→h

の2つが見つかりますから、生ファイルFとGを再ビルドすれば良い事がわかります。

ちなみに、ビルド方法が複雑になって中間ファイルが必要になった場合でも同じ方法で対処できます。例えば生ファイルAとBから中間ファイルcが、中間ファイルcからゲームファイルdとeができる場合、
A→c
B→c
c→d
c→e

となるので、もしゲームファイルdが消えていたなら、dに必要なのは中間ファイルcだと分かり、中間ファイルcを生成するためには生ファイルAとBが必要だと分かります。

長くなりましたが、これでやっとビルドツールがまともに動作するようになり、データの変換という煩わしい作業を自動化する事ができました。


ちなみに、こういった「〇〇を作るためには△△が必要」みたいな情報を管理する方法は、スキルツリーやアイテム生成などの管理にも使えます。たとえば薬を得るためには薬草とお湯が必要で、お湯を得るためには水と炎か必要だとか、そういったものです。

これに関してもっとちゃんとした説明が欲しい方は、グラフ理論、有向グラフ、依存関係グラフなどで検索すると、それっぽい話がいっぱい出てきますので参考にしてみてください。


フォロワ以上ー限定特典はサンプルプログラムです。

フォロワー以上限定無料

まずは無料プランで様子見を。 お気軽にフォローしてみて下さい。

無料

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

Heliodor 2018/10/20 15:35

ゲームデータの自動ビルドについて

全然需要ないかな?と思ったら意外と見ている人がいるみたいなので、またプログラムの話でも(長文です)。
今回は、ゲーム用の画像とか各種ファイルの扱いが意外と面倒だよね、みたいな話をします。

ゲームで使う画像には主にpngを使用しているのですが、pngは思いのほか展開に時間がかかるため、何十枚もあるpngをそのまま素直に使うと、ロードに結構な時間がかかってしまいます。

ところで意外と知らない方も多いのですが、png画像は圧縮レベルを0から9までの間で指定することができます。レベル0は無圧縮、レベル1が最低圧、レベル9が最高圧縮になります。レベルが高いほど高圧縮でサイズが小さくなりますが、展開には時間がかかります。逆にレベルが低いほど低圧縮でサイズは大きくなりますが、そのぶん高速に処理できます。(ちなみに圧縮レベルが指定できないツールを使った場合、レベル5で処理される事が多いみたいです)。ということは、全てのpng画像を圧縮レベル1(低圧縮、高速)で保存してあればロードも早くなるという事ですが、保存時にpngの圧縮レベルを指定できるものは意外と少ないですし、そもそも画像を保存するたびに圧縮レベルを指定する(デフォルト設定だとレベル5になっている場合が多い)のも面倒な話です。

このように、編集時とは別に、ゲームに最適な形になるようファイルを加工して使いたいというのは、なにも画像に限った事ではありません。

例えば音楽や効果音は、編集時はwavで扱う事が多いのですが、ゲームではサウンドファイルをogg形式で扱っているため、全てのwavファイルをoggに変換しておく必要がありますし、セリフなどのテキストデータを暗号化したり、プレーンテキストで書いてあるスクリプトを事前コンパイルしてバイナリ形式に変換しておいたりなど、ゲーム専用のファイルに変換しておきたいものはたくさんあります。

しかし、これらの作業をその都度手動で行うのはかなり面倒ですので、編集用ファイル(生ファイル)からゲームアプリにとって都合の良いファイル(ゲームファイル)に自動的に作成するためのツール「ゲームデータビルダー」を作りました。

このツールは、生ファイルの入っているフォルダを指定すると、フォルダ内のさまざまなファイルに対して必要な変換を施し、それらをゲームファイルとして出力フォルダに書き出すというものです。生ファイルを編集したときは、このツールを起動すれば勝手にファイルをビルドしてゲームファイルを作ってくれるというわけです。

ところが、ただツールを起動するだけのビルド作業、意外と忘れる事があるんですね。画像ファイルを修正したあとにビルドし忘れたままゲームを起動し、変更箇所がゲームに全く反映されず混乱するという事故が多発しました。

そこでヴィータ大脱出を開発するとき、ゲームアプリ本体にデータのビルドツールを組み込んでしまい、ゲーム起動時に自動的にデータビルドも実行してしまうことにしました(もちろん製品版として世に出すときは、このビルド機能を削除しておきます)。

とは言え、さすがにゲームを起動するたびに全ファイルに対してビルド処理が入ったのでは起動に時間がかかってイライラします。そこで、ビルド時間を短くするために、無駄なビルドをしないように工夫することにしました。

ビルドが必要なのは生ファイルが変更された時だけです。無変更なファイルはわざわざビルドしなおす必要がありません。前回のビルド結果をそのまま使えば良いからです。そのためにはビルド時にファイル情報を記録しておき、次回起動時にその記録と実際のファイルを比較し、ファイルに変化があれば再ビルドし、変化がなければスキップすれば良いことになります。

さて、肝心なのは「ファイルが変化したかどうか」をどうやって調べるかです。それならハッシュを記録しておいて前回と今回のハッシュを比較すれば楽勝です、と言いたいところですが、それはちょっと問題ありです。ハッシュを比較するためにはファイルの最新のハッシュを知る必要がありますが、ハッシュを計算するにはファイルのバイナリを最初から最後までスキャンしないといけません。どのファイルが変化したかを予め知ることはできない(というかそれを知るのが目的)なので、結局のところ全ての生ファイルのハッシュを再計算しないといけません。生ファイルが数百MBあったりするとハッシュ取得だけで時間がかかってしまいます。

そこで、単純にファイルのタイムスタンプだけを比較するようにします。ビルドした時にファイル名と最終更新日時のタイムスタンプを記録しておき、それと実際のタイムスタンプを比較して「異なっていたら」変更ありとみなします。ちなみに、前回と比べてタイムスタンプが「新しく」なっていたら変更ありとみなすのはダメです。なぜかというと、バックアップなどからファイルを取り出して復元した場合にはタイムスタンプが古くなってしまう場合があるからです。なので、1秒でも異なっていたらファイルが変更されたと判断します。

まとめるとこういう事です。

まずゲーム起動時に前回のビルド情報をロードします。ビルド情報がなければ初回ビルドだという事なので、無条件に全ファイルをビルドします。ビルド情報があれば、全ての生ファイルについて前回ビルド時のタイムスタンプと、現在のタイムスタンプを比較し、完全に一致するものはスキップし、そうでなければその生ファイルを再ビルドしてゲームファイルを作り直します。

これでカンペキですね!
...と言いたいことろですが、これだとうまくいかない場合があるのです。


(長くなったので今回はここまで、後編に続きます)

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

Heliodor 2018/09/28 19:12

たまにはプログラムの話2

前回、ヴィータ大脱出での不正終了イベントログの取得について書きましたが、あれって結構不格好な実装だったんですよ。なんたって、不正終了した時にそのイベントを取得できるのは次回起動時でしたからね。不正落ちしてそのまま電源を切って、翌日起動とかされると、15分制限に引っかかってイベント取得できなかったわけです。

かといってイベントの検索範囲を24時間とかにするとイベントの量が多くなり過ぎて検索に時間が掛かる=起動に時間が掛かりますし、どうせなら不正終了したその場でイベントを取得してテキストファイルに書き出してダイアログ表示したいじゃないですか。

そこで、もう一工夫して一度はボツにした SetUnhandledExceptionFilter を使ってみることにしました。
SetUnhandledExceptionFilter を使った時の問題点は、例外検出しても、その時点ではまだエラーイベントが作成されていないため、詳しいエラー情報を取得できないというものでした。ならば、アプリが完全に終了するまで待ってやればよいではないかと。


というわけで。次のように処理内容を変更しました。

  • ゲーム起動時に SetUnhandledExceptionFilter で例外フックを仕掛けておく
  • 例外フックが発動したら、自分のプロセスIDをコマンドラインパラメータにしてプログラムBを起動する
  • そのまま終了させる

  • プログラムBは起動時にコマンドライン引数を調べ、例外発生側のプロセスIDが指定されていれば、そのプロセスが終了するまで待機

  • 終了を確認したらWindowsのイベントログを調べる
  • 該当エラーイベントが見つかったら内容をテキストファイルに書き出してダイアログを出す

ここで、プログラムBとは誰ぞや?ということですが、これはゲームプログラム自身です。
ただのゲームとして起動されたのか、それとも例外報告用として起動されたのかはコマンドラインパラメータで識別します。

全体の流れは以下の通りです

WinMain(HINSTANCE hCurrInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
	if (例外報告モードで起動された?(lpCmdLine)) {
	
		DWORD pid = コマンドラインからプロセスIDを取得(lpCmdLine);
		
		// そのプロセスが終わるまで待つ
		HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
		WaitForSingleObject(hProcess, INFINITE);
		CloseHandle(hProcess);
	
		// 例外を出したプロセスが終了した。
		// Windowsイベントログに反映されるまでの時間を考慮して、少し待機する
		Sleep(200);
		
		std::string event_text = イベントログを検索();
		
		テキストファイルに保存(event_text);
		
		ダイアログメッセージ(event_text);
		
		// このまま何もしないで終了
	
	} else {
		
		// 普通のゲームモードで起動された
		SetUnhandledExceptionFilter(onExceptionOccurred); // 例外フックを登録
		
		...
		// 普通のゲーム処理
		...
	
	}
	return 0;
}

で、肝心の例外フックはこんな感じ

LONG WINAPI onExceptionOccurred(struct _EXCEPTION_POINTERS *e) {

	char cmdline[256];
	コマンドライン引数作成(cmdline, GetCurrentProcessId()); // <---自分のプロセスIDも含めておく

	// 自分自身の実行ファイル名
	char self_path[MAX_PATH] = {0};
	GetModuleFileName(NULL, self_path, MAX_PATH);

	// 例外チェックモードで自分の複製を起動する
	ShellExecute(NULL, "OPEN", self_path, cmdline, NULL, SW_SHOWNORMAL);

	return EXCEPTION_CONTINUE_SEARCH; // EXCEPTION_CONTINUE_SEARCH にしないとアプリ終了時にwindowsエラーログに残らない
}

これで実験してみると、見事に不正落ちしたその場でエラーの後始末が始まります。
ようやくスマートに後始末ができるようになりました。めでたしめでたし。


サンプルを置いておきますので、興味がありましたらどうぞ。

ErrorReport_sample.zip (7.74kB)

ダウンロード

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

Heliodor 2018/07/24 21:44

たまにはプログラムの話

めっちゃ長文になりました。
絵とか無いのでプログラム興味ない人はあんまり面白くないかもです。

不正落ち対策

初期のころのヴィータ大脱出は不正落ちすることが何度かあり、修正はしたものの、リリース後に不正落ちしたらどうしよう? というかどうやって原因を特定しよう? と悩んでおりました。
そこで、ゲームがユーザーの手に渡った後に不正落ちしても大丈夫(?)なように、不正落ちを自動的に検出してログを吐き出すという仕組みを入れることにしました。

ところで、不正落ちから原因箇所を特定するためには例外発生場所のアドレスが重要になりますが、普通に不正落ちしたときに出てくるWin32標準の例外ダイアログだと、その辺がよく分からないんですよね、アレ。あの情報からは元の個所を特定できる気がしません。

そんな時頼りになるのは、例外ダイアログではなくイベントビューアの方に記録されているアプリケーションのエラー情報です。これを見るとバッチリ例外発生アドレスが載っています。
これさえあれば、ビルド時に作った .map と .cod ファイルを使って、例外が発生した個所をソースコード上で特定できますね。

問題は、果たして不正落ちしたときにユーザーがイベントビューアを開いて膨大なリベントリストからヴィータ大脱出.EXEで発生したエラーのログを探し出してコピペしてくれるか? ですが……まぁ無理ですね。なので、そこは自動で取得するように頑張りました。

ちなみに最初は間違った方向に頑張ったため、

ShellExecute(NULL, "open", "eventvwr.exe", "/c:Application", NULL, SW_SHOWNORMAL);

を実行して自動的にイベントヴューアを開いてアプリケーションログを表示し、あとはユーザに自力で検索してもらう、という実装になりかけました。

とりあえずログを取得する

プログラムでWindowsのイベントログを取得する方法は

EVENTLOGRECORD
OpenEventLog
ReadEventLog
CloseEventLog

あたりを検索するとすぐ見つかると思います。特に、

http://www.yuboo.net/~ybsystem/sys_build/win_client/evtlog_read.html
「イベントログの読み込み」

とか

http://plaza.rakuten.co.jp/u703331/diary/200608110000/
「Event Log」
http://plaza.rakuten.co.jp/u703331/diary/200608110001/
「Event Log その2」
http://plaza.rakuten.co.jp/u703331/diary/200608110002/
「Event Log その3」
http://plaza.rakuten.co.jp/u703331/diary/200608110003/
「Event Log その4」

には大変お世話になりました。

つぎに、膨大なログの中からヴィータ大脱出の不正落ちの記録だけをピンポイントで見つける方法です。
まともに検索するとすごく時間がかかります。たぶん何万件とかいったレベルでイベントがあるんじゃないかと思います。
Windowsイベントログを定期的に消してる人なんていないと思いますし。
幸いにもログは新しい順に取得できるので、検索対象は相当絞り込むことができます。例えば、

  • 過去 5 分以内に発生したログだけを対象にする(それ以上古いログが出てきたら、そこで検索を打ち切る)
    EVENTLOGRECORD::TimeGenerated はそのまま time_t の値なので、これと現在時刻 time(NULL) の差をとり、
    	それが 5 * 60 [秒] 未満であるログだけ調べます
    
  • エラーメッセージだけを対象にする
    EVENTLOGRECORD::EventType が EVENTLOG_ERROR_TYPE なやつだけを調べます
    
  • ヴィータ大脱出の実行ファイル名を検索キーワードにする
    エラーメッセージの文字列パラメータは EVENTLOGRECORD の先頭を 0 バイト目としたときに、EVENTLOGRECORD::StringOffset バイト目以降に入っています。
    文字列パラメータが複数個ある場合はヌル文字で区切り、全部でNumString個の文字列があります。
    ちなみに EVENTLOGRECORD 構造体の直後にはソース文字列とコンピュータ名が同じくヌル文字区切りで入ってます。
    

イベントログのパラメータ文字列を得る

アドレスやら不正落ちした実行ファイル名やらの情報は、イベントの文字列パラメータという形で入っているので、これを取り出さないとどうしようもありません。これらの文字列を得るには、だいたいこんな感じにします。

#define MAX_EVENT_ARGS 32
#define OFFSET_PTR(p, off) (((uint8_t *)(p)) + (off)) // p の型に関係なく常に off バイトだけずらす
EVENTLOGRECORD *e = ...; // イベント
LPSTR SourceName = (LPSTR)OFFSET_PTR(e, sizeof(EVENTLOGRECORD)); // イベントソース
LPSTR ComputerName = SourceName + strlen(source) + 1; // コンピュータ名
LPSTR EventArgs[MAX_EVENT_ARGS]; // 文字列パラメータ数
LPSTR str = (LPSTR)OFFSET_PTR(e, e->StringOffset);
for (WORD i=0; i<e->NumStrings; i++) {
	EventArgs[i] = str; // i番目の文字列パラメータ(のアドレスだけ)
	str += strlen(str) + 1;
}

ちなみに、イベントビューアで見られるようなイベントメッセージをそっくりそのまま再現するためには、レジストリの

"SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application"

を見てメッセージフォーマットが定義されているファイルを調べ、それをロードして FormatMessage するとかいう複雑怪奇な手順が必要ですが、目的のエラーを探すだけだったら文字列パラメータを見ればよいだけなので、面倒な作業は完全に不要です。
が、一応載せておきますと、だいたい↓みたいな感じです。

EVENTLOGRECORD *e = ...; // イベント
CHAR szExpandModuleName[MAX_PATH] = {0};
{
	HKEY hAppKey = NULL;
	HKEY hSrcKey = NULL;
	WCHAR moduleName[MAX_PATH] = {0};
	DWORD moduleNameBytes = sizeof(moduleName);
	RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application", 0, KEY_READ, &hAppKey);
	RegOpenKeyEx(hAppKey, SourceName, 0, KEY_READ, &hSrcKey);
	RegQueryValueEx(hSrcKey, "EventMessageFile", NULL, NULL, (LPBYTE)moduleName, &moduleNameBytes);
	ExpandEnvironmentStrings(moduleName, szExpandModuleName, MAX_PATH);
	RegCloseKey(hSrcKey);
	RegCloseKey(hAppKey);
}
void *pMessage = NULL;
HMODULE hModule = LoadLibraryEx(szExpandModuleName, NULL, DONT_RESOLVE_DLL_REFERENCES|LOAD_LIBRARY_AS_DATAFILE);
FormatMessage(
	FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_HMODULE|FORMAT_MESSAGE_ARGUMENT_ARRAY,
	hModule,
	e->EventID, // ←イベントIDを指定
	MAKELANGID(LANG_JAPANESE,SUBLANG_JAPANESE_JAPAN), // ←日本語で取得するなら、こう
	 (LPSTR)&pMessage, 0, (va_list *)&EventArgs);
MessageDialog(NULL, (LPSTR)pMessage, "", 0);
LocalFree(pMessage);

めんどくさいですね~~~

目的のエラーログかどうかを調べる

エラーイベントのメッセージパラメータ(↑のコードでいうとEventArgsのどれか)には必ずエラーが発生した実行ファイルのファイル名が入ってますから、その名前で検索をかけてやるわけです。なお、フルパスではなく、ファイル名だけで比較しないと引っかかりません。

自分が不正落ちしたかどうかを調べ、不正落ちした形跡が見つかったらダイアログメッセージを出す

そもそもの目的はコレです。本当は例外発生に引っ掛けて、その時点でアドレスが分かればよいのですが例外フック(SetUnhandledExceptionFilter)で得られる情報は例外ダイアログのものと同じで、エラー発生個所の特定にはあまり役に立たなそうに見えます。
じゃあフックしたときにエラーログを見るか?とも思いましたが、残念ながらフックした段階ではまだエラーイベントは生成されていません。
実行ファイルがが例外で完全に終了して、やっとイベントが生成されるみたいです。

なので例外フックには頼るのはボツにしました。
その代わりに、ゲーム起動時に15分前までのイベントログを調べ、ヴィータ大脱出で発生したエラーイベントが見つかった場合は、前回起動時に不正終了したとみなし、イベントの内容を情報をテキストファイルに保存してダイアログメッセージを出す、というふうにしました。

ちなみに15分と長めに設定したのは、

ヴィータ大脱出をフルスクリーンで実行中に不正落ち
↓
フルスクリーン状態で止まっているのでデスクトップが操作できない
↓
仕方なくCtrl+Alt+Deleteでウィンドウを出すも、ゲームウィンドウの裏側に表示されてしまいまともに操作できない
↓
どうにかキーボード操作でログアウトする
↓
再起動

みたいな流れになって、復旧に手間取った場合を想定したからです。

テスト

テストです。適当なところに

int *a=0; *a=0; 

とか書いておいて実行してみます。ちなみに VisualStudio から実行してもダメです。
デバッガが起動してしまい、エラーログが追加されません。普通にエクスプローラから EXE を実行して確認します。



どうやらうまくいったようです。これでリリース後に不正落ちしても安心ですね!

ちなみに

リリース後、不正終了の報告はありませんでした……。

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

1 2 »

記事のタグから探す

月別アーカイブ

限定特典から探す

記事を検索