-
Notifications
You must be signed in to change notification settings - Fork 0
Plugin Development Guide FR
- 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
- Créez un nouveau projet C++ DLL dans Visual Studio
- Configurez le chemin d'inclusion vers le répertoire source de POEFixer (pour les en-têtes SDK et ImGui)
- Ajoutez les fichiers source ImGui à votre projet :
imgui.cpp,imgui_draw.cpp,imgui_tables.cpp,imgui_widgets.cpp - Incluez les en-têtes du Plugin SDK dans votre code source de plugin :
Ou utilisez l'en-tête de commodité d'ExamplePlugin :
#include "plugin_sdk/PluginAPI.h" #include "plugin_sdk/PluginContext.h" #include "imgui/imgui.h"
#include "sdk/PluginHelpers.h" // Includes all SDK headers + MemoryReader + utilities
- Définissez
PLUGIN_EXPORTSet_CRT_SECURE_NO_WARNINGSdans les définitions de préprocesseur de votre projet
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.)
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
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
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
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
- 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
Chaque plugin doit implémenter l'interface IPlugin (définie dans plugin_sdk/PluginAPI.h) :
- 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
-
Quand appelé : Une fois, après
SetPluginDirectory -
Paramètre : Pointeur vers le
PluginContextde 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
- Quand appelé : Quand l'utilisateur active le plugin, ou au démarrage s'il était précédemment activé
-
Paramètre :
truesi le processus du jeu est actuellement attaché - Objectif : Charger les paramètres, allouer les ressources, initialiser l'état
- Quand appelé : Quand l'utilisateur désactive le plugin
- Objectif : Libérer les ressources, arrêter le travail en arrière-plan
- 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
- Quand appelé : Chaque frame, dans l'onglet des paramètres Plugins (uniquement quand activé)
- Objectif : Rendre la configuration du plugin avec ImGui
- 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)
-
Retourne : Le nom d'affichage dans l'onglet Plugins (par ex.
"My Plugin")
-
Retourne :
PLUGIN_SDK_VERSION(actuellement 5) - Objectif : L'hôte vérifie la compatibilité — doit correspondre
-
Retourne :
truesi 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
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;
}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.
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éInnerCircleouOuterCircle. Voir Section 8 : Recettes courantes pour les détails.
Raccourci de commodité pour les statistiques vitales du joueur.
Retourne l'énumération de l'état actuel du jeu.
True si le processus du jeu est attaché et lisible.
True si actuellement en jeu (pas en chargement, pas sur l'écran de connexion).
True si la fenêtre du jeu est la fenêtre au premier plan.
Retourne l'ID du processus du jeu.
Lit tous les modificateurs d'un item.
Retourne : 0=Normal, 1=Magique, 2=Rare, 3=Unique
Retourne le nombre dans la pile pour les devises/items empilables.
Retourne le nom du type de base de l'item.
Retourne le chemin des métadonnées de l'item.
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.
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.
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.
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_NoInputspour rendre la fenêtre non-interactive
Voir Section 6 : Pattern d'overlay déplaçable pour l'implémentation complète.
Accès direct à la mémoire du processus du jeu. Toutes les lectures sont sûres (retournent 0/vide en cas d'échec).
Retourne l'adresse de base du module exécutable du jeu. Retourne 0 si non attaché.
Retourne la taille du module du jeu en octets. Retourne 0 si non attaché.
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));Lit une chaîne ASCII terminée par null depuis la mémoire du jeu (max 128 caractères).
Lit une chaîne Unicode (wide) terminée par null depuis la mémoire du jeu (max 128 wchars).
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 |
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");
}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.
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.
Retourne la hauteur du terrain à une position de grille. Retourne 0 si hors limites ou données non disponibles.
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.
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);
}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().
Lit un StdBucket depuis la mémoire du jeu (lit le StdVector intégré). L'appelant doit appeler free().
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);Lit un StdWString (structure de 32 octets avec tampon en ligne/sur le tas) depuis la mémoire du jeu.
Retourne le nom lisible pour un identifiant d'inventaire (par ex. 1 → "MainInventory1", 3 → "Weapon1", 64 → "Currency1").
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.
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.
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.
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.
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);
}
}Retourne l'adresse de base du composant ServerData.
Retourne tous les identifiants et adresses d'inventaire du joueur (depuis ServerData).
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.
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()));
}Retourne l'adresse de l'élément UI racine du jeu (pour la navigation dans l'arborescence UI en jeu).
Retourne l'adresse de l'élément UI racine de niveau supérieur.
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)(Translation pending)
SDK v5 adds direct UI element reading without the debug watch mechanism. Navigate the UI tree, check visibility, read text, and compute screen rectangles.
| Function | Return | Purpose |
|---|---|---|
ReadUiElement(addr) |
UiElementData |
Read core UI element properties |
GetUiChildren(addr) |
vector<uintptr_t> |
Get all child element addresses |
GetUiChildAt(addr, index) |
uintptr_t |
Get single child by index |
ReadUiChildChain(root, indices, count) |
uintptr_t |
Navigate child index path from root |
IsUiElementVisible(addr) |
bool |
Check visibility (including ancestors) |
GetUiStringId(addr) |
string |
Get element's string identifier |
ComputeUiScreenRect(addr, outX, outY, outW, outH) |
bool |
Compute screen rectangle with recursive scaling |
GetUiText(addr) |
string |
Get element's display text |
21 typed component reader functions. Each takes a component address from EntityComponentCache and returns a struct with Valid flag.
| Function | Return Struct | Component |
|---|---|---|
ReadLifeComponent(addr) |
PluginLifeData |
Life (HP/ES/Mana) |
ReadRenderComponent(addr) |
PluginRenderData |
Render (position, bounds) |
ReadPositionedComponent(addr) |
PluginPositionedData |
Positioned (reaction) |
ReadTargetableComponent(addr) |
PluginTargetableData |
Targetable (flags) |
ReadChestComponent(addr) |
PluginChestData |
Chest (opened, quality) |
ReadShrineComponent(addr) |
PluginShrineData |
Shrine (available) |
ReadStackComponent(addr) |
PluginStackData |
Stack (size) |
ReadChargesComponent(addr) |
PluginChargesData |
Charges |
ReadPlayerComponent(addr) |
PluginPlayerData |
Player (name, level) |
ReadAnimatedComponent(addr) |
PluginAnimatedData |
Animated (animation) |
ReadTransitionableComponent(addr) |
PluginTransitionableData |
Transitionable |
ReadTriggerableBlockageComponent(addr) |
PluginTriggerableBlockageData |
TriggerableBlockage |
ReadMinimapIconComponent(addr) |
PluginMinimapIconData |
MinimapIcon |
ReadStateMachineComponent(addr) |
PluginStateMachineData |
StateMachine |
ReadBaseComponent(addr) |
PluginBaseData |
Base (cell size, influence) |
ReadModsComponent(addr) |
PluginModsData |
Mods (rarity, mod lists) |
ReadStatsComponent(addr) |
PluginStatsData |
Stats (key-value pairs) |
ReadBuffsComponent(addr) |
PluginBuffsData |
Buffs (active buffs) |
ReadActorComponent(addr) |
PluginActorData |
Actor (skills, deploy) |
ReadNpcComponent(addr) |
PluginNpcData |
NPC (hidden, icon) |
ReadDiesAfterTimeComponent(addr) |
PluginDiesAfterTimeData |
DiesAfterTime |
| Helper | Signature | Description |
|---|---|---|
GetHealthPercent |
float (uintptr_t lifeAddr) |
HP percentage (0-100) |
GetEsPercent |
float (uintptr_t lifeAddr) |
Energy Shield percentage |
GetManaPercent |
float (uintptr_t lifeAddr) |
Mana percentage |
IsAlive |
bool (uintptr_t lifeAddr) |
True if HP > 0 |
IsChestOpenedHelper |
bool (uintptr_t chestAddr) |
True if chest opened |
GetWorldPosition |
bool (uintptr_t renderAddr, float*, float*, float*) |
Extract world position |
GetItemRarityFromMods |
int (uintptr_t modsAddr) |
Item rarity from Mods |
IsItemIdentifiedHelper |
bool (uintptr_t modsAddr) |
True if identified |
GetStackCountHelper |
int (uintptr_t stackAddr) |
Stack count |
GetPlayerNameHelper |
string (uintptr_t playerAddr) |
Player name |
// Read entity health via component reader
auto snapshot = m_Context->GetSnapshot();
for (auto& entity : snapshot->Entities) {
if (entity.ComponentCache.HasLife()) {
auto life = m_Context->ReadLifeComponent(entity.ComponentCache.LifeAddr);
if (life.Valid) {
float hpPct = m_Context->GetHealthPercent(entity.ComponentCache.LifeAddr);
}
}
}
// Navigate UI tree
uintptr_t gameUi = m_Context->GetGameUiRootAddress();
const int path[] = {5, 1, 2};
uintptr_t btn = m_Context->ReadUiChildChain(gameUi, path, 3);
if (m_Context->IsUiElementVisible(btn)) {
float x, y, w, h;
m_Context->ComputeUiScreenRect(btn, &x, &y, &w, &h);
}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é
Écrire dans le système de journalisation de l'hôte. Niveaux : "Debug", "Info", "Warning", "Error"
Le contexte ImGui de l'hôte. Appelez ImGui::SetCurrentContext() avec ceci dans SetContext().
Le ID3D11Device* de l'hôte. Castez et utilisez pour charger des textures.
Tous les types sont dans le namespace PluginSDK. Les plugins ajoutent généralement using namespace PluginSDK;.
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 champZonepour 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/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 |
É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 |
| 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
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 |
| Champ | Type | Description |
|---|---|---|
Key |
string |
Clé de statistique du mod |
Values |
vector<float> |
Valeurs du jet du mod |
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() |
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 |
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 |
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 |
| 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 |
| 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 |
| 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 |
| 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) |
(Translation pending)
| Field | Type | Description |
|---|---|---|
Valid |
bool |
Whether the read succeeded |
X / Y
|
float |
Element position |
Width / Height
|
float |
Element size |
ScaleX / ScaleY
|
float |
Scale factors |
IsVisible |
bool |
Visibility flag |
IsEnabled |
bool |
Enabled flag |
ChildCount |
int |
Number of children |
ParentAddr |
uintptr_t |
Parent element address |
SelfAddr |
uintptr_t |
Self pointer |
All component reader functions return structs with a Valid field. See the English guide for full field listings of all 22 structs (PluginLifeData, PluginRenderData, PluginPositionedData, PluginTargetableData, PluginChestData, PluginShrineData, PluginStackData, PluginChargesData, PluginPlayerData, PluginAnimatedData, PluginTransitionableData, PluginTriggerableBlockageData, PluginMinimapIconData, PluginStateMachineData, PluginBaseData, PluginModsData, PluginStatsData, PluginBuffsData, PluginActorData, PluginNpcData, PluginDiesAfterTimeData).
EntityTypes : Unidentified(0), Chest(1), NPC(2), Player(3), Shrine(4), Monster(5), DeliriumBomb(6), DeliriumSpawner(7), OtherImportantObjects(8), Item(9), Renderable(10), AreaTransition(11), ExpeditionMarker(12), ExpeditionRemnant(13)
EntitySubtypes : _Unidentified(0), _None(1), PlayerSelf(2), PlayerOther(3), ChestWithMagicRarity(4), ChestWithRareRarity(5), ExpeditionChest(6), BreachChest(7), Strongbox(8), SpecialNPC(9), POIMonster(10), PinnacleBoss(11), WorldItem(12), InventoryItem(13)
EntityStates : None(0), Useless(1), PlayerLeader(2), MonsterFriendly(3), PinnacleBossHidden(4)
NearbyZone : None(0), InnerCircle(1, ~60 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)
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().
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);- 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)
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));
}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.
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_Appearingdéfinit la position uniquement à la première apparition ; ImGui suit ensuite la position de déplacement -
NoTitleBar+ pas deNoMove= la fenêtre est déplaçable depuis n'importe quelle zone vide (comportement ImGui par défaut) -
NoInputsen mode non-interactif empêche l'overlay de voler le focus à traversWS_EX_TRANSPARENT - Vérifiez toujours le pointeur
IsMenuVisiblepour 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
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
}Stockez les paramètres dans <PluginDirectory>/config/ :
std::filesystem::path settingsPath =
std::filesystem::path(m_Directory) / "config" / "settings.txt";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);
}auto vitals = m_Context->GetPlayerVitals();
int hpPercent = vitals.HPPercent; // 0-100auto snapshot = m_Context->GetSnapshot();
for (auto& e : snapshot->Entities) {
if (e.entityType == EntityTypes::Monster &&
e.Zone == NearbyZone::InnerCircle) {
// e.CurrentHP, e.Path, e.WorldX/Y/Z...
}
}auto vitals = m_Context->GetPlayerVitals();
for (auto& buff : vitals.Buffs) {
if (buff.Name == "flask_effect_life") {
// buff.TimeLeft, buff.Charges...
}
}auto snapshot = m_Context->GetSnapshot();
std::string area = snapshot->CurrentAreaName;
bool isTown = snapshot->IsTown;
int level = snapshot->CurrentAreaLevel;for (auto& e : snapshot->Entities) {
if (e.entityType != EntityTypes::Monster) continue;
float sx, sy;
if (m_Context->WorldToScreen(e.WorldX, e.WorldY, e.WorldZ, &sx, &sy)) {
auto* dl = ImGui::GetBackgroundDrawList();
dl->AddText(ImVec2(sx, sy - 15), IM_COL32(255, 255, 0, 255), "Monster");
}
}// First, request an inventory scan (call periodically, e.g. every 2s)
m_Context->RequestInventoryScan(-1);
// Then read from snapshot (next frame)
auto snapshot = m_Context->GetSnapshot();
for (auto& inv : snapshot->Inventories) {
for (auto& item : inv.Items) {
auto mods = m_Context->ReadExtendedItemMods(item.Address);
// mods.ExplicitMods, mods.ImplicitMods...
}
}auto snapshot = m_Context->GetSnapshot();
int monsters = 0, items = 0;
for (auto& e : snapshot->Entities) {
if (e.entityType == EntityTypes::Monster) monsters++;
if (e.entityType == EntityTypes::Item) items++;
}static uint64_t lastAreaChange = 0;
auto snapshot = m_Context->GetSnapshot();
if (snapshot->AreaChangeCounter != lastAreaChange) {
lastAreaChange = snapshot->AreaChangeCounter;
// Area changed! Reset state...
}if (m_Context->GetCurrentState() == GameStateTypes::AreaLoadingState) {
// Currently loading...
}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.
// Read a struct from a known address
struct MyGameStruct { int field1; float field2; };
MyGameStruct data{};
if (m_Context->ReadProcessMemory(someAddress, &data, sizeof(data))) {
// data.field1, data.field2 are now populated
}
// Read a string from memory
std::string str = m_Context->ReadString(stringAddress);uintptr_t gameStatesAddr = m_Context->GetPatternAddress("Game States");
if (gameStatesAddr != 0) {
// Read data at the resolved pattern address
uint64_t value = 0;
m_Context->ReadProcessMemory(gameStatesAddr, &value, sizeof(value));
}int gridW = 0, gridH = 0;
const uint8_t* grid = m_Context->GetWalkableGrid(&gridW, &gridH);
if (grid && gridW > 0 && gridH > 0) {
int x = (int)snapshot->Player.GridPositionX;
int y = (int)snapshot->Player.GridPositionY;
if (x >= 0 && x < gridW && y >= 0 && y < gridH) {
bool walkable = grid[y * gridW + x] != 0;
}
}PluginSDK::MemoryReader mem(m_Context);
// Read a struct from a known address
struct GameData { int level; float health; };
auto data = mem.Read<GameData>(address);
// Read a StdVector of pointers
auto ptrs = mem.ReadStdVector<uintptr_t>(vectorAddr);
for (auto ptr : ptrs) { /* process each pointer */ }
// Read a StdMap<int, float>
auto entries = mem.ReadStdMap<int, float>(mapAddr);
for (auto& [key, value] : entries) { /* key, value */ }for (auto& inv : snapshot->Inventories) {
const char* name = m_Context->GetInventoryName(inv.Id);
// name is e.g. "MainInventory1", "Weapon1", "Currency1"
}for (auto& e : snapshot->Entities) {
auto& cc = e.ComponentCache;
if (cc.HasLife()) {
// cc.LifeAddr contains the Life component address
// Use MemoryReader to read component structs
}
if (cc.HasRender()) {
// cc.RenderAddr has the Render component address
}
}// Get all entities with debug info
auto entities = m_Context->GetEntityDebugList();
for (auto& e : entities) {
bool open = ImGui::TreeNode(e.Path.c_str());
if (open) {
m_Context->WatchEntity(e.Id);
auto comp = m_Context->GetWatchedEntityData(e.Id);
if (comp.HasLife) {
ImGui::Text("HP: %d/%d ES: %d/%d MP: %d/%d",
comp.Life.Health.Current, comp.Life.Health.Total,
comp.Life.EnergyShield.Current, comp.Life.EnergyShield.Total,
comp.Life.Mana.Current, comp.Life.Mana.Total);
}
if (comp.HasActor) {
ImGui::Text("Animation: %s (%d) Skills: %d",
comp.Actor.AnimationName.c_str(), comp.Actor.AnimationId,
(int)comp.Actor.ActiveSkills.size());
}
ImGui::TreePop();
} else {
m_Context->UnwatchEntity(e.Id);
}
}auto invList = m_Context->GetPlayerInventoryList();
if (!invList.empty()) {
m_Context->WatchInventory(invList[0].first);
auto inv = m_Context->GetWatchedInventoryData();
if (inv.InventoryId >= 0) {
ImGui::Text("Grid: %dx%d Items: %d",
inv.TotalBoxesX, inv.TotalBoxesY, (int)inv.Items.size());
for (auto& item : inv.Items) {
ImGui::Text("[R%d iLvl%d] %s (%s) Mods: %d/%d/%d/%d",
item.Rarity, item.ItemLevel,
item.BaseTypeName.c_str(), item.Path.c_str(),
(int)item.ImplicitMods.size(), (int)item.ExplicitMods.size(),
(int)item.EnchantMods.size(), (int)item.HellscapeMods.size());
}
}
}uintptr_t uiRoot = m_Context->GetGameUiRootAddress();
if (uiRoot) {
PluginSDK::MemoryReader mem(m_Context);
// Read children vector at offset 0x010
auto children = mem.ReadStdVector<uintptr_t>(uiRoot + 0x010);
for (auto childAddr : children) {
// Read StringId at offset 0x448
uintptr_t strPtr = mem.Read<uintptr_t>(childAddr + 0x448);
if (strPtr) {
std::string name = m_Context->ReadString(strPtr);
ImGui::Text("Child: %s (0x%llX)", name.c_str(), childAddr);
}
}
}auto mods = m_Context->ReadExtendedItemMods(item.Address);
ImGui::TextColored(
PluginSDK::GetRarityColor(mods.Rarity),
"Rarity: %s", PluginSDK::GetRarityName(mods.Rarity));
for (auto& mod : mods.ExplicitMods) {
ImGui::BulletText("%s", mod.Key.c_str());
}| Paramètre | Valeur |
|---|---|
| Configuration | Release |
| Platform | x64 |
| C++ Standard | /std:c++20 |
| Runtime Library |
/MD (Multi-threaded DLL) |
| Configuration Type | DLL |
- Votre/vos fichier(s)
.cppde 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.hpour le wrapperMemoryReaderet les fonctions utilitaires
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>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 :
- Copiez
sqlite3.cetsqlite3.hdans le répertoirelib/de votre plugin - Créez
lib/sqlite3-vcpkg-config.hpour remplacerSQLITE_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
- Ajoutez
sqlite3.cà votre.vcxprojen 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>
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.
Définissez votre répertoire de sortie :
$(SolutionDir)x64\Release\Plugins\YourPlugin\
Copiez votre DLL compilée vers Plugins/YourPlugin/YourPlugin.dll à côté de l'exécutable principal.
- Compilez votre DLL de plugin en mode Debug
- Démarrez l'application hôte
- Dans Visual Studio : Debug → Attacher au processus → sélectionnez l'exe de l'hôte
- Placez des points d'arrêt dans votre code source de plugin
- Le débogueur s'arrêtera quand votre code sera appelé
| 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). |