-
Notifications
You must be signed in to change notification settings - Fork 0
Plugin Development Guide ES
- MSVC v143 (Visual Studio 2022)
-
C++20 (
/std:c++20) - Compilacion x64 Release
-
Biblioteca en tiempo de ejecucion:
/MD(DLL multiproceso) -- debe coincidir con el host
- Crea un nuevo proyecto C++ DLL en Visual Studio
- Configura la ruta de inclusion para apuntar al directorio fuente de POEFixer (para los encabezados del SDK e ImGui)
- Agrega los archivos fuente de ImGui a tu proyecto:
imgui.cpp,imgui_draw.cpp,imgui_tables.cpp,imgui_widgets.cpp - Incluye los encabezados del SDK del plugin en tu codigo fuente:
O usa el encabezado de conveniencia del 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
- Define
PLUGIN_EXPORTSy_CRT_SECURE_NO_WARNINGSen las Definiciones del Preprocesador de tu proyecto
Plugins/
YourPlugin/
YourPlugin.dll <-- El nombre del DLL DEBE coincidir con el nombre de la carpeta
config/
settings.txt <-- Archivo de configuracion opcional
data/
... <-- Directorio de datos opcional (bases de datos, caches, etc.)
El ExamplePlugin demuestra la disposicion recomendada del proyecto:
Plugins/ExamplePlugin/
ExamplePlugin.cpp <-- Punto de entrada principal del plugin + exportaciones de fabrica
sdk/
PluginHelpers.h <-- MemoryReader, WideToNarrow, helpers de entidad/rareza
examples/
ExampleBuffs.h <-- Lista de buffs con filtrado y barras de progreso
ExampleEntities.h <-- Lista de depuracion de entidades con mecanismo de observacion, arboles de componentes, volcado JSON
ExampleInventory.h <-- ServerData, selector de inventario, cuadricula de ranuras, mods de items con rareza
ExampleMemory.h <-- Visor hexadecimal, demo de Read<T>, escaner de patrones
ExampleUiExplorer.h <-- Explorador completo de elementos de UI con busqueda, navegacion, resaltado
Un ejemplo mas completo de plugin con SQLite3, atlas de iconos y renderizado de overlay:
Plugins/KillCount/
KillCount.cpp <-- Punto de entrada principal del plugin, ciclo de vida IPlugin, UI de configuracion
KillCount.h <-- Declaracion de la clase del plugin
KillTracker.cpp/h <-- Motor de conteo de muertes/cofres/muertes del jugador
OverlayRenderer.cpp/h <-- Overlay ImGui con patron de arrastrar para reposicionar
IconAtlas.cpp/h <-- Carga de hojas de sprites de textura (D3D11 + stb_image)
Database.cpp/h <-- Wrapper de SQLite3 para estadisticas persistentes
DisplaySettings.h <-- Estructura de configuracion
sdk/
PluginHelpers.h <-- Copiado de ExamplePlugin
lib/
sqlite3.c/h <-- Amalgamacion de SQLite3 (compilado como C)
sqlite3-vcpkg-config.h <-- Sobreescritura local para enlace estatico
El nombre del archivo DLL debe coincidir exactamente con el nombre de la carpeta:
- Carpeta:
Plugins/MyPlugin/-> DLL:MyPlugin.dll - El host escanea cada subcarpeta en
Plugins/y busca<NombreDeCarpeta>.dll
Load DLL (LoadLibrary)
-> CreatePlugin() -- Fabrica: instancia tu IPlugin
-> SetContext(ctx) -- Recibe los servicios del host
-> SetPluginDirectory(dir) -- Recibe la ruta de tu carpeta
-> GetSDKVersion() -- Verificacion de compatibilidad
-> GetName() -- Nombre para mostrar en la UI
-> [if enabled] OnEnable() -- Inicializar recursos
|
Bucle principal (cada frame):
-> DrawUI() -- Renderizar tu overlay (solo si esta habilitado)
-> DrawSettings() -- Renderizar configuracion en la pestana Plugins
-> WantsOverlay() -- El host verifica si el plugin quiere modo overlay
|
Periodicamente / al cerrar:
-> SaveSettings() -- Persistir tu configuracion
|
-> OnDisable() -- Liberar recursos
-> DestroyPlugin(plugin) -- Fabrica: eliminar tu IPlugin
-> FreeLibrary -- Descargar DLL
- Todos los metodos
Draw*son llamados en el hilo principal/de renderizado -
GetSnapshot()y otras funciones de PluginContext son seguras entre hilos - NO crear hilos que llamen a ImGui -- ImGui no es seguro entre hilos
Cada plugin debe implementar la interfaz IPlugin (definida en plugin_sdk/PluginAPI.h):
- Cuando se llama: Una vez, inmediatamente despues de la creacion
-
Parametro: Ruta relativa como
"Plugins/YourPlugin" - Proposito: Almacenar esta ruta para cargar configuracion/recursos
-
Cuando se llama: Una vez, despues de
SetPluginDirectory -
Parametro: Puntero al
PluginContextdel host (valido durante la vida del plugin) - Proposito: Almacenar este puntero -- es tu puerta de acceso a todos los datos del juego
-
Importante: Llamar
ImGui::SetCurrentContext(ctx->ImGuiContext)aqui
- Cuando se llama: Cuando el usuario habilita el plugin, o al inicio si estaba previamente habilitado
-
Parametro:
truesi el proceso del juego esta actualmente vinculado - Proposito: Cargar configuracion, asignar recursos, inicializar estado
- Cuando se llama: Cuando el usuario deshabilita el plugin
- Proposito: Liberar recursos, detener trabajo en segundo plano
- Cuando se llama: Cada frame, solo cuando el plugin esta habilitado
- Proposito: Renderizar tu overlay usando ImGui
-
Nota: Usar IDs de ventana unicos como
"MyWindow##MyPlugin"para evitar conflictos
- Cuando se llama: Cada frame, en la pestana de configuracion de Plugins (solo cuando esta habilitado)
- Proposito: Renderizar la configuracion del plugin usando ImGui
- Cuando se llama: Periodicamente y al cerrar la aplicacion
-
Proposito: Guardar tu configuracion en disco (ej.,
Plugins/YourPlugin/config/settings.txt)
-
Retorna: Nombre para mostrar en la pestana Plugins (ej.,
"My Plugin")
-
Retorna:
PLUGIN_SDK_VERSION(actualmente 4) - Proposito: El host verifica esto para compatibilidad -- debe coincidir
-
Retorna:
truesi el plugin quiere renderizar en modo overlay (overlay transparente sobre el juego) -
Por defecto:
false-- el plugin solo renderiza en la ventana de configuracion normal -
Proposito: Cuando cualquier plugin retorna
true, el host entra en modo overlay incluso si ninguna funcion integrada lo requiere
Tu DLL debe exportar estas dos funciones C:
extern "C" PLUGIN_API IPlugin* CreatePlugin() {
return new MyPlugin();
}
extern "C" PLUGIN_API void DestroyPlugin(IPlugin* plugin) {
delete plugin;
}La estructura PluginContext (definida en plugin_sdk/PluginContext.h) proporciona punteros a funciones para acceder a los datos del juego. Todos los tipos estan en el espacio de nombres PluginSDK.
Retorna una instantanea completa del estado del juego. Se actualiza una vez por frame. Contiene:
| Campo | Tipo | Descripcion |
|---|---|---|
CurrentState |
GameStateTypes |
Estado actual del juego |
CurrentAreaName |
string |
Nombre del area (ej., "The Riverways") |
CurrentAreaHash |
string |
Hash unico de la instancia del area |
CurrentAreaLevel |
uint8_t |
Nivel de monstruos del area actual |
IsTown |
bool |
Verdadero si esta en la ciudad |
IsHideout |
bool |
Verdadero si esta en el refugio |
IsPaused |
bool |
Verdadero si el juego esta en pausa |
IsSkillTreeVisible |
bool |
Verdadero si el panel del arbol de habilidades esta abierto |
WorldToGridConvertor |
float |
Factor de conversion mundo->cuadricula |
Player |
RadarEntity |
Datos de la entidad del jugador local |
Entities |
vector<RadarEntity> |
Todas las entidades cercanas |
LargeMap / MiniMap
|
MapData |
Datos del overlay del mapa |
Vitals |
PlayerVitals |
HP/ES/MP del jugador + buffs |
ScreenWidth / ScreenHeight
|
int |
Dimensiones de la ventana del juego |
ProcessId |
DWORD |
ID del proceso del juego |
GameWindow |
HWND |
Handle de la ventana del juego |
GameWindowForeground |
bool |
Verdadero si el juego esta en primer plano |
IsAttached |
bool |
Verdadero si esta vinculado al proceso del juego |
IsWindowValid |
bool |
Verdadero si la ventana del juego es valida |
LastUpdateTime |
uint64_t |
Marca de tiempo de la ultima actualizacion |
AreaChangeCounter |
uint64_t |
Se incrementa al cambiar de area |
Inventories |
vector<InventoryInfo> |
Contenido del inventario del jugador |
CurrencyTotals |
map<string,int> |
Cantidades de moneda por ruta |
InventoryGrid |
InventoryGridInfo |
Informacion de la cuadricula del inventario |
WorldToScreenMatrix |
XMFLOAT4X4 |
Matriz de proyeccion 3D->2D |
Importante: Filtrado de entidades Las entidades muertas (aquellas con
EntityState == Useless) son filtradas FUERA de la instantanea antes de que los plugins la reciban. Esto significa que nunca observaras una transicion de HP de vivo a muerto. Si necesitas detectar muertes, usa la deteccion basada en desaparicion en su lugar -- rastrea los IDs de entidades por zona y cuentalos como muertos cuando desaparezcan de la lista de entidades mientras estan en proximidadInnerCircleuOuterCircle. Ver Seccion 8: Recetas comunes para mas detalles.
Atajo de conveniencia para los signos vitales del jugador.
Retorna el enum del estado actual del juego.
Verdadero si el proceso del juego esta vinculado y es legible.
Verdadero si actualmente esta en el juego (no cargando, no en pantalla de inicio de sesion).
Verdadero si la ventana del juego es la ventana en primer plano.
Retorna el ID del proceso del juego.
Lee todos los modificadores de una entidad de item.
Retorna: 0=Normal, 1=Magico, 2=Raro, 3=Unico
Retorna la cantidad de la pila para items de moneda/apilables.
Retorna el nombre del tipo base del item.
Retorna la ruta de metadatos del item.
Retorna el nombre legible del item (ej., "Divine Orb", "Chaos Orb"). A diferencia de ReadItemName que retorna la ruta de metadatos, este lee el nombre real desde BaseItemTypeData.DisplayNamePtr.
Retorna verdadero si el host esta actualmente en modo overlay (overlay transparente sobre la ventana del juego). Usa esto para ajustar tu renderizado -- ej., dibujar sobre el overlay del juego vs. dibujar en una ventana de configuracion.
Retorna verdadero cuando el menu de configuracion del host es visible (el overlay es interactivo). Cuando el menu esta oculto, la ventana de overlay es transparente al clic (WS_EX_TRANSPARENT), por lo que las ventanas de ImGui no pueden recibir entrada del raton.
Usa esto para implementar el patron de overlay arrastrable:
- Menu visible: Muestra un indicador de arrastre, permite interaccion (pestanas, botones)
-
Menu oculto: Elimina el indicador de arrastre, agrega
ImGuiWindowFlags_NoInputspara hacer la ventana no interactiva
Ver Seccion 6: Patron de overlay arrastrable para la implementacion completa.
Acceso directo a la memoria del proceso del juego. Todas las lecturas son seguras (retornan 0/vacio en caso de fallo).
Retorna la direccion base del modulo ejecutable del juego. Retorna 0 si no esta vinculado.
Retorna el tamano del modulo del juego en bytes. Retorna 0 si no esta vinculado.
Lee un bloque de bytes crudos del proceso del juego. El buffer debe tener al menos size bytes asignados. Retorna verdadero en caso de exito.
// 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));Lee una cadena ASCII terminada en nulo de la memoria del juego (max 128 caracteres).
Lee una cadena Unicode (ancha) terminada en nulo de la memoria del juego (max 128 wchars).
Obtiene una direccion resuelta de escaneo de patron por nombre. Retorna 0 si no se encuentra.
Patrones estandar:
| Nombre | Descripcion |
|---|---|
"Game States" |
Raiz del vector GameStates |
"File Root" |
Registro de archivos |
"AreaChangeCounter" |
Contador de transicion de area |
"Terrain Rotator Helper" |
Datos de rotacion |
"Terrain Rotation Selector" |
Selector de rotacion |
"GameCullSize" |
Valor de recorte de pantalla |
Convierte una posicion en espacio mundial a coordenadas de pantalla. Retorna verdadero si la posicion es visible en pantalla.
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");
}Solicita al host que escanee los inventarios. Pasa -1 para escanear todos los inventarios, o un ID de inventario especifico. Los datos del inventario en la instantanea se llenan despues de que el escaneo se completa (siguiente frame).
Nota: Los datos del inventario no se actualizan automaticamente -- debes llamar a esta funcion para activar un escaneo. Llamala periodicamente (ej., cada 2 segundos) si necesitas datos de inventario continuos.
Retorna un puntero a los datos de la cuadricula transitable. La cuadricula es un arreglo 2D donde 0 = no transitable, distinto de cero = transitable. Retorna nullptr si los datos no estan disponibles.
Retorna la altura del terreno en una posicion de cuadricula. Retorna 0 si esta fuera de limites o los datos no estan disponibles.
Estas funciones leen contenedores de la biblioteca estandar de C++ directamente de la memoria del juego, reflejando los metodos Core::Process del host.
Lee un StdVector (estructura de 24 bytes: {First, Last, End}) de la memoria del juego. Retorna un buffer asignado con malloc de elementos. El llamador debe hacer free() del puntero retornado. Retorna nullptr en caso de fallo.
// 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);
}Lee un StdList (estructura de 16 bytes: {Head, Size}) de la memoria del juego. Recorre la lista enlazada y retorna un buffer contiguo. El llamador debe hacer free().
Lee un StdBucket de la memoria del juego (lee el StdVector embebido). El llamador debe hacer free().
Recorre un StdMap (estructura de 16 bytes: {Head, Size}) y llama al callback por cada par clave-valor. Retorna el numero de nodos visitados.
// 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);Lee un StdWString (estructura de 32 bytes con buffer inline/heap) de la memoria del juego.
Retorna el nombre legible de un ID de inventario (ej., 1 -> "MainInventory1", 3 -> "Weapon1", 64 -> "Currency1").
El SDK v4 proporciona acceso directo a los datos de depuracion del host -- componentes de entidades, detalles de inventario y arbol de elementos de UI -- coincidiendo con las pestanas de depuracion integradas.
Retorna una lista de todas las entidades con metadatos de depuracion (Id, Address, Path, Type, SubType, State, Rarity, Zone). Esto refleja la pestana Debug->Entity List.
Comienza a observar los componentes de una entidad. El hilo de trabajo del host leera los datos completos de componentes de esta entidad cada frame.
Deja de observar los componentes de una entidad. Llama a esto cuando el usuario colapsa el nodo del arbol de la entidad para liberar recursos.
Retorna los datos completos de componentes de una entidad observada. Contiene sub-estructuras para los 8 componentes reconocidos (Life, Render, Positioned, Targetable, Animated, Stats, Actor, Buffs) mas la lista de todas las direcciones de componentes.
// 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);
}
}Retorna la direccion base del componente ServerData.
Retorna todos los IDs de inventario del jugador y sus direcciones (desde ServerData).
Comienza a observar un inventario para inspeccion detallada de depuracion. El host lee la ocupacion de ranuras, detalles de items y modificadores.
Retorna datos completos del inventario observado actualmente: dimensiones de cuadricula, ocupacion de ranuras, items con rareza y modificadores.
// 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()));
}Retorna la direccion del elemento raiz de la UI del juego (para navegacion del arbol de UI en el juego).
Retorna la direccion raiz de la UI de nivel superior.
Retorna el valor actual de GameCullSize, utilizado para calculos de escala de UI. Combinado con las dimensiones de pantalla, esto permite calcular con precision la posicion/tamano de los elementos de 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)El encabezado sdk/PluginHelpers.h (incluido con ExamplePlugin) proporciona una clase MemoryReader con tipos seguros que envuelve las funciones crudas 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);El MemoryReader tambien proporciona wrappers de conveniencia para ReadString(), ReadUnicodeString(), GetBaseAddress(), GetModuleSize(), y GetPatternAddress().
Utilidades adicionales en PluginHelpers.h:
-
WideToNarrow(wstring)-- conversion segura de wstring a string (perdida ASCII) -
GetEntityTypeName(type)-- enum a nombre para mostrar (incluyendo ExpeditionMarker/ExpeditionRemnant) -
GetNearbyZoneName(zone)-- zona a nombre para mostrar -
GetRarityName(rarity)/GetRarityColor(rarity)-- helpers de visualizacion de rareza
Escribe en el sistema de registro del host. Niveles: "Debug", "Info", "Warning", "Error"
El contexto ImGui del host. Llama a ImGui::SetCurrentContext() con esto en SetContext().
El ID3D11Device* del host. Haz cast y usalo para cargar texturas.
Todos los tipos estan en el espacio de nombres PluginSDK. Los plugins tipicamente agregan using namespace PluginSDK;.
Datos por entidad disponibles en snapshot->Entities:
| Campo | Tipo | Descripcion |
|---|---|---|
Id |
uint32_t |
ID unico de la entidad |
Address |
uintptr_t |
Direccion de memoria (para llamadas API de items) |
EntityDetailsAddress |
uintptr_t |
Direccion de la estructura de detalles de entidad |
RenderComponentAddress |
uintptr_t |
Direccion del componente Render (atajo) |
IsValid |
bool |
Flag de validez de la entidad |
entityType |
EntityTypes |
Categoria de la entidad |
entitySubtype |
EntitySubtypes |
Subcategoria de la entidad |
entityState |
EntityStates |
Estado de la entidad |
Rarity |
int |
0=Normal, 1=Magico, 2=Raro, 3=Unico |
Reaction |
uint8_t |
0=Hostil, 1=Neutral, 2=Amigable |
GridPositionX/Y |
float |
Posicion en la cuadricula del terreno |
TerrainHeight |
float |
Altura del terreno en la posicion de la entidad |
WorldX/Y/Z |
float |
Posicion en espacio mundial |
ModelBoundsZ |
float |
Altura del modelo |
Path |
wstring |
Ruta de metadatos de la entidad |
PlayerName |
wstring |
Nombre del jugador (si es entidad jugador) |
TgtPath |
string |
Ruta de destino (cadena estrecha) |
CurrentHP/MaxHP |
int |
Salud de la entidad |
CurrentES/MaxES |
int |
Escudo de energia de la entidad |
IsSleeping |
bool |
Flag de entidad lejana |
IsChestOpened |
bool |
Estado de cofre abierto |
Zone |
NearbyZone |
Proximidad al jugador |
ComponentCache |
EntityComponentCache |
Direcciones de componentes |
Importante: Las entidades muertas (
EntityState::Useless) son eliminadas de la instantanea antes de que llegue a los plugins. Nunca veras que el HP de un monstruo caiga a cero -- simplemente desaparece de la lista. Usa el campoZonepara distinguir muertes (la entidad desaparecio de InnerCircle/OuterCircle) de entidades que salen del rango (la entidad estaba en la zona Far).
Buff/debuff activo:
| Campo | Tipo | Descripcion |
|---|---|---|
Name |
string |
Nombre interno del buff (ej., "flask_effect_life") |
TimeLeft |
float |
Segundos restantes |
Charges |
short |
Cantidad de cargas |
TotalTime |
float |
Duracion total |
Estado del minimapa/mapa grande:
| Campo | Tipo | Descripcion |
|---|---|---|
CenterX/Y |
float |
Centro del mapa |
SizeX/Y |
float |
Dimensiones del mapa |
ShiftX/Y |
float |
Desplazamiento actual de panoramizacion |
DefaultShiftX/Y |
float |
Valores de desplazamiento predeterminados |
Zoom |
float |
Nivel de zoom |
Scale |
float |
Factor de escala del mapa |
IsVisible |
bool |
El mapa esta actualmente visible |
| Campo | Tipo | Descripcion |
|---|---|---|
Id |
int |
ID del inventario |
TotalBoxesX/Y |
int |
Dimensiones de la cuadricula |
Ptr |
uintptr_t |
Direccion de memoria del inventario |
Items |
vector<InventoryItemInfo> |
Items en el inventario |
Campos del item: Address, Name (ruta de metadatos), Path (igual que Name), DisplayName (legible, ej. "Divine Orb"), SlotX/Y, Width/Height, StackCount, IsCurrency
Retornado por ReadExtendedItemMods():
| Campo | Tipo | Descripcion |
|---|---|---|
ImplicitMods |
vector<ItemModData> |
Modificadores implicitos |
ExplicitMods |
vector<ItemModData> |
Modificadores explicitos |
EnchantMods |
vector<ItemModData> |
Modificadores de encantamiento |
HellscapeMods |
vector<ItemModData> |
Modificadores de Hellscape |
CrucibleMods |
vector<ItemModData> |
Modificadores de Crucible |
Rarity |
int |
0=Normal, 1=Magico, 2=Raro, 3=Unico |
| Campo | Tipo | Descripcion |
|---|---|---|
Key |
string |
Clave del mod de estadistica |
Values |
vector<float> |
Valores del mod |
Direcciones de componentes almacenadas en cache por entidad (disponibles via RadarEntity.ComponentCache):
| Campo | Tipo | Metodo-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() |
Metadatos de entidad de GetEntityDebugList():
| Campo | Tipo | Descripcion |
|---|---|---|
Id |
uint32_t |
ID de la entidad |
Address |
uintptr_t |
Direccion de memoria |
Path |
string |
Ruta de metadatos |
EntityType |
int |
Tipo de entidad (convertir a EntityTypes) |
EntitySubType |
int |
Subtipo de entidad (convertir a EntitySubtypes) |
EntityState |
int |
Estado de entidad (convertir a EntityStates) |
Rarity |
int |
0=Normal, 1=Magico, 2=Raro, 3=Unico |
Zone |
NearbyZone |
Proximidad al jugador |
ComponentAddresses |
vector<pair<string,uintptr_t>> |
Todos los pares nombre->direccion de componentes |
Datos completos de componentes de GetWatchedEntityData():
| Campo | Tipo | Descripcion |
|---|---|---|
EntityId |
uint32_t |
Para que entidad son estos datos |
Valid |
bool |
Si los datos fueron leidos exitosamente |
HasLife / Life
|
bool / DebugLifeComp
|
Componente Life -- Life.Health, Life.EnergyShield, Life.Mana (cada uno un DebugVital con .Current, .Total, .Regeneration, .ReservedFlat, .ReservedPercent) |
HasRender / Render
|
bool / DebugRenderComp
|
Posicion (WorldX/Y/Z, GridX/Y), TerrainHeight, ModelBounds (X/Y/Z) |
HasPositioned / Positioned
|
bool / DebugPositionedComp
|
Valor de Reaction, flag IsFriendly |
HasTargetable / Targetable
|
bool / DebugTargetableComp
|
IsTargetable, IsHighlightable, IsTargettedByPlayer, HiddenFromPlayer, NeedsTrue, MeetsQuestState, NeedsFalse |
HasAnimated / Animated
|
bool / DebugAnimatedComp
|
Path de animacion (string), Id (uint32) |
HasStats / Stats
|
bool / DebugStatsComp
|
CurrentWeaponIndex, IsShapeshifted, StatsItems/StatsBuff (vectores de pares ID de stat->valor) |
HasActor / Actor
|
bool / DebugActorComp
|
AnimationId, AnimationName, ActiveSkills (vector de DebugActiveSkill), DeployedCounts[256] |
HasBuffs / Buffs
|
bool / vector<DebugBuff>
|
Buffs activos: Name, TotalTime, TimeLeft, Charges, FlaskSlot, Effectiveness, SourceEntityId |
Detalles de inventario de GetWatchedInventoryData():
| Campo | Tipo | Descripcion |
|---|---|---|
InventoryId |
int |
ID del inventario (-1 si ninguno) |
Address |
uintptr_t |
Direccion del inventario |
TotalBoxesX/Y |
int |
Dimensiones de la cuadricula |
ServerRequestCounter |
int |
Contador de sincronizacion con el servidor |
SlotOccupied |
vector<bool> |
Ocupacion por ranura |
Items |
vector<DebugInventoryItem> |
Items con ruta, rareza y modificadores |
| Campo | Tipo | Descripcion |
|---|---|---|
Address |
uintptr_t |
Direccion de la entidad del item |
Path |
string |
Ruta de metadatos del item |
DisplayName |
string |
Nombre legible del item |
SlotX / SlotY
|
int |
Posicion en la cuadricula del inventario |
Rarity |
int |
0=Normal, 1=Magico, 2=Raro, 3=Unico |
ItemLevel |
int |
Nivel del item |
RequiredLevel |
int |
Nivel requerido del personaje |
IsIdentified |
bool |
Si el item esta identificado |
IsCorrupted |
bool |
Si el item esta corrompido |
CraftedModCount |
int |
Numero de mods creados |
ImplicitMods |
vector<DebugModInfo> |
Modificadores implicitos |
ExplicitMods |
vector<DebugModInfo> |
Modificadores explicitos |
EnchantMods |
vector<DebugModInfo> |
Modificadores de encantamiento |
HellscapeMods |
vector<DebugModInfo> |
Modificadores de Hellscape |
| Campo | Tipo | Descripcion |
|---|---|---|
Name |
string |
Nombre de la habilidad |
UseStage |
int |
Etapa de uso actual |
CastType |
int |
Tipo de lanzamiento |
TotalUses |
int |
Conteo total de usos |
TotalCooldownTimeInMs |
int |
Tiempo de enfriamiento en milisegundos |
CanBeUsed |
bool |
Si la habilidad puede usarse actualmente |
| Campo | Tipo | Descripcion |
|---|---|---|
Name |
string |
Nombre interno del buff |
TotalTime |
float |
Duracion total |
TimeLeft |
float |
Segundos restantes |
Charges |
short |
Cantidad de cargas |
FlaskSlot |
short |
Indice de ranura de frasco |
Effectiveness |
short |
Efectividad del buff |
SourceEntityId |
uint32_t |
Entidad que aplico este buff |
| Campo | Tipo | Descripcion |
|---|---|---|
Name |
string |
Nombre para mostrar del mod |
StatKey |
string |
Identificador de clave de estadistica |
AffixName |
string |
Nombre del afijo |
GenerationType |
int |
1=Prefijo, 2=Sufijo, 3=Implicito |
Value0 |
float |
Primer valor (NaN si ninguno) |
Value1 |
float |
Segundo valor (NaN si ninguno) |
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 unidades de cuadricula), OuterCircle(2, ~120 unidades de cuadricula), 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)
El host y el plugin comparten el mismo contexto de ImGui. Debes llamar:
ImGui::SetCurrentContext(static_cast<ImGuiContext*>(m_Context->ImGuiContext));en tu metodo SetContext().
Siempre usa IDs de ventana unicos para evitar conflictos con el host u otros plugins:
ImGui::Begin("My Window##MyPluginName", &showWindow);- Ventanas, pestanas, arboles, tablas, listas de dibujo
- Carga de texturas via dispositivo D3D11
- Renderizado de overlay via
ImGui::GetBackgroundDrawList() - Iconos FontAwesome 6 via
#include "imgui/IconsFontAwesome6.h"(ej.,ICON_FA_ARROWS_UP_DOWN_LEFT_RIGHT)
Usa WorldToScreen() para dibujar etiquetas/formas en posiciones de entidades:
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));
}Sobreescribe WantsOverlay() para retornar true para solicitar al host que entre en modo overlay:
bool WantsOverlay() override { return m_OverlayEnabled; }Cuando esta en modo overlay, la ventana del host es transparente y posicionada sobre el juego. Tus llamadas a DrawUI() renderizan directamente sobre la pantalla del juego.
El overlay del host usa WS_EX_TRANSPARENT para hacer la ventana transparente al clic cuando el menu esta oculto. Esto significa que las ventanas de ImGui no pueden recibir entrada del raton a menos que el menu este visible. Para crear una ventana de overlay arrastrable (como el Vitals Overlay integrado), usa este patron de doble modo:
#include "imgui/IconsFontAwesome6.h"
void MyPlugin::RenderOverlay() {
bool menuVisible = m_Context->IsMenuVisible ? m_Context->IsMenuVisible() : false;
if (menuVisible) {
// === MODO ARRASTRABLE ===
// Ventana con fondo, indicador de arrastre, controles interactivos
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 {
// === MODO NO INTERACTIVO ===
// Overlay estatico — sin arrastre, sin interaccion de raton
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();
}
}Puntos clave:
-
ImGuiCond_Appearingestablece la posicion solo en la primera aparicion; ImGui luego rastrea la posicion de arrastre -
NoTitleBar+ sinNoMove= la ventana es arrastrable desde cualquier area vacia (comportamiento predeterminado de ImGui) -
NoInputsen modo no interactivo evita que el overlay robe el foco a traves deWS_EX_TRANSPARENT - Siempre verifica nulo el puntero
IsMenuVisiblepara compatibilidad hacia atras:m_Context->IsMenuVisible ? m_Context->IsMenuVisible() : false - Guarda la posicion en tu archivo de configuracion para que persista entre sesiones
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
}Almacena la configuracion en <PluginDirectory>/config/:
std::filesystem::path settingsPath =
std::filesystem::path(m_Directory) / "config" / "settings.txt";Para plugins con muchas configuraciones, un formato de texto simple clave=valor funciona 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...
}Las entidades muertas son filtradas de la instantanea (EntityState::Useless), por lo que no puedes detectar el HP cayendo a 0. En su lugar, rastrea las entidades por ID y detecta cuando desaparecen de las zonas cercanas:
// 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;
}
}
}Por que funciona: Las entidades dentro de ~120 unidades de cuadricula que desaparecen repentinamente casi con certeza fueron eliminadas (no simplemente salieron del rango). Las entidades en la zona Far naturalmente aparecen y desaparecen de la lista de entidades -- no cuentes esas.
Importante: Limpia m_PrevEntities al cambiar de area (AreaChangeCounter cambio) para evitar falsos positivos.
// 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.DisplayName.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());
}| Configuracion | Valor |
|---|---|
| Configuration | Release |
| Platform | x64 |
| C++ Standard | /std:c++20 |
| Runtime Library |
/MD (DLL multiproceso) |
| Configuration Type | DLL |
- Tu(s) archivo(s)
.cppdel plugin - Archivos fuente de ImGui:
imgui.cpp,imgui_draw.cpp,imgui_tables.cpp,imgui_widgets.cpp - Ruta de inclusion al directorio raiz de POEFixer (para encabezados del SDK e ImGui)
- Opcional: Copiar
Plugins/ExamplePlugin/sdk/PluginHelpers.hpara el wrapperMemoryReadery funciones de utilidad
Tu .vcxproj debe tener estos directorios de inclusion adicionales:
<AdditionalIncludeDirectories>$(SolutionDir)POEFixer;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>Si usas bibliotecas de terceros locales (ej., SQLite3 en una subcarpeta lib/), agrega $(ProjectDir)lib antes de la ruta de la solucion para que los encabezados locales tengan prioridad:
<AdditionalIncludeDirectories>$(ProjectDir)lib;$(SolutionDir)POEFixer;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>Para usar SQLite3 en un plugin, debes compilar el codigo fuente de amalgamacion directamente en tu DLL -- Windows LoadLibrary no busca en el propio directorio del DLL las dependencias, por lo que enlazar dinamicamente sqlite3.dll fallara con error 126.
Pasos:
- Copia
sqlite3.cysqlite3.hen el directoriolib/de tu plugin - Crea
lib/sqlite3-vcpkg-config.hpara sobreescribirSQLITE_API(previene errores de__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
- Agrega
sqlite3.ca tu.vcxprojcomo archivo C con advertencias deshabilitadas:<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>
Para cargar texturas desde archivos de imagen (PNG, JPG), incluye stb_image en un archivo .cpp:
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"Luego usa el dispositivo D3D11 de m_Context->D3DDevice para crear texturas en la GPU.
Configura tu directorio de salida a:
$(SolutionDir)x64\Release\Plugins\YourPlugin\
Copia tu DLL compilado a Plugins/YourPlugin/YourPlugin.dll junto al ejecutable principal.
- Compila tu DLL del plugin en modo Debug
- Inicia la aplicacion host
- En Visual Studio: Depurar -> Vincular a proceso -> selecciona el .exe del host
- Establece puntos de interrupcion en el codigo fuente de tu plugin
- El depurador se detendra cuando se llame tu codigo
| Problema | Solucion |
|---|---|
| Plugin no carga | Verifica que el nombre del DLL coincida exactamente con el nombre de la carpeta |
| "SDK version mismatch" | Recompila el plugin con los ultimos encabezados del SDK (version actual: 4) |
| Error 126 de LoadLibrary | El DLL tiene dependencias no resueltas. Para bibliotecas de terceros como SQLite3, compilalas estaticamente en el DLL (ver Seccion 9). Usa dumpbin /dependents YourPlugin.dll para verificar. |
| Crash al cargar | Verifica incompatibilidad de CRT -- ambos deben usar /MD
|
| ImGui no renderiza | Asegurate de que ImGui::SetCurrentContext() se llama en SetContext()
|
| Datos estan vacios/cero | Verifica IsAttached() e IsInGame() antes de leer datos |
| Inventario esta vacio | Llama a RequestInventoryScan(-1) -- los datos de inventario son bajo demanda |
| Error "Missing exports" | Asegurate de que CreatePlugin y DestroyPlugin estan exportados con extern "C"
|
| Plugin hace crash al host | Esto no deberia pasar -- todas las llamadas de plugins estan protegidas por SEH. Revisa los registros. |
| Datos obsoletos |
GetSnapshot() retorna los datos del frame mas reciente. No almacenes el puntero en cache. |
| Lectura de memoria retorna 0 | Verifica que IsAttached() es verdadero y la direccion es valida |
| WorldToScreen retorna falso | La posicion puede estar detras de la camara o fuera de pantalla |
| Ventana de overlay no es clicable | El host usa WS_EX_TRANSPARENT cuando el menu esta oculto. Usa IsMenuVisible() para mostrar controles interactivos solo cuando el menu esta activo. Ver el Patron de Overlay Arrastrable en la Seccion 6. |
| Deteccion de muertes no funciona | Las entidades muertas son eliminadas de la instantanea. Usa deteccion basada en desaparicion en lugar de transicion de HP. Ver Seccion 8. |
| Errores C2491 "dllimport function" | Los encabezados de tu biblioteca de terceros definen __declspec(dllimport). Crea un encabezado de sobreescritura local que establezca la macro API como vacia (ver ejemplo de SQLite3 en la Seccion 9). |