Skip to content

Plugin Development Guide UA

Lafko edited this page Apr 9, 2026 · 15 revisions

← Home


Посібник з розробки плагінів

1. Початок роботи

Передумови

  • MSVC v143 (Visual Studio 2022)
  • C++20 (/std:c++20)
  • Збірка x64 Release
  • Бібліотека виконання: /MD (Multi-threaded DLL) — повинна відповідати хосту

Налаштування проєкту

  1. Створіть новий проєкт C++ DLL у Visual Studio
  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, вибір інвентарю, сітка слотів, модифікатори предметів з рідкістю
    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 amalgamation (компілюється як 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)

  • Коли викликається: Один раз, одразу після створення
  • Параметр: Відносний шлях, наприклад "Plugins/YourPlugin"
  • Призначення: Збережіть цей шлях для завантаження налаштувань/ресурсів

void SetContext(PluginContext* context)

  • Коли викликається: Один раз, після SetPluginDirectory
  • Параметр: Вказівник на PluginContext хоста (дійсний протягом життя плагіна)
  • Призначення: Збережіть цей вказівник — це ваш шлюз до всіх ігрових даних
  • Важливо: Викличте ImGui::SetCurrentContext(ctx->ImGuiContext) тут

void OnEnable(bool isGameOpened)

  • Коли викликається: Коли користувач увімкне плагін, або при запуску, якщо був увімкнений раніше
  • Параметр: true, якщо ігровий процес наразі приєднаний
  • Призначення: Завантаження налаштувань, виділення ресурсів, ініціалізація стану

void OnDisable()

  • Коли викликається: Коли користувач вимкне плагін
  • Призначення: Звільнення ресурсів, зупинка фонової роботи

void DrawUI()

  • Коли викликається: Кожен кадр, тільки коли плагін увімкнений
  • Призначення: Рендеринг оверлея за допомогою ImGui
  • Примітка: Використовуйте унікальні ідентифікатори вікон, наприклад "MyWindow##MyPlugin", щоб уникнути конфліктів

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 повинна експортувати ці дві C-функції:

extern "C" PLUGIN_API IPlugin* CreatePlugin() {
    return new MyPlugin();
}

extern "C" PLUGIN_API void DestroyPlugin(IPlugin* plugin) {
    delete plugin;
}

4. Довідник API PluginContext

Структура PluginContext (визначена у plugin_sdk/PluginContext.h) надає вказівники на функції для доступу до ігрових даних. Всі типи знаходяться у просторі імен PluginSDK.

Доступ до ігрових даних

GetSnapshot()shared_ptr<const PluginGameSnapshot>

Повертає повний знімок стану гри. Оновлюється один раз за кадр. Містить:

Поле Тип Опис
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

Зчитує всі модифікатори сутності предмета.

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, якщо хост наразі перебуває в режимі оверлея (прозорий оверлей поверх ігрового вікна). Використовуйте це для налаштування рендерингу — наприклад, малювання на ігровому оверлеї vs. малювання у вікні налаштувань.

Стан 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)

Починає спостереження за інвентарем для детального налагоджувального огляду. Хост зчитує зайнятість слотів, деталі предметів та модифікатори.

GetWatchedInventoryData()DebugInventoryData

Повертає повні дані для поточного спостережуваного інвентарю: розміри сітки, зайнятість слотів, предмети з рідкістю та модифікаторами.

// 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) надає типобезпечний клас MemoryReader, що обертає необроблені функції PluginContext:

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) — допоміжні функції відображення рідкості

Сервіси хоста

Log(level, message)

Запис у систему логування хоста. Рівні: "Debug", "Info", "Warning", "Error"

ImGuiContext (void*)

Контекст ImGui хоста. Викличте ImGui::SetCurrentContext() з ним у SetContext().

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> Неявні модифікатори
ExplicitMods vector<ItemModData> Явні модифікатори
EnchantMods vector<ItemModData> Модифікатори зачарування
HellscapeMods vector<ItemModData> Модифікатори Hellscape
CrucibleMods vector<ItemModData> Модифікатори Crucible
Rarity int 0=Звичайний, 1=Магічний, 2=Рідкісний, 3=Унікальний

ItemModData

Поле Тип Опис
Key string Ключ статистики модифікатора
Values vector<float> Значення кидка модифікатора

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> Предмети зі шляхом, рідкістю та модифікаторами

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 Кількість крафтових модифікаторів
ImplicitMods vector<DebugModInfo> Неявні модифікатори
ExplicitMods vector<DebugModInfo> Явні модифікатори
EnchantMods vector<DebugModInfo> Модифікатори зачарування
HellscapeMods vector<DebugModInfo> Модифікатори Hellscape

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 Відображувана назва модифікатора
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. Ви повинні викликати:

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

у вашому методі SetContext().

Ідентифікатори вікон

Завжди використовуйте унікальні ідентифікатори вікон, щоб уникнути конфліктів з хостом або іншими плагінами:

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

Доступні можливості

  • Вікна, вкладки, дерева, таблиці, списки малювання
  • Завантаження текстур через пристрій D3D11
  • Рендеринг оверлея через ImGui::GetBackgroundDrawList()
  • Іконки FontAwesome 6 через #include "imgui/IconsFontAwesome6.h" (наприклад, 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 за замовчуванням)
  • NoInputs у неінтерактивному режимі запобігає захопленню фокусу оверлеєм через WS_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");
    }
}

Читання модифікаторів предмета з інвентарю

// 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 природно з'являються та зникають зі списку сутностей — не рахуйте їх.

Важливо: Очищайте m_PrevEntities при зміні зони (змінився AreaChangeCounter), щоб уникнути хибних спрацювань.

Читання необробленої ігрової пам'яті (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);
        }
    }
}

Читання модифікаторів предметів з допоміжними функціями відображення (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)
  • Необов'язково: Скопіюйте Plugins/ExamplePlugin/sdk/PluginHelpers.h для обгортки MemoryReader та допоміжних функцій

Шляхи включення

Ваш .vcxproj повинен мати ці додаткові директорії включення:

<AdditionalIncludeDirectories>$(SolutionDir)POEFixer;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

Якщо використовуються локальні сторонні бібліотеки (наприклад, SQLite3 у підкаталозі lib/), додайте $(ProjectDir)lib перед шляхом рішення, щоб локальні заголовки мали пріоритет:

<AdditionalIncludeDirectories>$(ProjectDir)lib;$(SolutionDir)POEFixer;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

Сторонні бібліотеки

SQLite3 (статична лінковка)

Для використання SQLite3 у плагіні необхідно скомпілювати amalgamation-вихідний код безпосередньо у вашу DLL — Windows LoadLibrary не шукає залежності у власному каталозі DLL, тому динамічна лінковка sqlite3.dll завершиться помилкою 126.

Кроки:

  1. Скопіюйте sqlite3.c та sqlite3.h у каталог lib/ вашого плагіна
  2. Створіть lib/sqlite3-vcpkg-config.h для перевизначення SQLITE_API (запобігає помилкам __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) підключіть stb_image в одному .cpp файлі:

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

Потім використовуйте пристрій D3D11 з m_Context->D3DDevice для створення 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 не рендерить Переконайтеся, що ImGui::SetCurrentContext() викликається у SetContext()
Дані порожні/нульові Перевірте 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 в порожнє значення (див. приклад SQLite3 у Розділі 9).

← Home

Clone this wiki locally