東方の当たり判定を描画してみるよ!
完全にゲームハックなので多少はぐらかして書きます。そうするくらいなら公開するなという話ですが自己顕示欲を抑えきれなかった。
- まずやること
- 敵、自機の座標が格納されているメモリを探す
- 当たり判定処理を読んでどんな風になっているのかを把握する
本当はAIを作りたかった。しかし飽きた。
AIのデバッグ用に当たり判定の描画を作ってた。
- どう実現するか
- DLLインジェクションを使う。パラサイトルーチン書くのがとても楽。
どういう風にDLLインジェクションするかだが、出来るだけ元のプロセスのメモリを弄りたくない。
そこで毎フレーム呼び出される関数を見つけて、そこをDLL内の関数で上書きすることにした。
こうするとAttach時に関数を書き換え、Detach時に戻すことで復帰も容易。
毎フレーム呼び出される関数だが、これはキー情報を取得する関数(以下getKeyState())を使った。
インジェクションするDLLのコード。
EXPORT void frame_wrapper(){ char buf[256]; //スタックの処理 __asm{ push DWORD PTR SS:[ebp+8]; call getKeyState; add esp, 4; pushad; pushfd; } //ここにやりたい処理を書く。AIの関数とかが呼ばれてた。 __asm{ popfd; popad; } } void attach(){ //このDLLからExportしている上書き用関数 int p_frame_wrapper = (int)GetProcAddress(h_hook, "frame_wrapper"); //getKeyStateの関数ポインタ p_getKeyState; getKeyState = *(int *)p_getKeyState; *(int *)(p_getKeyState) = p_frame_wrapper; } void detach(){ *(int *)(p_getKeyState) = getKeyState; } BOOL WINAPI DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID lpReserved){ switch (reasonForCall){ case DLL_PROCESS_ATTACH: OutputDebugString("[*]Dll Attach"); attach(); break; case DLL_PROCESS_DETACH: OutputDebugString("[*]Dll Detach"); detach(); break; } return TRUE; }
インジェクションするためのexeのコード
#include <Windows.h> #include <iostream> #include <string> #include <tlhelp32.h> using namespace std; int main(){ char buf[256]; DWORD pid = 0; char libpath[] = "hook.dll"; PROCESSENTRY32 pe; HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); Process32First(hSnap, &pe); do{ if(!strcmp("東方紅魔郷.exe", pe.szExeFile)){ pid = pe.th32ProcessID; break; } }while(Process32Next(hSnap, &pe)); if(!pid){ cout << "紅魔郷を起動してください" << endl; return 0; } sprintf(buf, "[*]pid: %d", pid); OutputDebugString(buf); HANDLE proc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); HMODULE hm = GetModuleHandle("kernel32"); LPTHREAD_START_ROUTINE p_LoadLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(hm, "LoadLibraryA"); printf("p_LoadLibrary:%X\n", p_LoadLibrary); LPTHREAD_START_ROUTINE p_FreeLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(hm, "FreeLibrary"); printf("p_FreeLibrary:%X\n", p_FreeLibrary); int pathSize = strlen(libpath) + 1; LPVOID remoteLibPath = VirtualAllocEx(proc, NULL, pathSize, MEM_COMMIT, PAGE_READWRITE); WriteProcessMemory(proc, remoteLibPath, libpath, pathSize, NULL); char option[256]; HANDLE hThread; while(1){ scanf("%s", option); if(!strcmp(option, "load")){ cout << "[*] Loading DLL" << endl; hThread = CreateRemoteThread(proc, NULL, 0, p_LoadLibrary, remoteLibPath, 0, NULL); }else if(!strcmp(option, "unload")){ cout << "[*] Unloading DLL" << endl; DWORD exCode; GetExitCodeThread(hThread, &exCode); CreateRemoteThread(proc, NULL, 0, p_FreeLibrary, (LPDWORD)exCode, 0, NULL); } } return 0; }
exeの方のコードは普通のインジェクションのためのコード。Detachは苦労した。
あとはframe_wrapper()に好きな処理を書いていけば良い。
- どうやって描画するか
- 東方が元から使っているDirectXを利用させてもらう。
DirectXについては
動的リンクで呼び出している関数へのフック、DirectXへのフック その2
または
MSDNなどで調べてください。
DirectXで描画関数を使うにはIDirect3DDeviceインタフェースであるBeginSceneやらを使う。
これはIDirect3DDeviceの仮想関数テーブルからアクセス出来る。
IDirect3DDeviceはグローバル変数に入っていたのでそこから拝借。
lpVtbl = (int *)**(pplpVtbl); BeginScene = (HRESULT(__stdcall *)(int))*(lpVtbl+34); EndScene = (HRESULT(__stdcall *)(int))*(lpVtbl+35); Clear = (HRESULT(__stdcall *)(int, DWORD, int *, DWORD, D3DCOLOR, float, DWORD))*(lpVtbl+36); Present = (HRESULT(__stdcall *)(int, DWORD, DWORD, DWORD, DWORD))*(lpVtbl+15); DrawPrimitiveUP = (HRESULT(__stdcall *)(int, DWORD, DWORD, vertex*, DWORD))*(lpVtbl+72); SetVertexShader = (HRESULT(__stdcall *)(int, int))*(lpVtbl+76); GetVertexShader = (HRESULT(__stdcall *)(int, int *))*(lpVtbl+77);
これはattach()内に書いた。
あとはframe_wrapper()にこれらを利用して座標を描画する関数(collision_detection())を書くだけ。
void collision_detection(myship ms, vector<enemy>venemy, vector<etama>vetama){ int pFVF; char buf[256]; int i, j; int xl = 384; int yl = 448; //当たり判定の描画 GetVertexShader(_this, &pFVF); SetVertexShader(_this, FVF_VERTEX); BeginScene(_this); for(i=0; i<venemy.size(); i++){ for(j=0; j<4; j++){ vertexTbl[j].z = 0.0; vertexTbl[j].color = D3DCOLOR_XRGB(255, 0, 0); vertexTbl[j].rhw = 1.0; } int dx = 32; int dy = 16; vertexTbl[0].x = venemy[i].x-venemy[i].dx/2+32; vertexTbl[0].y = venemy[i].y-venemy[i].dy/2+16; vertexTbl[1].x = venemy[i].x-venemy[i].dx/2+32; vertexTbl[1].y = venemy[i].y+venemy[i].dy/2+16; vertexTbl[2].x = venemy[i].x+venemy[i].dx/2+32; vertexTbl[2].y = venemy[i].y+venemy[i].dy/2+16; vertexTbl[3].x = venemy[i].x+venemy[i].dx/2+32; vertexTbl[3].y = venemy[i].y-venemy[i].dy/2+16; if(dx < vertexTbl[0].x && vertexTbl[2].x < dx+xl && dy < vertexTbl[0].y && vertexTbl[2].y < dy+yl) DrawPrimitiveUP(_this, D3DPT_TRIANGLEFAN, 2, vertexTbl, sizeof(vertex)); } for(i=0; i<vetama.size(); i++){ for(j=0; j<4; j++){ vertexTbl[j].z = 0.0; vertexTbl[j].color = D3DCOLOR_XRGB(0, 0, 255); vertexTbl[j].rhw = 1.0; } int dx = 32; int dy = 16; vertexTbl[0].x = vetama[i].x-vetama[i].dx/2+32; vertexTbl[0].y = vetama[i].y-vetama[i].dy/2+16; vertexTbl[1].x = vetama[i].x-vetama[i].dx/2+32; vertexTbl[1].y = vetama[i].y+vetama[i].dy/2+16; vertexTbl[2].x = vetama[i].x+vetama[i].dx/2+32; vertexTbl[2].y = vetama[i].y+vetama[i].dy/2+16; vertexTbl[3].x = vetama[i].x+vetama[i].dx/2+32; vertexTbl[3].y = vetama[i].y-vetama[i].dy/2+16; if(dx < vertexTbl[0].x && vertexTbl[2].x < dx+xl && dy < vertexTbl[0].y && vertexTbl[2].y < dy+yl) DrawPrimitiveUP(_this, D3DPT_TRIANGLEFAN, 2, vertexTbl, sizeof(vertex)); } vertex my_vertex[4]; for(j=0; j<4; j++){ my_vertex[j].z = 0.0; vertexTbl[j].color = D3DCOLOR_XRGB(0, 255, 0); vertexTbl[j].rhw = 1.0; } int dx = 32; int dy = 16; vertexTbl[0].x = ms.x1+dx; vertexTbl[0].y = ms.y1+dy; vertexTbl[1].x = ms.x1+dx; vertexTbl[1].y = ms.y2+dy; vertexTbl[2].x = ms.x2+dx; vertexTbl[2].y = ms.y2+dy; vertexTbl[3].x = ms.x2+dx; vertexTbl[3].y = ms.y1+dy; DrawPrimitiveUP(_this, D3DPT_TRIANGLEFAN, 2, vertexTbl, sizeof(vertex)); EndScene(_this); SetVertexShader(_this, pFVF); }
frame_wrapper()で敵の座標やらをvectorに入れてその後にcollision_detection()を呼び出している。
もうちょっとスタイリッシュに出来る感じが溢れてる。
おわり