-
Notifications
You must be signed in to change notification settings - Fork 0
Plugin Development Guide JA
- MSVC v143 (Visual Studio 2022)
-
C++20 (
/std:c++20) - x64 Release ビルド
-
ランタイムライブラリ:
/MD(マルチスレッドDLL) — ホストと一致する必要があります
- Visual Studioで新しい C++ DLL プロジェクトを作成します
- インクルードパスをPOEFixerのソースディレクトリに設定します(SDKおよびImGuiヘッダー用)
- ImGuiソースファイルをプロジェクトに追加します:
imgui.cpp,imgui_draw.cpp,imgui_tables.cpp,imgui_widgets.cpp - プラグインソースにPlugin SDKヘッダーをインクルードします:
またはExamplePluginの便利なヘッダーを使用します:
#include "plugin_sdk/PluginAPI.h" #include "plugin_sdk/PluginContext.h" #include "imgui/imgui.h"
#include "sdk/PluginHelpers.h" // Includes all SDK headers + MemoryReader + utilities
- プロジェクトのプリプロセッサ定義に
PLUGIN_EXPORTSと_CRT_SECURE_NO_WARNINGSを定義します
Plugins/
YourPlugin/
YourPlugin.dll <-- DLL名はフォルダ名と一致する必要があります
config/
settings.txt <-- オプションの設定ファイル
data/
... <-- オプションのデータディレクトリ(データベース、キャッシュなど)
ExamplePluginは推奨されるプロジェクトレイアウトを実演します:
Plugins/ExamplePlugin/
ExamplePlugin.cpp <-- メインプラグインエントリポイント + ファクトリエクスポート
sdk/
PluginHelpers.h <-- MemoryReader、WideToNarrow、エンティティ/レアリティヘルパー
examples/
ExampleBuffs.h <-- フィルタリングとプログレスバー付きバフリスト
ExampleEntities.h <-- ウォッチ機構、コンポーネントツリー、JSONダンプ付きエンティティデバッグリスト
ExampleInventory.h <-- ServerData、インベントリセレクタ、スロットグリッド、レアリティ付きアイテムmod
ExampleMemory.h <-- Hexビューア、Read<T>デモ、パターンスキャナ
ExampleUiExplorer.h <-- 検索、ナビゲーション、ハイライト付きフルUIエレメントエクスプローラ
SQLite3、アイコンアトラス、オーバーレイレンダリングを含むより完全なプラグインの例:
Plugins/KillCount/
KillCount.cpp <-- メインプラグインエントリポイント、IPluginライフサイクル、設定UI
KillCount.h <-- プラグインクラス宣言
KillTracker.cpp/h <-- キル/チェスト/デスカウントエンジン
OverlayRenderer.cpp/h <-- ドラッグで位置変更パターン付きImGuiオーバーレイ
IconAtlas.cpp/h <-- スプライトシートテクスチャ読み込み(D3D11 + stb_image)
Database.cpp/h <-- 永続統計用SQLite3ラッパー
DisplaySettings.h <-- 設定構造体
sdk/
PluginHelpers.h <-- ExamplePluginからコピー
lib/
sqlite3.c/h <-- SQLite3アマルガメーション(Cとしてコンパイル)
sqlite3-vcpkg-config.h <-- 静的リンク用ローカルオーバーライド
DLLファイル名はフォルダ名と正確に一致する必要があります:
- フォルダ:
Plugins/MyPlugin/→ DLL:MyPlugin.dll - ホストは
Plugins/内の各サブフォルダをスキャンし、<FolderName>.dllを探します
Load DLL (LoadLibrary)
→ CreatePlugin() -- ファクトリ: IPluginのインスタンス化
→ SetContext(ctx) -- ホストサービスの受信
→ SetPluginDirectory(dir) -- フォルダパスの受信
→ GetSDKVersion() -- 互換性チェック
→ GetName() -- UI用表示名
→ [if enabled] OnEnable() -- リソースの初期化
↓
Main Loop (every frame):
→ DrawUI() -- オーバーレイのレンダリング(有効時のみ)
→ DrawSettings() -- Pluginsタブで設定をレンダリング
→ WantsOverlay() -- ホストがプラグインのオーバーレイモード要求を確認
↓
Periodically / on shutdown:
→ SaveSettings() -- 設定の永続化
↓
→ OnDisable() -- リソースのクリーンアップ
→ DestroyPlugin(plugin) -- ファクトリ: IPluginの削除
→ FreeLibrary -- DLLのアンロード
- すべての
Draw*メソッドはメイン/レンダースレッドで呼び出されます -
GetSnapshot()およびその他のPluginContext関数はスレッドセーフです - ImGuiを呼び出すスレッドを生成しないでください — ImGuiはスレッドセーフではありません
すべてのプラグインは IPlugin インターフェース(plugin_sdk/PluginAPI.h で定義)を実装する必要があります:
- 呼び出しタイミング: 作成直後に1回
-
パラメータ:
"Plugins/YourPlugin"のような相対パス - 目的: 設定/リソース読み込み用にこのパスを保存します
-
呼び出しタイミング:
SetPluginDirectoryの後に1回 -
パラメータ: ホストの
PluginContextへのポインタ(プラグインの生存期間中有効) - 目的: このポインタを保存します — すべてのゲームデータへのゲートウェイです
-
重要: ここで
ImGui::SetCurrentContext(ctx->ImGuiContext)を呼び出してください
- 呼び出しタイミング: ユーザーがプラグインを有効にした時、または以前有効だった場合は起動時
-
パラメータ: ゲームプロセスが現在アタッチされている場合は
true - 目的: 設定の読み込み、リソースの割り当て、状態の初期化
- 呼び出しタイミング: ユーザーがプラグインを無効にした時
- 目的: リソースの解放、バックグラウンド作業の停止
- 呼び出しタイミング: 毎フレーム、プラグインが有効な場合のみ
- 目的: ImGuiを使用してオーバーレイをレンダリング
-
注意: 競合を避けるため
"MyWindow##MyPlugin"のようなユニークなウィンドウIDを使用してください
- 呼び出しタイミング: 毎フレーム、Plugins設定タブ内(有効時のみ)
- 目的: ImGuiを使用してプラグイン設定をレンダリング
- 呼び出しタイミング: 定期的およびアプリケーション終了時
-
目的: ディスクに設定を保存(例:
Plugins/YourPlugin/config/settings.txt)
-
戻り値: Pluginsタブに表示される表示名(例:
"My Plugin")
-
戻り値:
PLUGIN_SDK_VERSION(現在4) - 目的: ホストが互換性を確認 — 一致する必要があります
-
戻り値: プラグインがオーバーレイモード(ゲーム上の透明オーバーレイ)でレンダリングしたい場合は
true -
デフォルト:
false— プラグインは通常の設定ウィンドウでのみレンダリング -
目的: いずれかのプラグインが
trueを返すと、組み込み機能が必要としなくてもホストはオーバーレイモードに入ります
DLLはこれら2つのC関数をエクスポートする必要があります:
extern "C" PLUGIN_API IPlugin* CreatePlugin() {
return new MyPlugin();
}
extern "C" PLUGIN_API void DestroyPlugin(IPlugin* plugin) {
delete plugin;
}PluginContext 構造体(plugin_sdk/PluginContext.h で定義)はゲームデータにアクセスするための関数ポインタを提供します。すべての型は PluginSDK 名前空間にあります。
ゲーム状態の完全なスナップショットを返します。フレームごとに1回更新されます。内容:
| フィールド | 型 | 説明 |
|---|---|---|
CurrentState |
GameStateTypes |
現在のゲーム状態 |
CurrentAreaName |
string |
エリア名(例: "The Riverways") |
CurrentAreaHash |
string |
ユニークなエリアインスタンスハッシュ |
CurrentAreaLevel |
uint8_t |
現在のエリアのモンスターレベル |
IsTown |
bool |
街にいる場合はtrue |
IsHideout |
bool |
ハイドアウトにいる場合はtrue |
IsPaused |
bool |
ゲームが一時停止している場合はtrue |
IsSkillTreeVisible |
bool |
スキルツリーパネルが開いている場合はtrue |
WorldToGridConvertor |
float |
ワールド→グリッド変換係数 |
Player |
RadarEntity |
ローカルプレイヤーエンティティデータ |
Entities |
vector<RadarEntity> |
近くのすべてのエンティティ |
LargeMap / MiniMap
|
MapData |
マップオーバーレイデータ |
Vitals |
PlayerVitals |
プレイヤーのHP/ES/MP + バフ |
ScreenWidth / ScreenHeight
|
int |
ゲームウィンドウの寸法 |
ProcessId |
DWORD |
ゲームプロセスID |
GameWindow |
HWND |
ゲームウィンドウハンドル |
GameWindowForeground |
bool |
ゲームがフォアグラウンドの場合はtrue |
IsAttached |
bool |
ゲームプロセスにアタッチされている場合はtrue |
IsWindowValid |
bool |
ゲームウィンドウが有効な場合はtrue |
LastUpdateTime |
uint64_t |
最終データ更新のタイムスタンプ |
AreaChangeCounter |
uint64_t |
エリア変更時にインクリメント |
Inventories |
vector<InventoryInfo> |
プレイヤーインベントリの内容 |
CurrencyTotals |
map<string,int> |
パス別カレンシー数 |
InventoryGrid |
InventoryGridInfo |
インベントリUIグリッド情報 |
WorldToScreenMatrix |
XMFLOAT4X4 |
3D→2D投影行列 |
重要: エンティティフィルタリング 死亡エンティティ(
EntityState == Uselessのもの)はプラグインが受け取る前にスナップショットからフィルタリングされます。これは、生存から死亡へのHP遷移を観察できないことを意味します。キルを検出する必要がある場合は、代わりに消失ベースの検出を使用してください — ゾーン別にエンティティIDを追跡し、InnerCircleまたはOuterCircle近接範囲内でエンティティリストから消えた場合にキルとしてカウントします。詳細はセクション8: よくあるレシピを参照してください。
プレイヤーバイタルへの便利なショートカットです。
現在のゲーム状態列挙を返します。
ゲームプロセスがアタッチされ読み取り可能な場合はtrue。
現在ゲーム内にいる場合はtrue(ロード中やログイン画面ではない)。
ゲームウィンドウがフォアグラウンドウィンドウの場合はtrue。
ゲームプロセスIDを返します。
アイテムエンティティのすべてのmodを読み取ります。
戻り値: 0=ノーマル、1=マジック、2=レア、3=ユニーク
カレンシー/スタック可能アイテムのスタック数を返します。
アイテムのベースタイプ名を返します。
アイテムのメタデータパスを返します。
アイテムのベースタイプ名を返します(例: "Divine Orb", "Chaos Orb")。メタデータパスを返す ReadItemName とは異なり、BaseItemTypeData.BaseTypeName から実際のベースタイプ名を読み取ります。
Words.datからユニークアイテム名を返します(例: "Headhunter", "Brimstone Call")。ユニーク以外のアイテムには空文字列を返します。
ホストが現在オーバーレイモード(ゲームウィンドウ上の透明オーバーレイ)にある場合はtrueを返します。レンダリングを調整するために使用してください — 例えば、ゲームオーバーレイへの描画と設定ウィンドウへの描画の切り替え。
ホストの設定メニューが表示されている(オーバーレイがインタラクティブ)場合はtrueを返します。メニューが非表示の場合、オーバーレイウィンドウはクリックスルー(WS_EX_TRANSPARENT)になるため、ImGuiウィンドウはマウス入力を受け取れません。
ドラッグ可能オーバーレイパターンの実装に使用してください:
- メニュー表示時: ドラッグハンドルを表示し、インタラクション(タブ、ボタン)を許可
-
メニュー非表示時: ドラッグハンドルを削除し、
ImGuiWindowFlags_NoInputsを追加してウィンドウを非インタラクティブに
完全な実装はセクション6: ドラッグ可能オーバーレイパターンを参照してください。
ゲームプロセスメモリへの直接アクセス。すべての読み取りは安全です(失敗時は0/空を返します)。
ゲーム実行モジュールのベースアドレスを返します。アタッチされていない場合は0を返します。
ゲームモジュールのバイトサイズを返します。アタッチされていない場合は0を返します。
ゲームプロセスから生のバイトブロックを読み取ります。buffer は少なくとも size バイトが割り当てられている必要があります。成功時はtrueを返します。
// Example: Read a 4-byte integer from game memory
uint32_t value = 0;
m_Context->ReadProcessMemory(address, &value, sizeof(value));
// Example: Read a struct
MyStruct data{};
m_Context->ReadProcessMemory(structAddress, &data, sizeof(data));ゲームメモリからnull終端ASCII文字列を読み取ります(最大128文字)。
ゲームメモリからnull終端Unicode(ワイド)文字列を読み取ります(最大128 wchar)。
名前で解決されたパターンスキャンアドレスを取得します。見つからない場合は0を返します。
標準パターン:
| 名前 | 説明 |
|---|---|
"Game States" |
GameStatesベクトルルート |
"File Root" |
ファイルレジストリ |
"AreaChangeCounter" |
エリア遷移カウンタ |
"Terrain Rotator Helper" |
回転データ |
"Terrain Rotation Selector" |
回転セレクタ |
"GameCullSize" |
スクリーンカル値 |
ワールド空間の位置をスクリーン座標に変換します。位置が画面上で可視の場合はtrueを返します。
float screenX, screenY;
if (m_Context->WorldToScreen(entity.WorldX, entity.WorldY, entity.WorldZ, &screenX, &screenY)) {
ImGui::GetBackgroundDrawList()->AddText(ImVec2(screenX, screenY), IM_COL32_WHITE, "Label");
}ホストにインベントリスキャンを要求します。-1 を渡すとすべてのインベントリをスキャンし、特定のインベントリIDを渡すこともできます。スナップショット内のインベントリデータはスキャン完了後(次のフレーム)に入力されます。
注意: インベントリデータは自動的に更新されません — スキャンをトリガーするにはこの関数を呼び出す必要があります。継続的なインベントリデータが必要な場合は定期的に(例: 2秒ごとに)呼び出してください。
歩行可能グリッドデータへのポインタを返します。グリッドは2D配列で、0 = 歩行不可、非ゼロ = 歩行可能です。データが利用できない場合はnullptrを返します。
グリッド位置の地形の高さを返します。範囲外またはデータ利用不可の場合は0を返します。
これらの関数はゲームメモリからC++標準ライブラリコンテナを直接読み取り、ホストの Core::Process メソッドをミラーリングします。
ゲームメモリから StdVector(24バイト構造体: {First, Last, End})を読み取ります。malloc で確保された要素のバッファを返します。呼び出し元は返されたポインタを free() する必要があります。失敗時は nullptr を返します。
// Example: Read a vector of uint32_t
int count = 0;
void* data = m_Context->ReadStdVector(vectorAddr, sizeof(uint32_t), &count);
if (data && count > 0) {
uint32_t* values = static_cast<uint32_t*>(data);
for (int i = 0; i < count; i++) { /* values[i] */ }
free(data);
}ゲームメモリから StdList(16バイト構造体: {Head, Size})を読み取ります。リンクリストをたどり、連続バッファを返します。呼び出し元は free() する必要があります。
ゲームメモリから StdBucket を読み取ります(埋め込まれた StdVector を読み取ります)。呼び出し元は free() する必要があります。
StdMap(16バイト構造体: {Head, Size})をたどり、各キーと値のペアに対して callback を呼び出します。訪問したノード数を返します。
// Example: Read a map<uint32_t, float>
struct MapResult { std::vector<std::pair<uint32_t, float>> entries; };
MapResult result;
m_Context->ReadStdMap(mapAddr, sizeof(uint32_t), sizeof(float),
[](const void* key, const void* value, void* userData) {
auto* r = static_cast<MapResult*>(userData);
uint32_t k; float v;
memcpy(&k, key, sizeof(k));
memcpy(&v, value, sizeof(v));
r->entries.push_back({k, v});
}, &result);ゲームメモリから StdWString(インライン/ヒープバッファ付き32バイト構造体)を読み取ります。
インベントリIDの人間が読める名前を返します(例: 1 → "MainInventory1", 3 → "Weapon1", 64 → "Currency1")。
SDK v4はホストのデバッグデータへの直接アクセスを提供します — エンティティコンポーネント、インベントリ詳細、UIエレメントツリー — 組み込みのDebugタブに対応しています。
デバッグメタデータ(Id、Address、Path、Type、SubType、State、Rarity、Zone)を含むすべてのエンティティのリストを返します。これはDebug→Entity Listタブに対応します。
エンティティのコンポーネントのウォッチを開始します。ホストのワーカースレッドがこのエンティティの完全なコンポーネントデータを毎フレーム読み取ります。
エンティティのコンポーネントのウォッチを停止します。ユーザーがエンティティツリーノードを折りたたんだ時にリソースを解放するために呼び出してください。
ウォッチされているエンティティの完全なコンポーネントデータを返します。8つの認識されたコンポーネント(Life、Render、Positioned、Targetable、Animated、Stats、Actor、Buffs)すべてのサブ構造体と、すべてのコンポーネントアドレスのリストを含みます。
// Example: Watch entity on expand, read components
auto entities = m_Context->GetEntityDebugList();
for (auto& e : entities) {
if (ImGui::TreeNode(e.Path.c_str())) {
m_Context->WatchEntity(e.Id);
auto data = m_Context->GetWatchedEntityData(e.Id);
if (data.HasLife) {
ImGui::Text("HP: %d / %d ES: %d / %d",
data.Life.Health.Current, data.Life.Health.Total,
data.Life.EnergyShield.Current, data.Life.EnergyShield.Total);
}
ImGui::TreePop();
} else {
m_Context->UnwatchEntity(e.Id);
}
}ServerDataコンポーネントのベースアドレスを返します。
すべてのプレイヤーインベントリIDとそのアドレス(ServerDataから)を返します。
詳細なデバッグ検査のためにインベントリのウォッチを開始します。ホストがスロット占有状況、アイテム詳細、modを読み取ります。
現在ウォッチされているインベントリの完全なデータを返します: グリッド寸法、スロット占有状況、レアリティとmod付きのアイテム。
// Example: Inventory inspector
auto invList = m_Context->GetPlayerInventoryList();
m_Context->WatchInventory(invList[0].first);
auto inv = m_Context->GetWatchedInventoryData();
for (auto& item : inv.Items) {
ImGui::Text("[%s] Rarity=%d Mods=%d", item.Path.c_str(), item.Rarity,
(int)(item.ImplicitMods.size() + item.ExplicitMods.size()));
}ルートゲームUIエレメントアドレスを返します(ゲーム内UIツリーナビゲーション用)。
最上位UIルートアドレスを返します。
現在のGameCullSize値を返します。UIスケール計算に使用されます。画面寸法と組み合わせることで、UIエレメントの位置/サイズを正確に計算できます。
// Example: UI scale calculation (matching host logic)
int cullValue = m_Context->GetGameCullValue();
auto snapshot = m_Context->GetSnapshot();
// Scale for index 1 (width): screenWidth / (cullValue / baseWidth)
// Scale for index 2 (height): screenHeight / (cullValue / baseHeight)sdk/PluginHelpers.h ヘッダー(ExamplePluginに含まれる)は、生のPluginContext関数をラップする型安全な MemoryReader クラスを提供します:
PluginSDK::MemoryReader mem(m_Context);
// Read a single struct
auto data = mem.Read<MyStruct>(address);
// Read an array
auto arr = mem.ReadArray<uint32_t>(address, count);
// Read native containers — returns std::vector<T>
auto vec = mem.ReadStdVector<uint32_t>(vectorAddr);
auto list = mem.ReadStdList<MyNode>(listAddr);
auto bucket = mem.ReadStdBucket<MyEntry>(bucketAddr);
auto map = mem.ReadStdMap<uint32_t, float>(mapAddr);
auto wstr = mem.ReadStdWString(wstringAddr);MemoryReader は ReadString()、ReadUnicodeString()、GetBaseAddress()、GetModuleSize()、GetPatternAddress() の便利なラッパーも提供します。
PluginHelpers.h のその他のユーティリティ:
-
WideToNarrow(wstring)— 安全なwstring→string変換(ASCII損失あり) -
GetEntityTypeName(type)— 列挙から表示名へ(ExpeditionMarker/ExpeditionRemnantを含む) -
GetNearbyZoneName(zone)— ゾーンから表示名へ -
GetRarityName(rarity)/GetRarityColor(rarity)— レアリティ表示ヘルパー
ホストのログシステムに書き込みます。レベル: "Debug", "Info", "Warning", "Error"
ホストのImGuiコンテキスト。SetContext() 内で ImGui::SetCurrentContext() を呼び出してください。
ホストの ID3D11Device*。キャストしてテクスチャ読み込みに使用してください。
すべての型は PluginSDK 名前空間にあります。プラグインは通常 using namespace PluginSDK; を追加します。
snapshot->Entities で利用可能なエンティティごとのデータ:
| フィールド | 型 | 説明 |
|---|---|---|
Id |
uint32_t |
ユニークなエンティティID |
Address |
uintptr_t |
メモリアドレス(アイテムAPI呼び出し用) |
EntityDetailsAddress |
uintptr_t |
エンティティ詳細構造体アドレス |
RenderComponentAddress |
uintptr_t |
Renderコンポーネントアドレス(ショートカット) |
IsValid |
bool |
エンティティ有効性フラグ |
entityType |
EntityTypes |
エンティティカテゴリ |
entitySubtype |
EntitySubtypes |
エンティティサブカテゴリ |
entityState |
EntityStates |
エンティティ状態 |
Rarity |
int |
0=ノーマル、1=マジック、2=レア、3=ユニーク |
Reaction |
uint8_t |
0=敵対、1=中立、2=友好 |
GridPositionX/Y |
float |
地形グリッド上の位置 |
TerrainHeight |
float |
エンティティ位置の地形の高さ |
WorldX/Y/Z |
float |
ワールド空間の位置 |
ModelBoundsZ |
float |
モデルの高さ |
Path |
wstring |
エンティティメタデータパス |
PlayerName |
wstring |
プレイヤー名(プレイヤーエンティティの場合) |
TgtPath |
string |
ターゲットパス(ナロー文字列) |
CurrentHP/MaxHP |
int |
エンティティ体力 |
CurrentES/MaxES |
int |
エンティティエナジーシールド |
IsSleeping |
bool |
遠方エンティティフラグ |
IsChestOpened |
bool |
チェスト開放状態 |
Zone |
NearbyZone |
プレイヤーへの近接度 |
ComponentCache |
EntityComponentCache |
コンポーネントアドレス |
重要: 死亡エンティティ(
EntityState::Useless)はプラグインに届く前にスナップショットから除去されます。モンスターのHPがゼロになるのを見ることはありません — リストから消えるだけです。Zoneフィールドを使用してキル(エンティティがInnerCircle/OuterCircleから消えた)と範囲外に出たエンティティ(エンティティがFarゾーンにいた)を区別してください。
アクティブなバフ/デバフ:
| フィールド | 型 | 説明 |
|---|---|---|
Name |
string |
内部バフ名(例: "flask_effect_life") |
TimeLeft |
float |
残り秒数 |
Charges |
short |
スタック数 |
TotalTime |
float |
合計持続時間 |
ミニマップ/ラージマップの状態:
| フィールド | 型 | 説明 |
|---|---|---|
CenterX/Y |
float |
マップ中心 |
SizeX/Y |
float |
マップ寸法 |
ShiftX/Y |
float |
現在のパンオフセット |
DefaultShiftX/Y |
float |
デフォルトシフト値 |
Zoom |
float |
ズームレベル |
Scale |
float |
マップスケール係数 |
IsVisible |
bool |
マップが現在表示されている |
| フィールド | 型 | 説明 |
|---|---|---|
Id |
int |
インベントリID |
TotalBoxesX/Y |
int |
グリッド寸法 |
Ptr |
uintptr_t |
インベントリメモリアドレス |
Items |
vector<InventoryItemInfo> |
インベントリ内のアイテム |
アイテムフィールド: Address, Name(メタデータパス), Path(Nameと同じ), BaseTypeName(ベースタイプ名、例: "Divine Orb"), UniqueName(Words.datからのユニークアイテム名、例: "Headhunter"、ユニーク以外は空文字列), SlotX/Y, Width/Height, StackCount, IsCurrency
ReadExtendedItemMods() の戻り値:
| フィールド | 型 | 説明 |
|---|---|---|
ImplicitMods |
vector<ItemModData> |
暗黙のmod |
ExplicitMods |
vector<ItemModData> |
明示的mod |
EnchantMods |
vector<ItemModData> |
エンチャントmod |
HellscapeMods |
vector<ItemModData> |
Hellscape mod |
CrucibleMods |
vector<ItemModData> |
Crucible mod |
Rarity |
int |
0=ノーマル、1=マジック、2=レア、3=ユニーク |
| フィールド | 型 | 説明 |
|---|---|---|
Key |
string |
mod統計キー |
Values |
vector<float> |
modロール値 |
エンティティごとにキャッシュされたコンポーネントアドレス(RadarEntity.ComponentCache 経由で利用可能):
| フィールド | 型 | 存在確認メソッド |
|---|---|---|
RenderAddr |
uintptr_t |
HasRender() |
PositionedAddr |
uintptr_t |
HasPositioned() |
ChestAddr |
uintptr_t |
HasChest() |
PlayerAddr |
uintptr_t |
HasPlayer() |
ShrineAddr |
uintptr_t |
HasShrine() |
LifeAddr |
uintptr_t |
HasLife() |
TargetableAddr |
uintptr_t |
HasTargetable() |
OMPAddr |
uintptr_t |
HasOMP() |
NPCAddr |
uintptr_t |
HasNPC() |
TriggerableBlockageAddr |
uintptr_t |
HasTriggerableBlockage() |
DiesAfterTimeAddr |
uintptr_t |
HasDiesAfterTime() |
BuffsAddr |
uintptr_t |
HasBuffs() |
WorldItemAddr |
uintptr_t |
HasWorldItem() |
AreaTransitionAddr |
uintptr_t |
HasAreaTransition() |
MinimapIconAddr |
uintptr_t |
HasMinimapIcon() |
StatsAddr |
uintptr_t |
HasStats() |
GetEntityDebugList() からのエンティティメタデータ:
| フィールド | 型 | 説明 |
|---|---|---|
Id |
uint32_t |
エンティティID |
Address |
uintptr_t |
メモリアドレス |
Path |
string |
メタデータパス |
EntityType |
int |
エンティティタイプ(EntityTypes にキャスト) |
EntitySubType |
int |
エンティティサブタイプ(EntitySubtypes にキャスト) |
EntityState |
int |
エンティティ状態(EntityStates にキャスト) |
Rarity |
int |
0=ノーマル、1=マジック、2=レア、3=ユニーク |
Zone |
NearbyZone |
プレイヤーへの近接度 |
ComponentAddresses |
vector<pair<string,uintptr_t>> |
すべてのコンポーネント名→アドレスペア |
GetWatchedEntityData() からの完全なコンポーネントデータ:
| フィールド | 型 | 説明 |
|---|---|---|
EntityId |
uint32_t |
このデータが対応するエンティティ |
Valid |
bool |
データが正常に読み取れたか |
HasLife / Life
|
bool / DebugLifeComp
|
Lifeコンポーネント — Life.Health, Life.EnergyShield, Life.Mana(各 DebugVital: .Current, .Total, .Regeneration, .ReservedFlat, .ReservedPercent) |
HasRender / Render
|
bool / DebugRenderComp
|
位置(WorldX/Y/Z、GridX/Y)、TerrainHeight、ModelBounds(X/Y/Z) |
HasPositioned / Positioned
|
bool / DebugPositionedComp
|
Reaction値、IsFriendlyフラグ |
HasTargetable / Targetable
|
bool / DebugTargetableComp
|
IsTargetable、IsHighlightable、IsTargettedByPlayer、HiddenFromPlayer、MeetsQuestState、MeetsItemRequirements |
HasAnimated / Animated
|
bool / DebugAnimatedComp
|
Animation Path(string)、Id(uint32) |
HasStats / Stats
|
bool / DebugStatsComp
|
CurrentWeaponIndex、IsShapeshifted、StatsItems/StatsBuff(統計ID→値ペアのベクトル) |
HasActor / Actor
|
bool / DebugActorComp
|
AnimationId、AnimationName、ActiveSkills(DebugActiveSkill のベクトル)、DeployedCounts[256] |
HasBuffs / Buffs
|
bool / vector<DebugBuff>
|
アクティブバフ: Name、TotalTime、TimeLeft、Charges、FlaskSlot、Effectiveness、SourceEntityId |
GetWatchedInventoryData() からのインベントリ詳細:
| フィールド | 型 | 説明 |
|---|---|---|
InventoryId |
int |
インベントリID(なしの場合-1) |
Address |
uintptr_t |
インベントリアドレス |
TotalBoxesX/Y |
int |
グリッド寸法 |
ServerRequestCounter |
int |
サーバー同期カウンタ |
GridScreenX / GridScreenY
|
float |
グリッドUIのスクリーン位置 |
CellSize |
float |
グリッドセルのピクセルサイズ |
GridValid |
bool |
グリッドUIデータが有効かどうか |
SlotOccupied |
vector<bool> |
スロットごとの占有状況 |
Items |
vector<DebugInventoryItem> |
パス、レアリティ、mod付きアイテム |
| フィールド | 型 | 説明 |
|---|---|---|
Address |
uintptr_t |
アイテムエンティティアドレス |
Path |
string |
アイテムメタデータパス |
BaseTypeName |
string |
ベースタイプ名(例: "Divine Orb") |
UniqueName |
string |
Words.datからのユニークアイテム名(ユニーク以外は空文字列) |
SlotX / SlotY
|
int |
インベントリ内のグリッド位置 |
Rarity |
int |
0=ノーマル、1=マジック、2=レア、3=ユニーク |
ItemLevel |
int |
アイテムレベル |
RequiredLevel |
int |
必要キャラクターレベル |
IsIdentified |
bool |
アイテムが鑑定済みか |
IsCorrupted |
bool |
アイテムが汚染されているか |
CraftedModCount |
int |
クラフトmod数 |
ImplicitMods |
vector<DebugModInfo> |
暗黙のmod |
ExplicitMods |
vector<DebugModInfo> |
明示的mod |
EnchantMods |
vector<DebugModInfo> |
エンチャントmod |
HellscapeMods |
vector<DebugModInfo> |
Hellscape mod |
| フィールド | 型 | 説明 |
|---|---|---|
Name |
string |
スキル名 |
UseStage |
int |
現在の使用ステージ |
CastType |
int |
キャストタイプ |
TotalUses |
int |
合計使用回数 |
TotalCooldownTimeInMs |
int |
クールダウン(ミリ秒) |
CanBeUsed |
bool |
スキルが現在使用可能か |
| フィールド | 型 | 説明 |
|---|---|---|
Name |
string |
内部バフ名 |
TotalTime |
float |
合計持続時間 |
TimeLeft |
float |
残り秒数 |
Charges |
short |
スタック数 |
FlaskSlot |
short |
フラスコスロットインデックス |
Effectiveness |
short |
バフ効果 |
SourceEntityId |
uint32_t |
このバフを適用したエンティティ |
| フィールド | 型 | 説明 |
|---|---|---|
Name |
string |
mod表示名 |
StatKey |
string |
統計キー識別子 |
AffixName |
string |
接辞名 |
GenerationType |
int |
1=Prefix、2=Suffix、3=Implicit |
Value0 |
float |
第一値(なしの場合NaN) |
Value1 |
float |
第二値(なしの場合NaN) |
EntityTypes: Unidentified(0), Chest(1), NPC(2), Player(3), Shrine(4), Monster(5), DeliriumBomb(6), DeliriumSpawner(7), OtherImportantObjects(8), Item(9), Renderable(10), AreaTransition(11), ExpeditionMarker(12), ExpeditionRemnant(13)
EntitySubtypes: _Unidentified(0), _None(1), PlayerSelf(2), PlayerOther(3), ChestWithMagicRarity(4), ChestWithRareRarity(5), ExpeditionChest(6), BreachChest(7), Strongbox(8), SpecialNPC(9), POIMonster(10), PinnacleBoss(11), WorldItem(12), InventoryItem(13)
EntityStates: None(0), Useless(1), PlayerLeader(2), MonsterFriendly(3), PinnacleBossHidden(4)
NearbyZone: None(0), InnerCircle(1, 約60グリッドユニット), OuterCircle(2, 約120グリッドユニット), Far(3)
GameStateTypes: AreaLoadingState(0), ChangePasswordState(1), CreditsState(2), EscapeState(3), InGameState(4), PreGameState(5), LoginState(6), WaitingState(7), CreateCharacterState(8), SelectCharacterState(9), DeleteCharacterState(10), LoadingState(11), GameNotLoaded(12)
ホストとプラグインは同じImGuiコンテキストを共有します。SetContext() メソッドで以下を必ず呼び出してください:
ImGui::SetCurrentContext(static_cast<ImGuiContext*>(m_Context->ImGuiContext));ホストや他のプラグインとの競合を避けるため、常にユニークなウィンドウIDを使用してください:
ImGui::Begin("My Window##MyPluginName", &showWindow);- ウィンドウ、タブ、ツリー、テーブル、描画リスト
- D3D11デバイスによるテクスチャ読み込み
-
ImGui::GetBackgroundDrawList()によるオーバーレイレンダリング -
#include "imgui/IconsFontAwesome6.h"によるFontAwesome 6アイコン(例:ICON_FA_ARROWS_UP_DOWN_LEFT_RIGHT)
WorldToScreen() を使用してエンティティ位置にラベル/図形を描画します:
float sx, sy;
if (m_Context->WorldToScreen(entity.WorldX, entity.WorldY, entity.WorldZ, &sx, &sy)) {
auto* drawList = ImGui::GetBackgroundDrawList();
drawList->AddText(ImVec2(sx, sy - 20), IM_COL32(255, 255, 0, 255), "Monster");
drawList->AddCircleFilled(ImVec2(sx, sy), 4.0f, IM_COL32(255, 0, 0, 255));
}ホストにオーバーレイモードへの移行を要求するには、WantsOverlay() をオーバーライドして true を返します:
bool WantsOverlay() override { return m_OverlayEnabled; }オーバーレイモードでは、ホストウィンドウは透明でゲーム上に配置されます。DrawUI() の呼び出しはゲーム画面に直接レンダリングされます。
ホストオーバーレイは WS_EX_TRANSPARENT を使用して、メニューが非表示の時にウィンドウをクリックスルーにします。これはImGuiウィンドウがメニューが表示されていない限りマウス入力を受け取れないことを意味します。ドラッグ可能なオーバーレイウィンドウ(組み込みのVitals Overlayのような)を作成するには、このデュアルモードパターンを使用してください:
#include "imgui/IconsFontAwesome6.h"
void MyPlugin::RenderOverlay() {
bool menuVisible = m_Context->IsMenuVisible ? m_Context->IsMenuVisible() : false;
if (menuVisible) {
// === ドラッグモード ===
// 背景、ドラッグヒント、インタラクティブコントロール付きウィンドウ
ImGui::SetNextWindowPos(ImVec2(m_PosX, m_PosY), ImGuiCond_Appearing);
ImGui::SetNextWindowBgAlpha(m_Alpha);
ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoFocusOnAppearing;
ImGui::Begin("##MyOverlay", nullptr, flags);
// Drag hint (the entire window is draggable since there's no title bar)
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
ICON_FA_ARROWS_UP_DOWN_LEFT_RIGHT " Drag to reposition");
ImGui::Spacing();
// ... your content here (tabs, text, icons, buttons — all interactive) ...
// Persist position when user drags the window
ImVec2 pos = ImGui::GetWindowPos();
if (pos.x != m_PosX || pos.y != m_PosY) {
m_PosX = pos.x;
m_PosY = pos.y;
// Save to settings on next SaveSettings() call
}
ImGui::End();
}
else {
// === 非インタラクティブモード ===
// 静的オーバーレイ — ドラッグなし、マウスインタラクションなし
ImGui::SetNextWindowPos(ImVec2(m_PosX, m_PosY));
ImGui::SetNextWindowBgAlpha(m_Alpha);
ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoInputs;
ImGui::Begin("##MyOverlay", nullptr, flags);
// ... your content here (display-only, no interactive controls) ...
ImGui::End();
}
}キーポイント:
-
ImGuiCond_Appearingは最初の表示時にのみ位置を設定し、その後ImGuiがドラッグ位置を追跡します -
NoTitleBar+NoMoveなし = ウィンドウは空いた領域からドラッグ可能(ImGuiのデフォルト動作) - 非インタラクティブモードの
NoInputsはWS_EX_TRANSPARENTを通じたフォーカス奪取を防止します - 後方互換性のため
IsMenuVisibleポインタの null チェックを常に行ってください:m_Context->IsMenuVisible ? m_Context->IsMenuVisible() : false - セッション間で位置が保持されるよう設定ファイルに位置を保存してください
void OnEnable(bool isGameOpened) override {
LoadSettings(); // Load from Plugins/YourPlugin/config/settings.txt
}
void SaveSettings() override {
// Write to Plugins/YourPlugin/config/settings.txt
// The host calls this periodically and on shutdown
}設定は <PluginDirectory>/config/ に保存します:
std::filesystem::path settingsPath =
std::filesystem::path(m_Directory) / "config" / "settings.txt";多くの設定を持つプラグインには、シンプルなキー=値のテキスト形式が適しています:
// Save
std::ofstream file(configDir / "settings.txt");
file << "ShowOverlay=" << (m_Settings.ShowOverlay ? 1 : 0) << "\n";
file << "WindowAlpha=" << m_Settings.WindowAlpha << "\n";
file << "PosX=" << m_Settings.PosX << "\n";
file << "PosY=" << m_Settings.PosY << "\n";
// Load
std::ifstream file(settingsPath);
std::string line;
while (std::getline(file, line)) {
auto eq = line.find('=');
if (eq == std::string::npos) continue;
std::string key = line.substr(0, eq);
std::string val = line.substr(eq + 1);
if (key == "ShowOverlay") m_Settings.ShowOverlay = (val == "1");
else if (key == "WindowAlpha") m_Settings.WindowAlpha = std::stof(val);
else if (key == "PosX") m_Settings.PosX = std::stof(val);
else if (key == "PosY") m_Settings.PosY = std::stof(val);
}auto vitals = m_Context->GetPlayerVitals();
int hpPercent = vitals.HPPercent; // 0-100auto snapshot = m_Context->GetSnapshot();
for (auto& e : snapshot->Entities) {
if (e.entityType == EntityTypes::Monster &&
e.Zone == NearbyZone::InnerCircle) {
// e.CurrentHP, e.Path, e.WorldX/Y/Z...
}
}auto vitals = m_Context->GetPlayerVitals();
for (auto& buff : vitals.Buffs) {
if (buff.Name == "flask_effect_life") {
// buff.TimeLeft, buff.Charges...
}
}auto snapshot = m_Context->GetSnapshot();
std::string area = snapshot->CurrentAreaName;
bool isTown = snapshot->IsTown;
int level = snapshot->CurrentAreaLevel;for (auto& e : snapshot->Entities) {
if (e.entityType != EntityTypes::Monster) continue;
float sx, sy;
if (m_Context->WorldToScreen(e.WorldX, e.WorldY, e.WorldZ, &sx, &sy)) {
auto* dl = ImGui::GetBackgroundDrawList();
dl->AddText(ImVec2(sx, sy - 15), IM_COL32(255, 255, 0, 255), "Monster");
}
}// First, request an inventory scan (call periodically, e.g. every 2s)
m_Context->RequestInventoryScan(-1);
// Then read from snapshot (next frame)
auto snapshot = m_Context->GetSnapshot();
for (auto& inv : snapshot->Inventories) {
for (auto& item : inv.Items) {
auto mods = m_Context->ReadExtendedItemMods(item.Address);
// mods.ExplicitMods, mods.ImplicitMods...
}
}auto snapshot = m_Context->GetSnapshot();
int monsters = 0, items = 0;
for (auto& e : snapshot->Entities) {
if (e.entityType == EntityTypes::Monster) monsters++;
if (e.entityType == EntityTypes::Item) items++;
}static uint64_t lastAreaChange = 0;
auto snapshot = m_Context->GetSnapshot();
if (snapshot->AreaChangeCounter != lastAreaChange) {
lastAreaChange = snapshot->AreaChangeCounter;
// Area changed! Reset state...
}if (m_Context->GetCurrentState() == GameStateTypes::AreaLoadingState) {
// Currently loading...
}死亡エンティティはスナップショットからフィルタリングされる(EntityState::Useless)ため、HPが0になることは検出できません。代わりに、エンティティをIDで追跡し、近くのゾーンから消えた時を検出してください:
// In your tracker class:
struct TrackedEntity {
uint32_t Id;
int Rarity;
PluginSDK::NearbyZone Zone;
};
std::unordered_map<uint32_t, TrackedEntity> m_PrevEntities;
void DetectKills(const std::vector<PluginSDK::RadarEntity>& entities) {
std::unordered_set<uint32_t> currentIds;
// Update tracking map with current monsters
for (const auto& e : entities) {
if (e.entityType != EntityTypes::Monster) continue;
if (e.entityState == EntityStates::MonsterFriendly) continue;
currentIds.insert(e.Id);
m_PrevEntities[e.Id] = { e.Id, e.Rarity, e.Zone };
}
// Disappeared from InnerCircle/OuterCircle = killed
for (auto it = m_PrevEntities.begin(); it != m_PrevEntities.end(); ) {
if (currentIds.count(it->first) == 0) {
if (it->second.Zone == NearbyZone::InnerCircle ||
it->second.Zone == NearbyZone::OuterCircle) {
OnMonsterKilled(it->second.Rarity);
}
it = m_PrevEntities.erase(it);
} else {
++it;
}
}
}これが機能する理由: 約120グリッドユニット以内のエンティティが突然消えた場合、ほぼ確実にキルされた(単に射程外に出たのではない)と言えます。Far ゾーンのエンティティはエンティティリストに自然に出入りするため、カウントしないでください。
重要: エリア変更時(AreaChangeCounter が変わった時)に m_PrevEntities をクリアして、偽陽性を防いでください。
// Read a struct from a known address
struct MyGameStruct { int field1; float field2; };
MyGameStruct data{};
if (m_Context->ReadProcessMemory(someAddress, &data, sizeof(data))) {
// data.field1, data.field2 are now populated
}
// Read a string from memory
std::string str = m_Context->ReadString(stringAddress);uintptr_t gameStatesAddr = m_Context->GetPatternAddress("Game States");
if (gameStatesAddr != 0) {
// Read data at the resolved pattern address
uint64_t value = 0;
m_Context->ReadProcessMemory(gameStatesAddr, &value, sizeof(value));
}int gridW = 0, gridH = 0;
const uint8_t* grid = m_Context->GetWalkableGrid(&gridW, &gridH);
if (grid && gridW > 0 && gridH > 0) {
int x = (int)snapshot->Player.GridPositionX;
int y = (int)snapshot->Player.GridPositionY;
if (x >= 0 && x < gridW && y >= 0 && y < gridH) {
bool walkable = grid[y * gridW + x] != 0;
}
}PluginSDK::MemoryReader mem(m_Context);
// Read a struct from a known address
struct GameData { int level; float health; };
auto data = mem.Read<GameData>(address);
// Read a StdVector of pointers
auto ptrs = mem.ReadStdVector<uintptr_t>(vectorAddr);
for (auto ptr : ptrs) { /* process each pointer */ }
// Read a StdMap<int, float>
auto entries = mem.ReadStdMap<int, float>(mapAddr);
for (auto& [key, value] : entries) { /* key, value */ }for (auto& inv : snapshot->Inventories) {
const char* name = m_Context->GetInventoryName(inv.Id);
// name is e.g. "MainInventory1", "Weapon1", "Currency1"
}for (auto& e : snapshot->Entities) {
auto& cc = e.ComponentCache;
if (cc.HasLife()) {
// cc.LifeAddr contains the Life component address
// Use MemoryReader to read component structs
}
if (cc.HasRender()) {
// cc.RenderAddr has the Render component address
}
}// Get all entities with debug info
auto entities = m_Context->GetEntityDebugList();
for (auto& e : entities) {
bool open = ImGui::TreeNode(e.Path.c_str());
if (open) {
m_Context->WatchEntity(e.Id);
auto comp = m_Context->GetWatchedEntityData(e.Id);
if (comp.HasLife) {
ImGui::Text("HP: %d/%d ES: %d/%d MP: %d/%d",
comp.Life.Health.Current, comp.Life.Health.Total,
comp.Life.EnergyShield.Current, comp.Life.EnergyShield.Total,
comp.Life.Mana.Current, comp.Life.Mana.Total);
}
if (comp.HasActor) {
ImGui::Text("Animation: %s (%d) Skills: %d",
comp.Actor.AnimationName.c_str(), comp.Actor.AnimationId,
(int)comp.Actor.ActiveSkills.size());
}
ImGui::TreePop();
} else {
m_Context->UnwatchEntity(e.Id);
}
}auto invList = m_Context->GetPlayerInventoryList();
if (!invList.empty()) {
m_Context->WatchInventory(invList[0].first);
auto inv = m_Context->GetWatchedInventoryData();
if (inv.InventoryId >= 0) {
ImGui::Text("Grid: %dx%d Items: %d",
inv.TotalBoxesX, inv.TotalBoxesY, (int)inv.Items.size());
for (auto& item : inv.Items) {
ImGui::Text("[R%d iLvl%d] %s (%s) Mods: %d/%d/%d/%d",
item.Rarity, item.ItemLevel,
item.BaseTypeName.c_str(), item.Path.c_str(),
(int)item.ImplicitMods.size(), (int)item.ExplicitMods.size(),
(int)item.EnchantMods.size(), (int)item.HellscapeMods.size());
}
}
}uintptr_t uiRoot = m_Context->GetGameUiRootAddress();
if (uiRoot) {
PluginSDK::MemoryReader mem(m_Context);
// Read children vector at offset 0x010
auto children = mem.ReadStdVector<uintptr_t>(uiRoot + 0x010);
for (auto childAddr : children) {
// Read StringId at offset 0x448
uintptr_t strPtr = mem.Read<uintptr_t>(childAddr + 0x448);
if (strPtr) {
std::string name = m_Context->ReadString(strPtr);
ImGui::Text("Child: %s (0x%llX)", name.c_str(), childAddr);
}
}
}auto mods = m_Context->ReadExtendedItemMods(item.Address);
ImGui::TextColored(
PluginSDK::GetRarityColor(mods.Rarity),
"Rarity: %s", PluginSDK::GetRarityName(mods.Rarity));
for (auto& mod : mods.ExplicitMods) {
ImGui::BulletText("%s", mod.Key.c_str());
}| 設定 | 値 |
|---|---|
| Configuration | Release |
| Platform | x64 |
| C++ Standard | /std:c++20 |
| Runtime Library |
/MD (Multi-threaded DLL) |
| Configuration Type | DLL |
- プラグインの
.cppファイル - ImGuiソースファイル:
imgui.cpp,imgui_draw.cpp,imgui_tables.cpp,imgui_widgets.cpp - POEFixerルートへのインクルードパス(SDKおよびImGuiヘッダー用)
- オプション:
MemoryReaderラッパーとユーティリティ関数用にPlugins/ExamplePlugin/sdk/PluginHelpers.hをコピー
.vcxproj にこれらの追加インクルードディレクトリが必要です:
<AdditionalIncludeDirectories>$(SolutionDir)POEFixer;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>ローカルのサードパーティライブラリ(例: lib/ サブフォルダのSQLite3)を使用する場合、ソリューションパスの前に $(ProjectDir)lib を追加してローカルヘッダーが優先されるようにしてください:
<AdditionalIncludeDirectories>$(ProjectDir)lib;$(SolutionDir)POEFixer;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>プラグインでSQLite3を使用するには、アマルガメーションソースをDLLに直接コンパイルする必要があります — Windows LoadLibrary はDLL自体のディレクトリで依存関係を検索しないため、sqlite3.dll の動的リンクはエラー126で失敗します。
手順:
-
sqlite3.cとsqlite3.hをプラグインのlib/ディレクトリにコピーします -
SQLITE_APIをオーバーライドするlib/sqlite3-vcpkg-config.hを作成します(__declspec(dllimport)エラーを防止):#ifndef SQLITE_API #define SQLITE_API #endif #define SQLITE_ENABLE_UNLOCK_NOTIFY 1 #define SQLITE_OS_WIN 1 #define SQLITE_ENABLE_COLUMN_METADATA 1
-
sqlite3.cを.vcxprojにCファイルとして追加し、警告を無効にします:<ClCompile Include="lib\sqlite3.c"> <CompileAs>CompileAsC</CompileAs> <WarningLevel>TurnOffAllWarnings</WarningLevel> <SDLCheck>false</SDLCheck> <PreprocessorDefinitions>SQLITE_THREADSAFE=1;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> </ClCompile>
画像ファイル(PNG、JPG)からテクスチャを読み込むには、1つの .cpp ファイルにstb_imageをインクルードします:
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"その後、m_Context->D3DDevice のD3D11デバイスを使用してGPUテクスチャを作成します。
出力ディレクトリを設定します:
$(SolutionDir)x64\Release\Plugins\YourPlugin\
ビルドしたDLLをメイン実行ファイルの隣の Plugins/YourPlugin/YourPlugin.dll にコピーします。
- プラグインDLLをDebugモードでビルドします
- ホストアプリケーションを起動します
- Visual Studio: Debug → Attach to Process → ホスト .exe を選択
- プラグインソースにブレークポイントを設定します
- コードが呼び出された時にデバッガが停止します
| 問題 | 解決策 |
|---|---|
| プラグインが読み込まれない | DLL名がフォルダ名と正確に一致しているか確認 |
| "SDK version mismatch" | 最新のSDKヘッダーでプラグインを再ビルド(現在のバージョン: 4) |
| LoadLibraryエラー126 | DLLに未解決の依存関係があります。SQLite3などのサードパーティライブラリは静的にDLLにコンパイルしてください(セクション9参照)。dumpbin /dependents YourPlugin.dll で確認してください。 |
| ロード時にクラッシュ | CRTの不一致を確認 — 両方とも /MD を使用する必要があります |
| ImGuiが表示されない |
SetContext() で ImGui::SetCurrentContext() が呼び出されていることを確認 |
| データが空/ゼロ | データを読み取る前に IsAttached() と IsInGame() を確認 |
| インベントリが空 |
RequestInventoryScan(-1) を呼び出してください — インベントリデータはオンデマンドです |
| "Missing exports"エラー |
CreatePlugin と DestroyPlugin が extern "C" でエクスポートされていることを確認 |
| プラグインがホストをクラッシュさせる | これは起こるべきではありません — すべてのプラグイン呼び出しはSEH保護されています。ログを確認してください。 |
| データが古い |
GetSnapshot() は最新フレームのデータを返します。ポインタをキャッシュしないでください。 |
| メモリ読み取りが0を返す |
IsAttached() がtrueでアドレスが有効であることを確認 |
| WorldToScreenがfalseを返す | 位置がカメラの後ろまたは画面外の可能性があります |
| オーバーレイウィンドウがクリックできない | メニュー非表示時、ホストは WS_EX_TRANSPARENT を使用します。IsMenuVisible() を使用してメニューがアクティブな時のみインタラクティブコントロールを表示してください。セクション6のドラッグ可能オーバーレイパターンを参照。 |
| キル/デス検出が動作しない | 死亡エンティティはスナップショットから除去されます。HP遷移の代わりに消失ベースの検出を使用してください。セクション8を参照。 |
| C2491 "dllimport function"エラー | サードパーティライブラリのヘッダーが __declspec(dllimport) を定義しています。APIマクロを空に設定するローカルオーバーライドヘッダーを作成してください(セクション9のSQLite3の例を参照)。 |