Skip to content

Plugin Development Guide FR

Lafko edited this page Apr 9, 2026 · 14 revisions

← Home


Guide de développement de plugins

1. Pour commencer

Prérequis

  • MSVC v143 (Visual Studio 2022)
  • C++20 (/std:c++20)
  • Build x64 Release
  • Bibliothèque d'exécution : /MD (Multi-threaded DLL) — doit correspondre à l'hôte

Configuration du projet

  1. Créez un nouveau projet C++ DLL dans Visual Studio
  2. Configurez le chemin d'inclusion vers le répertoire source de POEFixer (pour les en-têtes SDK et ImGui)
  3. Ajoutez les fichiers source ImGui à votre projet : imgui.cpp, imgui_draw.cpp, imgui_tables.cpp, imgui_widgets.cpp
  4. Incluez les en-têtes du Plugin SDK dans votre code source de plugin :
    #include "plugin_sdk/PluginAPI.h"
    #include "plugin_sdk/PluginContext.h"
    #include "imgui/imgui.h"
    Ou utilisez l'en-tête de commodité d'ExamplePlugin :
    #include "sdk/PluginHelpers.h"  // Includes all SDK headers + MemoryReader + utilities
  5. Définissez PLUGIN_EXPORTS et _CRT_SECURE_NO_WARNINGS dans les définitions de préprocesseur de votre projet

Structure des dossiers

Plugins/
  YourPlugin/
    YourPlugin.dll      <-- Le nom de la DLL DOIT correspondre au nom du dossier
    config/
      settings.txt      <-- Fichier de paramètres optionnel
    data/
      ...               <-- Répertoire de données optionnel (bases de données, caches, etc.)

Structure du projet ExamplePlugin

L'ExamplePlugin illustre la structure de projet recommandée :

Plugins/ExamplePlugin/
  ExamplePlugin.cpp        <-- Point d'entrée principal du plugin + exports factory
  sdk/
    PluginHelpers.h        <-- MemoryReader, WideToNarrow, utilitaires d'entité/rareté
  examples/
    ExampleBuffs.h         <-- Liste de buffs avec filtrage et barres de progression
    ExampleEntities.h      <-- Liste d'entités de débogage avec mécanisme de surveillance, arbres de composants, dump JSON
    ExampleInventory.h     <-- ServerData, sélecteur d'inventaire, grille de slots, mods d'items avec rareté
    ExampleMemory.h        <-- Visualiseur hex, démo Read<T>, scanner de patterns
    ExampleUiExplorer.h    <-- Explorateur complet d'éléments UI avec recherche, navigation, mise en surbrillance

Structure du plugin KillCount

Un exemple de plugin plus complet avec SQLite3, atlas d'icônes et rendu d'overlay :

Plugins/KillCount/
  KillCount.cpp            <-- Point d'entrée principal du plugin, cycle de vie IPlugin, UI des paramètres
  KillCount.h              <-- Déclaration de la classe du plugin
  KillTracker.cpp/h        <-- Moteur de comptage des kills/coffres/morts
  OverlayRenderer.cpp/h    <-- Overlay ImGui avec pattern de déplacement par glisser
  IconAtlas.cpp/h           <-- Chargement de textures de feuilles de sprites (D3D11 + stb_image)
  Database.cpp/h           <-- Wrapper SQLite3 pour les statistiques persistantes
  DisplaySettings.h        <-- Structure de paramètres
  sdk/
    PluginHelpers.h        <-- Copié depuis ExamplePlugin
  lib/
    sqlite3.c/h            <-- Amalgamation SQLite3 (compilé en C)
    sqlite3-vcpkg-config.h <-- Remplacement local pour le linkage statique

Convention de nommage

Le nom du fichier DLL doit correspondre exactement au nom du dossier :

  • Dossier : Plugins/MyPlugin/ → DLL : MyPlugin.dll
  • L'hôte scanne chaque sous-dossier dans Plugins/ et cherche <FolderName>.dll

2. Cycle de vie du plugin

Load DLL (LoadLibrary)
  → CreatePlugin()           -- Factory : instancier votre IPlugin
  → SetContext(ctx)           -- Recevoir les services de l'hôte
  → SetPluginDirectory(dir)   -- Recevoir le chemin de votre dossier
  → GetSDKVersion()           -- Vérification de compatibilité
  → GetName()                 -- Nom d'affichage pour l'UI
  → [if enabled] OnEnable()  -- Initialiser les ressources
  ↓
  Boucle principale (chaque frame) :
    → DrawUI()                -- Rendre l'overlay (uniquement si activé)
    → DrawSettings()          -- Rendre les paramètres dans l'onglet Plugins
    → WantsOverlay()          -- L'hôte vérifie si le plugin veut le mode overlay
  ↓
  Périodiquement / à l'arrêt :
    → SaveSettings()          -- Sauvegarder les paramètres
  ↓
  → OnDisable()               -- Libérer les ressources
  → DestroyPlugin(plugin)     -- Factory : supprimer votre IPlugin
  → FreeLibrary               -- Décharger la DLL

Threads

  • Toutes les méthodes Draw* sont appelées sur le thread principal/de rendu
  • GetSnapshot() et les autres fonctions PluginContext sont thread-safe
  • NE créez PAS de threads qui appellent ImGui — ImGui n'est pas thread-safe

3. Référence de l'interface IPlugin

Chaque plugin doit implémenter l'interface IPlugin (définie dans plugin_sdk/PluginAPI.h) :

void SetPluginDirectory(const char* dir)

  • Quand appelé : Une fois, immédiatement après la création
  • Paramètre : Chemin relatif comme "Plugins/YourPlugin"
  • Objectif : Stockez ce chemin pour charger les paramètres/ressources

void SetContext(PluginContext* context)

  • Quand appelé : Une fois, après SetPluginDirectory
  • Paramètre : Pointeur vers le PluginContext de l'hôte (valide pendant toute la durée de vie du plugin)
  • Objectif : Stockez ce pointeur — c'est votre passerelle vers toutes les données du jeu
  • Important : Appelez ImGui::SetCurrentContext(ctx->ImGuiContext) ici

void OnEnable(bool isGameOpened)

  • Quand appelé : Quand l'utilisateur active le plugin, ou au démarrage s'il était précédemment activé
  • Paramètre : true si le processus du jeu est actuellement attaché
  • Objectif : Charger les paramètres, allouer les ressources, initialiser l'état

void OnDisable()

  • Quand appelé : Quand l'utilisateur désactive le plugin
  • Objectif : Libérer les ressources, arrêter le travail en arrière-plan

void DrawUI()

  • Quand appelé : Chaque frame, uniquement quand le plugin est activé
  • Objectif : Rendre l'overlay avec ImGui
  • Note : Utilisez des identifiants de fenêtre uniques comme "MyWindow##MyPlugin" pour éviter les conflits

void DrawSettings()

  • Quand appelé : Chaque frame, dans l'onglet des paramètres Plugins (uniquement quand activé)
  • Objectif : Rendre la configuration du plugin avec ImGui

void SaveSettings()

  • Quand appelé : Périodiquement et à l'arrêt de l'application
  • Objectif : Sauvegarder vos paramètres sur le disque (par ex. Plugins/YourPlugin/config/settings.txt)

const char* GetName()

  • Retourne : Le nom d'affichage dans l'onglet Plugins (par ex. "My Plugin")

int GetSDKVersion()

  • Retourne : PLUGIN_SDK_VERSION (actuellement 5)
  • Objectif : L'hôte vérifie la compatibilité — doit correspondre

bool WantsOverlay() (SDK v2)

  • Retourne : true si le plugin veut rendre en mode overlay (overlay transparent par-dessus le jeu)
  • Par défaut : false — le plugin ne rend que dans la fenêtre de paramètres normale
  • Objectif : Quand un plugin retourne true, l'hôte passe en mode overlay même si aucune fonctionnalité intégrée ne l'exige

Exports factory

Votre DLL doit exporter ces deux fonctions C :

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

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

4. Référence de l'API PluginContext

La structure PluginContext (définie dans plugin_sdk/PluginContext.h) fournit des pointeurs de fonctions pour accéder aux données du jeu. Tous les types se trouvent dans le namespace PluginSDK.

Accès aux données du jeu

GetSnapshot()shared_ptr<const PluginGameSnapshot>

Retourne un instantané complet de l'état du jeu. Mis à jour une fois par frame. Contient :

Champ Type Description
CurrentState GameStateTypes État actuel du jeu
CurrentAreaName string Nom de la zone (par ex. "The Riverways")
CurrentAreaHash string Hash unique de l'instance de zone
CurrentAreaLevel uint8_t Niveau des monstres de la zone actuelle
IsTown bool True si en ville
IsHideout bool True si dans le repaire
IsPaused bool True si le jeu est en pause
IsSkillTreeVisible bool True si le panneau de l'arbre de compétences est ouvert
WorldToGridConvertor float Facteur de conversion monde→grille
Player RadarEntity Données de l'entité du joueur local
Entities vector<RadarEntity> Toutes les entités à proximité
LargeMap / MiniMap MapData Données de l'overlay de carte
Vitals PlayerVitals HP/ES/MP du joueur + buffs
ScreenWidth / ScreenHeight int Dimensions de la fenêtre du jeu
ProcessId DWORD ID du processus du jeu
GameWindow HWND Handle de la fenêtre du jeu
GameWindowForeground bool True si la fenêtre du jeu est au premier plan
IsAttached bool True si attaché au processus du jeu
IsWindowValid bool True si la fenêtre du jeu est valide
LastUpdateTime uint64_t Horodatage de la dernière mise à jour des données
AreaChangeCounter uint64_t S'incrémente lors d'un changement de zone
Inventories vector<InventoryInfo> Contenu de l'inventaire du joueur
CurrencyTotals map<string,int> Comptages de devises par chemin
InventoryGrid InventoryGridInfo Informations sur la grille de l'UI d'inventaire
WorldToScreenMatrix XMFLOAT4X4 Matrice de projection 3D→2D

Important : Filtrage des entités Les entités mortes (celles avec EntityState == Useless) sont filtrées de l'instantané avant que les plugins ne le reçoivent. Cela signifie que vous n'observerez jamais une transition de HP de vivant à mort. Si vous devez détecter les kills, utilisez plutôt la détection par disparition — suivez les identifiants d'entités par zone et comptez-les comme tués quand ils disparaissent de la liste d'entités alors qu'ils se trouvaient dans la proximité InnerCircle ou OuterCircle. Voir Section 8 : Recettes courantes pour les détails.

GetPlayerVitals()PlayerVitals

Raccourci de commodité pour les statistiques vitales du joueur.

GetCurrentState()GameStateTypes

Retourne l'énumération de l'état actuel du jeu.

IsAttached()bool

True si le processus du jeu est attaché et lisible.

IsInGame()bool

True si actuellement en jeu (pas en chargement, pas sur l'écran de connexion).

IsGameForeground()bool

True si la fenêtre du jeu est la fenêtre au premier plan.

GetProcessId()DWORD

Retourne l'ID du processus du jeu.

Accès aux données des items

ReadExtendedItemMods(entityAddress)ExtendedItemModInfo

Lit tous les modificateurs d'un item.

ReadItemRarity(entityAddress)int

Retourne : 0=Normal, 1=Magique, 2=Rare, 3=Unique

ReadItemStackCount(entityAddress)int

Retourne le nombre dans la pile pour les devises/items empilables.

ReadItemName(entityAddress)string

Retourne le nom du type de base de l'item.

ReadItemPath(entityAddress)string

Retourne le chemin des métadonnées de l'item.

ReadItemBaseTypeName(entityAddress)string

Retourne le nom du type de base de l'item (par ex. "Divine Orb", "Chaos Orb"). Contrairement à ReadItemName qui retourne le chemin des métadonnées, cette fonction lit le nom réel du type de base depuis BaseItemTypeData.BaseTypeName.

ReadItemUniqueName(entityAddress)string

Retourne le nom unique de l'item depuis Words.dat (par ex. "Headhunter", "Brimstone Call"). Retourne une chaîne vide pour les items non-uniques.

Mode overlay (SDK v2)

IsOverlayMode()bool

Retourne true si l'hôte est actuellement en mode overlay (overlay transparent par-dessus la fenêtre du jeu). Utilisez ceci pour ajuster votre rendu — par ex. dessiner sur l'overlay du jeu vs. dessiner dans une fenêtre de paramètres.

État de l'UI (SDK v4)

IsMenuVisible()bool

Retourne true quand le menu de paramètres de l'hôte est visible (l'overlay est interactif). Quand le menu est caché, la fenêtre de l'overlay est transparente aux clics (WS_EX_TRANSPARENT), donc les fenêtres ImGui ne peuvent pas recevoir d'entrées souris.

Utilisez ceci pour implémenter le pattern d'overlay déplaçable :

  • Menu visible : Afficher la poignée de déplacement, permettre l'interaction (onglets, boutons)
  • Menu caché : Retirer la poignée de déplacement, ajouter ImGuiWindowFlags_NoInputs pour rendre la fenêtre non-interactive

Voir Section 6 : Pattern d'overlay déplaçable pour l'implémentation complète.

Lecture de mémoire (SDK v2)

Accès direct à la mémoire du processus du jeu. Toutes les lectures sont sûres (retournent 0/vide en cas d'échec).

GetBaseAddress()uintptr_t

Retourne l'adresse de base du module exécutable du jeu. Retourne 0 si non attaché.

GetModuleSize()uintptr_t

Retourne la taille du module du jeu en octets. Retourne 0 si non attaché.

ReadProcessMemory(address, buffer, size)bool

Lit un bloc d'octets bruts depuis le processus du jeu. Le buffer doit avoir au moins size octets alloués. Retourne true en cas de succès.

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

Lit une chaîne ASCII terminée par null depuis la mémoire du jeu (max 128 caractères).

ReadUnicodeString(address)wstring

Lit une chaîne Unicode (wide) terminée par null depuis la mémoire du jeu (max 128 wchars).

GetPatternAddress(patternName)uintptr_t

Obtient une adresse de scan de pattern résolue par nom. Retourne 0 si non trouvé.

Patterns standards :

Nom Description
"Game States" Racine du vecteur GameStates
"File Root" Registre des fichiers
"AreaChangeCounter" Compteur de transitions de zone
"Terrain Rotator Helper" Données de rotation
"Terrain Rotation Selector" Sélecteur de rotation
"GameCullSize" Valeur de culling d'écran

Projection monde-vers-écran (SDK v2)

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

Convertit une position dans l'espace monde en coordonnées d'écran. Retourne true si la position est visible à l'écran.

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

Inventaire (SDK v2)

RequestInventoryScan(inventoryId)

Demande à l'hôte de scanner les inventaires. Passez -1 pour scanner tous les inventaires, ou un identifiant d'inventaire spécifique. Les données d'inventaire dans l'instantané sont remplies après la fin du scan (frame suivante).

Note : Les données d'inventaire ne sont pas rafraîchies automatiquement — vous devez appeler cette fonction pour déclencher un scan. Appelez-la périodiquement (par ex. toutes les 2 secondes) si vous avez besoin de données d'inventaire continues.

Données de terrain (SDK v2)

GetWalkableGrid(outWidth, outHeight)const uint8_t*

Retourne un pointeur vers les données de la grille de praticabilité. La grille est un tableau 2D où 0 = non praticable, non-zéro = praticable. Retourne nullptr si les données ne sont pas disponibles.

GetTerrainHeight(gridX, gridY)float

Retourne la hauteur du terrain à une position de grille. Retourne 0 si hors limites ou données non disponibles.

Lecture de conteneurs natifs (SDK v3)

Ces fonctions lisent les conteneurs de la bibliothèque standard C++ directement depuis la mémoire du jeu, reproduisant les méthodes Core::Process de l'hôte.

ReadStdVector(containerAddress, elementSize, outCount)void*

Lit un StdVector (structure de 24 octets : {First, Last, End}) depuis la mémoire du jeu. Retourne un tampon d'éléments alloué par malloc. L'appelant doit libérer le pointeur retourné avec free(). Retourne nullptr en cas d'échec.

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

Lit une StdList (structure de 16 octets : {Head, Size}) depuis la mémoire du jeu. Parcourt la liste chaînée et retourne un tampon contigu. L'appelant doit appeler free().

ReadStdBucket(containerAddress, elementSize, outCount)void*

Lit un StdBucket depuis la mémoire du jeu (lit le StdVector intégré). L'appelant doit appeler free().

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

Parcourt une StdMap (structure de 16 octets : {Head, Size}) et appelle callback pour chaque paire clé-valeur. Retourne le nombre de nœuds visités.

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

Lit un StdWString (structure de 32 octets avec tampon en ligne/sur le tas) depuis la mémoire du jeu.

GetInventoryName(inventoryId)const char*

Retourne le nom lisible pour un identifiant d'inventaire (par ex. 1 → "MainInventory1", 3 → "Weapon1", 64 → "Currency1").

Accès aux données de débogage (SDK v4)

Le SDK v4 fournit un accès direct aux données de débogage de l'hôte — composants d'entités, détails d'inventaire et arborescence d'éléments UI — correspondant aux onglets Debug intégrés.

Liste d'entités de débogage

GetEntityDebugList()vector<DebugEntityInfo>

Retourne une liste de toutes les entités avec des métadonnées de débogage (Id, Address, Path, Type, SubType, State, Rarity, Zone). Correspond à l'onglet Debug→Entity List.

WatchEntity(entityId)

Commence la surveillance des composants d'une entité. Le thread worker de l'hôte lira les données complètes des composants de cette entité à chaque frame.

UnwatchEntity(entityId)

Arrête la surveillance des composants d'une entité. Appelez ceci quand l'utilisateur réduit le nœud d'arbre de l'entité pour libérer des ressources.

GetWatchedEntityData(entityId)DebugEntityComponents

Retourne les données complètes des composants pour une entité surveillée. Contient des sous-structures pour les 8 composants reconnus (Life, Render, Positioned, Targetable, Animated, Stats, Actor, Buffs) plus la liste de toutes les adresses de composants.

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

Débogage d'inventaire

GetServerDataAddress()uintptr_t

Retourne l'adresse de base du composant ServerData.

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

Retourne tous les identifiants et adresses d'inventaire du joueur (depuis ServerData).

WatchInventory(inventoryId)

Commence la surveillance d'un inventaire pour une inspection de débogage détaillée. L'hôte lit l'occupation des slots, les détails des items et les modificateurs.

GetWatchedInventoryData()DebugInventoryData

Retourne les données complètes de l'inventaire actuellement surveillé : dimensions de la grille, occupation des slots, items avec rareté et modificateurs.

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

Arborescence des éléments UI

GetGameUiRootAddress()uintptr_t

Retourne l'adresse de l'élément UI racine du jeu (pour la navigation dans l'arborescence UI en jeu).

GetUiRootAddress()uintptr_t

Retourne l'adresse de l'élément UI racine de niveau supérieur.

GetGameCullValue()int

Retourne la valeur actuelle de GameCullSize, utilisée pour les calculs de mise à l'échelle de l'UI. Combinée avec les dimensions de l'écran, cela permet un calcul précis de la position/taille des éléments UI.

// Example: UI scale calculation (matching host logic)
int cullValue = m_Context->GetGameCullValue();
auto snapshot = m_Context->GetSnapshot();
// Scale for index 1 (width): screenWidth / (cullValue / baseWidth)
// Scale for index 2 (height): screenHeight / (cullValue / baseHeight)

UI Element API (SDK v5)

(Translation pending)

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

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

Component Reader API (SDK v5)

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

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

Convenience Helpers (SDK v5)

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

SDK v5 Usage Example

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

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

PluginHelpers.h — Wrapper de commodité (SDK v3)

L'en-tête sdk/PluginHelpers.h (inclus avec ExamplePlugin) fournit une classe MemoryReader type-safe qui encapsule les fonctions brutes de 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);

Le MemoryReader fournit également des wrappers de commodité pour ReadString(), ReadUnicodeString(), GetBaseAddress(), GetModuleSize() et GetPatternAddress().

Utilitaires supplémentaires dans PluginHelpers.h :

  • WideToNarrow(wstring) — conversion sûre wstring→string (perte ASCII)
  • GetEntityTypeName(type) — enum vers nom d'affichage (incluant ExpeditionMarker/ExpeditionRemnant)
  • GetNearbyZoneName(zone) — zone vers nom d'affichage
  • GetRarityName(rarity) / GetRarityColor(rarity) — utilitaires d'affichage de rareté

Services de l'hôte

Log(level, message)

Écrire dans le système de journalisation de l'hôte. Niveaux : "Debug", "Info", "Warning", "Error"

ImGuiContext (void*)

Le contexte ImGui de l'hôte. Appelez ImGui::SetCurrentContext() avec ceci dans SetContext().

D3DDevice (void*)

Le ID3D11Device* de l'hôte. Castez et utilisez pour charger des textures.


5. Référence des structures de données

Tous les types sont dans le namespace PluginSDK. Les plugins ajoutent généralement using namespace PluginSDK;.

RadarEntity

Données par entité disponibles dans snapshot->Entities :

Champ Type Description
Id uint32_t Identifiant unique d'entité
Address uintptr_t Adresse mémoire (pour les appels d'API d'items)
EntityDetailsAddress uintptr_t Adresse de la structure de détails d'entité
RenderComponentAddress uintptr_t Adresse du composant Render (raccourci)
IsValid bool Drapeau de validité de l'entité
entityType EntityTypes Catégorie d'entité
entitySubtype EntitySubtypes Sous-catégorie d'entité
entityState EntityStates État de l'entité
Rarity int 0=Normal, 1=Magique, 2=Rare, 3=Unique
Reaction uint8_t 0=Hostile, 1=Neutre, 2=Amical
GridPositionX/Y float Position sur la grille du terrain
TerrainHeight float Hauteur du terrain à la position de l'entité
WorldX/Y/Z float Position dans l'espace monde
ModelBoundsZ float Hauteur du modèle
Path wstring Chemin des métadonnées de l'entité
PlayerName wstring Nom du joueur (si entité joueur)
TgtPath string Chemin cible (chaîne étroite)
CurrentHP/MaxHP int Santé de l'entité
CurrentES/MaxES int Bouclier d'énergie de l'entité
IsSleeping bool Drapeau d'entité éloignée
IsChestOpened bool État d'ouverture du coffre
Zone NearbyZone Proximité avec le joueur
ComponentCache EntityComponentCache Adresses des composants

Important : Les entités mortes (EntityState::Useless) sont supprimées de l'instantané avant qu'il n'atteigne les plugins. Vous ne verrez jamais les HP d'un monstre tomber à zéro — il disparaît simplement de la liste. Utilisez le champ Zone pour distinguer les kills (l'entité a disparu d'InnerCircle/OuterCircle) des entités hors de portée (l'entité était dans la zone Far).

Buff

Buff/debuff actif :

Champ Type Description
Name string Nom interne du buff (par ex. "flask_effect_life")
TimeLeft float Secondes restantes
Charges short Nombre de piles
TotalTime float Durée totale

MapData

État de la minicarte/grande carte :

Champ Type Description
CenterX/Y float Centre de la carte
SizeX/Y float Dimensions de la carte
ShiftX/Y float Décalage de panoramique actuel
DefaultShiftX/Y float Valeurs de décalage par défaut
Zoom float Niveau de zoom
Scale float Facteur d'échelle de la carte
IsVisible bool La carte est actuellement affichée

InventoryInfo / InventoryItemInfo

Champ Type Description
Id int Identifiant d'inventaire
TotalBoxesX/Y int Dimensions de la grille
Ptr uintptr_t Adresse mémoire de l'inventaire
Items vector<InventoryItemInfo> Items dans l'inventaire

Champs d'item : Address, Name (chemin des métadonnées), Path (identique à Name), BaseTypeName (nom du type de base, par ex. "Divine Orb"), UniqueName (nom unique de l'item depuis Words.dat, par ex. "Headhunter", vide pour les non-uniques), SlotX/Y, Width/Height, StackCount, IsCurrency

ExtendedItemModInfo

Retourné par ReadExtendedItemMods() :

Champ Type Description
ImplicitMods vector<ItemModData> Modificateurs implicites
ExplicitMods vector<ItemModData> Modificateurs explicites
EnchantMods vector<ItemModData> Modificateurs d'enchantement
HellscapeMods vector<ItemModData> Modificateurs Hellscape
CrucibleMods vector<ItemModData> Modificateurs de creuset
Rarity int 0=Normal, 1=Magique, 2=Rare, 3=Unique

ItemModData

Champ Type Description
Key string Clé de statistique du mod
Values vector<float> Valeurs du jet du mod

EntityComponentCache

Adresses de composants mises en cache par entité (disponibles via RadarEntity.ComponentCache) :

Champ Type Méthode 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)

Métadonnées d'entité depuis GetEntityDebugList() :

Champ Type Description
Id uint32_t Identifiant d'entité
Address uintptr_t Adresse mémoire
Path string Chemin des métadonnées
EntityType int Type d'entité (caster vers EntityTypes)
EntitySubType int Sous-type d'entité (caster vers EntitySubtypes)
EntityState int État d'entité (caster vers EntityStates)
Rarity int 0=Normal, 1=Magique, 2=Rare, 3=Unique
Zone NearbyZone Proximité avec le joueur
ComponentAddresses vector<pair<string,uintptr_t>> Toutes les paires nom de composant→adresse

DebugEntityComponents (SDK v4)

Données complètes des composants depuis GetWatchedEntityData() :

Champ Type Description
EntityId uint32_t Pour quelle entité sont ces données
Valid bool Si les données ont été lues avec succès
HasLife / Life bool / DebugLifeComp Composant Life — Life.Health, Life.EnergyShield, Life.Mana (chacun un DebugVital avec .Current, .Total, .Regeneration, .ReservedFlat, .ReservedPercent)
HasRender / Render bool / DebugRenderComp Position (WorldX/Y/Z, GridX/Y), TerrainHeight, ModelBounds (X/Y/Z)
HasPositioned / Positioned bool / DebugPositionedComp Valeur Reaction, drapeau IsFriendly
HasTargetable / Targetable bool / DebugTargetableComp IsTargetable, IsHighlightable, IsTargettedByPlayer, HiddenFromPlayer, MeetsQuestState, MeetsItemRequirements
HasAnimated / Animated bool / DebugAnimatedComp Path d'animation (string), Id (uint32)
HasStats / Stats bool / DebugStatsComp CurrentWeaponIndex, IsShapeshifted, StatsItems/StatsBuff (vecteurs de paires ID de stat→valeur)
HasActor / Actor bool / DebugActorComp AnimationId, AnimationName, ActiveSkills (vecteur de DebugActiveSkill), DeployedCounts[256]
HasBuffs / Buffs bool / vector<DebugBuff> Buffs actifs : Name, TotalTime, TimeLeft, Charges, FlaskSlot, Effectiveness, SourceEntityId

DebugInventoryData (SDK v4)

Détails d'inventaire depuis GetWatchedInventoryData() :

Champ Type Description
InventoryId int Identifiant d'inventaire (-1 si aucun)
Address uintptr_t Adresse de l'inventaire
TotalBoxesX/Y int Dimensions de la grille
ServerRequestCounter int Compteur de synchronisation serveur
GridScreenX / GridScreenY float Position de la grille UI à l'écran
CellSize float Taille d'une cellule de grille en pixels
GridValid bool Si les données de la grille UI sont valides
SlotOccupied vector<bool> Occupation par slot
Items vector<DebugInventoryItem> Items avec chemin, rareté et modificateurs

DebugInventoryItem (SDK v4)

Champ Type Description
Address uintptr_t Adresse de l'entité item
Path string Chemin des métadonnées de l'item
BaseTypeName string Nom du type de base (par ex. "Divine Orb")
UniqueName string Nom unique de l'item depuis Words.dat (vide pour les non-uniques)
SlotX / SlotY int Position dans la grille de l'inventaire
Rarity int 0=Normal, 1=Magique, 2=Rare, 3=Unique
ItemLevel int Niveau de l'item
RequiredLevel int Niveau de personnage requis
IsIdentified bool Si l'item est identifié
IsCorrupted bool Si l'item est corrompu
CraftedModCount int Nombre de mods artisanaux
ImplicitMods vector<DebugModInfo> Modificateurs implicites
ExplicitMods vector<DebugModInfo> Modificateurs explicites
EnchantMods vector<DebugModInfo> Modificateurs d'enchantement
HellscapeMods vector<DebugModInfo> Modificateurs Hellscape

DebugActiveSkill (SDK v4)

Champ Type Description
Name string Nom de la compétence
UseStage int Étape d'utilisation actuelle
CastType int Type de lancement
TotalUses int Nombre total d'utilisations
TotalCooldownTimeInMs int Temps de recharge en millisecondes
CanBeUsed bool Si la compétence peut actuellement être utilisée

DebugBuff (SDK v4)

Champ Type Description
Name string Nom interne du buff
TotalTime float Durée totale
TimeLeft float Secondes restantes
Charges short Nombre de piles
FlaskSlot short Index du slot de fiole
Effectiveness short Efficacité du buff
SourceEntityId uint32_t Entité qui a appliqué ce buff

DebugModInfo (SDK v4)

Champ Type Description
Name string Nom d'affichage du mod
StatKey string Identifiant de clé de statistique
AffixName string Nom de l'affixe
GenerationType int 1=Préfixe, 2=Suffixe, 3=Implicite
Value0 float Première valeur (NaN si aucune)
Value1 float Deuxième valeur (NaN si aucune)

UiElementData (SDK v5)

(Translation pending)

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

Component Data Structs (SDK v5)

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

Énumérations

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 unités de grille), OuterCircle(2, ~120 unités de grille), 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. Utilisation d'ImGui dans les plugins

Contexte partagé

L'hôte et le plugin partagent le même contexte ImGui. Vous devez appeler :

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

dans votre méthode SetContext().

Identifiants de fenêtre

Utilisez toujours des identifiants de fenêtre uniques pour éviter les conflits avec l'hôte ou d'autres plugins :

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

Fonctionnalités disponibles

  • Fenêtres, onglets, arbres, tables, listes de dessin
  • Chargement de textures via le périphérique D3D11
  • Rendu d'overlay via ImGui::GetBackgroundDrawList()
  • Icônes FontAwesome 6 via #include "imgui/IconsFontAwesome6.h" (par ex. ICON_FA_ARROWS_UP_DOWN_LEFT_RIGHT)

Rendu d'overlay (SDK v2)

Utilisez WorldToScreen() pour dessiner des étiquettes/formes aux positions des entités :

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

Mode overlay

Remplacez WantsOverlay() pour retourner true afin de demander à l'hôte d'entrer en mode overlay :

bool WantsOverlay() override { return m_OverlayEnabled; }

En mode overlay, la fenêtre de l'hôte est transparente et positionnée par-dessus le jeu. Vos appels DrawUI() rendent directement sur l'écran du jeu.

Pattern d'overlay déplaçable (SDK v4)

L'overlay de l'hôte utilise WS_EX_TRANSPARENT pour rendre la fenêtre transparente aux clics quand le menu est caché. Cela signifie que les fenêtres ImGui ne peuvent pas recevoir d'entrées souris sauf si le menu est visible. Pour créer une fenêtre d'overlay déplaçable (comme l'overlay Vitals intégré), utilisez ce pattern à double mode :

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

Points clés :

  • ImGuiCond_Appearing définit la position uniquement à la première apparition ; ImGui suit ensuite la position de déplacement
  • NoTitleBar + pas de NoMove = la fenêtre est déplaçable depuis n'importe quelle zone vide (comportement ImGui par défaut)
  • NoInputs en mode non-interactif empêche l'overlay de voler le focus à travers WS_EX_TRANSPARENT
  • Vérifiez toujours le pointeur IsMenuVisible pour la compatibilité ascendante : m_Context->IsMenuVisible ? m_Context->IsMenuVisible() : false
  • Sauvegardez la position dans votre fichier de paramètres pour qu'elle persiste entre les sessions

7. Persistance des paramètres

Pattern recommandé

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
}

Emplacement du fichier

Stockez les paramètres dans <PluginDirectory>/config/ :

std::filesystem::path settingsPath =
    std::filesystem::path(m_Directory) / "config" / "settings.txt";

Format clé-valeur simple

Pour les plugins avec de nombreux paramètres, un format texte simple clé=valeur fonctionne bien :

// 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. Recettes courantes

Obtenir le pourcentage de HP du joueur

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

Lister tous les monstres dans le cercle intérieur

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

Vérifier si le joueur a un buff spécifique

auto vitals = m_Context->GetPlayerVitals();
for (auto& buff : vitals.Buffs) {
    if (buff.Name == "flask_effect_life") {
        // buff.TimeLeft, buff.Charges...
    }
}

Obtenir les informations de la zone actuelle

auto snapshot = m_Context->GetSnapshot();
std::string area = snapshot->CurrentAreaName;
bool isTown = snapshot->IsTown;
int level = snapshot->CurrentAreaLevel;

Dessiner du texte à la position monde d'une entité (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");
    }
}

Lire les mods d'items depuis l'inventaire

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

Compter les entités par type

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

Détecter un changement de zone

static uint64_t lastAreaChange = 0;
auto snapshot = m_Context->GetSnapshot();
if (snapshot->AreaChangeCounter != lastAreaChange) {
    lastAreaChange = snapshot->AreaChangeCounter;
    // Area changed! Reset state...
}

Vérifier l'écran de chargement

if (m_Context->GetCurrentState() == GameStateTypes::AreaLoadingState) {
    // Currently loading...
}

Détecter les kills de monstres (basé sur la disparition)

Les entités mortes sont filtrées de l'instantané (EntityState::Useless), donc vous ne pouvez pas détecter les HP tombant à 0. Au lieu de cela, suivez les entités par ID et détectez quand elles disparaissent des zones proches :

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

Pourquoi cela fonctionne : Les entités dans un rayon de ~120 unités de grille qui disparaissent soudainement ont presque certainement été tuées (et ne se sont pas simplement éloignées). Les entités dans la zone Far apparaissent et disparaissent naturellement de la liste d'entités — ne les comptez pas.

Important : Videz m_PrevEntities lors d'un changement de zone (AreaChangeCounter a changé) pour éviter les faux positifs.

Lire la mémoire brute du jeu (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);

Utiliser les résultats de scan de patterns (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));
}

Vérifier le terrain praticable (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;
    }
}

Lecture de mémoire typée avec 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 */ }

Obtenir le nom d'inventaire (SDK v3)

for (auto& inv : snapshot->Inventories) {
    const char* name = m_Context->GetInventoryName(inv.Id);
    // name is e.g. "MainInventory1", "Weapon1", "Currency1"
}

Lire les adresses des composants d'entité

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

Inspecter les composants d'entité via la surveillance de débogage (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);
    }
}

Inspecter l'inventaire avec la grille de slots (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());
        }
    }
}

Naviguer dans l'arborescence des éléments 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);
        }
    }
}

Lire les mods d'items avec les utilitaires d'affichage (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. Compilation et déploiement

Paramètres de compilation

Paramètre Valeur
Configuration Release
Platform x64
C++ Standard /std:c++20
Runtime Library /MD (Multi-threaded DLL)
Configuration Type DLL

Fichiers requis dans le projet du plugin

  • Votre/vos fichier(s) .cpp de plugin
  • Fichiers source ImGui : imgui.cpp, imgui_draw.cpp, imgui_tables.cpp, imgui_widgets.cpp
  • Chemin d'inclusion vers la racine de POEFixer (pour les en-têtes SDK et ImGui)
  • Optionnel : Copiez Plugins/ExamplePlugin/sdk/PluginHelpers.h pour le wrapper MemoryReader et les fonctions utilitaires

Chemins d'inclusion

Votre .vcxproj devrait avoir ces répertoires d'inclusion supplémentaires :

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

Si vous utilisez des bibliothèques tierces locales (par ex. SQLite3 dans un sous-dossier lib/), ajoutez $(ProjectDir)lib avant le chemin de la solution pour que les en-têtes locaux aient la priorité :

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

Bibliothèques tierces

SQLite3 (Linkage statique)

Pour utiliser SQLite3 dans un plugin, vous devez compiler le source amalgamation directement dans votre DLL — Windows LoadLibrary ne cherche pas les dépendances dans le propre répertoire de la DLL, donc le linkage dynamique de sqlite3.dll échouera avec l'erreur 126.

Étapes :

  1. Copiez sqlite3.c et sqlite3.h dans le répertoire lib/ de votre plugin
  2. Créez lib/sqlite3-vcpkg-config.h pour remplacer SQLITE_API (empêche les erreurs __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. Ajoutez sqlite3.c à votre .vcxproj en tant que fichier C avec les avertissements désactivés :
    <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 (Chargement de textures)

Pour charger des textures depuis des fichiers image (PNG, JPG), incluez stb_image dans un fichier .cpp :

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

Puis utilisez le périphérique D3D11 depuis m_Context->D3DDevice pour créer des textures GPU.

Répertoire de sortie

Définissez votre répertoire de sortie :

$(SolutionDir)x64\Release\Plugins\YourPlugin\

Déploiement

Copiez votre DLL compilée vers Plugins/YourPlugin/YourPlugin.dll à côté de l'exécutable principal.

Débogage

  1. Compilez votre DLL de plugin en mode Debug
  2. Démarrez l'application hôte
  3. Dans Visual Studio : Debug → Attacher au processus → sélectionnez l'exe de l'hôte
  4. Placez des points d'arrêt dans votre code source de plugin
  5. Le débogueur s'arrêtera quand votre code sera appelé

10. Résolution de problèmes

Problème Solution
Plugin not loading Vérifiez que le nom de la DLL correspond exactement au nom du dossier
"SDK version mismatch" Recompilez le plugin avec les derniers en-têtes SDK (version actuelle : 5)
LoadLibrary error 126 La DLL a des dépendances non résolues. Pour les bibliothèques tierces comme SQLite3, compilez-les statiquement dans la DLL (voir Section 9). Utilisez dumpbin /dependents YourPlugin.dll pour vérifier.
Crash on load Vérifiez l'incompatibilité CRT — les deux doivent utiliser /MD
ImGui not rendering Assurez-vous que ImGui::SetCurrentContext() est appelé dans SetContext()
Data is empty/zero Vérifiez IsAttached() et IsInGame() avant de lire les données
Inventory is empty Appelez RequestInventoryScan(-1) — les données d'inventaire sont à la demande
"Missing exports" error Assurez-vous que CreatePlugin et DestroyPlugin sont exportés avec extern "C"
Plugin crashes host Cela ne devrait pas arriver — tous les appels de plugin sont protégés par SEH. Vérifiez les logs.
Stale data GetSnapshot() retourne les données du dernier frame. Ne mettez pas le pointeur en cache.
Memory read returns 0 Vérifiez que IsAttached() retourne true et que l'adresse est valide
WorldToScreen returns false La position peut être derrière la caméra ou hors de l'écran
Overlay window not clickable L'hôte utilise WS_EX_TRANSPARENT quand le menu est caché. Utilisez IsMenuVisible() pour afficher les contrôles interactifs uniquement quand le menu est actif. Voir le pattern d'overlay déplaçable dans la Section 6.
Kill/death detection doesn't work Les entités mortes sont supprimées de l'instantané. Utilisez la détection basée sur la disparition au lieu de la transition HP. Voir Section 8.
C2491 "dllimport function" errors Les en-têtes de votre bibliothèque tierce définissent __declspec(dllimport). Créez un en-tête de remplacement local qui met le macro API à vide (voir l'exemple SQLite3 dans la Section 9).

← Home

Clone this wiki locally