Skip to content

Plugin Development Guide DE

Lafko edited this page Apr 9, 2026 · 14 revisions

← Home


Plugin-Entwicklungshandbuch

1. Erste Schritte

Voraussetzungen

  • MSVC v143 (Visual Studio 2022)
  • C++20 (/std:c++20)
  • x64 Release-Build
  • Laufzeitbibliothek: /MD (Multi-threaded DLL) — muss mit dem Host übereinstimmen

Projekteinrichtung

  1. Erstellen Sie ein neues C++ DLL-Projekt in Visual Studio
  2. Setzen Sie den Include-Pfad auf das POEFixer-Quellverzeichnis (für SDK- und ImGui-Header)
  3. Fügen Sie die ImGui-Quelldateien zu Ihrem Projekt hinzu: imgui.cpp, imgui_draw.cpp, imgui_tables.cpp, imgui_widgets.cpp
  4. Binden Sie die Plugin-SDK-Header in Ihren Plugin-Quellcode ein:
    #include "plugin_sdk/PluginAPI.h"
    #include "plugin_sdk/PluginContext.h"
    #include "imgui/imgui.h"
    Oder verwenden Sie den Komfort-Header aus dem ExamplePlugin:
    #include "sdk/PluginHelpers.h"  // Includes all SDK headers + MemoryReader + utilities
  5. Definieren Sie PLUGIN_EXPORTS und _CRT_SECURE_NO_WARNINGS in den Präprozessordefinitionen Ihres Projekts

Ordnerstruktur

Plugins/
  YourPlugin/
    YourPlugin.dll      <-- DLL-Name MUSS mit dem Ordnernamen übereinstimmen
    config/
      settings.txt      <-- Optionale Einstellungsdatei
    data/
      ...               <-- Optionales Datenverzeichnis (Datenbanken, Caches usw.)

ExamplePlugin-Projektstruktur

Das ExamplePlugin demonstriert die empfohlene Projektstruktur:

Plugins/ExamplePlugin/
  ExamplePlugin.cpp        <-- Haupt-Einstiegspunkt des Plugins + Factory-Exporte
  sdk/
    PluginHelpers.h        <-- MemoryReader, WideToNarrow, Entity/Seltenheits-Hilfsfunktionen
  examples/
    ExampleBuffs.h         <-- Buff-Liste mit Filterung und Fortschrittsbalken
    ExampleEntities.h      <-- Debug-Entity-Liste mit Überwachungsmechanismus, Komponentenbäumen, JSON-Dump
    ExampleInventory.h     <-- ServerData, Inventarauswahl, Slot-Raster, Item-Mods mit Seltenheit
    ExampleMemory.h        <-- Hex-Viewer, Read<T>-Demo, Pattern-Scanner
    ExampleUiExplorer.h    <-- Vollständiger UI-Element-Explorer mit Suche, Navigation, Hervorhebung

KillCount-Plugin-Struktur

Ein umfangreicheres Plugin-Beispiel mit SQLite3, Icon-Atlas und Overlay-Rendering:

Plugins/KillCount/
  KillCount.cpp            <-- Haupt-Einstiegspunkt, IPlugin-Lebenszyklus, Einstellungs-UI
  KillCount.h              <-- Plugin-Klassendeklaration
  KillTracker.cpp/h        <-- Kill/Truhen/Tod-Zählwerk
  OverlayRenderer.cpp/h    <-- ImGui-Overlay mit Drag-to-Reposition-Muster
  IconAtlas.cpp/h           <-- Spritesheet-Texturladung (D3D11 + stb_image)
  Database.cpp/h           <-- SQLite3-Wrapper für persistente Statistiken
  DisplaySettings.h        <-- Einstellungsstruktur
  sdk/
    PluginHelpers.h        <-- Kopiert aus ExamplePlugin
  lib/
    sqlite3.c/h            <-- SQLite3-Amalgamation (als C kompiliert)
    sqlite3-vcpkg-config.h <-- Lokale Überschreibung für statisches Linken

Namenskonvention

Der DLL-Dateiname muss exakt mit dem Ordnernamen übereinstimmen:

  • Ordner: Plugins/MyPlugin/ → DLL: MyPlugin.dll
  • Der Host durchsucht jeden Unterordner in Plugins/ und sucht nach <FolderName>.dll

2. Plugin-Lebenszyklus

Load DLL (LoadLibrary)
  → CreatePlugin()           -- Factory: IPlugin instanziieren
  → SetContext(ctx)           -- Host-Dienste empfangen
  → SetPluginDirectory(dir)   -- Ordnerpfad empfangen
  → GetSDKVersion()           -- Kompatibilitätsprüfung
  → GetName()                 -- Anzeigename für UI
  → [if enabled] OnEnable()  -- Ressourcen initialisieren
  ↓
  Hauptschleife (jeden Frame):
    → DrawUI()                -- Overlay rendern (nur wenn aktiviert)
    → DrawSettings()          -- Einstellungen im Plugins-Tab rendern
    → WantsOverlay()          -- Host prüft, ob Plugin Overlay-Modus wünscht
  ↓
  Periodisch / beim Beenden:
    → SaveSettings()          -- Einstellungen speichern
  ↓
  → OnDisable()               -- Ressourcen freigeben
  → DestroyPlugin(plugin)     -- Factory: IPlugin löschen
  → FreeLibrary               -- DLL entladen

Threading

  • Alle Draw*-Methoden werden im Haupt-/Render-Thread aufgerufen
  • GetSnapshot() und andere PluginContext-Funktionen sind thread-sicher
  • Erstellen Sie KEINE Threads, die ImGui aufrufen — ImGui ist nicht thread-sicher

3. IPlugin-Schnittstellenreferenz

Jedes Plugin muss die IPlugin-Schnittstelle implementieren (definiert in plugin_sdk/PluginAPI.h):

void SetPluginDirectory(const char* dir)

  • Wann aufgerufen: Einmal, direkt nach der Erstellung
  • Parameter: Relativer Pfad wie "Plugins/YourPlugin"
  • Zweck: Speichern Sie diesen Pfad zum Laden von Einstellungen/Ressourcen

void SetContext(PluginContext* context)

  • Wann aufgerufen: Einmal, nach SetPluginDirectory
  • Parameter: Zeiger auf den PluginContext des Hosts (gültig für die gesamte Lebensdauer des Plugins)
  • Zweck: Speichern Sie diesen Zeiger — er ist Ihr Zugang zu allen Spieldaten
  • Wichtig: Rufen Sie ImGui::SetCurrentContext(ctx->ImGuiContext) hier auf

void OnEnable(bool isGameOpened)

  • Wann aufgerufen: Wenn der Benutzer das Plugin aktiviert oder beim Start, wenn es zuvor aktiviert war
  • Parameter: true, wenn der Spielprozess aktuell angebunden ist
  • Zweck: Einstellungen laden, Ressourcen zuweisen, Zustand initialisieren

void OnDisable()

  • Wann aufgerufen: Wenn der Benutzer das Plugin deaktiviert
  • Zweck: Ressourcen freigeben, Hintergrundarbeit stoppen

void DrawUI()

  • Wann aufgerufen: Jeden Frame, nur wenn das Plugin aktiviert ist
  • Zweck: Overlay mit ImGui rendern
  • Hinweis: Verwenden Sie eindeutige Fenster-IDs wie "MyWindow##MyPlugin", um Konflikte zu vermeiden

void DrawSettings()

  • Wann aufgerufen: Jeden Frame, im Plugins-Einstellungstab (nur wenn aktiviert)
  • Zweck: Plugin-Konfiguration mit ImGui rendern

void SaveSettings()

  • Wann aufgerufen: Periodisch und beim Beenden der Anwendung
  • Zweck: Einstellungen auf Festplatte speichern (z.B. Plugins/YourPlugin/config/settings.txt)

const char* GetName()

  • Gibt zurück: Anzeigename im Plugins-Tab (z.B. "My Plugin")

int GetSDKVersion()

  • Gibt zurück: PLUGIN_SDK_VERSION (aktuell 4)
  • Zweck: Host prüft dies auf Kompatibilität — muss übereinstimmen

bool WantsOverlay() (SDK v2)

  • Gibt zurück: true, wenn das Plugin im Overlay-Modus rendern möchte (transparentes Overlay über dem Spiel)
  • Standard: false — Plugin rendert nur im normalen Einstellungsfenster
  • Zweck: Wenn ein Plugin true zurückgibt, wechselt der Host in den Overlay-Modus, auch wenn keine eingebauten Funktionen dies erfordern

Factory-Exporte

Ihre DLL muss diese zwei C-Funktionen exportieren:

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

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

4. PluginContext-API-Referenz

Die PluginContext-Struktur (definiert in plugin_sdk/PluginContext.h) stellt Funktionszeiger für den Zugriff auf Spieldaten bereit. Alle Typen befinden sich im Namespace PluginSDK.

Zugriff auf Spieldaten

GetSnapshot()shared_ptr<const PluginGameSnapshot>

Gibt einen vollständigen Schnappschuss des Spielzustands zurück. Wird einmal pro Frame aktualisiert. Enthält:

Feld Typ Beschreibung
CurrentState GameStateTypes Aktueller Spielzustand
CurrentAreaName string Gebietsname (z.B. "The Riverways")
CurrentAreaHash string Eindeutiger Gebietsinstanz-Hash
CurrentAreaLevel uint8_t Monsterlevel des aktuellen Gebiets
IsTown bool True, wenn in der Stadt
IsHideout bool True, wenn im Versteck
IsPaused bool True, wenn das Spiel pausiert ist
IsSkillTreeVisible bool True, wenn das Skilltree-Panel geöffnet ist
WorldToGridConvertor float Umrechnungsfaktor für Welt→Raster
Player RadarEntity Daten der lokalen Spieler-Entity
Entities vector<RadarEntity> Alle nahegelegenen Entitäten
LargeMap / MiniMap MapData Karten-Overlay-Daten
Vitals PlayerVitals Spieler-HP/ES/MP + Buffs
ScreenWidth / ScreenHeight int Spielfenster-Dimensionen
ProcessId DWORD Spielprozess-ID
GameWindow HWND Spielfenster-Handle
GameWindowForeground bool True, wenn Spielfenster im Vordergrund
IsAttached bool True, wenn an Spielprozess angebunden
IsWindowValid bool True, wenn Spielfenster gültig
LastUpdateTime uint64_t Zeitstempel der letzten Datenaktualisierung
AreaChangeCounter uint64_t Erhöht sich bei Gebietswechsel
Inventories vector<InventoryInfo> Spieler-Inventarinhalt
CurrencyTotals map<string,int> Währungszählung nach Pfad
InventoryGrid InventoryGridInfo Inventar-UI-Rasterinformation
WorldToScreenMatrix XMFLOAT4X4 3D→2D-Projektionsmatrix

Wichtig: Entity-Filterung Tote Entitäten (mit EntityState == Useless) werden aus dem Schnappschuss gefiltert, bevor Plugins ihn erhalten. Das bedeutet, dass Sie niemals einen HP-Übergang von lebendig zu tot beobachten werden. Wenn Sie Kills erkennen müssen, verwenden Sie stattdessen verschwindungsbasierte Erkennung — verfolgen Sie Entity-IDs nach Zone und zählen Sie sie als getötet, wenn sie aus der Entity-Liste verschwinden, während sie sich in InnerCircle- oder OuterCircle-Nähe befinden. Siehe Abschnitt 8: Häufige Rezepte für Details.

GetPlayerVitals()PlayerVitals

Komfortfunktion für Spieler-Vitalwerte.

GetCurrentState()GameStateTypes

Gibt das aktuelle Spielzustands-Enum zurück.

IsAttached()bool

True, wenn der Spielprozess angebunden und lesbar ist.

IsInGame()bool

True, wenn aktuell im Spiel (nicht beim Laden, nicht auf dem Login-Bildschirm).

IsGameForeground()bool

True, wenn das Spielfenster das Vordergrundfenster ist.

GetProcessId()DWORD

Gibt die Spielprozess-ID zurück.

Zugriff auf Item-Daten

ReadExtendedItemMods(entityAddress)ExtendedItemModInfo

Liest alle Modifikatoren eines Items.

ReadItemRarity(entityAddress)int

Gibt zurück: 0=Normal, 1=Magisch, 2=Selten, 3=Einzigartig

ReadItemStackCount(entityAddress)int

Gibt die Stapelanzahl für Währungs-/stapelbare Items zurück.

ReadItemName(entityAddress)string

Gibt den Basistypnamen des Items zurück.

ReadItemPath(entityAddress)string

Gibt den Metadaten-Pfad des Items zurück.

ReadItemBaseTypeName(entityAddress)string

Gibt den Basistypnamen des Items zurück (z.B. "Divine Orb", "Chaos Orb"). Im Gegensatz zu ReadItemName, das den Metadaten-Pfad zurückgibt, liest diese Funktion den tatsächlichen Basistypnamen aus BaseItemTypeData.BaseTypeName.

ReadItemUniqueName(entityAddress)string

Gibt den einzigartigen Itemnamen aus Words.dat zurück (z.B. "Headhunter", "Brimstone Call"). Gibt einen leeren String für nicht-einzigartige Items zurück.

Overlay-Modus (SDK v2)

IsOverlayMode()bool

Gibt true zurück, wenn der Host sich aktuell im Overlay-Modus befindet (transparentes Overlay über dem Spielfenster). Verwenden Sie dies, um Ihr Rendering anzupassen — z.B. auf dem Spiel-Overlay oder in einem Einstellungsfenster zeichnen.

UI-Zustand (SDK v4)

IsMenuVisible()bool

Gibt true zurück, wenn das Einstellungsmenü des Hosts sichtbar ist (Overlay ist interaktiv). Wenn das Menü verborgen ist, ist das Overlay-Fenster klickdurchlässig (WS_EX_TRANSPARENT), sodass ImGui-Fenster keine Mauseingaben empfangen können.

Verwenden Sie dies zur Implementierung des ziehbaren Overlay-Musters:

  • Menü sichtbar: Ziehgriff anzeigen, Interaktion ermöglichen (Tabs, Buttons)
  • Menü verborgen: Ziehgriff entfernen, ImGuiWindowFlags_NoInputs hinzufügen, um das Fenster nicht-interaktiv zu machen

Siehe Abschnitt 6: Ziehbares Overlay-Muster für die vollständige Implementierung.

Speicherlesen (SDK v2)

Direkter Zugriff auf den Spielprozessspeicher. Alle Lesevorgänge sind sicher (geben 0/leer bei Fehler zurück).

GetBaseAddress()uintptr_t

Gibt die Basisadresse des Spielmoduls zurück. Gibt 0 zurück, wenn nicht angebunden.

GetModuleSize()uintptr_t

Gibt die Größe des Spielmoduls in Bytes zurück. Gibt 0 zurück, wenn nicht angebunden.

ReadProcessMemory(address, buffer, size)bool

Liest einen Block von Roh-Bytes aus dem Spielprozess. Der buffer muss mindestens size Bytes zugewiesen haben. Gibt true bei Erfolg zurück.

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

Liest einen nullterminierten ASCII-String aus dem Spielspeicher (max. 128 Zeichen).

ReadUnicodeString(address)wstring

Liest einen nullterminierten Unicode-(Wide-)String aus dem Spielspeicher (max. 128 wchars).

GetPatternAddress(patternName)uintptr_t

Gibt eine aufgelöste Pattern-Scan-Adresse nach Name zurück. Gibt 0 zurück, wenn nicht gefunden.

Standard-Patterns:

Name Beschreibung
"Game States" GameStates-Vektor-Wurzel
"File Root" Dateiregistrierung
"AreaChangeCounter" Gebietsübergangszähler
"Terrain Rotator Helper" Rotationsdaten
"Terrain Rotation Selector" Rotationsauswahl
"GameCullSize" Bildschirm-Culling-Wert

Welt-zu-Bildschirm-Projektion (SDK v2)

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

Konvertiert eine Weltraumposition in Bildschirmkoordinaten. Gibt true zurück, wenn die Position auf dem Bildschirm sichtbar ist.

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

Inventar (SDK v2)

RequestInventoryScan(inventoryId)

Fordert den Host auf, Inventare zu scannen. Übergeben Sie -1 zum Scannen aller Inventare oder eine bestimmte Inventar-ID. Inventardaten im Schnappschuss werden nach Abschluss des Scans befüllt (nächster Frame).

Hinweis: Inventardaten werden nicht automatisch aktualisiert — Sie müssen diese Funktion aufrufen, um einen Scan auszulösen. Rufen Sie sie periodisch auf (z.B. alle 2 Sekunden), wenn Sie kontinuierlich aktualisierte Inventardaten benötigen.

Geländedaten (SDK v2)

GetWalkableGrid(outWidth, outHeight)const uint8_t*

Gibt einen Zeiger auf die Begehbarkeitsrasterdaten zurück. Das Raster ist ein 2D-Array, wobei 0 = nicht begehbar, ungleich Null = begehbar. Gibt nullptr zurück, wenn keine Daten verfügbar sind.

GetTerrainHeight(gridX, gridY)float

Gibt die Geländehöhe an einer Rasterposition zurück. Gibt 0 zurück, wenn außerhalb der Grenzen oder Daten nicht verfügbar.

Lesen nativer Container (SDK v3)

Diese Funktionen lesen C++-Standardbibliotheks-Container direkt aus dem Spielspeicher und spiegeln die Core::Process-Methoden des Hosts wider.

ReadStdVector(containerAddress, elementSize, outCount)void*

Liest einen StdVector (24-Byte-Struktur: {First, Last, End}) aus dem Spielspeicher. Gibt einen mit malloc zugewiesenen Elementpuffer zurück. Der Aufrufer muss den zurückgegebenen Zeiger mit free() freigeben. Gibt nullptr bei Fehler zurück.

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

Liest eine StdList (16-Byte-Struktur: {Head, Size}) aus dem Spielspeicher. Durchläuft die verkettete Liste und gibt einen zusammenhängenden Puffer zurück. Aufrufer muss free() aufrufen.

ReadStdBucket(containerAddress, elementSize, outCount)void*

Liest einen StdBucket aus dem Spielspeicher (liest den eingebetteten StdVector). Aufrufer muss free() aufrufen.

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

Durchläuft eine StdMap (16-Byte-Struktur: {Head, Size}) und ruft callback für jedes Schlüssel-Wert-Paar auf. Gibt die Anzahl der besuchten Knoten zurück.

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

Liest einen StdWString (32-Byte-Struktur mit Inline-/Heap-Puffer) aus dem Spielspeicher.

GetInventoryName(inventoryId)const char*

Gibt den menschenlesbaren Namen für eine Inventar-ID zurück (z.B. 1 → "MainInventory1", 3 → "Weapon1", 64 → "Currency1").

Zugriff auf Debug-Daten (SDK v4)

SDK v4 bietet direkten Zugriff auf die Debug-Daten des Hosts — Entity-Komponenten, Inventardetails und UI-Elementbaum — entsprechend den eingebauten Debug-Tabs.

Debug-Entity-Liste

GetEntityDebugList()vector<DebugEntityInfo>

Gibt eine Liste aller Entitäten mit Debug-Metadaten zurück (Id, Address, Path, Type, SubType, State, Rarity, Zone). Dies entspricht dem Debug→Entity-Liste-Tab.

WatchEntity(entityId)

Beginnt die Überwachung der Komponenten einer Entity. Der Worker-Thread des Hosts liest jeden Frame vollständige Komponentendaten für diese Entity.

UnwatchEntity(entityId)

Beendet die Überwachung der Komponenten einer Entity. Rufen Sie dies auf, wenn der Benutzer den Entity-Baumknoten einklappt, um Ressourcen freizugeben.

GetWatchedEntityData(entityId)DebugEntityComponents

Gibt die vollständigen Komponentendaten für eine überwachte Entity zurück. Enthält Unterstrukturen für alle 8 erkannten Komponenten (Life, Render, Positioned, Targetable, Animated, Stats, Actor, Buffs) plus die Liste aller Komponentenadressen.

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

Inventar-Debug

GetServerDataAddress()uintptr_t

Gibt die Basisadresse der ServerData-Komponente zurück.

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

Gibt alle Spieler-Inventar-IDs und deren Adressen zurück (aus ServerData).

WatchInventory(inventoryId)

Beginnt die Überwachung eines Inventars für detaillierte Debug-Inspektion. Der Host liest Slot-Belegung, Itemdetails und Modifikatoren.

GetWatchedInventoryData()DebugInventoryData

Gibt vollständige Daten für das aktuell überwachte Inventar zurück: Rasterabmessungen, Slot-Belegung, Items mit Seltenheit und Modifikatoren.

// Example: Inventory inspector
auto invList = m_Context->GetPlayerInventoryList();
m_Context->WatchInventory(invList[0].first);
auto inv = m_Context->GetWatchedInventoryData();
for (auto& item : inv.Items) {
    ImGui::Text("[%s] Rarity=%d Mods=%d", item.Path.c_str(), item.Rarity,
        (int)(item.ImplicitMods.size() + item.ExplicitMods.size()));
}

UI-Elementbaum

GetGameUiRootAddress()uintptr_t

Gibt die Adresse des Wurzel-Spiel-UI-Elements zurück (für die Navigation im In-Game-UI-Baum).

GetUiRootAddress()uintptr_t

Gibt die Adresse des obersten UI-Wurzelelements zurück.

GetGameCullValue()int

Gibt den aktuellen GameCullSize-Wert zurück, der für UI-Skalierungsberechnungen verwendet wird. In Kombination mit den Bildschirmabmessungen ermöglicht dies eine genaue Berechnung der UI-Element-Position/Größe.

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

PluginHelpers.h — Komfort-Wrapper (SDK v3)

Der Header sdk/PluginHelpers.h (im ExamplePlugin enthalten) stellt eine typsichere MemoryReader-Klasse bereit, die die rohen PluginContext-Funktionen umhüllt:

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

Der MemoryReader bietet auch Komfort-Wrapper für ReadString(), ReadUnicodeString(), GetBaseAddress(), GetModuleSize() und GetPatternAddress().

Zusätzliche Hilfsfunktionen in PluginHelpers.h:

  • WideToNarrow(wstring) — sichere wstring→string-Konvertierung (ASCII-verlustbehaftet)
  • GetEntityTypeName(type) — Enum zu Anzeigename (einschließlich ExpeditionMarker/ExpeditionRemnant)
  • GetNearbyZoneName(zone) — Zone zu Anzeigename
  • GetRarityName(rarity) / GetRarityColor(rarity) — Seltenheits-Anzeige-Hilfsfunktionen

Host-Dienste

Log(level, message)

In das Protokollierungssystem des Hosts schreiben. Stufen: "Debug", "Info", "Warning", "Error"

ImGuiContext (void*)

Der ImGui-Kontext des Hosts. Rufen Sie ImGui::SetCurrentContext() damit in SetContext() auf.

D3DDevice (void*)

Das ID3D11Device* des Hosts. Casten und zum Laden von Texturen verwenden.


5. Datenstruktur-Referenz

Alle Typen befinden sich im Namespace PluginSDK. Plugins fügen normalerweise using namespace PluginSDK; hinzu.

RadarEntity

Daten pro Entity, verfügbar in snapshot->Entities:

Feld Typ Beschreibung
Id uint32_t Eindeutige Entity-ID
Address uintptr_t Speicheradresse (für Item-API-Aufrufe)
EntityDetailsAddress uintptr_t Adresse der Entity-Details-Struktur
RenderComponentAddress uintptr_t Adresse der Render-Komponente (Abkürzung)
IsValid bool Entity-Gültigkeitsflag
entityType EntityTypes Entity-Kategorie
entitySubtype EntitySubtypes Entity-Unterkategorie
entityState EntityStates Entity-Zustand
Rarity int 0=Normal, 1=Magisch, 2=Selten, 3=Einzigartig
Reaction uint8_t 0=Feindlich, 1=Neutral, 2=Freundlich
GridPositionX/Y float Position im Geländeraster
TerrainHeight float Geländehöhe an der Entity-Position
WorldX/Y/Z float Weltraum-Position
ModelBoundsZ float Modellhöhe
Path wstring Entity-Metadaten-Pfad
PlayerName wstring Spielername (wenn Spieler-Entity)
TgtPath string Zielpfad (schmaler String)
CurrentHP/MaxHP int Entity-Gesundheit
CurrentES/MaxES int Entity-Energieschild
IsSleeping bool Flag für weit entfernte Entity
IsChestOpened bool Truhen-Öffnungszustand
Zone NearbyZone Nähe zum Spieler
ComponentCache EntityComponentCache Komponentenadressen

Wichtig: Tote Entitäten (EntityState::Useless) werden aus dem Schnappschuss entfernt, bevor er die Plugins erreicht. Sie werden nie sehen, wie die HP eines Monsters auf Null fallen — es verschwindet einfach aus der Liste. Verwenden Sie das Zone-Feld, um Kills (Entity verschwand aus InnerCircle/OuterCircle) von Entitäten außerhalb der Reichweite (Entity war in der Far-Zone) zu unterscheiden.

Buff

Aktiver Buff/Debuff:

Feld Typ Beschreibung
Name string Interner Buff-Name (z.B. "flask_effect_life")
TimeLeft float Verbleibende Sekunden
Charges short Stapelanzahl
TotalTime float Gesamtdauer

MapData

Minikarten-/Großkartenzustand:

Feld Typ Beschreibung
CenterX/Y float Kartenmitte
SizeX/Y float Kartenabmessungen
ShiftX/Y float Aktueller Schwenkversatz
DefaultShiftX/Y float Standard-Versatzwerte
Zoom float Zoomstufe
Scale float Kartenskalierungsfaktor
IsVisible bool Karte wird aktuell angezeigt

InventoryInfo / InventoryItemInfo

Feld Typ Beschreibung
Id int Inventar-ID
TotalBoxesX/Y int Rasterabmessungen
Ptr uintptr_t Inventar-Speicheradresse
Items vector<InventoryItemInfo> Items im Inventar

Item-Felder: Address, Name (Metadaten-Pfad), Path (gleich wie Name), BaseTypeName (Basistypname, z.B. "Divine Orb"), UniqueName (einzigartiger Itemname aus Words.dat, z.B. "Headhunter", leer bei nicht-einzigartigen), SlotX/Y, Width/Height, StackCount, IsCurrency

ExtendedItemModInfo

Wird von ReadExtendedItemMods() zurückgegeben:

Feld Typ Beschreibung
ImplicitMods vector<ItemModData> Implizite Modifikatoren
ExplicitMods vector<ItemModData> Explizite Modifikatoren
EnchantMods vector<ItemModData> Verzauberungs-Modifikatoren
HellscapeMods vector<ItemModData> Hellscape-Modifikatoren
CrucibleMods vector<ItemModData> Tiegel-Modifikatoren
Rarity int 0=Normal, 1=Magisch, 2=Selten, 3=Einzigartig

ItemModData

Feld Typ Beschreibung
Key string Mod-Stat-Schlüssel
Values vector<float> Mod-Würfelwerte

EntityComponentCache

Komponentenadressen, pro Entity zwischengespeichert (verfügbar über RadarEntity.ComponentCache):

Feld Typ Has-Methode
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()

DebugEntityInfo (SDK v4)

Entity-Metadaten aus GetEntityDebugList():

Feld Typ Beschreibung
Id uint32_t Entity-ID
Address uintptr_t Speicheradresse
Path string Metadaten-Pfad
EntityType int Entity-Typ (zu EntityTypes casten)
EntitySubType int Entity-Untertyp (zu EntitySubtypes casten)
EntityState int Entity-Zustand (zu EntityStates casten)
Rarity int 0=Normal, 1=Magisch, 2=Selten, 3=Einzigartig
Zone NearbyZone Nähe zum Spieler
ComponentAddresses vector<pair<string,uintptr_t>> Alle Komponentenname→Adresse-Paare

DebugEntityComponents (SDK v4)

Vollständige Komponentendaten aus GetWatchedEntityData():

Feld Typ Beschreibung
EntityId uint32_t Für welche Entity diese Daten sind
Valid bool Ob die Daten erfolgreich gelesen wurden
HasLife / Life bool / DebugLifeComp Life-Komponente — Life.Health, Life.EnergyShield, Life.Mana (jeweils ein DebugVital mit .Current, .Total, .Regeneration, .ReservedFlat, .ReservedPercent)
HasRender / Render bool / DebugRenderComp Position (WorldX/Y/Z, GridX/Y), TerrainHeight, ModelBounds (X/Y/Z)
HasPositioned / Positioned bool / DebugPositionedComp Reaction-Wert, IsFriendly-Flag
HasTargetable / Targetable bool / DebugTargetableComp IsTargetable, IsHighlightable, IsTargettedByPlayer, HiddenFromPlayer, MeetsQuestState, MeetsItemRequirements
HasAnimated / Animated bool / DebugAnimatedComp Animations-Path (string), Id (uint32)
HasStats / Stats bool / DebugStatsComp CurrentWeaponIndex, IsShapeshifted, StatsItems/StatsBuff (Vektoren von Stat-ID→Wert-Paaren)
HasActor / Actor bool / DebugActorComp AnimationId, AnimationName, ActiveSkills (Vektor von DebugActiveSkill), DeployedCounts[256]
HasBuffs / Buffs bool / vector<DebugBuff> Aktive Buffs: Name, TotalTime, TimeLeft, Charges, FlaskSlot, Effectiveness, SourceEntityId

DebugInventoryData (SDK v4)

Inventardetails aus GetWatchedInventoryData():

Feld Typ Beschreibung
InventoryId int Inventar-ID (-1 wenn keines)
Address uintptr_t Inventaradresse
TotalBoxesX/Y int Rasterabmessungen
ServerRequestCounter int Server-Synchronisationszähler
GridScreenX / GridScreenY float Raster-UI-Bildschirmposition
CellSize float Rasterzellengröße in Pixeln
GridValid bool Ob die Raster-UI-Daten gültig sind
SlotOccupied vector<bool> Belegung pro Slot
Items vector<DebugInventoryItem> Items mit Pfad, Seltenheit und Modifikatoren

DebugInventoryItem (SDK v4)

Feld Typ Beschreibung
Address uintptr_t Item-Entity-Adresse
Path string Item-Metadaten-Pfad
BaseTypeName string Basistypname (z.B. "Divine Orb")
UniqueName string Einzigartiger Itemname aus Words.dat (leer bei nicht-einzigartigen)
SlotX / SlotY int Rasterposition im Inventar
Rarity int 0=Normal, 1=Magisch, 2=Selten, 3=Einzigartig
ItemLevel int Itemstufe
RequiredLevel int Erforderliche Charakterstufe
IsIdentified bool Ob das Item identifiziert ist
IsCorrupted bool Ob das Item verdorben ist
CraftedModCount int Anzahl der hergestellten Modifikatoren
ImplicitMods vector<DebugModInfo> Implizite Modifikatoren
ExplicitMods vector<DebugModInfo> Explizite Modifikatoren
EnchantMods vector<DebugModInfo> Verzauberungs-Modifikatoren
HellscapeMods vector<DebugModInfo> Hellscape-Modifikatoren

DebugActiveSkill (SDK v4)

Feld Typ Beschreibung
Name string Skillname
UseStage int Aktuelle Nutzungsstufe
CastType int Wirkungstyp
TotalUses int Gesamte Nutzungsanzahl
TotalCooldownTimeInMs int Abklingzeit in Millisekunden
CanBeUsed bool Ob der Skill aktuell verwendet werden kann

DebugBuff (SDK v4)

Feld Typ Beschreibung
Name string Interner Buff-Name
TotalTime float Gesamtdauer
TimeLeft float Verbleibende Sekunden
Charges short Stapelanzahl
FlaskSlot short Fläschchen-Slot-Index
Effectiveness short Buff-Effektivität
SourceEntityId uint32_t Entity, die diesen Buff angewendet hat

DebugModInfo (SDK v4)

Feld Typ Beschreibung
Name string Mod-Anzeigename
StatKey string Stat-Schlüssel-Bezeichner
AffixName string Affixname
GenerationType int 1=Präfix, 2=Suffix, 3=Implizit
Value0 float Erster Wert (NaN wenn keiner)
Value1 float Zweiter Wert (NaN wenn keiner)

Enumerationen

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 Rastereinheiten), OuterCircle(2, ~120 Rastereinheiten), Far(3)

GameStateTypes: AreaLoadingState(0), ChangePasswordState(1), CreditsState(2), EscapeState(3), InGameState(4), PreGameState(5), LoginState(6), WaitingState(7), CreateCharacterState(8), SelectCharacterState(9), DeleteCharacterState(10), LoadingState(11), GameNotLoaded(12)


6. ImGui-Verwendung in Plugins

Gemeinsamer Kontext

Host und Plugin teilen sich denselben ImGui-Kontext. Sie müssen aufrufen:

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

in Ihrer SetContext()-Methode.

Fenster-IDs

Verwenden Sie immer eindeutige Fenster-IDs, um Konflikte mit dem Host oder anderen Plugins zu vermeiden:

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

Verfügbare Funktionen

  • Fenster, Tabs, Bäume, Tabellen, Zeichenlisten
  • Texturladung über D3D11-Gerät
  • Overlay-Rendering über ImGui::GetBackgroundDrawList()
  • FontAwesome-6-Icons über #include "imgui/IconsFontAwesome6.h" (z.B. ICON_FA_ARROWS_UP_DOWN_LEFT_RIGHT)

Overlay-Rendering (SDK v2)

Verwenden Sie WorldToScreen(), um Beschriftungen/Formen an Entity-Positionen zu zeichnen:

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

Overlay-Modus

Überschreiben Sie WantsOverlay(), um true zurückzugeben und den Host aufzufordern, in den Overlay-Modus zu wechseln:

bool WantsOverlay() override { return m_OverlayEnabled; }

Im Overlay-Modus ist das Host-Fenster transparent und über dem Spiel positioniert. Ihre DrawUI()-Aufrufe rendern direkt auf dem Spielbildschirm.

Ziehbares Overlay-Muster (SDK v4)

Das Host-Overlay verwendet WS_EX_TRANSPARENT, um das Fenster klickdurchlässig zu machen, wenn das Menü verborgen ist. Das bedeutet, ImGui-Fenster können keine Mauseingaben empfangen, es sei denn, das Menü ist sichtbar. Um ein ziehbares Overlay-Fenster zu erstellen (wie das eingebaute Vitals-Overlay), verwenden Sie dieses Zweimodus-Muster:

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

Wichtige Punkte:

  • ImGuiCond_Appearing setzt die Position nur beim ersten Anzeigen; ImGui verfolgt dann die Ziehposition
  • NoTitleBar + kein NoMove = Fenster ist von jedem leeren Bereich ziehbar (ImGui-Standardverhalten)
  • NoInputs im nicht-interaktiven Modus verhindert, dass das Overlay den Fokus durch WS_EX_TRANSPARENT stiehlt
  • Prüfen Sie den IsMenuVisible-Zeiger immer auf Null für Abwärtskompatibilität: m_Context->IsMenuVisible ? m_Context->IsMenuVisible() : false
  • Speichern Sie die Position in Ihrer Einstellungsdatei, damit sie zwischen Sitzungen erhalten bleibt

7. Einstellungspersistenz

Empfohlenes Muster

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
}

Dateispeicherort

Speichern Sie Einstellungen in <PluginDirectory>/config/:

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

Einfaches Schlüssel-Wert-Format

Für Plugins mit vielen Einstellungen funktioniert ein einfaches Schlüssel=Wert-Textformat gut:

// 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. Häufige Rezepte

Spieler-HP-Prozentsatz abrufen

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

Alle Monster im inneren Kreis auflisten

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

Prüfen, ob der Spieler einen bestimmten Buff hat

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

Aktuelle Gebietsinformationen abrufen

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

Text an Entity-Weltposition zeichnen (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");
    }
}

Item-Modifikatoren aus dem Inventar lesen

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

Entitäten nach Typ zählen

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

Gebietswechsel erkennen

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

Ladebildschirm prüfen

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

Monsterkills erkennen (verschwindungsbasiert)

Tote Entitäten werden aus dem Schnappschuss gefiltert (EntityState::Useless), sodass Sie keinen HP-Abfall auf 0 erkennen können. Stattdessen verfolgen Sie Entitäten nach ID und erkennen, wann sie aus nahegelegenen Zonen verschwinden:

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

Warum das funktioniert: Entitäten innerhalb von ~120 Rastereinheiten, die plötzlich verschwinden, wurden fast sicher getötet (sind nicht einfach aus der Reichweite gelaufen). Entitäten in der Far-Zone tauchen natürlich in der Entity-Liste auf und verschwinden wieder — zählen Sie diese nicht.

Wichtig: Leeren Sie m_PrevEntities beim Gebietswechsel (wenn AreaChangeCounter sich ändert), um Fehlalarme zu vermeiden.

Rohen Spielspeicher lesen (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);

Pattern-Scan-Ergebnisse verwenden (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));
}

Begehbares Gelände prüfen (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;
    }
}

Typisiertes Speicherlesen mit 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 */ }

Inventarnamen abrufen (SDK v3)

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

Entity-Komponentenadressen lesen

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

Entity-Komponenten über Debug-Überwachung inspizieren (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);
    }
}

Inventar mit Slot-Raster inspizieren (SDK v4)

auto invList = m_Context->GetPlayerInventoryList();
if (!invList.empty()) {
    m_Context->WatchInventory(invList[0].first);
    auto inv = m_Context->GetWatchedInventoryData();
    if (inv.InventoryId >= 0) {
        ImGui::Text("Grid: %dx%d  Items: %d",
            inv.TotalBoxesX, inv.TotalBoxesY, (int)inv.Items.size());
        for (auto& item : inv.Items) {
            ImGui::Text("[R%d iLvl%d] %s (%s)  Mods: %d/%d/%d/%d",
                item.Rarity, item.ItemLevel,
                item.BaseTypeName.c_str(), item.Path.c_str(),
                (int)item.ImplicitMods.size(), (int)item.ExplicitMods.size(),
                (int)item.EnchantMods.size(), (int)item.HellscapeMods.size());
        }
    }
}

UI-Elementbaum navigieren (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);
        }
    }
}

Item-Mods mit Anzeige-Hilfsfunktionen lesen (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. Erstellen und Bereitstellen

Build-Einstellungen

Einstellung Wert
Configuration Release
Platform x64
C++ Standard /std:c++20
Runtime Library /MD (Multi-threaded DLL)
Configuration Type DLL

Erforderliche Dateien im Plugin-Projekt

  • Ihre Plugin-.cpp-Datei(en)
  • ImGui-Quelldateien: imgui.cpp, imgui_draw.cpp, imgui_tables.cpp, imgui_widgets.cpp
  • Include-Pfad zum POEFixer-Stammverzeichnis (für SDK- und ImGui-Header)
  • Optional: Kopieren Sie Plugins/ExamplePlugin/sdk/PluginHelpers.h für den MemoryReader-Wrapper und Hilfsfunktionen

Include-Pfade

Ihr .vcxproj sollte diese zusätzlichen Include-Verzeichnisse haben:

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

Wenn Sie lokale Drittanbieter-Bibliotheken verwenden (z.B. SQLite3 in einem lib/-Unterordner), fügen Sie $(ProjectDir)lib vor dem Lösungspfad hinzu, damit lokale Header Vorrang haben:

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

Drittanbieter-Bibliotheken

SQLite3 (Statisches Linken)

Um SQLite3 in einem Plugin zu verwenden, müssen Sie den Amalgamation-Quellcode direkt in Ihre DLL kompilieren — Windows LoadLibrary durchsucht nicht das eigene Verzeichnis der DLL nach Abhängigkeiten, sodass das dynamische Linken von sqlite3.dll mit Fehler 126 fehlschlagen wird.

Schritte:

  1. Kopieren Sie sqlite3.c und sqlite3.h in das lib/-Verzeichnis Ihres Plugins
  2. Erstellen Sie lib/sqlite3-vcpkg-config.h, um SQLITE_API zu überschreiben (verhindert __declspec(dllimport)-Fehler):
    #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. Fügen Sie sqlite3.c als C-Datei mit deaktivierten Warnungen zu Ihrem .vcxproj hinzu:
    <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 (Texturladung)

Zum Laden von Texturen aus Bilddateien (PNG, JPG) binden Sie stb_image in einer .cpp-Datei ein:

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

Verwenden Sie dann das D3D11-Gerät aus m_Context->D3DDevice, um GPU-Texturen zu erstellen.

Ausgabeverzeichnis

Setzen Sie Ihr Ausgabeverzeichnis auf:

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

Bereitstellung

Kopieren Sie Ihre erstellte DLL nach Plugins/YourPlugin/YourPlugin.dll neben die Hauptanwendung.

Debugging

  1. Erstellen Sie Ihre Plugin-DLL im Debug-Modus
  2. Starten Sie die Host-Anwendung
  3. In Visual Studio: Debug → An Prozess anhängen → Host-.exe auswählen
  4. Setzen Sie Haltepunkte in Ihrem Plugin-Quellcode
  5. Der Debugger wird anhalten, wenn Ihr Code aufgerufen wird

10. Fehlerbehebung

Problem Lösung
Plugin not loading Stellen Sie sicher, dass der DLL-Name exakt mit dem Ordnernamen übereinstimmt
"SDK version mismatch" Erstellen Sie das Plugin mit den neuesten SDK-Headern neu (aktuelle Version: 4)
LoadLibrary error 126 DLL hat unaufgelöste Abhängigkeiten. Für Drittanbieter-Bibliotheken wie SQLite3 kompilieren Sie diese statisch in die DLL (siehe Abschnitt 9). Verwenden Sie dumpbin /dependents YourPlugin.dll zur Überprüfung.
Crash on load Prüfen Sie CRT-Inkompatibilität — beide müssen /MD verwenden
ImGui not rendering Stellen Sie sicher, dass ImGui::SetCurrentContext() in SetContext() aufgerufen wird
Data is empty/zero Prüfen Sie IsAttached() und IsInGame() vor dem Lesen von Daten
Inventory is empty Rufen Sie RequestInventoryScan(-1) auf — Inventardaten werden bei Bedarf geladen
"Missing exports" error Stellen Sie sicher, dass CreatePlugin und DestroyPlugin mit extern "C" exportiert werden
Plugin crashes host Dies sollte nicht passieren — alle Plugin-Aufrufe sind SEH-geschützt. Prüfen Sie die Logs.
Stale data GetSnapshot() gibt die Daten des aktuellen Frames zurück. Cachen Sie den Zeiger nicht.
Memory read returns 0 Stellen Sie sicher, dass IsAttached() true zurückgibt und die Adresse gültig ist
WorldToScreen returns false Die Position kann hinter der Kamera oder außerhalb des Bildschirms sein
Overlay window not clickable Der Host verwendet WS_EX_TRANSPARENT, wenn das Menü verborgen ist. Verwenden Sie IsMenuVisible(), um interaktive Steuerungen nur bei aktivem Menü anzuzeigen. Siehe das ziehbare Overlay-Muster in Abschnitt 6.
Kill/death detection doesn't work Tote Entitäten werden aus dem Schnappschuss entfernt. Verwenden Sie verschwindungsbasierte Erkennung anstelle von HP-Übergangsbeobachtung. Siehe Abschnitt 8.
C2491 "dllimport function" errors Die Header Ihrer Drittanbieter-Bibliothek definieren __declspec(dllimport). Erstellen Sie einen lokalen Überschreibungs-Header, der das API-Makro leer setzt (siehe SQLite3-Beispiel in Abschnitt 9).

← Home

Clone this wiki locally