Skip to content

Plugin Development Guide RU

Lafko edited this page Apr 9, 2026 · 16 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. Укажите путь включения (include path) на директорию исходного кода 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()  -- Инициализация ресурсов
  ↓
  Основной цикл (каждый кадр):
    → DrawUI()                -- Рендеринг оверлея (только если включён)
    → DrawSettings()          -- Рендеринг настроек во вкладке Plugins
    → WantsOverlay()          -- Хост проверяет, нужен ли плагину режим оверлея
  ↓
  Периодически / при завершении:
    → 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()

  • Когда вызывается: Каждый кадр, во вкладке настроек плагинов (только когда включён)
  • Назначение: Рендеринг конфигурации плагина с помощью 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, если хост в данный момент находится в режиме оверлея (прозрачный оверлей поверх окна игры). Используйте это для настройки рендеринга — например, рисование на игровом оверлее или в окне настроек.

Состояние 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

Считывает строку ASCII с нулевым завершением из памяти игры (максимум 128 символов).

ReadUnicodeString(address)wstring

Считывает строку Unicode (wide) с нулевым завершением из памяти игры (максимум 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)

API UI-элементов (SDK v5)

SDK v5 добавляет прямое чтение UI-элементов без механизма отладочного наблюдения. Навигация по дереву UI, проверка видимости, чтение текста и вычисление экранных прямоугольников.

ReadUiElement(addr) -> UiElementData

Чтение основных свойств UI-элемента (позиция, масштаб, флаги видимости, количество дочерних, адрес родителя).

GetUiChildren(addr) -> vector<uintptr_t>

Возвращает адреса всех дочерних элементов.

GetUiChildAt(addr, index) -> uintptr_t

Возвращает адрес дочернего элемента по индексу. Возвращает 0, если индекс вне диапазона.

ReadUiChildChain(root, indices, count) -> uintptr_t

Навигация по пути дочерних индексов от корневого элемента. Например, {5, 1, 2} обходит root -> child[5] -> child[1] -> child[2]. Возвращает 0, если любой шаг не удался.

IsUiElementVisible(addr) -> bool

Возвращает true, если элемент и все его предки видимы.

GetUiStringId(addr) -> string

Возвращает строковый идентификатор элемента (например, "Stash", "Log In").

ComputeUiScreenRect(addr, outX, outY, outW, outH) -> bool

Вычисляет экранный прямоугольник элемента с полным рекурсивным масштабированием. Возвращает false, если адрес элемента невалиден.

GetUiText(addr) -> string

Возвращает текстовое содержимое элемента.

API чтения компонентов (SDK v5)

SDK v5 предоставляет 21 типизированную функцию чтения компонентов. Каждая принимает адрес компонента (из EntityComponentCache) и возвращает структуру данных с флагом Valid.

Функция Возвращаемая структура Компонент
ReadLifeComponent(addr) PluginLifeData Life (здоровье/ES/мана)
ReadRenderComponent(addr) PluginRenderData Render (мировая позиция, границы модели)
ReadPositionedComponent(addr) PluginPositionedData Positioned (реакция, флаг дружественности)
ReadTargetableComponent(addr) PluginTargetableData Targetable (флаги доступности для выделения)
ReadChestComponent(addr) PluginChestData Chest (состояние открытия, качество)
ReadShrineComponent(addr) PluginShrineData Shrine (флаг доступности)
ReadStackComponent(addr) PluginStackData Stack (текущий и максимальный размер)
ReadChargesComponent(addr) PluginChargesData Charges (текущие, максимальные, за использование)
ReadPlayerComponent(addr) PluginPlayerData Player (имя, опыт, уровень, класс)
ReadAnimatedComponent(addr) PluginAnimatedData Animated (путь анимации, ID)
ReadTransitionableComponent(addr) PluginTransitionableData Transitionable (флаг)
ReadTriggerableBlockageComponent(addr) PluginTriggerableBlockageData TriggerableBlockage (заблокирован, мин/макс)
ReadMinimapIconComponent(addr) PluginMinimapIconData MinimapIcon (флаг иконки)
ReadStateMachineComponent(addr) PluginStateMachineData StateMachine (текущие значения состояний)
ReadBaseComponent(addr) PluginBaseData Base (размер ячейки предмета, флаги влияния)
ReadModsComponent(addr) PluginModsData Mods (редкость, опознан, списки модов)
ReadStatsComponent(addr) PluginStatsData Stats (пары ключ-значение характеристик)
ReadBuffsComponent(addr) PluginBuffsData Buffs (список активных баффов)
ReadActorComponent(addr) PluginActorData Actor (анимация, навыки, счётчики деплоя)
ReadNpcComponent(addr) PluginNpcData NPC (флаг скрытости, путь иконки)
ReadDiesAfterTimeComponent(addr) PluginDiesAfterTimeData DiesAfterTime (общее время)

Вспомогательные хелперы (SDK v5)

Встроенные вспомогательные методы PluginContext, оборачивающие функции чтения компонентов для типичных операций:

Хелпер Сигнатура Описание
GetHealthPercent float (uintptr_t lifeAddr) Процент HP (0-100)
GetEsPercent float (uintptr_t lifeAddr) Процент энергетического щита (0-100)
GetManaPercent float (uintptr_t lifeAddr) Процент маны (0-100)
IsAlive bool (uintptr_t lifeAddr) True, если HP > 0
IsChestOpenedHelper bool (uintptr_t chestAddr) True, если сундук открыт
GetWorldPosition bool (uintptr_t renderAddr, float* x, float* y, float* z) Извлечение мировой позиции из Render
GetItemRarityFromMods int (uintptr_t modsAddr) Редкость предмета из компонента Mods
IsItemIdentifiedHelper bool (uintptr_t modsAddr) True, если предмет опознан
GetStackCountHelper int (uintptr_t stackAddr) Текущий размер стака
GetPlayerNameHelper string (uintptr_t playerAddr) Имя персонажа игрока

Пример использования SDK v5

// Чтение здоровья сущности через чтение компонентов
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);
        }
    }
}

// Навигация по дереву UI
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> Модификаторы горнила
Rarity int 0=Обычный, 1=Магический, 2=Редкий, 3=Уникальный

ItemModData

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

EntityComponentCache

Адреса компонентов, кэшированные для каждой сущности (доступны через RadarEntity.ComponentCache):

Поле Тип Has-метод
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 Путь анимации (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=Префикс, 2=Суффикс, 3=Неявный
Value0 float Первое значение (NaN, если отсутствует)
Value1 float Второе значение (NaN, если отсутствует)

UiElementData (SDK v5)

Свойства UI-элемента из ReadUiElement():

Поле Тип Описание
Valid bool Успешно ли выполнено чтение
X / Y float Позиция элемента
Width / Height float Размер элемента
ScaleX / ScaleY float Коэффициенты масштабирования
IsVisible bool Флаг видимости
IsEnabled bool Флаг активности
ChildCount int Количество дочерних элементов
ParentAddr uintptr_t Адрес родительского элемента
SelfAddr uintptr_t Указатель на себя

Структуры данных компонентов (SDK v5)

Все функции чтения компонентов возвращают структуры с полем Valid. Основные структуры:

PluginLifeDataHealth, EnergyShield, Mana (каждое — PluginVitalData с Current, Total, Regeneration, ReservedFlat, ReservedPercent)

PluginRenderDataWorldX/Y/Z, GridX/Y, TerrainHeight, ModelBoundsX/Y/Z

PluginPositionedDataReaction, IsFriendly, GridX/Y

PluginTargetableDataIsTargetable, IsHighlightable, IsTargettedByPlayer, HiddenFromPlayer, MeetsQuestState, MeetsItemRequirements

PluginChestDataIsOpened, IsLocked, Quality

PluginShrineDataIsAvailable

PluginStackDataCurrentSize, MaxSize

PluginChargesDataCurrentCharges, MaxCharges, ChargesPerUse

PluginPlayerDataName, XP, Level, ClassId, ClassNameHash

PluginAnimatedDataAnimationPath, AnimationId, IsAnimating

PluginTransitionableDataFlag

PluginTriggerableBlockageDataIsBlocked, MinValue, MaxValue

PluginMinimapIconDataIsIcon

PluginStateMachineDataCurrentState1, CurrentState2, CurrentState3

PluginBaseDataCellSizeX, CellSizeY, IsElder, IsShaper, IsHunter, IsCrusader, IsRedeemer, IsWarlord

PluginModsDataRarity, IsIdentified, ItemLevel, RequiredLevel, IsMirrored, IsSplit, IsCorrupted, ImplicitMods, ExplicitMods, EnchantMods, HellscapeMods, CrucibleMods (каждое — vector<PluginModData> с Key и Values)

PluginStatsDataStats (vector<pair<int,int>> из ID характеристики в значение)

PluginBuffsDataBuffs (vector<PluginBuffData> с Name, TotalTime, TimeLeft, Charges, FlaskSlot, Effectiveness, SourceEntityId)

PluginActorDataAnimationId, AnimationName, ActiveSkills (vector<PluginActiveSkillData>), DeployedCounts[256]

PluginNpcDataIsHidden, IconPath

PluginDiesAfterTimeDataTotalTime

Перечисления

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().

Идентификаторы окон

Всегда используйте уникальные ID окон, чтобы избежать конфликтов с хостом или другими плагинами:

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) {
        // === DRAGGABLE MODE ===
        // Window with background, drag hint, interactive controls
        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 {
        // === NON-INTERACTIVE MODE ===
        // Static overlay — no drag, no mouse interaction
        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. Устранение неполадок

Проблема Решение
Plugin not loading Проверьте, что имя DLL точно совпадает с именем папки
"SDK version mismatch" Пересоберите плагин с последними заголовками SDK (текущая версия: 5)
LoadLibrary error 126 У DLL есть неразрешённые зависимости. Для сторонних библиотек, таких как SQLite3, компилируйте их статически в DLL (см. Раздел 9). Используйте dumpbin /dependents YourPlugin.dll для проверки.
Crash on load Проверьте несовпадение CRT — оба должны использовать /MD
ImGui not rendering Убедитесь, что ImGui::SetCurrentContext() вызван в SetContext()
Data is empty/zero Проверьте IsAttached() и IsInGame() перед чтением данных
Inventory is empty Вызовите RequestInventoryScan(-1) — данные инвентаря загружаются по запросу
"Missing exports" error Убедитесь, что CreatePlugin и DestroyPlugin экспортированы с extern "C"
Plugin crashes host Этого не должно происходить — все вызовы плагинов защищены SEH. Проверьте логи.
Stale data GetSnapshot() возвращает данные последнего кадра. Не кэшируйте указатель.
Memory read returns 0 Убедитесь, что IsAttached() возвращает true и адрес валиден
WorldToScreen returns false Позиция может быть за камерой или за пределами экрана
Overlay window not clickable Хост использует WS_EX_TRANSPARENT, когда меню скрыто. Используйте IsMenuVisible() для показа интерактивных элементов только при активном меню. См. паттерн перетаскиваемого оверлея в Разделе 6.
Kill/death detection doesn't work Мёртвые сущности удаляются из снимка. Используйте обнаружение по исчезновению вместо отслеживания перехода HP. См. Раздел 8.
C2491 "dllimport function" errors Заголовки вашей сторонней библиотеки определяют __declspec(dllimport). Создайте локальный заголовок-переопределение, устанавливающий макрос API в пустое значение (см. пример SQLite3 в Разделе 9).

← Home

Clone this wiki locally