Skip to content

Plugin Development Guide JA

Lafko edited this page Apr 9, 2026 · 14 revisions

← Home


プラグイン開発ガイド

1. はじめに

前提条件

  • MSVC v143 (Visual Studio 2022)
  • C++20 (/std:c++20)
  • x64 Release ビルド
  • ランタイムライブラリ: /MD (マルチスレッドDLL) — ホストと一致する必要があります

プロジェクトのセットアップ

  1. Visual Studioで新しい C++ DLL プロジェクトを作成します
  2. インクルードパスをPOEFixerのソースディレクトリに設定します(SDKおよびImGuiヘッダー用)
  3. ImGuiソースファイルをプロジェクトに追加します: imgui.cpp, imgui_draw.cpp, imgui_tables.cpp, imgui_widgets.cpp
  4. プラグインソースにPlugin SDKヘッダーをインクルードします:
    #include "plugin_sdk/PluginAPI.h"
    #include "plugin_sdk/PluginContext.h"
    #include "imgui/imgui.h"
    またはExamplePluginの便利なヘッダーを使用します:
    #include "sdk/PluginHelpers.h"  // Includes all SDK headers + MemoryReader + utilities
  5. プロジェクトのプリプロセッサ定義に PLUGIN_EXPORTS_CRT_SECURE_NO_WARNINGS を定義します

フォルダ構造

Plugins/
  YourPlugin/
    YourPlugin.dll      <-- DLL名はフォルダ名と一致する必要があります
    config/
      settings.txt      <-- オプションの設定ファイル
    data/
      ...               <-- オプションのデータディレクトリ(データベース、キャッシュなど)

ExamplePluginプロジェクト構造

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エレメントエクスプローラ

KillCountプラグイン構造

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 を探します

2. プラグインのライフサイクル

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はスレッドセーフではありません

3. IPluginインターフェースリファレンス

すべてのプラグインは IPlugin インターフェース(plugin_sdk/PluginAPI.h で定義)を実装する必要があります:

void SetPluginDirectory(const char* dir)

  • 呼び出しタイミング: 作成直後に1回
  • パラメータ: "Plugins/YourPlugin" のような相対パス
  • 目的: 設定/リソース読み込み用にこのパスを保存します

void SetContext(PluginContext* context)

  • 呼び出しタイミング: SetPluginDirectory の後に1回
  • パラメータ: ホストの PluginContext へのポインタ(プラグインの生存期間中有効)
  • 目的: このポインタを保存します — すべてのゲームデータへのゲートウェイです
  • 重要: ここで ImGui::SetCurrentContext(ctx->ImGuiContext) を呼び出してください

void OnEnable(bool isGameOpened)

  • 呼び出しタイミング: ユーザーがプラグインを有効にした時、または以前有効だった場合は起動時
  • パラメータ: ゲームプロセスが現在アタッチされている場合は true
  • 目的: 設定の読み込み、リソースの割り当て、状態の初期化

void OnDisable()

  • 呼び出しタイミング: ユーザーがプラグインを無効にした時
  • 目的: リソースの解放、バックグラウンド作業の停止

void DrawUI()

  • 呼び出しタイミング: 毎フレーム、プラグインが有効な場合のみ
  • 目的: ImGuiを使用してオーバーレイをレンダリング
  • 注意: 競合を避けるため "MyWindow##MyPlugin" のようなユニークなウィンドウIDを使用してください

void DrawSettings()

  • 呼び出しタイミング: 毎フレーム、Plugins設定タブ内(有効時のみ)
  • 目的: ImGuiを使用してプラグイン設定をレンダリング

void SaveSettings()

  • 呼び出しタイミング: 定期的およびアプリケーション終了時
  • 目的: ディスクに設定を保存(例: Plugins/YourPlugin/config/settings.txt

const char* GetName()

  • 戻り値: Pluginsタブに表示される表示名(例: "My Plugin"

int GetSDKVersion()

  • 戻り値: PLUGIN_SDK_VERSION(現在5)
  • 目的: ホストが互換性を確認 — 一致する必要があります

bool WantsOverlay() (SDK v2)

  • 戻り値: プラグインがオーバーレイモード(ゲーム上の透明オーバーレイ)でレンダリングしたい場合は 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;
}

4. PluginContext APIリファレンス

PluginContext 構造体(plugin_sdk/PluginContext.h で定義)はゲームデータにアクセスするための関数ポインタを提供します。すべての型は PluginSDK 名前空間にあります。

ゲームデータアクセス

GetSnapshot()shared_ptr<const PluginGameSnapshot>

ゲーム状態の完全なスナップショットを返します。フレームごとに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: よくあるレシピを参照してください。

GetPlayerVitals()PlayerVitals

プレイヤーバイタルへの便利なショートカットです。

GetCurrentState()GameStateTypes

現在のゲーム状態列挙を返します。

IsAttached()bool

ゲームプロセスがアタッチされ読み取り可能な場合はtrue。

IsInGame()bool

現在ゲーム内にいる場合はtrue(ロード中やログイン画面ではない)。

IsGameForeground()bool

ゲームウィンドウがフォアグラウンドウィンドウの場合はtrue。

GetProcessId()DWORD

ゲームプロセスIDを返します。

アイテムデータアクセス

ReadExtendedItemMods(entityAddress)ExtendedItemModInfo

アイテムエンティティのすべてのmodを読み取ります。

ReadItemRarity(entityAddress)int

戻り値: 0=ノーマル、1=マジック、2=レア、3=ユニーク

ReadItemStackCount(entityAddress)int

カレンシー/スタック可能アイテムのスタック数を返します。

ReadItemName(entityAddress)string

アイテムのベースタイプ名を返します。

ReadItemPath(entityAddress)string

アイテムのメタデータパスを返します。

ReadItemBaseTypeName(entityAddress)string

アイテムのベースタイプ名を返します(例: "Divine Orb", "Chaos Orb")。メタデータパスを返す ReadItemName とは異なり、BaseItemTypeData.BaseTypeName から実際のベースタイプ名を読み取ります。

ReadItemUniqueName(entityAddress)string

Words.datからユニークアイテム名を返します(例: "Headhunter", "Brimstone Call")。ユニーク以外のアイテムには空文字列を返します。

オーバーレイモード (SDK v2)

IsOverlayMode()bool

ホストが現在オーバーレイモード(ゲームウィンドウ上の透明オーバーレイ)にある場合はtrueを返します。レンダリングを調整するために使用してください — 例えば、ゲームオーバーレイへの描画と設定ウィンドウへの描画の切り替え。

UI状態 (SDK v4)

IsMenuVisible()bool

ホストの設定メニューが表示されている(オーバーレイがインタラクティブ)場合はtrueを返します。メニューが非表示の場合、オーバーレイウィンドウはクリックスルー(WS_EX_TRANSPARENT)になるため、ImGuiウィンドウはマウス入力を受け取れません。

ドラッグ可能オーバーレイパターンの実装に使用してください:

  • メニュー表示時: ドラッグハンドルを表示し、インタラクション(タブ、ボタン)を許可
  • メニュー非表示時: ドラッグハンドルを削除し、ImGuiWindowFlags_NoInputs を追加してウィンドウを非インタラクティブに

完全な実装はセクション6: ドラッグ可能オーバーレイパターンを参照してください。

メモリ読み取り (SDK v2)

ゲームプロセスメモリへの直接アクセス。すべての読み取りは安全です(失敗時は0/空を返します)。

GetBaseAddress()uintptr_t

ゲーム実行モジュールのベースアドレスを返します。アタッチされていない場合は0を返します。

GetModuleSize()uintptr_t

ゲームモジュールのバイトサイズを返します。アタッチされていない場合は0を返します。

ReadProcessMemory(address, buffer, size)bool

ゲームプロセスから生のバイトブロックを読み取ります。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));

ReadString(address)string

ゲームメモリからnull終端ASCII文字列を読み取ります(最大128文字)。

ReadUnicodeString(address)wstring

ゲームメモリからnull終端Unicode(ワイド)文字列を読み取ります(最大128 wchar)。

GetPatternAddress(patternName)uintptr_t

名前で解決されたパターンスキャンアドレスを取得します。見つからない場合は0を返します。

標準パターン:

名前 説明
"Game States" GameStatesベクトルルート
"File Root" ファイルレジストリ
"AreaChangeCounter" エリア遷移カウンタ
"Terrain Rotator Helper" 回転データ
"Terrain Rotation Selector" 回転セレクタ
"GameCullSize" スクリーンカル値

ワールドからスクリーンへの投影 (SDK v2)

WorldToScreen(worldX, worldY, worldZ, outX, outY)bool

ワールド空間の位置をスクリーン座標に変換します。位置が画面上で可視の場合は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");
}

インベントリ (SDK v2)

RequestInventoryScan(inventoryId)

ホストにインベントリスキャンを要求します。-1 を渡すとすべてのインベントリをスキャンし、特定のインベントリIDを渡すこともできます。スナップショット内のインベントリデータはスキャン完了後(次のフレーム)に入力されます。

注意: インベントリデータは自動的に更新されません — スキャンをトリガーするにはこの関数を呼び出す必要があります。継続的なインベントリデータが必要な場合は定期的に(例: 2秒ごとに)呼び出してください。

地形データ (SDK v2)

GetWalkableGrid(outWidth, outHeight)const uint8_t*

歩行可能グリッドデータへのポインタを返します。グリッドは2D配列で、0 = 歩行不可、非ゼロ = 歩行可能です。データが利用できない場合はnullptrを返します。

GetTerrainHeight(gridX, gridY)float

グリッド位置の地形の高さを返します。範囲外またはデータ利用不可の場合は0を返します。

ネイティブコンテナ読み取り (SDK v3)

これらの関数はゲームメモリからC++標準ライブラリコンテナを直接読み取り、ホストの Core::Process メソッドをミラーリングします。

ReadStdVector(containerAddress, elementSize, outCount)void*

ゲームメモリから 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);
}

ReadStdList(containerAddress, elementSize, outCount)void*

ゲームメモリから StdList(16バイト構造体: {Head, Size})を読み取ります。リンクリストをたどり、連続バッファを返します。呼び出し元は free() する必要があります。

ReadStdBucket(containerAddress, elementSize, outCount)void*

ゲームメモリから StdBucket を読み取ります(埋め込まれた StdVector を読み取ります)。呼び出し元は free() する必要があります。

ReadStdMap(containerAddress, keySize, valueSize, callback, userData)int

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);

ReadStdWString(containerAddress)wstring

ゲームメモリから StdWString(インライン/ヒープバッファ付き32バイト構造体)を読み取ります。

GetInventoryName(inventoryId)const char*

インベントリIDの人間が読める名前を返します(例: 1 → "MainInventory1", 3 → "Weapon1", 64 → "Currency1")。

デバッグデータアクセス (SDK v4)

SDK v4はホストのデバッグデータへの直接アクセスを提供します — エンティティコンポーネント、インベントリ詳細、UIエレメントツリー — 組み込みのDebugタブに対応しています。

エンティティデバッグリスト

GetEntityDebugList()vector<DebugEntityInfo>

デバッグメタデータ(Id、Address、Path、Type、SubType、State、Rarity、Zone)を含むすべてのエンティティのリストを返します。これはDebug→Entity Listタブに対応します。

WatchEntity(entityId)

エンティティのコンポーネントのウォッチを開始します。ホストのワーカースレッドがこのエンティティの完全なコンポーネントデータを毎フレーム読み取ります。

UnwatchEntity(entityId)

エンティティのコンポーネントのウォッチを停止します。ユーザーがエンティティツリーノードを折りたたんだ時にリソースを解放するために呼び出してください。

GetWatchedEntityData(entityId)DebugEntityComponents

ウォッチされているエンティティの完全なコンポーネントデータを返します。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);
    }
}

インベントリデバッグ

GetServerDataAddress()uintptr_t

ServerDataコンポーネントのベースアドレスを返します。

GetPlayerInventoryList()vector<pair<int, uintptr_t>>

すべてのプレイヤーインベントリIDとそのアドレス(ServerDataから)を返します。

WatchInventory(inventoryId)

詳細なデバッグ検査のためにインベントリのウォッチを開始します。ホストがスロット占有状況、アイテム詳細、modを読み取ります。

GetWatchedInventoryData()DebugInventoryData

現在ウォッチされているインベントリの完全なデータを返します: グリッド寸法、スロット占有状況、レアリティと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エレメントツリー

GetGameUiRootAddress()uintptr_t

ルートゲームUIエレメントアドレスを返します(ゲーム内UIツリーナビゲーション用)。

GetUiRootAddress()uintptr_t

最上位UIルートアドレスを返します。

GetGameCullValue()int

現在の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)

UI Element API (SDK v5)

(Translation pending)

SDK v5 adds direct UI element reading without the debug watch mechanism. Navigate the UI tree, check visibility, read text, and compute screen rectangles.

Function Return Purpose
ReadUiElement(addr) UiElementData Read core UI element properties
GetUiChildren(addr) vector<uintptr_t> Get all child element addresses
GetUiChildAt(addr, index) uintptr_t Get single child by index
ReadUiChildChain(root, indices, count) uintptr_t Navigate child index path from root
IsUiElementVisible(addr) bool Check visibility (including ancestors)
GetUiStringId(addr) string Get element's string identifier
ComputeUiScreenRect(addr, outX, outY, outW, outH) bool Compute screen rectangle with recursive scaling
GetUiText(addr) string Get element's display text

Component Reader API (SDK v5)

21 typed component reader functions. Each takes a component address from EntityComponentCache and returns a struct with Valid flag.

Function Return Struct Component
ReadLifeComponent(addr) PluginLifeData Life (HP/ES/Mana)
ReadRenderComponent(addr) PluginRenderData Render (position, bounds)
ReadPositionedComponent(addr) PluginPositionedData Positioned (reaction)
ReadTargetableComponent(addr) PluginTargetableData Targetable (flags)
ReadChestComponent(addr) PluginChestData Chest (opened, quality)
ReadShrineComponent(addr) PluginShrineData Shrine (available)
ReadStackComponent(addr) PluginStackData Stack (size)
ReadChargesComponent(addr) PluginChargesData Charges
ReadPlayerComponent(addr) PluginPlayerData Player (name, level)
ReadAnimatedComponent(addr) PluginAnimatedData Animated (animation)
ReadTransitionableComponent(addr) PluginTransitionableData Transitionable
ReadTriggerableBlockageComponent(addr) PluginTriggerableBlockageData TriggerableBlockage
ReadMinimapIconComponent(addr) PluginMinimapIconData MinimapIcon
ReadStateMachineComponent(addr) PluginStateMachineData StateMachine
ReadBaseComponent(addr) PluginBaseData Base (cell size, influence)
ReadModsComponent(addr) PluginModsData Mods (rarity, mod lists)
ReadStatsComponent(addr) PluginStatsData Stats (key-value pairs)
ReadBuffsComponent(addr) PluginBuffsData Buffs (active buffs)
ReadActorComponent(addr) PluginActorData Actor (skills, deploy)
ReadNpcComponent(addr) PluginNpcData NPC (hidden, icon)
ReadDiesAfterTimeComponent(addr) PluginDiesAfterTimeData DiesAfterTime

Convenience Helpers (SDK v5)

Helper Signature Description
GetHealthPercent float (uintptr_t lifeAddr) HP percentage (0-100)
GetEsPercent float (uintptr_t lifeAddr) Energy Shield percentage
GetManaPercent float (uintptr_t lifeAddr) Mana percentage
IsAlive bool (uintptr_t lifeAddr) True if HP > 0
IsChestOpenedHelper bool (uintptr_t chestAddr) True if chest opened
GetWorldPosition bool (uintptr_t renderAddr, float*, float*, float*) Extract world position
GetItemRarityFromMods int (uintptr_t modsAddr) Item rarity from Mods
IsItemIdentifiedHelper bool (uintptr_t modsAddr) True if identified
GetStackCountHelper int (uintptr_t stackAddr) Stack count
GetPlayerNameHelper string (uintptr_t playerAddr) Player name

SDK v5 Usage Example

// Read entity health via component reader
auto snapshot = m_Context->GetSnapshot();
for (auto& entity : snapshot->Entities) {
    if (entity.ComponentCache.HasLife()) {
        auto life = m_Context->ReadLifeComponent(entity.ComponentCache.LifeAddr);
        if (life.Valid) {
            float hpPct = m_Context->GetHealthPercent(entity.ComponentCache.LifeAddr);
        }
    }
}

// Navigate UI tree
uintptr_t gameUi = m_Context->GetGameUiRootAddress();
const int path[] = {5, 1, 2};
uintptr_t btn = m_Context->ReadUiChildChain(gameUi, path, 3);
if (m_Context->IsUiElementVisible(btn)) {
    float x, y, w, h;
    m_Context->ComputeUiScreenRect(btn, &x, &y, &w, &h);
}

PluginHelpers.h — 便利なラッパー (SDK v3)

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);

MemoryReaderReadString()ReadUnicodeString()GetBaseAddress()GetModuleSize()GetPatternAddress() の便利なラッパーも提供します。

PluginHelpers.h のその他のユーティリティ:

  • WideToNarrow(wstring) — 安全なwstring→string変換(ASCII損失あり)
  • GetEntityTypeName(type) — 列挙から表示名へ(ExpeditionMarker/ExpeditionRemnantを含む)
  • GetNearbyZoneName(zone) — ゾーンから表示名へ
  • GetRarityName(rarity) / GetRarityColor(rarity) — レアリティ表示ヘルパー

ホストサービス

Log(level, message)

ホストのログシステムに書き込みます。レベル: "Debug", "Info", "Warning", "Error"

ImGuiContext (void*)

ホストのImGuiコンテキスト。SetContext() 内で ImGui::SetCurrentContext() を呼び出してください。

D3DDevice (void*)

ホストの ID3D11Device*。キャストしてテクスチャ読み込みに使用してください。


5. データ構造リファレンス

すべての型は PluginSDK 名前空間にあります。プラグインは通常 using namespace PluginSDK; を追加します。

RadarEntity

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ゾーンにいた)を区別してください。

Buff

アクティブなバフ/デバフ:

フィールド 説明
Name string 内部バフ名(例: "flask_effect_life"
TimeLeft float 残り秒数
Charges short スタック数
TotalTime float 合計持続時間

MapData

ミニマップ/ラージマップの状態:

フィールド 説明
CenterX/Y float マップ中心
SizeX/Y float マップ寸法
ShiftX/Y float 現在のパンオフセット
DefaultShiftX/Y float デフォルトシフト値
Zoom float ズームレベル
Scale float マップスケール係数
IsVisible bool マップが現在表示されている

InventoryInfo / InventoryItemInfo

フィールド 説明
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

ExtendedItemModInfo

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=ユニーク

ItemModData

フィールド 説明
Key string mod統計キー
Values vector<float> modロール値

EntityComponentCache

エンティティごとにキャッシュされたコンポーネントアドレス(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()
ActorAddr uintptr_t HasActor()
AnimatedAddr uintptr_t HasAnimated()
BaseAddr uintptr_t HasBase()
ChargesAddr uintptr_t HasCharges()
ModsAddr uintptr_t HasMods()
StackAddr uintptr_t HasStack()
TransitionableAddr uintptr_t HasTransitionable()
StateMachineAddr uintptr_t HasStateMachine()

DebugEntityInfo (SDK v4)

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>> すべてのコンポーネント名→アドレスペア

DebugEntityComponents (SDK v4)

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

DebugInventoryData (SDK v4)

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付きアイテム

DebugInventoryItem (SDK v4)

フィールド 説明
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

DebugActiveSkill (SDK v4)

フィールド 説明
Name string スキル名
UseStage int 現在の使用ステージ
CastType int キャストタイプ
TotalUses int 合計使用回数
TotalCooldownTimeInMs int クールダウン(ミリ秒)
CanBeUsed bool スキルが現在使用可能か

DebugBuff (SDK v4)

フィールド 説明
Name string 内部バフ名
TotalTime float 合計持続時間
TimeLeft float 残り秒数
Charges short スタック数
FlaskSlot short フラスコスロットインデックス
Effectiveness short バフ効果
SourceEntityId uint32_t このバフを適用したエンティティ

DebugModInfo (SDK v4)

フィールド 説明
Name string mod表示名
StatKey string 統計キー識別子
AffixName string 接辞名
GenerationType int 1=Prefix、2=Suffix、3=Implicit
Value0 float 第一値(なしの場合NaN)
Value1 float 第二値(なしの場合NaN)

UiElementData (SDK v5)

(Translation pending)

Field Type Description
Valid bool Whether the read succeeded
X / Y float Element position
Width / Height float Element size
ScaleX / ScaleY float Scale factors
IsVisible bool Visibility flag
IsEnabled bool Enabled flag
ChildCount int Number of children
ParentAddr uintptr_t Parent element address
SelfAddr uintptr_t Self pointer

Component Data Structs (SDK v5)

All component reader functions return structs with a Valid field. See the English guide for full field listings of all 22 structs (PluginLifeData, PluginRenderData, PluginPositionedData, PluginTargetableData, PluginChestData, PluginShrineData, PluginStackData, PluginChargesData, PluginPlayerData, PluginAnimatedData, PluginTransitionableData, PluginTriggerableBlockageData, PluginMinimapIconData, PluginStateMachineData, PluginBaseData, PluginModsData, PluginStatsData, PluginBuffsData, PluginActorData, PluginNpcData, PluginDiesAfterTimeData).

列挙型

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)


6. プラグインでのImGui使用

共有コンテキスト

ホストとプラグインは同じImGuiコンテキストを共有します。SetContext() メソッドで以下を必ず呼び出してください:

ImGui::SetCurrentContext(static_cast<ImGuiContext*>(m_Context->ImGuiContext));

ウィンドウID

ホストや他のプラグインとの競合を避けるため、常にユニークなウィンドウIDを使用してください:

ImGui::Begin("My Window##MyPluginName", &showWindow);

利用可能な機能

  • ウィンドウ、タブ、ツリー、テーブル、描画リスト
  • D3D11デバイスによるテクスチャ読み込み
  • ImGui::GetBackgroundDrawList() によるオーバーレイレンダリング
  • #include "imgui/IconsFontAwesome6.h" によるFontAwesome 6アイコン(例: ICON_FA_ARROWS_UP_DOWN_LEFT_RIGHT

オーバーレイレンダリング (SDK v2)

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() の呼び出しはゲーム画面に直接レンダリングされます。

ドラッグ可能オーバーレイパターン (SDK v4)

ホストオーバーレイは 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のデフォルト動作)
  • 非インタラクティブモードの NoInputsWS_EX_TRANSPARENT を通じたフォーカス奪取を防止します
  • 後方互換性のため IsMenuVisible ポインタの null チェックを常に行ってください: m_Context->IsMenuVisible ? m_Context->IsMenuVisible() : false
  • セッション間で位置が保持されるよう設定ファイルに位置を保存してください

7. 設定の永続化

推奨パターン

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);
}

8. よくあるレシピ

プレイヤーHPパーセンテージの取得

auto vitals = m_Context->GetPlayerVitals();
int hpPercent = vitals.HPPercent; // 0-100

インナーサークル内のすべてのモンスターをリスト

auto 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;

エンティティのワールド位置にテキストを描画 (SDK v2)

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");
    }
}

インベントリからアイテムmodを読み取る

// 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 をクリアして、偽陽性を防いでください。

生のゲームメモリを読み取る (SDK v2)

// 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);

パターンスキャン結果を使用 (SDK v2)

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));
}

歩行可能な地形を確認 (SDK v2)

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;
    }
}

MemoryReaderで型付きメモリ読み取り (SDK v3)

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 */ }

インベントリ名の取得 (SDK v3)

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
    }
}

デバッグウォッチによるエンティティコンポーネントの検査 (SDK v4)

// 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);
    }
}

スロットグリッド付きインベントリの検査 (SDK v4)

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());
        }
    }
}

UIエレメントツリーのナビゲーション (SDK v4)

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);
        }
    }
}

表示ヘルパー付きアイテムmodの読み取り (SDK v3)

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());
}

9. ビルドとデプロイ

ビルド設定

設定
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(静的リンク)

プラグインでSQLite3を使用するには、アマルガメーションソースをDLLに直接コンパイルする必要があります — Windows LoadLibrary はDLL自体のディレクトリで依存関係を検索しないため、sqlite3.dll の動的リンクはエラー126で失敗します。

手順:

  1. sqlite3.csqlite3.h をプラグインの lib/ ディレクトリにコピーします
  2. 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
  3. 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>

stb_image(テクスチャ読み込み)

画像ファイル(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 にコピーします。

デバッグ

  1. プラグインDLLをDebugモードでビルドします
  2. ホストアプリケーションを起動します
  3. Visual Studio: Debug → Attach to Process → ホスト .exe を選択
  4. プラグインソースにブレークポイントを設定します
  5. コードが呼び出された時にデバッガが停止します

10. トラブルシューティング

問題 解決策
プラグインが読み込まれない DLL名がフォルダ名と正確に一致しているか確認
"SDK version mismatch" 最新のSDKヘッダーでプラグインを再ビルド(現在のバージョン: 5)
LoadLibraryエラー126 DLLに未解決の依存関係があります。SQLite3などのサードパーティライブラリは静的にDLLにコンパイルしてください(セクション9参照)。dumpbin /dependents YourPlugin.dll で確認してください。
ロード時にクラッシュ CRTの不一致を確認 — 両方とも /MD を使用する必要があります
ImGuiが表示されない SetContext()ImGui::SetCurrentContext() が呼び出されていることを確認
データが空/ゼロ データを読み取る前に IsAttached()IsInGame() を確認
インベントリが空 RequestInventoryScan(-1) を呼び出してください — インベントリデータはオンデマンドです
"Missing exports"エラー CreatePluginDestroyPluginextern "C" でエクスポートされていることを確認
プラグインがホストをクラッシュさせる これは起こるべきではありません — すべてのプラグイン呼び出しはSEH保護されています。ログを確認してください。
データが古い GetSnapshot() は最新フレームのデータを返します。ポインタをキャッシュしないでください。
メモリ読み取りが0を返す IsAttached() がtrueでアドレスが有効であることを確認
WorldToScreenがfalseを返す 位置がカメラの後ろまたは画面外の可能性があります
オーバーレイウィンドウがクリックできない メニュー非表示時、ホストは WS_EX_TRANSPARENT を使用します。IsMenuVisible() を使用してメニューがアクティブな時のみインタラクティブコントロールを表示してください。セクション6のドラッグ可能オーバーレイパターンを参照。
キル/デス検出が動作しない 死亡エンティティはスナップショットから除去されます。HP遷移の代わりに消失ベースの検出を使用してください。セクション8を参照。
C2491 "dllimport function"エラー サードパーティライブラリのヘッダーが __declspec(dllimport) を定義しています。APIマクロを空に設定するローカルオーバーライドヘッダーを作成してください(セクション9のSQLite3の例を参照)。

← Home

Clone this wiki locally