-
Notifications
You must be signed in to change notification settings - Fork 0
Plugin Development Guide UA
- MSVC v143 (Visual Studio 2022)
-
C++20 (
/std:c++20) - Збірка x64 Release
-
Бібліотека виконання:
/MD(Multi-threaded DLL) — повинна відповідати хосту
- Створіть новий проєкт C++ DLL у Visual Studio
- Вкажіть шлях включення на кореневу директорію POEFixer (для заголовків SDK та ImGui)
- Додайте вихідні файли ImGui до вашого проєкту:
imgui.cpp,imgui_draw.cpp,imgui_tables.cpp,imgui_widgets.cpp - Підключіть заголовки Plugin SDK у вихідному коді плагіна:
Або використовуйте зручний заголовок з ExamplePlugin:
#include "plugin_sdk/PluginAPI.h" #include "plugin_sdk/PluginContext.h" #include "imgui/imgui.h"
#include "sdk/PluginHelpers.h" // Includes all SDK headers + MemoryReader + utilities
- Визначте
PLUGIN_EXPORTSта_CRT_SECURE_NO_WARNINGSу визначеннях препроцесора вашого проєкту
Plugins/
YourPlugin/
YourPlugin.dll <-- Ім'я DLL ПОВИННО збігатися з ім'ям каталогу
config/
settings.txt <-- Необов'язковий файл налаштувань
data/
... <-- Необов'язковий каталог даних (бази даних, кеші тощо)
ExamplePlugin демонструє рекомендований макет проєкту:
Plugins/ExamplePlugin/
ExamplePlugin.cpp <-- Головна точка входу плагіна + експорт фабрики
sdk/
PluginHelpers.h <-- MemoryReader, WideToNarrow, допоміжні функції сутностей/рідкості
examples/
ExampleBuffs.h <-- Список бафів з фільтрацією та індикаторами прогресу
ExampleEntities.h <-- Відлагоджувальний список сутностей з механізмом спостереження, деревами компонентів, дампом JSON
ExampleInventory.h <-- ServerData, вибір інвентарю, сітка слотів, модифікатори предметів з рідкістю
ExampleMemory.h <-- Hex-переглядач, демо Read<T>, сканер патернів
ExampleUiExplorer.h <-- Повний оглядач елементів UI з пошуком, навігацією, підсвічуванням
Більш повний приклад плагіна з SQLite3, атласом іконок та рендерингом оверлея:
Plugins/KillCount/
KillCount.cpp <-- Головна точка входу плагіна, життєвий цикл IPlugin, UI налаштувань
KillCount.h <-- Оголошення класу плагіна
KillTracker.cpp/h <-- Двигун підрахунку вбивств/скринь/смертей
OverlayRenderer.cpp/h <-- ImGui оверлей з патерном перетягування для зміни позиції
IconAtlas.cpp/h <-- Завантаження текстури спрайт-листа (D3D11 + stb_image)
Database.cpp/h <-- Обгортка SQLite3 для збереження статистики
DisplaySettings.h <-- Структура налаштувань
sdk/
PluginHelpers.h <-- Скопійовано з ExamplePlugin
lib/
sqlite3.c/h <-- SQLite3 amalgamation (компілюється як C)
sqlite3-vcpkg-config.h <-- Локальне перевизначення для статичної лінковки
Ім'я файлу DLL повинно точно збігатися з ім'ям каталогу:
- Каталог:
Plugins/MyPlugin/→ DLL:MyPlugin.dll - Хост сканує кожен підкаталог у
Plugins/та шукає<FolderName>.dll
Load DLL (LoadLibrary)
→ CreatePlugin() -- Фабрика: створення екземпляра IPlugin
→ SetContext(ctx) -- Отримання сервісів хоста
→ SetPluginDirectory(dir) -- Отримання шляху до каталогу плагіна
→ GetSDKVersion() -- Перевірка сумісності
→ GetName() -- Відображуване ім'я для UI
→ [if enabled] OnEnable() -- Ініціалізація ресурсів
↓
Main Loop (every frame):
→ DrawUI() -- Рендеринг оверлея (тільки якщо увімкнено)
→ DrawSettings() -- Рендеринг налаштувань у вкладці Plugins
→ WantsOverlay() -- Хост перевіряє, чи потрібен плагіну режим оверлея
↓
Periodically / on shutdown:
→ SaveSettings() -- Збереження налаштувань
↓
→ OnDisable() -- Звільнення ресурсів
→ DestroyPlugin(plugin) -- Фабрика: видалення IPlugin
→ FreeLibrary -- Вивантаження DLL
- Всі методи
Draw*викликаються в головному/рендер-потоці -
GetSnapshot()та інші функції PluginContext є потокобезпечними - НЕ створюйте потоки, що викликають ImGui — ImGui не є потокобезпечним
Кожен плагін повинен реалізувати інтерфейс IPlugin (визначений у plugin_sdk/PluginAPI.h):
- Коли викликається: Один раз, одразу після створення
-
Параметр: Відносний шлях, наприклад
"Plugins/YourPlugin" - Призначення: Збережіть цей шлях для завантаження налаштувань/ресурсів
-
Коли викликається: Один раз, після
SetPluginDirectory -
Параметр: Вказівник на
PluginContextхоста (дійсний протягом життя плагіна) - Призначення: Збережіть цей вказівник — це ваш шлюз до всіх ігрових даних
-
Важливо: Викличте
ImGui::SetCurrentContext(ctx->ImGuiContext)тут
- Коли викликається: Коли користувач увімкне плагін, або при запуску, якщо був увімкнений раніше
-
Параметр:
true, якщо ігровий процес наразі приєднаний - Призначення: Завантаження налаштувань, виділення ресурсів, ініціалізація стану
- Коли викликається: Коли користувач вимкне плагін
- Призначення: Звільнення ресурсів, зупинка фонової роботи
- Коли викликається: Кожен кадр, тільки коли плагін увімкнений
- Призначення: Рендеринг оверлея за допомогою ImGui
-
Примітка: Використовуйте унікальні ідентифікатори вікон, наприклад
"MyWindow##MyPlugin", щоб уникнути конфліктів
- Коли викликається: Кожен кадр, у вкладці налаштувань Plugins (тільки коли увімкнений)
- Призначення: Рендеринг конфігурації плагіна за допомогою ImGui
- Коли викликається: Періодично та при завершенні роботи програми
-
Призначення: Збереження налаштувань на диск (наприклад,
Plugins/YourPlugin/config/settings.txt)
-
Повертає: Відображуване ім'я у вкладці Plugins (наприклад,
"My Plugin")
-
Повертає:
PLUGIN_SDK_VERSION(наразі 5) - Призначення: Хост перевіряє це для сумісності — повинно збігатися
-
Повертає:
true, якщо плагін хоче рендерити в режимі оверлея (прозорий оверлей поверх гри) -
За замовчуванням:
false— плагін рендерить тільки у звичайному вікні налаштувань -
Призначення: Коли будь-який плагін повертає
true, хост переходить у режим оверлея, навіть якщо жодна вбудована функція цього не потребує
Ваша DLL повинна експортувати ці дві C-функції:
extern "C" PLUGIN_API IPlugin* CreatePlugin() {
return new MyPlugin();
}
extern "C" PLUGIN_API void DestroyPlugin(IPlugin* plugin) {
delete plugin;
}Структура PluginContext (визначена у plugin_sdk/PluginContext.h) надає вказівники на функції для доступу до ігрових даних. Всі типи знаходяться у просторі імен PluginSDK.
Повертає повний знімок стану гри. Оновлюється один раз за кадр. Містить:
| Поле | Тип | Опис |
|---|---|---|
CurrentState |
GameStateTypes |
Поточний стан гри |
CurrentAreaName |
string |
Назва зони (наприклад, "The Riverways") |
CurrentAreaHash |
string |
Унікальний хеш екземпляра зони |
CurrentAreaLevel |
uint8_t |
Рівень монстрів поточної зони |
IsTown |
bool |
True, якщо в місті |
IsHideout |
bool |
True, якщо в притулку |
IsPaused |
bool |
True, якщо гра на паузі |
IsSkillTreeVisible |
bool |
True, якщо відкрита панель дерева умінь |
WorldToGridConvertor |
float |
Коефіцієнт перетворення світ→сітка |
Player |
RadarEntity |
Дані сутності локального гравця |
Entities |
vector<RadarEntity> |
Всі найближчі сутності |
LargeMap / MiniMap
|
MapData |
Дані карти-оверлея |
Vitals |
PlayerVitals |
HP/ES/MP гравця + бафи |
ScreenWidth / ScreenHeight
|
int |
Розміри ігрового вікна |
ProcessId |
DWORD |
ID ігрового процесу |
GameWindow |
HWND |
Дескриптор ігрового вікна |
GameWindowForeground |
bool |
True, якщо вікно гри на передньому плані |
IsAttached |
bool |
True, якщо приєднано до ігрового процесу |
IsWindowValid |
bool |
True, якщо ігрове вікно дійсне |
LastUpdateTime |
uint64_t |
Мітка часу останнього оновлення даних |
AreaChangeCounter |
uint64_t |
Збільшується при зміні зони |
Inventories |
vector<InventoryInfo> |
Вміст інвентарю гравця |
CurrencyTotals |
map<string,int> |
Кількість валюти за шляхом |
InventoryGrid |
InventoryGridInfo |
Інформація про сітку інвентарю UI |
WorldToScreenMatrix |
XMFLOAT4X4 |
Матриця проєкції 3D→2D |
Важливо: Фільтрація сутностей Мертві сутності (ті, що мають
EntityState == Useless) фільтруються ІЗ знімка до того, як плагіни його отримають. Це означає, що ви ніколи не побачите перехід HP від живого до мертвого. Якщо вам потрібно виявляти вбивства, використовуйте виявлення на основі зникнення — відстежуйте ID сутностей за зоною і вважайте їх вбитими, коли вони зникають зі списку сутностей, перебуваючи в зоніInnerCircleабоOuterCircle. Див. Розділ 8: Поширені рецепти для деталей.
Зручний скорочений доступ до показників здоров'я гравця.
Повертає перерахування поточного стану гри.
True, якщо ігровий процес приєднаний та доступний для читання.
True, якщо наразі в грі (не завантаження, не екран входу).
True, якщо ігрове вікно є вікном переднього плану.
Повертає ID ігрового процесу.
Зчитує всі модифікатори сутності предмета.
Повертає: 0=Звичайний, 1=Магічний, 2=Рідкісний, 3=Унікальний
Повертає кількість у стопці для валюти/стопкованих предметів.
Повертає назву базового типу предмета.
Повертає шлях метаданих предмета.
Повертає назву базового типу предмета (наприклад, "Divine Orb", "Chaos Orb"). На відміну від ReadItemName, який повертає шлях метаданих, ця функція зчитує фактичну назву базового типу з BaseItemTypeData.BaseTypeName.
Повертає унікальну назву предмета з Words.dat (наприклад, "Headhunter", "Brimstone Call"). Повертає порожній рядок для неунікальних предметів.
Повертає true, якщо хост наразі перебуває в режимі оверлея (прозорий оверлей поверх ігрового вікна). Використовуйте це для налаштування рендерингу — наприклад, малювання на ігровому оверлеї vs. малювання у вікні налаштувань.
Повертає true, коли меню налаштувань хоста видиме (оверлей інтерактивний). Коли меню приховане, вікно оверлея є наскрізним для кліків (WS_EX_TRANSPARENT), тому вікна ImGui не можуть отримувати введення миші.
Використовуйте це для реалізації патерну перетягуваного оверлея:
- Меню видиме: Показуйте маркер перетягування, дозволяйте взаємодію (вкладки, кнопки)
-
Меню приховане: Приберіть маркер перетягування, додайте
ImGuiWindowFlags_NoInputs, щоб вікно було неінтерактивним
Див. Розділ 6: Патерн перетягуваного оверлея для повної реалізації.
Прямий доступ до пам'яті ігрового процесу. Всі операції читання безпечні (повертають 0/порожнє при помилці).
Повертає базову адресу модуля виконуваного файлу гри. Повертає 0, якщо не приєднано.
Повертає розмір ігрового модуля в байтах. Повертає 0, якщо не приєднано.
Зчитує блок необроблених байтів з ігрового процесу. buffer повинен мати щонайменше size байтів. Повертає true при успіху.
// Example: Read a 4-byte integer from game memory
uint32_t value = 0;
m_Context->ReadProcessMemory(address, &value, sizeof(value));
// Example: Read a struct
MyStruct data{};
m_Context->ReadProcessMemory(structAddress, &data, sizeof(data));Зчитує null-термінований ASCII рядок з ігрової пам'яті (максимум 128 символів).
Зчитує null-термінований Unicode (широкий) рядок з ігрової пам'яті (максимум 128 wchar).
Отримує розв'язану адресу сканування патерну за ім'ям. Повертає 0, якщо не знайдено.
Стандартні патерни:
| Ім'я | Опис |
|---|---|
"Game States" |
Корінь вектора GameStates |
"File Root" |
Реєстр файлів |
"AreaChangeCounter" |
Лічильник переходів між зонами |
"Terrain Rotator Helper" |
Дані обертання |
"Terrain Rotation Selector" |
Селектор обертання |
"GameCullSize" |
Значення відсікання екрану |
Перетворює позицію у світових координатах на екранні координати. Повертає true, якщо позиція видима на екрані.
float screenX, screenY;
if (m_Context->WorldToScreen(entity.WorldX, entity.WorldY, entity.WorldZ, &screenX, &screenY)) {
ImGui::GetBackgroundDrawList()->AddText(ImVec2(screenX, screenY), IM_COL32_WHITE, "Label");
}Запит до хоста на сканування інвентарів. Передайте -1 для сканування всіх інвентарів, або конкретний ID інвентарю. Дані інвентарю в знімку заповнюються після завершення сканування (наступний кадр).
Примітка: Дані інвентарю не оновлюються автоматично — ви повинні викликати цю функцію для запуску сканування. Викликайте її періодично (наприклад, кожні 2 секунди), якщо потрібні постійні дані інвентарю.
Повертає вказівник на дані сітки прохідності. Сітка є 2D масивом, де 0 = непрохідно, ненульове значення = прохідно. Повертає nullptr, якщо дані недоступні.
Повертає висоту рельєфу в позиції сітки. Повертає 0, якщо за межами або дані недоступні.
Ці функції зчитують контейнери стандартної бібліотеки C++ безпосередньо з ігрової пам'яті, відображаючи методи Core::Process хоста.
Зчитує StdVector (24-байтова структура: {First, Last, End}) з ігрової пам'яті. Повертає буфер, виділений через malloc. Викликач повинен викликати free() для повернутого вказівника. Повертає nullptr при помилці.
// Example: Read a vector of uint32_t
int count = 0;
void* data = m_Context->ReadStdVector(vectorAddr, sizeof(uint32_t), &count);
if (data && count > 0) {
uint32_t* values = static_cast<uint32_t*>(data);
for (int i = 0; i < count; i++) { /* values[i] */ }
free(data);
}Зчитує StdList (16-байтова структура: {Head, Size}) з ігрової пам'яті. Обходить зв'язаний список та повертає суцільний буфер. Викликач повинен викликати free().
Зчитує StdBucket з ігрової пам'яті (зчитує вбудований StdVector). Викликач повинен викликати free().
Обходить StdMap (16-байтова структура: {Head, Size}) та викликає callback для кожної пари ключ-значення. Повертає кількість відвіданих вузлів.
// Example: Read a map<uint32_t, float>
struct MapResult { std::vector<std::pair<uint32_t, float>> entries; };
MapResult result;
m_Context->ReadStdMap(mapAddr, sizeof(uint32_t), sizeof(float),
[](const void* key, const void* value, void* userData) {
auto* r = static_cast<MapResult*>(userData);
uint32_t k; float v;
memcpy(&k, key, sizeof(k));
memcpy(&v, value, sizeof(v));
r->entries.push_back({k, v});
}, &result);Зчитує StdWString (32-байтова структура з вбудованим/купчастим буфером) з ігрової пам'яті.
Повертає зрозумілу для людини назву ID інвентарю (наприклад, 1 → "MainInventory1", 3 → "Weapon1", 64 → "Currency1").
SDK v4 надає прямий доступ до налагоджувальних даних хоста — компоненти сутностей, деталі інвентарю та дерево елементів UI — відповідно до вбудованих вкладок Debug.
Повертає список всіх сутностей з налагоджувальними метаданими (Id, Address, Path, Type, SubType, State, Rarity, Zone). Це відповідає вкладці Debug→Entity List.
Починає спостереження за компонентами сутності. Робочий потік хоста буде зчитувати повні дані компонентів для цієї сутності кожен кадр.
Припиняє спостереження за компонентами сутності. Викличте це, коли користувач згортає вузол дерева сутності, щоб звільнити ресурси.
Повертає повні дані компонентів для спостережуваної сутності. Містить підструктури для всіх 8 розпізнаних компонентів (Life, Render, Positioned, Targetable, Animated, Stats, Actor, Buffs) плюс список усіх адрес компонентів.
// Example: Watch entity on expand, read components
auto entities = m_Context->GetEntityDebugList();
for (auto& e : entities) {
if (ImGui::TreeNode(e.Path.c_str())) {
m_Context->WatchEntity(e.Id);
auto data = m_Context->GetWatchedEntityData(e.Id);
if (data.HasLife) {
ImGui::Text("HP: %d / %d ES: %d / %d",
data.Life.Health.Current, data.Life.Health.Total,
data.Life.EnergyShield.Current, data.Life.EnergyShield.Total);
}
ImGui::TreePop();
} else {
m_Context->UnwatchEntity(e.Id);
}
}Повертає базову адресу компонента ServerData.
Повертає всі ID та адреси інвентарів гравця (з ServerData).
Починає спостереження за інвентарем для детального налагоджувального огляду. Хост зчитує зайнятість слотів, деталі предметів та модифікатори.
Повертає повні дані для поточного спостережуваного інвентарю: розміри сітки, зайнятість слотів, предмети з рідкістю та модифікаторами.
// Example: Inventory inspector
auto invList = m_Context->GetPlayerInventoryList();
m_Context->WatchInventory(invList[0].first);
auto inv = m_Context->GetWatchedInventoryData();
for (auto& item : inv.Items) {
ImGui::Text("[%s] Rarity=%d Mods=%d", item.Path.c_str(), item.Rarity,
(int)(item.ImplicitMods.size() + item.ExplicitMods.size()));
}Повертає адресу кореневого елемента ігрового UI (для навігації по дереву UI в грі).
Повертає адресу кореня UI верхнього рівня.
Повертає поточне значення GameCullSize, яке використовується для обчислення масштабу UI. В поєднанні з розмірами екрану це дозволяє точно обчислити позицію/розмір елементів UI.
// Example: UI scale calculation (matching host logic)
int cullValue = m_Context->GetGameCullValue();
auto snapshot = m_Context->GetSnapshot();
// Scale for index 1 (width): screenWidth / (cullValue / baseWidth)
// Scale for index 2 (height): screenHeight / (cullValue / baseHeight)(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 |
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 |
| 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 |
// 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);
}Заголовок 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)— допоміжні функції відображення рідкості
Запис у систему логування хоста. Рівні: "Debug", "Info", "Warning", "Error"
Контекст ImGui хоста. Викличте ImGui::SetCurrentContext() з ним у SetContext().
ID3D11Device* хоста. Приведіть тип та використовуйте для завантаження текстур.
Всі типи знаходяться у просторі імен PluginSDK. Плагіни зазвичай додають using namespace PluginSDK;.
Дані по кожній сутності, доступні у snapshot->Entities:
| Поле | Тип | Опис |
|---|---|---|
Id |
uint32_t |
Унікальний ID сутності |
Address |
uintptr_t |
Адреса в пам'яті (для викликів API предметів) |
EntityDetailsAddress |
uintptr_t |
Адреса структури деталей сутності |
RenderComponentAddress |
uintptr_t |
Адреса компонента Render (скорочення) |
IsValid |
bool |
Прапорець дійсності сутності |
entityType |
EntityTypes |
Категорія сутності |
entitySubtype |
EntitySubtypes |
Підкатегорія сутності |
entityState |
EntityStates |
Стан сутності |
Rarity |
int |
0=Звичайний, 1=Магічний, 2=Рідкісний, 3=Унікальний |
Reaction |
uint8_t |
0=Ворожий, 1=Нейтральний, 2=Дружній |
GridPositionX/Y |
float |
Позиція на сітці рельєфу |
TerrainHeight |
float |
Висота рельєфу в позиції сутності |
WorldX/Y/Z |
float |
Позиція у світових координатах |
ModelBoundsZ |
float |
Висота моделі |
Path |
wstring |
Шлях метаданих сутності |
PlayerName |
wstring |
Ім'я гравця (якщо сутність-гравець) |
TgtPath |
string |
Цільовий шлях (вузький рядок) |
CurrentHP/MaxHP |
int |
Здоров'я сутності |
CurrentES/MaxES |
int |
Енергетичний щит сутності |
IsSleeping |
bool |
Прапорець далекої сутності |
IsChestOpened |
bool |
Стан відкритості скрині |
Zone |
NearbyZone |
Близькість до гравця |
ComponentCache |
EntityComponentCache |
Адреси компонентів |
Важливо: Мертві сутності (
EntityState::Useless) видаляються зі знімка до того, як він потрапить до плагінів. Ви ніколи не побачите, як HP монстра падає до нуля — він просто зникає зі списку. Використовуйте полеZone, щоб розрізняти вбивства (сутність зникла з InnerCircle/OuterCircle) від сутностей, що вийшли за межі дальності (сутність була в зоні Far).
Активний баф/дебаф:
| Поле | Тип | Опис |
|---|---|---|
Name |
string |
Внутрішнє ім'я бафа (наприклад, "flask_effect_life") |
TimeLeft |
float |
Залишок часу в секундах |
Charges |
short |
Кількість стеків |
TotalTime |
float |
Загальна тривалість |
Стан мінікарти/великої карти:
| Поле | Тип | Опис |
|---|---|---|
CenterX/Y |
float |
Центр карти |
SizeX/Y |
float |
Розміри карти |
ShiftX/Y |
float |
Поточне зміщення |
DefaultShiftX/Y |
float |
Значення зміщення за замовчуванням |
Zoom |
float |
Рівень масштабування |
Scale |
float |
Масштабний коефіцієнт карти |
IsVisible |
bool |
Карта наразі відображається |
| Поле | Тип | Опис |
|---|---|---|
Id |
int |
ID інвентарю |
TotalBoxesX/Y |
int |
Розміри сітки |
Ptr |
uintptr_t |
Адреса інвентарю в пам'яті |
Items |
vector<InventoryItemInfo> |
Предмети в інвентарі |
Поля предмета: Address, Name (шлях метаданих), Path (те саме, що Name), BaseTypeName (назва базового типу, наприклад "Divine Orb"), UniqueName (унікальна назва предмета з Words.dat, наприклад "Headhunter", порожній для неунікальних), SlotX/Y, Width/Height, StackCount, IsCurrency
Повертається ReadExtendedItemMods():
| Поле | Тип | Опис |
|---|---|---|
ImplicitMods |
vector<ItemModData> |
Неявні модифікатори |
ExplicitMods |
vector<ItemModData> |
Явні модифікатори |
EnchantMods |
vector<ItemModData> |
Модифікатори зачарування |
HellscapeMods |
vector<ItemModData> |
Модифікатори Hellscape |
CrucibleMods |
vector<ItemModData> |
Модифікатори Crucible |
Rarity |
int |
0=Звичайний, 1=Магічний, 2=Рідкісний, 3=Унікальний |
| Поле | Тип | Опис |
|---|---|---|
Key |
string |
Ключ статистики модифікатора |
Values |
vector<float> |
Значення кидка модифікатора |
Адреси компонентів, кешовані для кожної сутності (доступні через 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() |
Метадані сутності з GetEntityDebugList():
| Поле | Тип | Опис |
|---|---|---|
Id |
uint32_t |
ID сутності |
Address |
uintptr_t |
Адреса в пам'яті |
Path |
string |
Шлях метаданих |
EntityType |
int |
Тип сутності (приведення до EntityTypes) |
EntitySubType |
int |
Підтип сутності (приведення до EntitySubtypes) |
EntityState |
int |
Стан сутності (приведення до EntityStates) |
Rarity |
int |
0=Звичайний, 1=Магічний, 2=Рідкісний, 3=Унікальний |
Zone |
NearbyZone |
Близькість до гравця |
ComponentAddresses |
vector<pair<string,uintptr_t>> |
Всі пари ім'я компонента→адреса |
Повні дані компонентів з GetWatchedEntityData():
| Поле | Тип | Опис |
|---|---|---|
EntityId |
uint32_t |
Для якої сутності ці дані |
Valid |
bool |
Чи були дані успішно зчитані |
HasLife / Life
|
bool / DebugLifeComp
|
Компонент Life — Life.Health, Life.EnergyShield, Life.Mana (кожен — DebugVital з .Current, .Total, .Regeneration, .ReservedFlat, .ReservedPercent) |
HasRender / Render
|
bool / DebugRenderComp
|
Позиція (WorldX/Y/Z, GridX/Y), TerrainHeight, ModelBounds (X/Y/Z) |
HasPositioned / Positioned
|
bool / DebugPositionedComp
|
Значення Reaction, прапорець IsFriendly |
HasTargetable / Targetable
|
bool / DebugTargetableComp
|
IsTargetable, IsHighlightable, IsTargettedByPlayer, HiddenFromPlayer, MeetsQuestState, MeetsItemRequirements |
HasAnimated / Animated
|
bool / DebugAnimatedComp
|
Animation Path (string), Id (uint32) |
HasStats / Stats
|
bool / DebugStatsComp
|
CurrentWeaponIndex, IsShapeshifted, StatsItems/StatsBuff (вектори пар ID статистики→значення) |
HasActor / Actor
|
bool / DebugActorComp
|
AnimationId, AnimationName, ActiveSkills (вектор DebugActiveSkill), DeployedCounts[256] |
HasBuffs / Buffs
|
bool / vector<DebugBuff>
|
Активні бафи: Name, TotalTime, TimeLeft, Charges, FlaskSlot, Effectiveness, SourceEntityId |
Деталі інвентарю з GetWatchedInventoryData():
| Поле | Тип | Опис |
|---|---|---|
InventoryId |
int |
ID інвентарю (-1, якщо немає) |
Address |
uintptr_t |
Адреса інвентарю |
TotalBoxesX/Y |
int |
Розміри сітки |
ServerRequestCounter |
int |
Лічильник синхронізації з сервером |
GridScreenX / GridScreenY
|
float |
Екранна позиція UI сітки |
CellSize |
float |
Розмір комірки сітки в пікселях |
GridValid |
bool |
Чи дійсні дані UI сітки |
SlotOccupied |
vector<bool> |
Зайнятість кожного слоту |
Items |
vector<DebugInventoryItem> |
Предмети зі шляхом, рідкістю та модифікаторами |
| Поле | Тип | Опис |
|---|---|---|
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 |
| Поле | Тип | Опис |
|---|---|---|
Name |
string |
Назва вміння |
UseStage |
int |
Поточна стадія використання |
CastType |
int |
Тип застосування |
TotalUses |
int |
Загальна кількість використань |
TotalCooldownTimeInMs |
int |
Відновлення у мілісекундах |
CanBeUsed |
bool |
Чи можна наразі використати вміння |
| Поле | Тип | Опис |
|---|---|---|
Name |
string |
Внутрішнє ім'я бафа |
TotalTime |
float |
Загальна тривалість |
TimeLeft |
float |
Залишок часу в секундах |
Charges |
short |
Кількість стеків |
FlaskSlot |
short |
Індекс слоту фляжки |
Effectiveness |
short |
Ефективність бафа |
SourceEntityId |
uint32_t |
Сутність, що наклала цей баф |
| Поле | Тип | Опис |
|---|---|---|
Name |
string |
Відображувана назва модифікатора |
StatKey |
string |
Ідентифікатор ключа статистики |
AffixName |
string |
Назва афікса |
GenerationType |
int |
1=Prefix, 2=Suffix, 3=Implicit |
Value0 |
float |
Перше значення (NaN, якщо немає) |
Value1 |
float |
Друге значення (NaN, якщо немає) |
(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 |
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)
Хост та плагін використовують один і той же контекст 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)
Використовуйте WorldToScreen() для малювання підписів/фігур у позиціях сутностей:
float sx, sy;
if (m_Context->WorldToScreen(entity.WorldX, entity.WorldY, entity.WorldZ, &sx, &sy)) {
auto* drawList = ImGui::GetBackgroundDrawList();
drawList->AddText(ImVec2(sx, sy - 20), IM_COL32(255, 255, 0, 255), "Monster");
drawList->AddCircleFilled(ImVec2(sx, sy), 4.0f, IM_COL32(255, 0, 0, 255));
}Перевизначте WantsOverlay() для повернення true, щоб запросити у хоста перехід у режим оверлея:
bool WantsOverlay() override { return m_OverlayEnabled; }У режимі оверлея вікно хоста прозоре та розташоване поверх гри. Ваші виклики DrawUI() рендерять безпосередньо на ігровому екрані.
Оверлей хоста використовує WS_EX_TRANSPARENT, щоб зробити вікно наскрізним для кліків, коли меню приховане. Це означає, що вікна ImGui не можуть отримувати введення миші, якщо меню не видиме. Для створення вікна оверлея з можливістю перетягування (як вбудований Vitals Overlay) використовуйте цей двохрежимний патерн:
#include "imgui/IconsFontAwesome6.h"
void MyPlugin::RenderOverlay() {
bool menuVisible = m_Context->IsMenuVisible ? m_Context->IsMenuVisible() : false;
if (menuVisible) {
// === РЕЖИМ ПЕРЕТЯГУВАННЯ ===
// Вікно з фоном, підказкою перетягування, інтерактивними елементами
ImGui::SetNextWindowPos(ImVec2(m_PosX, m_PosY), ImGuiCond_Appearing);
ImGui::SetNextWindowBgAlpha(m_Alpha);
ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoFocusOnAppearing;
ImGui::Begin("##MyOverlay", nullptr, flags);
// Drag hint (the entire window is draggable since there's no title bar)
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
ICON_FA_ARROWS_UP_DOWN_LEFT_RIGHT " Drag to reposition");
ImGui::Spacing();
// ... your content here (tabs, text, icons, buttons — all interactive) ...
// Persist position when user drags the window
ImVec2 pos = ImGui::GetWindowPos();
if (pos.x != m_PosX || pos.y != m_PosY) {
m_PosX = pos.x;
m_PosY = pos.y;
// Save to settings on next SaveSettings() call
}
ImGui::End();
}
else {
// === НЕІНТЕРАКТИВНИЙ РЕЖИМ ===
// Статичний оверлей — без перетягування, без взаємодії мишею
ImGui::SetNextWindowPos(ImVec2(m_PosX, m_PosY));
ImGui::SetNextWindowBgAlpha(m_Alpha);
ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoInputs;
ImGui::Begin("##MyOverlay", nullptr, flags);
// ... your content here (display-only, no interactive controls) ...
ImGui::End();
}
}Ключові моменти:
-
ImGuiCond_Appearingвстановлює позицію лише при першому показі; потім ImGui відстежує позицію перетягування -
NoTitleBar+ безNoMove= вікно можна перетягувати з будь-якої порожньої області (поведінка ImGui за замовчуванням) -
NoInputsу неінтерактивному режимі запобігає захопленню фокусу оверлеєм черезWS_EX_TRANSPARENT - Завжди перевіряйте вказівник
IsMenuVisibleна null для зворотної сумісності:m_Context->IsMenuVisible ? m_Context->IsMenuVisible() : false - Зберігайте позицію у файлі налаштувань, щоб вона зберігалася між сесіями
void OnEnable(bool isGameOpened) override {
LoadSettings(); // Load from Plugins/YourPlugin/config/settings.txt
}
void SaveSettings() override {
// Write to Plugins/YourPlugin/config/settings.txt
// The host calls this periodically and on shutdown
}Зберігайте налаштування у <PluginDirectory>/config/:
std::filesystem::path settingsPath =
std::filesystem::path(m_Directory) / "config" / "settings.txt";Для плагінів з багатьма налаштуваннями простий текстовий формат ключ=значення працює добре:
// Save
std::ofstream file(configDir / "settings.txt");
file << "ShowOverlay=" << (m_Settings.ShowOverlay ? 1 : 0) << "\n";
file << "WindowAlpha=" << m_Settings.WindowAlpha << "\n";
file << "PosX=" << m_Settings.PosX << "\n";
file << "PosY=" << m_Settings.PosY << "\n";
// Load
std::ifstream file(settingsPath);
std::string line;
while (std::getline(file, line)) {
auto eq = line.find('=');
if (eq == std::string::npos) continue;
std::string key = line.substr(0, eq);
std::string val = line.substr(eq + 1);
if (key == "ShowOverlay") m_Settings.ShowOverlay = (val == "1");
else if (key == "WindowAlpha") m_Settings.WindowAlpha = std::stof(val);
else if (key == "PosX") m_Settings.PosX = std::stof(val);
else if (key == "PosY") m_Settings.PosY = std::stof(val);
}auto vitals = m_Context->GetPlayerVitals();
int hpPercent = vitals.HPPercent; // 0-100auto snapshot = m_Context->GetSnapshot();
for (auto& e : snapshot->Entities) {
if (e.entityType == EntityTypes::Monster &&
e.Zone == NearbyZone::InnerCircle) {
// e.CurrentHP, e.Path, e.WorldX/Y/Z...
}
}auto vitals = m_Context->GetPlayerVitals();
for (auto& buff : vitals.Buffs) {
if (buff.Name == "flask_effect_life") {
// buff.TimeLeft, buff.Charges...
}
}auto snapshot = m_Context->GetSnapshot();
std::string area = snapshot->CurrentAreaName;
bool isTown = snapshot->IsTown;
int level = snapshot->CurrentAreaLevel;for (auto& e : snapshot->Entities) {
if (e.entityType != EntityTypes::Monster) continue;
float sx, sy;
if (m_Context->WorldToScreen(e.WorldX, e.WorldY, e.WorldZ, &sx, &sy)) {
auto* dl = ImGui::GetBackgroundDrawList();
dl->AddText(ImVec2(sx, sy - 15), IM_COL32(255, 255, 0, 255), "Monster");
}
}// First, request an inventory scan (call periodically, e.g. every 2s)
m_Context->RequestInventoryScan(-1);
// Then read from snapshot (next frame)
auto snapshot = m_Context->GetSnapshot();
for (auto& inv : snapshot->Inventories) {
for (auto& item : inv.Items) {
auto mods = m_Context->ReadExtendedItemMods(item.Address);
// mods.ExplicitMods, mods.ImplicitMods...
}
}auto snapshot = m_Context->GetSnapshot();
int monsters = 0, items = 0;
for (auto& e : snapshot->Entities) {
if (e.entityType == EntityTypes::Monster) monsters++;
if (e.entityType == EntityTypes::Item) items++;
}static uint64_t lastAreaChange = 0;
auto snapshot = m_Context->GetSnapshot();
if (snapshot->AreaChangeCounter != lastAreaChange) {
lastAreaChange = snapshot->AreaChangeCounter;
// Area changed! Reset state...
}if (m_Context->GetCurrentState() == GameStateTypes::AreaLoadingState) {
// Currently loading...
}Мертві сутності фільтруються зі знімка (EntityState::Useless), тому ви не можете виявити падіння HP до 0. Замість цього відстежуйте сутності за ID та виявляйте, коли вони зникають з найближчих зон:
// In your tracker class:
struct TrackedEntity {
uint32_t Id;
int Rarity;
PluginSDK::NearbyZone Zone;
};
std::unordered_map<uint32_t, TrackedEntity> m_PrevEntities;
void DetectKills(const std::vector<PluginSDK::RadarEntity>& entities) {
std::unordered_set<uint32_t> currentIds;
// Update tracking map with current monsters
for (const auto& e : entities) {
if (e.entityType != EntityTypes::Monster) continue;
if (e.entityState == EntityStates::MonsterFriendly) continue;
currentIds.insert(e.Id);
m_PrevEntities[e.Id] = { e.Id, e.Rarity, e.Zone };
}
// Disappeared from InnerCircle/OuterCircle = killed
for (auto it = m_PrevEntities.begin(); it != m_PrevEntities.end(); ) {
if (currentIds.count(it->first) == 0) {
if (it->second.Zone == NearbyZone::InnerCircle ||
it->second.Zone == NearbyZone::OuterCircle) {
OnMonsterKilled(it->second.Rarity);
}
it = m_PrevEntities.erase(it);
} else {
++it;
}
}
}Чому це працює: Сутності в межах ~120 одиниць сітки, що раптово зникли, майже напевно були вбиті (не просто вийшли за межі дальності). Сутності в зоні Far природно з'являються та зникають зі списку сутностей — не рахуйте їх.
Важливо: Очищайте m_PrevEntities при зміні зони (змінився AreaChangeCounter), щоб уникнути хибних спрацювань.
// Read a struct from a known address
struct MyGameStruct { int field1; float field2; };
MyGameStruct data{};
if (m_Context->ReadProcessMemory(someAddress, &data, sizeof(data))) {
// data.field1, data.field2 are now populated
}
// Read a string from memory
std::string str = m_Context->ReadString(stringAddress);uintptr_t gameStatesAddr = m_Context->GetPatternAddress("Game States");
if (gameStatesAddr != 0) {
// Read data at the resolved pattern address
uint64_t value = 0;
m_Context->ReadProcessMemory(gameStatesAddr, &value, sizeof(value));
}int gridW = 0, gridH = 0;
const uint8_t* grid = m_Context->GetWalkableGrid(&gridW, &gridH);
if (grid && gridW > 0 && gridH > 0) {
int x = (int)snapshot->Player.GridPositionX;
int y = (int)snapshot->Player.GridPositionY;
if (x >= 0 && x < gridW && y >= 0 && y < gridH) {
bool walkable = grid[y * gridW + x] != 0;
}
}PluginSDK::MemoryReader mem(m_Context);
// Read a struct from a known address
struct GameData { int level; float health; };
auto data = mem.Read<GameData>(address);
// Read a StdVector of pointers
auto ptrs = mem.ReadStdVector<uintptr_t>(vectorAddr);
for (auto ptr : ptrs) { /* process each pointer */ }
// Read a StdMap<int, float>
auto entries = mem.ReadStdMap<int, float>(mapAddr);
for (auto& [key, value] : entries) { /* key, value */ }for (auto& inv : snapshot->Inventories) {
const char* name = m_Context->GetInventoryName(inv.Id);
// name is e.g. "MainInventory1", "Weapon1", "Currency1"
}for (auto& e : snapshot->Entities) {
auto& cc = e.ComponentCache;
if (cc.HasLife()) {
// cc.LifeAddr contains the Life component address
// Use MemoryReader to read component structs
}
if (cc.HasRender()) {
// cc.RenderAddr has the Render component address
}
}// Get all entities with debug info
auto entities = m_Context->GetEntityDebugList();
for (auto& e : entities) {
bool open = ImGui::TreeNode(e.Path.c_str());
if (open) {
m_Context->WatchEntity(e.Id);
auto comp = m_Context->GetWatchedEntityData(e.Id);
if (comp.HasLife) {
ImGui::Text("HP: %d/%d ES: %d/%d MP: %d/%d",
comp.Life.Health.Current, comp.Life.Health.Total,
comp.Life.EnergyShield.Current, comp.Life.EnergyShield.Total,
comp.Life.Mana.Current, comp.Life.Mana.Total);
}
if (comp.HasActor) {
ImGui::Text("Animation: %s (%d) Skills: %d",
comp.Actor.AnimationName.c_str(), comp.Actor.AnimationId,
(int)comp.Actor.ActiveSkills.size());
}
ImGui::TreePop();
} else {
m_Context->UnwatchEntity(e.Id);
}
}auto invList = m_Context->GetPlayerInventoryList();
if (!invList.empty()) {
m_Context->WatchInventory(invList[0].first);
auto inv = m_Context->GetWatchedInventoryData();
if (inv.InventoryId >= 0) {
ImGui::Text("Grid: %dx%d Items: %d",
inv.TotalBoxesX, inv.TotalBoxesY, (int)inv.Items.size());
for (auto& item : inv.Items) {
ImGui::Text("[R%d iLvl%d] %s (%s) Mods: %d/%d/%d/%d",
item.Rarity, item.ItemLevel,
item.BaseTypeName.c_str(), item.Path.c_str(),
(int)item.ImplicitMods.size(), (int)item.ExplicitMods.size(),
(int)item.EnchantMods.size(), (int)item.HellscapeMods.size());
}
}
}uintptr_t uiRoot = m_Context->GetGameUiRootAddress();
if (uiRoot) {
PluginSDK::MemoryReader mem(m_Context);
// Read children vector at offset 0x010
auto children = mem.ReadStdVector<uintptr_t>(uiRoot + 0x010);
for (auto childAddr : children) {
// Read StringId at offset 0x448
uintptr_t strPtr = mem.Read<uintptr_t>(childAddr + 0x448);
if (strPtr) {
std::string name = m_Context->ReadString(strPtr);
ImGui::Text("Child: %s (0x%llX)", name.c_str(), childAddr);
}
}
}auto mods = m_Context->ReadExtendedItemMods(item.Address);
ImGui::TextColored(
PluginSDK::GetRarityColor(mods.Rarity),
"Rarity: %s", PluginSDK::GetRarityName(mods.Rarity));
for (auto& mod : mods.ExplicitMods) {
ImGui::BulletText("%s", mod.Key.c_str());
}| Налаштування | Значення |
|---|---|
| Configuration | Release |
| Platform | x64 |
| C++ Standard | /std:c++20 |
| Runtime Library |
/MD (Multi-threaded DLL) |
| Configuration Type | DLL |
- Ваш файл(и)
.cppплагіна - Вихідні файли ImGui:
imgui.cpp,imgui_draw.cpp,imgui_tables.cpp,imgui_widgets.cpp - Шлях включення до кореня POEFixer (для заголовків SDK та ImGui)
- Необов'язково: Скопіюйте
Plugins/ExamplePlugin/sdk/PluginHelpers.hдля обгорткиMemoryReaderта допоміжних функцій
Ваш .vcxproj повинен мати ці додаткові директорії включення:
<AdditionalIncludeDirectories>$(SolutionDir)POEFixer;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>Якщо використовуються локальні сторонні бібліотеки (наприклад, SQLite3 у підкаталозі lib/), додайте $(ProjectDir)lib перед шляхом рішення, щоб локальні заголовки мали пріоритет:
<AdditionalIncludeDirectories>$(ProjectDir)lib;$(SolutionDir)POEFixer;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>Для використання SQLite3 у плагіні необхідно скомпілювати amalgamation-вихідний код безпосередньо у вашу DLL — Windows LoadLibrary не шукає залежності у власному каталозі DLL, тому динамічна лінковка sqlite3.dll завершиться помилкою 126.
Кроки:
- Скопіюйте
sqlite3.cтаsqlite3.hу каталогlib/вашого плагіна - Створіть
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
- Додайте
sqlite3.cу ваш.vcxprojяк C-файл з вимкненими попередженнями:<ClCompile Include="lib\sqlite3.c"> <CompileAs>CompileAsC</CompileAs> <WarningLevel>TurnOffAllWarnings</WarningLevel> <SDLCheck>false</SDLCheck> <PreprocessorDefinitions>SQLITE_THREADSAFE=1;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> </ClCompile>
Для завантаження текстур з файлів зображень (PNG, JPG) підключіть 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 поряд з головним виконуваним файлом.
- Зберіть DLL плагіна в режимі Debug
- Запустіть основну програму
- У Visual Studio: Debug → Attach to Process → виберіть .exe хоста
- Встановіть точки зупинки у вихідному коді плагіна
- Налагоджувач зупиниться, коли ваш код буде викликано
| Проблема | Рішення |
|---|---|
| Плагін не завантажується | Перевірте, що ім'я 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). |