http://wordpress.click3.org/garakuta/th135_ai.zip
コンボ情報取得とクラッシュバグ修正。
今までに比べてボリュームがだいぶ小さいのはネタ切れが原因。
土日で2P固定問題をなんとかしたら、そこで一段落付ける予定。
http://wordpress.click3.org/garakuta/th135_ai.zip
コンボ情報取得とクラッシュバグ修正。
今までに比べてボリュームがだいぶ小さいのはネタ切れが原因。
土日で2P固定問題をなんとかしたら、そこで一段落付ける予定。
http://wordpress.click3.org/garakuta/th135_ai.zip
ひとまずクラッシュなどの致命的なバグもとれて、必要最低限の機能も実装できた版。
まだまだ足りない機能はあるが、今の時点である程度のAIまでは組めると思われる。
この辺りで機能拡張は一旦休憩にして、ドキュメントの整備とかサンプルコードの追加とかやるべきかもね。
http://wordpress.click3.org/garakuta/th135_ai.zip
土日中に形にしたかっただけなのでかなり強引にリリースした。
ドキュメント系もやっつけ仕事だし、クラッシュしたりユーザーにやさしくない動作もたくさんある。
でもまぁ、そのへんは今後のリリースで良くしていくということで。
前回ようやく心綺楼の解析に目処が付いたので、th135_ai(仮)開発に向けてライブラリの選定などをしていました。
その関係でmrubyに触れる機会があったのですが、いつもの様にマイクロスレッドもどきを作ってみたところ、200回以上resumuするとクラッシュというよくわからない事態に遭遇。
最初は自分のコードにバグがあるかと思ったのですが、最小再現コードを作ってみればrubyコードだけでクラッシュしていることが判明。
どうやらmrubyのバグを踏んだようです。
というわけで報告して来ました。
https://github.com/mruby/mruby/issues/1434
最小再現コード
t = Fiber.new do while true do 1.times do Fiber.yield end end end while true do [t].each do |t| t.resume end end
無意味そうなwhileやtimesがありますが、削ると発生しなくなります、謎。
windows8.1 previewという非正規な環境ではありますが、発生率は100%
mruby.exeで発生するので、自作コードによるものではないことは確実。
原因は何なんでしょうね?
Fiberによる状態の保存と復帰処理が、ある程度以上複雑なコールスタック上で行われると壊れるとかですかね?
まぁどっちにしろ、これで転けられるとマイクロスレッド中毒な自分からしたら話にならないので
すぐ治るようでなければ、th135_aiでは別言語を採用しないといけなそうです。
何がいいだろう、個人的にはSquirrelとかLuaの貧弱さが気に入らないので
もっと高機能の載せたいんですが、そうすると今度は組み込みコスト高くてなぁ……
まぁ次はpythonでも試してみましょうかね。
追記:作者さんからリプ飛んできて速攻直りました。
Matzさんすごいなー。
If-Modified-Sinceはその日時より後に更新があった場合だけ本文をくれとお願いするHTTPの機能。
wordpressは今見ているここでも実際に使われているブログソフトウェア。
コメントのフィードはこのブログでは以下のurlで見れる。
rss
atom
バグの内容を簡単に説明すると、コメントのフィードにも関わらず、最後に記事を投稿もしくは編集した時刻を元に判定しており、If-Modified-Sinceを使用するといくらコメントしても記事を投稿するまで見えない、というもの。
気付いたのは、google readerからTiny Tiny RSSへと移行をしていた時。
漏れがあると怖いので両方をチェックするようにしていたのだが、なぜかgoogle readerの方にだけスパムコメント付いた旨が通知されてくる。
そこでTiny Tiny RSSの更新処理のログをチェックしてみたところ
[21:06:48/13726] local cache will not be used for this feed [21:06:48/13726] fetching [http://#{domain}/?feed=comments-rss2]... [21:06:48/13726] If-Modified-Since: Fri, 07 Jun 2013 05:15:00 GMT [21:06:48/13726] fetch done. [21:06:48/13726] unable to fetch: HTTP Code: 304 [304] [21:06:48/13726] source claims data not modified, nothing to do.
との表示が。
自分でコメント日時より前のIf-Modified-Sinceをつけてアクセスしてみれば確かに304が返ってくる。
そこでwordpressのソースコードを読んでみて前述のバグを発見。
trunkでの再現チェック、パッチ作成、説明する英文書きを経て、開発チームのトラックへバグチケットを投げたという形だ。
出来ればテストコードも付けてパッチを投げたかったのだが、該当関数は現在ノーテストであったうえ、付与するheaderのリストアップから実際にレスポンスヘッダーに追加する処理までがひとつの関数内で行われており、さらに内部でexitまで実行していて、環境依存にならないテスト方法が思いつかなかったため断念。
リファクタリングしてまでテスト突っ込むのは気力的にも、初バグ報告という立場的にも難しかった。
以上、備忘録兼情報共有ということで記事を書いた。
バグなので、wordpress3.6には入るんじゃないかなと。
現在これで困っているという人は該当チケットのパッチを当てておけばいいかと思います。
VirtualDLLというものを知っているだろうか?
自分も由来などは知らないが、asciiの記事によるとWindows7から導入されたDLLのAPI呼び出しを別のDLLへ転送するものとか。
ちょっと気になったので仕組みを色々調べてみた。
※ちなみにMSDNをVirtualDllで検索しても何も出てこない
なのでVirtualDllというのは非公式な造語の可能性がある
が、他にそれらしい名称が見つからなかったので本記事ではVirtualDllと呼称することにする。
まずAPIの転送と聞くと、.defファイルに「#{API名} = #{転送先DLL名}.#{転送先API名}」と記述することで行えるエクスポート転送が思い当たる。
実際Kernel32.dllなどを覗いてみるとapi-ms-win-base-bootconfig-l1-1-0.dll(Windows8の場合)など、見覚えのない数個のDLLへの転送が行われている。
だが、実際に転送先のdllを覗いてみてもさらに自分自身へ転送して無限ループしているだけで実体に行き着かない。
実際にLoadLibraryしてみるとadvapi32.dllがロードされるのだが、api-ms-win-base-bootconfig-l1-1-0.dllの中にはadvapi32.dllなどという文字列は出てこないうえ、暗号化の類を施している形跡もない。
それどころか、中にはファイルとして存在しないdllもあり、通常のエクスポート転送だけではないことがうかがえる。
上記のasciiの記事によればapisetschema.dllを介して転送されるとある。
中をのぞいてみると、確かにms-win-base-bootconfig-l1-1-0(なぜか最初のapi-が削られている)や、advapi32.dllという文字列が見える。
どういうことかは不明だが、これを介して特殊な転送が行われているらしいことは確かなようだ。
※apisetschema.dllをwindows8 64bit上で32bitアプリから開く場合C:\windows\sysnative\apisetschema.dllでないと開けないことに注意
windows7ならばSystem32とSysWOW64の両方に存在するので問題はない
apisetschema.dllを実際に調べてみると.textや.rsrcなどの通常のセクションに混ざって.apisetという見覚えのないセクションが含まれているのがわかる。
結論から言ってしまうとここがまさにVirtualDllの要なのだが、解析した道筋を書いていくと無駄に長いうえ対して得るものもないので
コードだけ示す。
簡単にいうと、転送元dll名と転送先dll名のペアがいくつも格納されているだけというシンプルな構造。
格納の仕方にいくつか癖があるが、それは重要な部分ではないので、気になる方は実際のソースコードを読んでみてください。
ちなみに、最初四文字がapi-かext-だと決まっているようで、apisetschema.dll内では省略されている。
別にそんなところ節約したってしょうがないのだから含めてしまえばいいと思うのだが、なにか別の事情があるのだろうか……?
先頭がapi-かext-で始まるdllは、まずapisetschema内のテーブルを参照し
転送先が記述されている場合はそちらのdllをロード。
そうでない場合に通常のdllロードが行われる。
それがVirtualDllの仕組みのようだ。
逆にいえば、apisetschemaを書き換えることができれば、一部処理を自作dllに向けることも可能。
全アプリケーションに対してdllインジェクションしようとするなら、割と視野に入るかもしれない。
もっとも、そんなアプリケーションはよっぽどのことがない限りユーザーに嫌われますが。
C/C++において構造体(クラス)内のメモリーの配置がどうなるかというのは案外重要な情報だ。
組み込み開発の話ではない(組み込みでも重要だけど)、バイナリデータの入出力に置いて便利だからだ。
たとえば1byte/2byte/4byte/4byteと並んでいるバイナリデータがあったとして、それを構造体
struct Data { uint8_t a; uint16_t b; uint32_t c; uint32_t d; };
に読み込もうとした場合を考えよう。
仕様にのっとって礼儀正しく書くとこうなる
void readData(const uint8_t * const in, Data &out) { out.a = in[0]; out.b = in[1] | (in[2] << 8); out.c = in[3] | (in[4] << 8) | (in[5] << 16) | (in[6] << 24); out.d = in[7] | (in[8] << 8) | (in[9] << 16) | (in[10] << 24); }
だが仮にアライメントにより読み書きの制限がなく、構造体内のメンバーのメモリー配置が宣言した順序に1byteの隙間もなく敷き詰められており、なおかつ余分なデータが含まれないと仮定できる場合、以下のように書くことができる。
void readData(const uint8_t * const in, Data &out) { out = *reinterpret_cast<const Data *>(in); }
ちなみにこれはmemcpyでも同様の結果が得られる。
そして、x86/64ではアライメントによる読み書きの制限はなく、主要な開発環境であるVC++やgccには構造体に詰め物を挟まない設定が存在する。
よって、下のように書くことは現実に可能である。
あくまで自分の主観ではあるが、バイナリ入出力に置いてこのテクニックは基本であり、事実今まで多用し続けてきた。
しかし、最近あることと組み合わせるとこの機能をうまく使えないことが分かった。
その機能とは、”多重継承”である。
そもそも継承している時点でスーパークラスとサブクラスのメンバーの配置がどうなるかは仕様の範囲外なのだから、使えなくて当然だと思うかもしれないが
そういうことではなく、単純にメソッドのみを持つ所謂インターフェースクラスを継承した場合の話だ。
ちなみに、JIS X 3014上では
※引用ここから
9.0.1: クラスのオブジェクトは、メンバおよび基底クラスのオブジェクトの列(空であってもよい。)から成る。
9.2.17: C互換構造体オブジェクトを指すポインタは、reinterpret_castを使って適切に変換することによって、 先頭のメンバを指すポインタとなる(又はそのメンバがビットフィールドの場合は、その格納単位を指す)。 また、逆も正しい。 参考: したがって、C互換構造体オブジェクト内に、 適切に境界調整するために名前なしの詰め物が置かれることがあるが、 先頭に置かれることはない。
9.0.4: C互換構造体は、次のいずれももたない集成体クラスとする。 ―非C互換構造体(又はその型の配列)である非静的データメンバー ―非C互換共用体(又はその型の配列)である非静的データメンバ ―参照型である非静的データメンバ ―利用者定義のコピー代入演算子 ―利用者定義のデストラクタ
8.5.1.1: 次のいずれをも持たない配列またはクラスを、集成体と呼ぶ。 -利用者宣言のコンストラクタ -非公開又は限定公開の非静的データメンバ -基底クラス -仮想関数
※引用ここまで
となっているため、多重継承している=基底クラスを持っている=集成体ではなくなるため、先頭のメンバーとのreinterpret_castでの変換自体保証されていないというのが仕様上の扱いである。
では実際のところどうなるのか、コードを交えていろいろ試して行こう。
とりあえず基本形
#include <cstdio> #include <cstdint> #ifdef _MSC_VER #define __attribute__(attr) #endif #pragma pack(push, 1) struct Data { uint8_t a; uint16_t b; uint32_t c; uint32_t d; } __attribute__((packed)); #pragma pack(pop) void readData(const uint8_t * const in, Data &out) { out = *reinterpret_cast<const Data *>(in); } int main() { const uint8_t in[] = {0x12, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12}; Data out; readData(in, out); printf("sizeof: %d\n", sizeof(Data)); printf("out.a: %08x\n", out.a); printf("out.b: %08x\n", out.b); printf("out.c: %08x\n", out.c); printf("out.d: %08x\n", out.d); return 0; }
※以後変更点のみ記載
実行結果:
Microsoft Visual Studio 2010 Verion 10.0.40219.1 SP1Rel(以下VC++)、gcc4.6.3(以下gcc)共に
sizeof: 11 out.a: 00000012 out.b: 00001234 out.c: 12345678 out.d: 12345678
となり、意図したとおりに動作していることが分かる。
一つ空の構造体を継承させてみる
struct Hoge { }; struct Data : Hoge { uint8_t a; uint16_t b; uint32_t c; uint32_t d; } __attribute__((packed));
sizeof: 11 out.a: 00000012 out.b: 00001234 out.c: 12345678 out.d: 12345678
VC++、gccともに実行結果に変化なし
さらに継承ツリーを伸ばしてみる
struct Fuga { }; struct Hoge : Fuga { }; struct Data : Hoge { uint8_t a; uint16_t b; uint32_t c; uint32_t d; } __attribute__((packed));
sizeof: 11 out.a: 00000012 out.b: 00001234 out.c: 12345678 out.d: 12345678
VC++、gccともに実行結果に変化なし
多重継承にしてみる
struct Fuga { }; struct Hoge { }; struct Data : Hoge, Fuga { uint8_t a; uint16_t b; uint32_t c; uint32_t d; } __attribute__((packed));
VC++
sizeof: 12 out.a: 00000034 out.b: 00007812 out.c: 78123456 out.d: cc123456
gcc
sizeof: 11 out.a: 00000012 out.b: 00001234 out.c: 12345678 out.d: 12345678
VC++だけサイズが1byte増え、範囲外アクセスを起こしてしまいました。
aの中身を見るに、メンバーaの前に余分な詰め物が1byteあるようです。
多重継承したものを継承してみる。
struct Fuga { }; struct Hoge { }; struct Haga : Hoge, Fuga { }; struct Data : Haga { uint8_t a; uint16_t b; uint32_t c; uint32_t d; } __attribute__((packed));
VC++
sizeof: 12 out.a: 00000034 out.b: 00007812 out.c: 78123456 out.d: cc123456
gcc
sizeof: 11 out.a: 00000012 out.b: 00001234 out.c: 12345678 out.d: 12345678
単純に多重継承したのと同じ結果、どうやら直接にしろ間接にしろ多重継承が混ざること自体がだめらしい。
さらに多重継承してみる。
struct Fuga { }; struct Hoge { }; struct Haga { }; struct Data : Hoge, Fuga, Haga { uint8_t a; uint16_t b; uint32_t c; uint32_t d; } __attribute__((packed));
VC++
sizeof: 13 out.a: 00000012 out.b: 00005678 out.c: 56781234 out.d: cccc1234
gcc
sizeof: 11 out.a: 00000012 out.b: 00001234 out.c: 12345678 out.d: 12345678
VC++がさらに悪化。
多重継承が一個増えるごとに謎の詰め物が一byteずつ追加されていく仕組みらしい。
結論:
VC++ではバイナリデーターからの一括読み込みなどに構造体を使用する場合、メソッドやコピー禁止クラスなどを多重継承させてはいけない。
gccなら問題なし。
この記事書く前にいろいろ試した段階ではgccでも壊れるパターンがあったはずなのだが思い出せなかった……orz
DxLibの汎用キーコンフィグソフト
DxLibKeyConfig.zip
winmm.dllとDxLibKeyConfig.exeをDxLibで作られたアプリと同じディレクトリに配置し
あとはDxLibKeyConfig.exeで設定するだけで大体どんなソフトでもキーコンフィグできてしまうソフト。
仕組みとしてはwinmm.dllの振りをしてアプリ本体にインジェクション。
その後ImportAddressTableの書き換えと仮想関数テーブルの書き換えを用いて
GetProcAddress=>CoCreateInstance=>CreateDevice or CreateDeviceEx=>SetDataFormat and GetDeviceState
という風にダミーの関数に置き換えてたどっていき
GetDeviceStateの動作を変更することでゲームパッドの入力変更を実現している。
副産物として、同様の方法でゲームパッドを無効化するだけのツールも作成した
=>DisableDxLibJoy.zip
ちなみに、専用にカスタマイズした版をかの人に押し付けてきたので、そのうち正規のパッチに付属する形で配布されるはず。
魔法使いの夜というノベルゲーム(詳細はググって)がありますが
Windows8 64bit(のConsumer Preview)では動作しません。
2013/01/14追記:すでに公式で修正パッチが出ています、単純に魔法使いの夜をプレイしたいだけの人は公式のサポートページへGO。
動作しない理由は簡単、不正行為防止用のプロテクトが不正環境だと誤認しているからです。
このページではWindows8 64bitで魔法使いの夜をプレイする方法と、その原理を説明します。
説明とかいいからうごかし方だけ教えろという方向けの完成品。
Win8WOH.zip
使い方はzipの中のversion.dllを魔法使いの夜.exeと同じディレクトリに置く、それだけ。
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追記:
いろいろなところからリンクされててびっくりしました。
どうやら魔法使いの夜に限らず、ワムソフト版と呼ばれる吉里吉里エンジンを使用したゲーム全般で発生する問題だったようで、
なまじ汎用的に作っちゃったのでどのゲームもこれで動くようになった、ということらしいです。
ワムソフトさんではすでに対応版を出しているらしく、動きの速いところでは公式で修正パッチが出ているそうなので、出ているものはそちらを使いましょう。
それでもだめなら、だめもとで使ってみるといいかも?
動作確認していないのでちゃんと動くかは保証しかねますが。