after C84
コミケ参加した方々、お疲れ様でした。
予想外に反響があって、久しぶりにすごく手応えを得られた回になりました。スペースに足を運んでくださった方々、声をかけてくださった方々、ありがとうございました。
ただ、今回マスターデータにソースコード入れ忘れたまま途中まで CD 焼いてしまい、半分くらいはソースコードが入っていないという不手際をやらかしてしまいました。今回パッケージは Web 公開したくない事情があるため、ソースだけここに公開します。(といっても今も github でフルオープン状態なのですが)
https://github.com/i-saint/atomic/archive/C84.zip
今回ので 自機の攻撃行動 (バリア設置とか)、歯車、流体だけ通す/通さない壁、ブラウザから敵キャラ操作 などの基礎ギミックがとりあえず動いたところで、今後はこれらのギミックを応用してステージを作っていきます。
もう少しでレベルエディタなどのコンテンツ量産の仕組みが整うため、冬までにはそこそこ遊べる内容になる予定です。ネットワーク要素にややチャレンジングな内容が含まれるのを考慮すると、完成は冬だとやや厳しくて目標は今年度中、といった感じでしょうか。
exception を出した頃と比べると、同人ゲームでも Unity を使ってマルチプラットフォームの 3D ゲームを出すところが出てきていたり、新興サークルも結構出ていたり、元々開発レベル高かったサークルはますます高くなっていたり、デモ展示を見て「これ iOS でも動きますか?」と聞かれることがあったり (!)、色々な変化が感じ取れます。
しかし、ゲームが好きな人達が、ギリギリまで泥臭い作業を続けて、当日成果を見せ合う、という流れと熱気は今後も変わらないのでしょう。
before C84
またしてもギリギリの告知になってしまいましたが、C84 版 atomic はこんな仕上がりになりました。開発中バージョンにつき 100 円、いつも通りソースコードも同梱です。3日目東ホ-44b primitive。
書き残しておきたい開発記録なんかもいくつかあるのですが、そちらはまた後日。
function hooking
ここ数ヶ月でいろんな関数 hook を試したのでメモ。Windows でしか試していませんが、x86/64 であれば他のプラットフォームでも同等のことができるはずです。
関数 hook と dll injection は解析/デバッグツールやら動画キャプチャツールの類を作るための基礎技術になります。
・vftable Swapping (sample code)
vftable をすり替えることで virtual 関数を hook する方法。vftable の構造がコンパイラ依存ではあるものの、OS の API などを必要とせずお手軽です。
interface class のみが提供され、実装が dll にある場合 (DirectX とか) この方法が役立ちます。
D3D11 のリソースリークチェッカの実装に用いました。
・vftable Override (sample code)
vftable を直接書き換える方法。vftable Swapping はインスタンス単位で挙動を変えられますが、こちらは class 単位で効果があります。
vftable がある領域は通常 write protect がかかっているため、それを無効化する必要があります。(Windows の VirtualProtect() や POSIX 系 OS の mprotect())
・Import Address Table Override (sample code)
インポートライブラリでリンクされている dll の関数を hook するための方法。
dll から import する関数は実行時までアドレスが決まらないため、exe や dll には import 関数の名前とアドレスを保持する領域 (Import Address Table) があります。この領域にある関数のアドレスを書き換えてやれば任意の関数にリダイレクトさせることができます。
Import Address Table はモジュール (exe,dll) 毎に存在するため、必要なモジュール全てに対して同様の処理をする必要がある点に注意が必要です。
MemoryLeakBuster の実装に用いました。
・Export Address Table Override (sample code)
LoadLibrary() & GetProcAddress() で取得している関数をすり替えるための方法。
dllexport なシンボルがある exe や dll には、export しているシンボルの情報を保持する領域 (Export Address Table) があります。この領域にある関数への相対アドレスを書き換えてやることで、GetProcAddress() の結果を変えることができまs。
WebController の実装に用いました。
・DLL Swapping
dll そのものを入れ替えて関数を入れ替える方法。
Windows は dll の依存を解決する際、カレントディレクトリと環境変数 PATH のディレクトリを見に行きます。このとき同名の dll があればカレントディレクトリのものを優先します。これを利用して、目的の dll と同名の dll を用意して関数を乗っ取るというものです。
例えば opengl32.dll の関数を hook したい場合、元の dll と同じシグネチャの関数を export した opengl32.dll を独自に作成し、exe と同じディレクトリに置くことで、独自 opengl32.dll の方の関数を呼ばせることができます。
・return 1 Override (sample code)
x86 では return で 32 bit 整数 literal を返す処理は 5 byte の命令になります。(例: return 1 -> mov eax, 1 -> B8 01 00 00 00) 5 byte なので任意の関数への call に書き換えることができます。call 先関数が同じ結果を return すれば元の挙動を壊すこともありません。
mov eax, (整数リテラル) を書き換えるというテクニックなので 1 以外にも適用できますが、0 は適応できません。(0 の場合 xor eax, eax になってしまうため)
また、32 bit 未満の整数だと命令も縮んでしまうため適用できません。(組み込み型の bool は 1 byte のため残念ながら適用できません)
WinAPI にはよく BOOL を返す関数がありますが、BOOL は 32 bit 整数であるため、return TRUE してる箇所を hook したい時にこのテクニックが使えるそうです。
・Hotpatch (sample code)
hotpatch という仕組みがあります。これが有効な関数は、先頭に 2 byte の無意味な命令 (mov edi, edi)、関数の前に 5 byte の padding があります。先頭 2 byte を padding 領域への short jmp に書き換え、padding 領域を任意の関数への jmp に書き換えることで、任意の関数へとリダイレクトさせることができます。patch 後も本来のアドレスの 2 byte 先を call すれば元の関数を呼ぶことができます。
VisualC++ ならコンパイルオプション /hotpatch で、gcc や clang だと関数に __attribute__( (ms_hook_prologue) ) をつけることで hotpatch 対応にできます。また、WinAPI は hook したくなるようなものは大抵対応してたりします。
x64 の場合は注意が必要で、x84 と違って先頭 2 byte は無意味な命令にはなりません。つまり、元のアドレスの 2 byte 先を call で patch 前の関数を呼べるようにはなっていません。(関数の先頭が 2 byte 以上の命令であることしか保証されていないそうです) このため、x64 で元の関数を呼べるようにしつつ hook したい場合、後述の MHook style hook を使う必要があります。
hotpatch は w_o さんに存在を教えて頂き、id:NyaRuRu さんに x64 での仕様などの詳細教えて頂きました。感謝。
・MHook style hook (sample code : 説明用。命令の解釈いい加減なので MHook 使った方がいいです)
MHook がやっている方式の hook です。
関数の先頭 5 byte を hook 先への jmp に書き換え、元の 5 byte を含む命令はどこか別の場所に移して後ろに元の場所への jmp を加えておく (=これを call すれば patch 前の関数が実行される)、というものです。
関数先頭の書き換えだけなら簡単で、元の関数を呼ぶ必要がないならそれで事足りるのですが、元の 5 byte を含む命令を移すのはかなりの重労働になります。x86/64 は命令が可変長なので、命令を正しく解釈して必要なサイズを求めないといけません。その命令に相対アドレスが含まれる場合はつなぎ変え処理までやる必要があります。
実装は大変なものの、極めて汎用性が高い方法です。逆に言うと、virtual 関数でも dllimport/export 関数でもなく hotpatch にも対応していない場合はこれ以外選択肢がありません。
DynamicPatcher はこの方法を用いていますが、x86 の命令の解釈は外部ライブラリ tDisasm を用いました。(MHook がそれを使っていたのを真似た)
vftable 書き換え系以外は、x64 の場合に相対アドレスが 32bit に収まらないケースへの対処が必要になります。
具体的には 32bit 範囲内のどこかにトランポリンコードを用意して jmp 先をそこにします。任意のアドレスへのメモリ割り当ては VirtualAlloc() で行い、トランポリンコードは FF 25 00 00 00 00 [hook 先へのポインタ 8byte] の 14 byte でいけます (これも MHook から学びました)。
WebController
ブラウザからゲームを操作するツールをしばらく前に作りました。主にモバイル機器から Wii U コントローラ的にゲームを操作するのを目的としたものです。
上の動画は Nexus7 から REVOLVER360 RE:ACTOR (C83) を遠隔操作しているところ。見ての通り既存のゲームをソースレベルの変更なしで操作できています。
やってることは、対象ゲームのプロセスに dll を注入して HTTP サーバーを立て、入力 API を乗っ取ってブラウザから送られて来た入力データで結果を差し替える、というものです。
HTTP サーバーはいつも通り Poco のおかげでとても楽に実装できました。
dll の注入については 過去 に書いた CreateRemoteThread() で LoadLibraryA() 呼ばせる方法そのままです。プロセス起動直後に注入する必要があるため、suspend モードでプロセスを起動して dll を注入してから実行を継続させるランチャーを用意する必要がありました。
入力 API を乗っ取る処理が今回面白かったところです。
今回はコントローラだけを扱い、入力 API は XInput、DirectInput、winmm (joyGetPosEx()) の 3 つを想定しています。これらは dll であり、インポートライブラリでリンクされていれば、実行時にメモリにマップされた exe の import address table を書き換えることで簡単に関数を hook できます。(MemoryLeakBuster の時に触れた方法)
しかし、LoadLibrary() & GetProcAddress() で実行時に関数を取得している場合はこれが通用しません。そして入力 API では互換性のためにこの手順が取られることがしばしばあります。(XInput -> DirectInput -> winmm の順で使えるものを試すというもの)
対策は 2 通り考えられます。1 つは入力 API の dll の export address table を書き換える方法。もう 1 つは GetProcAddress() 自体を書き換える方法です。大抵はどっちでも上手くいくと思われますが、GetProcAddress() は使わないケースがありうるため、今回はより抜本的な前者を使いました。
dll や exe には export している関数のリスト (export address table) もあり、これを書き換えれば GetProcAddress() の結果も変わります。このため、LoadLibary を hook して、ロードしようとしてるのが XInput や DirectInput の dll であればロード直後に export address table を書き換える、という手順で乗っ取ることができます。
template<class T> inline void ForceWrite(T &dst, const T &src) { DWORD old_flag; ::VirtualProtect(&dst, sizeof(T), PAGE_EXECUTE_READWRITE, &old_flag); dst = src; ::VirtualProtect(&dst, sizeof(T), old_flag, &old_flag); } // dll が export している関数 (への RVA) を書き換える。これにより GetProcAddress() が返す関数をすり替える。 // すり替え前の関数へのポインタを返す。 inline void* OverrideDLLExport(HMODULE module, const char *funcname, void *replacement) { if(!module) { return nullptr; } size_t ImageBase = (size_t)module; PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)ImageBase; if(pDosHeader->e_magic!=IMAGE_DOS_SIGNATURE) { return nullptr; } PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)(ImageBase + pDosHeader->e_lfanew); DWORD RVAExports = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; if(RVAExports==0) { return nullptr; } IMAGE_EXPORT_DIRECTORY *pExportDirectory = (IMAGE_EXPORT_DIRECTORY *)(ImageBase + RVAExports); DWORD *RVANames = (DWORD*)(ImageBase+pExportDirectory->AddressOfNames); WORD *RVANameOrdinals = (WORD*)(ImageBase+pExportDirectory->AddressOfNameOrdinals); DWORD *RVAFunctions = (DWORD*)(ImageBase+pExportDirectory->AddressOfFunctions); for(DWORD i=0; i<pExportDirectory->NumberOfFunctions; ++i) { char *pName = (char*)(ImageBase+RVANames[i]); if(strcmp(pName, funcname)==0) { void *before = (void*)(ImageBase+RVAFunctions[RVANameOrdinals[i]]); ForceWrite<DWORD>(RVAFunctions[RVANameOrdinals[i]], (DWORD)replacement - ImageBase); // x64 だとトランポリン挟む必要が出てくるかもしれないので注意 return before; } } return nullptr; } void example() { OverrideDLLExport(::GetModuleHandle("xinput1_3.dll"), "XInputGetState", &fake_XInputGetState)); }
余談ですが、C# (というか CLR) の場合、dll は LoadLibrary()&GetProcAddress() 相当の方法でロード&リンクされるようです。このときロードには LoadLibraryExW() が使われるので注意が必要です。当初非 Ex の A/W を見てて捕捉できず悩みました…。また、LoadLibrary A,W,ExA は全て内部的に ExW を呼ぶようになっているようです。
Wii U コントローラのように画面をストリーミング動画で転送したりもしたかったんですが、当分検証する余裕がなさそうで今回は未実装です。
画面のキャプチャ自体はそこまで難しくないことがわかっていて、画面更新の API (OpenGL の wglSwapBuffers() とか) を hook して更新直後のフレームバッファを取得し、動画用 API (Video for Windows や DirectShow) に流しこめば実現できます。(.kkapture がこれをやっていて、ソースも公開していて参考になります)
それをストリーミングしてブラウザでゲームプレイに耐える遅延で再生できるのかについては未知数です。
PC 同士であれば、描画 API を乗っ取ってクライアント側にリダイレクトさせるようにし、描画処理を全部クライアント側でやらせる、みたいなアプローチも考えられます。このへんは応用範囲が広そうで妄想だけは広がります。
generic function call
サイト本体では既に告知していますが、コミケ受かりました。スペースは 3日目東ホ-44b primitive です。
予定より大きく遅れていますが、前回よりは色々進歩したものを出せる予定です。
最近の状況。
それっぽく動く水車、raymarching による背景、以前 書いたブラウザから RTS モード (兼レベルエディタ) がそれなりに形になってきたところ。
第三者がブラウザ経由でザコ敵を操作することで、サーバープレイヤーにはザコ敵が普段見せない動きで急襲してきたり、たまに助けてくれたりするように見える、ようになる予定です。(マウスドラッグでキャラ移動はさすがにレベルエディタ専用)
今作では汎用関数呼び出し機能が用意されており、地味ながら根幹を支える重要な役割を果たしています。なんとなくこれについて書いてみます。
例えば、実装を知らないオブジェクトのメンバ関数を呼びたいケースがしばしばあります。(interface 以下の実装見せたくないけど、interface に関数増やしたくないとか) また、関数呼び出しのコンテキストを保存して後で呼びたいケース、関数を ID で呼びたいケースもしばしばあります。(プレイバックしたい場合、ネットワーク経由で関数呼びたい場合とか) こういう状況のための機能です。
仕組みとしてはとても単純で、関数に ID をつけ、その ID と void* の引数&戻り値で対応する関数を呼ぶようにするだけです。
例えば以下のような class を対応させるには
class Hoge { public: void setPosition(const vec3 &pos); const vec3& getPosition() const; void doSomething(int arg1, int arg2); };
このようにします。
enum FunctionID { FID_setPosition, FID_getPosition, FID_doSomething, }; class ICallable { public: virtual ~ICallable() {} virtual bool call(FunctionID fid, const void *args, void *ret=nullptr) { return false; } }; class Hoge : public ICallable { public: void setPosition(const vec3 &pos); const vec3& getPosition() const; void doSomething(int arg1, int arg2); bool call(FunctionID fid, const void *args, void *ret=nullptr) override { switch(fid) { case FID_setPosition: setPosition(*(vec3*)args); return true; case FID_getPosition: if(ret){*(vec3*)ret=getPosition();} return true; case FID_doSomething: doSomething(std::get<0>(*(std::tuple<int,int>*)args), std::get<1>(*(std::tuple<int,int>*)args)); return true; } return false; } }; void usage(ICallable *hoge) { vec3 pos; std::tuple<int,int> args(0,0); hoge->call(FID_getPosition, nullptr, &pos); hoge->call(FID_setPosition, &pos); hoge->call(FID_doSomething, &args); }
FID_setPosition とかの enum のリストは string symbol とかを使えば省略できますが、個人開発では大した手間でもないので最適化の一環でこのままにしています。
機能的には上の実装で十分で、あとは必要な class の必要な関数に同様の処理を書くだけです。
しかし、このままだと switch(fid) {} の中を書くのがめんどくさそうな感じです。いかにもミスしやすそうな上、ポインタの cast はミスったら分かりにくいバグを生む可能性があります。
こういうのはがんばって template をこね回せば型安全にしつつ記述を一般化できるので、安全のためにもそうしてみます。
手順としては、関数の型 (普通の関数,メンバ関数,constメンバ関数 * 引数の数) それぞれについて template で実装しつつ、入り口となる関数は overload しまくってユーザー視点では 1 種類になるように保つ、という感じです。
// 長くなるのでメンバ関数引数 1 個版のみ例示 // template 関数は特殊化できないため、内部実装用 struct を用意 template<class R, class C, class A0> struct Call_MemFn1 { typedef R (C::*F)(A0); void call(F f, C &o, void *r, const void *a) { // ValueHolder<R> は R から参照と const を取っ払って値で保持する代物 // ValueList は std::tuple の類似品だが、参照と const を取っ払って値で保持する。引数の受け渡しはこれで行う想定。 typedef ValueHolder<R> RT; typedef ValueList<A0> Args; Args &args = *(Args*)a; if(r) { *(RT*)r=(o.*f)(args.a0); } else { (o.*f)(args.a0); } } }; // 戻り値が void の場合は特殊化しないといけない template<class C, class A0> struct Call_MemFn1<void, C, A0> { typedef void (C::*F)(A0); void call(F f, C &o, void *r, const void *a) { typedef ValueList<A0> Args; Args &args = *(Args*)a; (o.*f)(args.a0); } }; template<class R, class C, class A0> inline void BinaryCall(R (C::*f)(A0), C &o, void *r, const void *a) { Call_MemFn1<R,C,A0>().call(f,o,r,a); } // こういうのを // (非メンバ関数、メンバ関数、const メンバ関数 の 3 バリエーション) * (引数の数) // 分用意する。 // さすがに人力ではしんどいのでスクリプトなりマクロなりで自動生成させる。
(完全な実装例。こちらは参照をポインタで扱うバリエーションなんかも用意しています。)
ものすごく泥臭い実装で、なんかもっと上手いやり方がありそうな気もしますが、とりあえずこれを使うと前述の switch の中がスマートに書けるようになります。
class Hoge : public ICallable { public: // ... virtual bool call(FunctionID fid, const void *args, void *ret=nullptr) { switch(fid) { case FID_setPosition: BinaryCall(&Hoge::setPosition, *this, ret, args); return true; case FID_getPosition: BinaryCall(&Hoge::getPosition, *this, ret, args); return true; case FID_doSomething: BinaryCall(&Hoge::doSomething, *this, ret, args); return true; } return false; } };
これはマクロで包むことで大きく簡略化できます。
#define CallBlock(...)\ virtual bool call(FunctionID fid, const void *args, void *ret=nullptr)\ {\ typedef std::remove_reference<decltype(*this)>::type this_t;\ switch(fid) {\ __VA_ARGS__\ }\ return false;\ } #define CallMethod(name) case FID_##name: BinaryCall(&this_t::name, *this, ret, args); return true; #define Call(obj,func,...) obj->call(FID_##func, __VA_ARGS__) class Hoge : public ICallable { public: // ... CallBlock( CallMethod(setPosition) CallMethod(getPosition) CallMethod(doSomething) ) }; void usage(ICallable *hoge) { vec3 pos; ValueList<int,int> args(0,0); Call(hoge, getPosition, nullptr, &pos); Call(hoge, setPosition, &pos); Call(hoge, doSomething, &args); }
CallBlock の実装に若干注意が必要です。メンバ関数ポインタは &class_name::func_name と書く必要があり、CallBlock マクロは this の型を知る必要があります。
幸い C++11 では std::remove_reference
また、親 class や別のオブジェクトの call() へリダイレクトさせたいケースもあるので、実際には CallBlock() はもう少し複雑になります。
ともあれ、これだけ楽に書ければ実運用に耐えられそうです。
virtual 関数呼び出しよりもコストは高くつきますが、よほどの回数回さない限り問題にはならないと思われます。レベルエディタとの通信、オブジェクト同士の通信、リプレイなどの実装に役立っています。
DynamicPatcher - Runtime C++ Editing
[2013/12/25 追記] この DynamicPatcher をさらに改良し、Visual Studio のアドインとして実装した Alcantarea をリリースしました
DynamicObjLoader を改良し、実用に耐えそうな実行時 C++ コード編集機能を実装しました。
C++ コードを編集してそれをリアルタイムに反映させることができます。
DynamicPatcher
https://github.com/i-saint/DynamicPatcher (bin)
DynamicObjLoader の時点で一応は同等機能を実現していたのですが、更新したい関数を事前にマクロで包む必要があったり、virtual 関数を持つ class はシリアライズが必要だったりと、運用上色々面倒な点がありました。
今回はそのへんが大きく改良されています。
・前準備なしに既存のほぼ全ての関数を差し替えられるようになりました。CRT や WinAPI の関数でも差し替え可能です。
・virtual 関数を持つ class でもシリアライズなしで実行を継続可能になりました。(データ構造が変わる変更だとさすがに無理で、シリアライズするとか事前に余白を設けておくとかの対処が必要です)
・.obj .lib に加え、.dll もロードできるようになりました。.obj だとデバッガでソースを追えませんが、.dll だと可能なため、お手軽に編集したい場合 .obj、ちゃんとデバッグしたい場合 .dll 化、という使い分けができます。(.obj のままデバッグできれば理想的で、実現できないか調査中ですが望み薄です…)
編集した C++ コードをその場でコンパイルし、できた .obj (or .lib .dll) を自力でロード&リンクして中の関数を使えるようにし、既存の関数を差し替える、という大まかな仕組みは以前と同じです。
今回大きく変わったのは、関数を差し替える処理です。以前は差し替え可能にしたい関数を関数ポインタで間接参照させていましたが、今回は関数の中身を直接書き換えるようにしています。
具体的には、関数の先頭 5 byte を新しい関数への jmp に書き換えます。元の 5 byte 分を含むコードは別の領域に移し、末尾に元の場所への jmp を加えておきます。(==これを call すれば更新前の関数が実行される)
この方法だと関数のアドレスは変わらないので関数ポインタによる間接参照は必要なくなり、virtual 関数がある class でも vftable の更新が必要なくなります。
また、この DynamicPatcher を dll injection で既存のプロセスに強引に仕込むツールも用意しています。
ソースの更新を監視するディレクトリ、ビルドコマンド、ロードするファイルなどは設定ファイルで外部から編集できるため、全く前準備していない既存のプロセスを実行時編集することも可能です。
(ただこの場合、最初にロードするモジュールで dpUpdate() を定期的に呼ばせる処理をどこかにねじ込む必要があります。そうしないと更新を反映できません)
単純な使用例:
#include <windows.h> // Sleep() #include <cstdio> #include "DynamicPatcher.h" // dpPatch をつけておくとロード時に同名の関数を自動的に更新する。 // (dpPatch は単なる dllexport。.obj に情報が残るいい指定方法が他に見当たらなかったので仕方なく…。 // この自動ロードは dpInitialize() のオプションで切ることも可能) dpPatch void MaybeOverridden() { // ここを書き換えるとリアルタイムに挙動が変わる puts("MaybeOverridden()\n"); } // CRT の関数を差し替える例。今回の犠牲者は puts() int puts_hook(const char *s) { typedef int (*puts_t)(const char *s); puts_t orig_puts = (puts_t)dpGetUnpatched(&puts); // hook 前の関数を取得 orig_puts("puts_hook()"); return orig_puts(s); } // dpOnLoad() の中身はロード時に自動的に実行される。アンロード時に実行される dpOnUnload() も記述可能 dpOnLoad( // dpPatch による自動差し替えを使わない場合、on load 時とかに手動で差し替える必要がある。 // 元関数と新しい関数の名前が違うケースでは手動指定するしかない。 dpPatchAddressToAddress(&puts, &puts_hook); // puts() を puts_hook() に差し替える ) int main() { dpInitialize(); dpAddSourcePath("."); // このディレクトリのファイルに変更があったらビルドコマンドを呼ぶ dpAddModulePath("example.obj"); // ビルドが終わったらこのファイルをロードする // cl.exe でコンパイル。msbuild や任意のコマンドも指定可能。 // 実運用の際は msbuild を使うか、自動ビルドは使用せすユーザー制御になると思われる。 // (dpStartAutoBuild() を呼ばなかった場合、dpUpdate() はロード済み or module path にあるモジュールに更新があればそれをリロードする) dpAddCLBuildCommand("example.cpp /c /Zi"); dpStartAutoBuild(); // 自動ビルド開始 for(;;) { MaybeOverridden(); ::Sleep(2000); dpUpdate(); } dpFinalize(); } // cl /Zi example.cpp && ./example
課題。
.obj と .lib の場合、DynamicObjLoader で言及した制限がついてきます。キツいのは RTTI が有効だと vftable の構造が変わるのと、デバッガでソースを追えないのと、リンク時コード生成を有効にしてコンパイルされたものはロードできないあたりでしょうか。
また、リンクの際デバッグ API でシンボルの情報を引くのに初回はえらい時間がかかるようです。規模が大きいプログラムだと数十秒止まったりします。(2 回目以降は速い)
さしあたって .dll ならこれらの制限は回避できます。
また、x64 も対応してるつもりですが、デバッグが不十分なのでおかしなことが起きるかもしれません。追々改良していきます。
あとは x86(64) であれば Linux 系 OS でも同じ事ができるはずなのでいずれ対応したいところです。(Linux 対応までできれば Runtime-Compiled C++ に完勝と言っても過言ではないでしょう!)
実装に際し、Mhook が関数 hook のいい参考になりました。
PE/COFF のファイルフォーマット資料 はロード&リンク処理の細かい仕様を詰めるのにとても役立ちました。