-
Notifications
You must be signed in to change notification settings - Fork 0
Plugin Development Guide PT
- MSVC v143 (Visual Studio 2022)
-
C++20 (
/std:c++20) - Build x64 Release
-
Biblioteca de Runtime:
/MD(DLL Multi-threaded) -- deve corresponder ao host
- Crie um novo projeto C++ DLL no Visual Studio
- Configure o caminho de inclusao para apontar ao diretorio fonte do POEFixer (para os headers do SDK e ImGui)
- Adicione os arquivos fonte do ImGui ao seu projeto:
imgui.cpp,imgui_draw.cpp,imgui_tables.cpp,imgui_widgets.cpp - Inclua os headers do SDK do plugin no seu codigo fonte:
Ou use o header de conveniencia do 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
- Defina
PLUGIN_EXPORTSe_CRT_SECURE_NO_WARNINGSnas Definicoes de Preprocessador do seu projeto
Plugins/
YourPlugin/
YourPlugin.dll <-- O nome do DLL DEVE corresponder ao nome da pasta
config/
settings.txt <-- Arquivo de configuracao opcional
data/
... <-- Diretorio de dados opcional (bancos de dados, caches, etc.)
O ExamplePlugin demonstra o layout recomendado do projeto:
Plugins/ExamplePlugin/
ExamplePlugin.cpp <-- Ponto de entrada principal do plugin + exportacoes de fabrica
sdk/
PluginHelpers.h <-- MemoryReader, WideToNarrow, helpers de entidade/raridade
examples/
ExampleBuffs.h <-- Lista de buffs com filtragem e barras de progresso
ExampleEntities.h <-- Lista de depuracao de entidades com mecanismo de observacao, arvores de componentes, dump JSON
ExampleInventory.h <-- ServerData, seletor de inventario, grade de slots, mods de itens com raridade
ExampleMemory.h <-- Visualizador hexadecimal, demo de Read<T>, scanner de padroes
ExampleUiExplorer.h <-- Explorador completo de elementos de UI com busca, navegacao, destaque
Um exemplo mais completo de plugin com SQLite3, atlas de icones e renderizacao de overlay:
Plugins/KillCount/
KillCount.cpp <-- Ponto de entrada principal do plugin, ciclo de vida IPlugin, UI de configuracao
KillCount.h <-- Declaracao da classe do plugin
KillTracker.cpp/h <-- Motor de contagem de kills/baus/mortes
OverlayRenderer.cpp/h <-- Overlay ImGui com padrao de arrastar para reposicionar
IconAtlas.cpp/h <-- Carregamento de folha de sprites de textura (D3D11 + stb_image)
Database.cpp/h <-- Wrapper SQLite3 para estatisticas persistentes
DisplaySettings.h <-- Struct de configuracao
sdk/
PluginHelpers.h <-- Copiado do ExamplePlugin
lib/
sqlite3.c/h <-- Amalgamacao SQLite3 (compilado como C)
sqlite3-vcpkg-config.h <-- Override local para linkagem estatica
O nome do arquivo DLL deve corresponder exatamente ao nome da pasta:
- Pasta:
Plugins/MyPlugin/-> DLL:MyPlugin.dll - O host escaneia cada subpasta em
Plugins/e procura por<NomeDaPasta>.dll
Load DLL (LoadLibrary)
-> CreatePlugin() -- Fabrica: instancia seu IPlugin
-> SetContext(ctx) -- Recebe os servicos do host
-> SetPluginDirectory(dir) -- Recebe o caminho da sua pasta
-> GetSDKVersion() -- Verificacao de compatibilidade
-> GetName() -- Nome de exibicao para a UI
-> [if enabled] OnEnable() -- Inicializar recursos
|
Loop principal (cada frame):
-> DrawUI() -- Renderizar seu overlay (somente se habilitado)
-> DrawSettings() -- Renderizar configuracoes na aba Plugins
-> WantsOverlay() -- Host verifica se o plugin quer modo overlay
|
Periodicamente / ao encerrar:
-> SaveSettings() -- Persistir suas configuracoes
|
-> OnDisable() -- Liberar recursos
-> DestroyPlugin(plugin) -- Fabrica: deletar seu IPlugin
-> FreeLibrary -- Descarregar DLL
- Todos os metodos
Draw*sao chamados na thread principal/de renderizacao -
GetSnapshot()e outras funcoes do PluginContext sao thread-safe - NAO crie threads que chamem ImGui -- ImGui nao e thread-safe
Todo plugin deve implementar a interface IPlugin (definida em plugin_sdk/PluginAPI.h):
- Quando chamado: Uma vez, imediatamente apos a criacao
-
Parametro: Caminho relativo como
"Plugins/YourPlugin" - Proposito: Armazene este caminho para carregar configuracoes/recursos
-
Quando chamado: Uma vez, apos
SetPluginDirectory -
Parametro: Ponteiro para o
PluginContextdo host (valido durante a vida do plugin) - Proposito: Armazene este ponteiro -- e sua porta de acesso a todos os dados do jogo
-
Importante: Chame
ImGui::SetCurrentContext(ctx->ImGuiContext)aqui
- Quando chamado: Quando o usuario habilita o plugin, ou na inicializacao se estava previamente habilitado
-
Parametro:
truese o processo do jogo esta atualmente vinculado - Proposito: Carregar configuracoes, alocar recursos, inicializar estado
- Quando chamado: Quando o usuario desabilita o plugin
- Proposito: Liberar recursos, parar trabalho em segundo plano
- Quando chamado: Cada frame, somente quando o plugin esta habilitado
- Proposito: Renderizar seu overlay usando ImGui
-
Nota: Use IDs de janela unicos como
"MyWindow##MyPlugin"para evitar conflitos
- Quando chamado: Cada frame, na aba de configuracoes de Plugins (somente quando habilitado)
- Proposito: Renderizar a configuracao do plugin usando ImGui
- Quando chamado: Periodicamente e ao encerrar a aplicacao
-
Proposito: Salvar suas configuracoes em disco (ex.,
Plugins/YourPlugin/config/settings.txt)
-
Retorna: Nome de exibicao mostrado na aba Plugins (ex.,
"My Plugin")
-
Retorna:
PLUGIN_SDK_VERSION(atualmente 5) - Proposito: O host verifica isso para compatibilidade -- deve corresponder
-
Retorna:
truese o plugin quer renderizar em modo overlay (overlay transparente sobre o jogo) -
Padrao:
false-- o plugin somente renderiza na janela de configuracoes normal -
Proposito: Quando qualquer plugin retorna
true, o host entra em modo overlay mesmo que nenhum recurso integrado o exija
Seu DLL deve exportar estas duas funcoes C:
extern "C" PLUGIN_API IPlugin* CreatePlugin() {
return new MyPlugin();
}
extern "C" PLUGIN_API void DestroyPlugin(IPlugin* plugin) {
delete plugin;
}A struct PluginContext (definida em plugin_sdk/PluginContext.h) fornece ponteiros de funcao para acessar os dados do jogo. Todos os tipos estao no namespace PluginSDK.
Retorna um snapshot completo do estado do jogo. Atualizado uma vez por frame. Contem:
| Campo | Tipo | Descricao |
|---|---|---|
CurrentState |
GameStateTypes |
Estado atual do jogo |
CurrentAreaName |
string |
Nome da area (ex., "The Riverways") |
CurrentAreaHash |
string |
Hash unico da instancia da area |
CurrentAreaLevel |
uint8_t |
Nivel de monstros da area atual |
IsTown |
bool |
Verdadeiro se esta na cidade |
IsHideout |
bool |
Verdadeiro se esta no esconderijo |
IsPaused |
bool |
Verdadeiro se o jogo esta pausado |
IsSkillTreeVisible |
bool |
Verdadeiro se o painel da arvore de habilidades esta aberto |
WorldToGridConvertor |
float |
Fator de conversao mundo->grade |
Player |
RadarEntity |
Dados da entidade do jogador local |
Entities |
vector<RadarEntity> |
Todas as entidades proximas |
LargeMap / MiniMap
|
MapData |
Dados do overlay do mapa |
Vitals |
PlayerVitals |
HP/ES/MP do jogador + buffs |
ScreenWidth / ScreenHeight
|
int |
Dimensoes da janela do jogo |
ProcessId |
DWORD |
ID do processo do jogo |
GameWindow |
HWND |
Handle da janela do jogo |
GameWindowForeground |
bool |
Verdadeiro se o jogo esta em primeiro plano |
IsAttached |
bool |
Verdadeiro se esta vinculado ao processo do jogo |
IsWindowValid |
bool |
Verdadeiro se a janela do jogo e valida |
LastUpdateTime |
uint64_t |
Timestamp da ultima atualizacao |
AreaChangeCounter |
uint64_t |
Incrementa na mudanca de area |
Inventories |
vector<InventoryInfo> |
Conteudo do inventario do jogador |
CurrencyTotals |
map<string,int> |
Contagens de moeda por caminho |
InventoryGrid |
InventoryGridInfo |
Info da grade do inventario |
WorldToScreenMatrix |
XMFLOAT4X4 |
Matriz de projecao 3D->2D |
Importante: Filtragem de entidades Entidades mortas (aquelas com
EntityState == Useless) sao filtradas PARA FORA do snapshot antes que os plugins o recebam. Isso significa que voce nunca observara uma transicao de HP de vivo para morto. Se voce precisa detectar kills, use a deteccao baseada em desaparecimento -- rastreie IDs de entidades por zona e conte-os como mortos quando desaparecerem da lista de entidades enquanto estiverem na proximidadeInnerCircleouOuterCircle. Veja Secao 8: Receitas comuns para detalhes.
Atalho de conveniencia para os sinais vitais do jogador.
Retorna o enum do estado atual do jogo.
Verdadeiro se o processo do jogo esta vinculado e legivel.
Verdadeiro se esta atualmente no jogo (nao carregando, nao na tela de login).
Verdadeiro se a janela do jogo e a janela em primeiro plano.
Retorna o ID do processo do jogo.
Le todos os modificadores de uma entidade de item.
Retorna: 0=Normal, 1=Magico, 2=Raro, 3=Unico
Retorna a contagem da pilha para itens de moeda/empilaveis.
Retorna o nome do tipo base do item.
Retorna o caminho de metadados do item.
Retorna o nome do tipo base do item (ex., "Divine Orb", "Chaos Orb"). Diferente de ReadItemName que retorna o caminho de metadados, este le o nome real do tipo base de BaseItemTypeData.BaseTypeName.
Retorna o nome unico do item de Words.dat (ex., "Headhunter", "Brimstone Call"). Retorna string vazia para itens nao unicos.
Retorna verdadeiro se o host esta atualmente em modo overlay (overlay transparente sobre a janela do jogo). Use isso para ajustar sua renderizacao -- ex., desenhar no overlay do jogo vs. desenhar em uma janela de configuracoes.
Retorna verdadeiro quando o menu de configuracoes do host esta visivel (o overlay e interativo). Quando o menu esta oculto, a janela de overlay e transparente ao clique (WS_EX_TRANSPARENT), entao janelas ImGui nao podem receber entrada do mouse.
Use isso para implementar o padrao de overlay arrastavel:
- Menu visivel: Mostra um indicador de arraste, permite interacao (abas, botoes)
-
Menu oculto: Remove o indicador de arraste, adiciona
ImGuiWindowFlags_NoInputspara tornar a janela nao interativa
Veja Secao 6: Padrao de overlay arrastavel para a implementacao completa.
Acesso direto a memoria do processo do jogo. Todas as leituras sao seguras (retornam 0/vazio em caso de falha).
Retorna o endereco base do modulo executavel do jogo. Retorna 0 se nao esta vinculado.
Retorna o tamanho do modulo do jogo em bytes. Retorna 0 se nao esta vinculado.
Le um bloco de bytes brutos do processo do jogo. O buffer deve ter pelo menos size bytes alocados. Retorna verdadeiro em caso de sucesso.
// 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));Le uma string ASCII terminada em nulo da memoria do jogo (max 128 caracteres).
Le uma string Unicode (wide) terminada em nulo da memoria do jogo (max 128 wchars).
Obtem um endereco resolvido de scan de padrao por nome. Retorna 0 se nao encontrado.
Padroes padrao:
| Nome | Descricao |
|---|---|
"Game States" |
Raiz do vetor GameStates |
"File Root" |
Registro de arquivos |
"AreaChangeCounter" |
Contador de transicao de area |
"Terrain Rotator Helper" |
Dados de rotacao |
"Terrain Rotation Selector" |
Seletor de rotacao |
"GameCullSize" |
Valor de recorte de tela |
Converte uma posicao no espaco mundial para coordenadas de tela. Retorna verdadeiro se a posicao esta visivel na tela.
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 ao host que escaneie os inventarios. Passe -1 para escanear todos os inventarios, ou um ID de inventario especifico. Os dados do inventario no snapshot sao preenchidos apos o scan ser completado (proximo frame).
Nota: Os dados do inventario nao sao atualizados automaticamente -- voce deve chamar esta funcao para disparar um scan. Chame-a periodicamente (ex., a cada 2 segundos) se voce precisa de dados de inventario continuos.
Retorna um ponteiro para os dados da grade transitavel. A grade e um array 2D onde 0 = nao transitavel, diferente de zero = transitavel. Retorna nullptr se os dados nao estao disponiveis.
Retorna a altura do terreno em uma posicao de grade. Retorna 0 se fora dos limites ou dados indisponiveis.
Estas funcoes leem containers da biblioteca padrao C++ diretamente da memoria do jogo, espelhando os metodos Core::Process do host.
Le um StdVector (struct de 24 bytes: {First, Last, End}) da memoria do jogo. Retorna um buffer alocado com malloc de elementos. O chamador deve fazer free() do ponteiro retornado. Retorna nullptr em caso de falha.
// 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);
}Le um StdList (struct de 16 bytes: {Head, Size}) da memoria do jogo. Percorre a lista encadeada e retorna um buffer contíguo. O chamador deve fazer free().
Le um StdBucket da memoria do jogo (le o StdVector embutido). O chamador deve fazer free().
Percorre um StdMap (struct de 16 bytes: {Head, Size}) e chama o callback para cada par chave-valor. Retorna o numero de nos 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);Le um StdWString (struct de 32 bytes com buffer inline/heap) da memoria do jogo.
Retorna o nome legivel de um ID de inventario (ex., 1 -> "MainInventory1", 3 -> "Weapon1", 64 -> "Currency1").
O SDK v4 fornece acesso direto aos dados de depuracao do host -- componentes de entidades, detalhes de inventario e arvore de elementos de UI -- correspondendo as abas de depuracao integradas.
Retorna uma lista de todas as entidades com metadados de depuracao (Id, Address, Path, Type, SubType, State, Rarity, Zone). Isso espelha a aba Debug->Entity List.
Comeca a observar os componentes de uma entidade. A thread de trabalho do host lera os dados completos de componentes desta entidade a cada frame.
Para de observar os componentes de uma entidade. Chame isso quando o usuario colapsar o no da arvore da entidade para liberar recursos.
Retorna os dados completos de componentes de uma entidade observada. Contem sub-structs para os 8 componentes reconhecidos (Life, Render, Positioned, Targetable, Animated, Stats, Actor, Buffs) mais a lista de todos os enderecos 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 o endereco base do componente ServerData.
Retorna todos os IDs de inventario do jogador e seus enderecos (do ServerData).
Comeca a observar um inventario para inspecao detalhada de depuracao. O host le a ocupacao de slots, detalhes de itens e modificadores.
Retorna dados completos do inventario observado atualmente: dimensoes da grade, ocupacao de slots, itens com raridade e 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 o endereco do elemento raiz da UI do jogo (para navegacao da arvore de UI no jogo).
Retorna o endereco raiz da UI de nivel superior.
Retorna o valor atual do GameCullSize, utilizado para calculos de escala de UI. Combinado com as dimensoes da tela, isso permite calculo preciso de posicao/tamanho dos 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)(Translation pending)
SDK v5 adds direct UI element reading without the debug watch mechanism. Navigate the UI tree, check visibility, read text, and compute screen rectangles.
| Function | Return | Purpose |
|---|---|---|
ReadUiElement(addr) |
UiElementData |
Read core UI element properties |
GetUiChildren(addr) |
vector<uintptr_t> |
Get all child element addresses |
GetUiChildAt(addr, index) |
uintptr_t |
Get single child by index |
ReadUiChildChain(root, indices, count) |
uintptr_t |
Navigate child index path from root |
IsUiElementVisible(addr) |
bool |
Check visibility (including ancestors) |
GetUiStringId(addr) |
string |
Get element's string identifier |
ComputeUiScreenRect(addr, outX, outY, outW, outH) |
bool |
Compute screen rectangle with recursive scaling |
GetUiText(addr) |
string |
Get element's display text |
21 typed component reader functions. Each takes a component address from EntityComponentCache and returns a struct with Valid flag.
| Function | Return Struct | Component |
|---|---|---|
ReadLifeComponent(addr) |
PluginLifeData |
Life (HP/ES/Mana) |
ReadRenderComponent(addr) |
PluginRenderData |
Render (position, bounds) |
ReadPositionedComponent(addr) |
PluginPositionedData |
Positioned (reaction) |
ReadTargetableComponent(addr) |
PluginTargetableData |
Targetable (flags) |
ReadChestComponent(addr) |
PluginChestData |
Chest (opened, quality) |
ReadShrineComponent(addr) |
PluginShrineData |
Shrine (available) |
ReadStackComponent(addr) |
PluginStackData |
Stack (size) |
ReadChargesComponent(addr) |
PluginChargesData |
Charges |
ReadPlayerComponent(addr) |
PluginPlayerData |
Player (name, level) |
ReadAnimatedComponent(addr) |
PluginAnimatedData |
Animated (animation) |
ReadTransitionableComponent(addr) |
PluginTransitionableData |
Transitionable |
ReadTriggerableBlockageComponent(addr) |
PluginTriggerableBlockageData |
TriggerableBlockage |
ReadMinimapIconComponent(addr) |
PluginMinimapIconData |
MinimapIcon |
ReadStateMachineComponent(addr) |
PluginStateMachineData |
StateMachine |
ReadBaseComponent(addr) |
PluginBaseData |
Base (cell size, influence) |
ReadModsComponent(addr) |
PluginModsData |
Mods (rarity, mod lists) |
ReadStatsComponent(addr) |
PluginStatsData |
Stats (key-value pairs) |
ReadBuffsComponent(addr) |
PluginBuffsData |
Buffs (active buffs) |
ReadActorComponent(addr) |
PluginActorData |
Actor (skills, deploy) |
ReadNpcComponent(addr) |
PluginNpcData |
NPC (hidden, icon) |
ReadDiesAfterTimeComponent(addr) |
PluginDiesAfterTimeData |
DiesAfterTime |
| Helper | Signature | Description |
|---|---|---|
GetHealthPercent |
float (uintptr_t lifeAddr) |
HP percentage (0-100) |
GetEsPercent |
float (uintptr_t lifeAddr) |
Energy Shield percentage |
GetManaPercent |
float (uintptr_t lifeAddr) |
Mana percentage |
IsAlive |
bool (uintptr_t lifeAddr) |
True if HP > 0 |
IsChestOpenedHelper |
bool (uintptr_t chestAddr) |
True if chest opened |
GetWorldPosition |
bool (uintptr_t renderAddr, float*, float*, float*) |
Extract world position |
GetItemRarityFromMods |
int (uintptr_t modsAddr) |
Item rarity from Mods |
IsItemIdentifiedHelper |
bool (uintptr_t modsAddr) |
True if identified |
GetStackCountHelper |
int (uintptr_t stackAddr) |
Stack count |
GetPlayerNameHelper |
string (uintptr_t playerAddr) |
Player name |
// Read entity health via component reader
auto snapshot = m_Context->GetSnapshot();
for (auto& entity : snapshot->Entities) {
if (entity.ComponentCache.HasLife()) {
auto life = m_Context->ReadLifeComponent(entity.ComponentCache.LifeAddr);
if (life.Valid) {
float hpPct = m_Context->GetHealthPercent(entity.ComponentCache.LifeAddr);
}
}
}
// Navigate UI tree
uintptr_t gameUi = m_Context->GetGameUiRootAddress();
const int path[] = {5, 1, 2};
uintptr_t btn = m_Context->ReadUiChildChain(gameUi, path, 3);
if (m_Context->IsUiElementVisible(btn)) {
float x, y, w, h;
m_Context->ComputeUiScreenRect(btn, &x, &y, &w, &h);
}O header sdk/PluginHelpers.h (incluido com o ExamplePlugin) fornece uma classe MemoryReader com tipagem segura que encapsula as funcoes brutas do 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);O MemoryReader tambem fornece wrappers de conveniencia para ReadString(), ReadUnicodeString(), GetBaseAddress(), GetModuleSize(), e GetPatternAddress().
Utilitarios adicionais em PluginHelpers.h:
-
WideToNarrow(wstring)-- conversao segura de wstring para string (perda ASCII) -
GetEntityTypeName(type)-- enum para nome de exibicao (incluindo ExpeditionMarker/ExpeditionRemnant) -
GetNearbyZoneName(zone)-- zona para nome de exibicao -
GetRarityName(rarity)/GetRarityColor(rarity)-- helpers de exibicao de raridade
Escreve no sistema de log do host. Niveis: "Debug", "Info", "Warning", "Error"
O contexto ImGui do host. Chame ImGui::SetCurrentContext() com isso em SetContext().
O ID3D11Device* do host. Faca cast e use para carregar texturas.
Todos os tipos estao no namespace PluginSDK. Plugins tipicamente adicionam using namespace PluginSDK;.
Dados por entidade disponiveis em snapshot->Entities:
| Campo | Tipo | Descricao |
|---|---|---|
Id |
uint32_t |
ID unico da entidade |
Address |
uintptr_t |
Endereco de memoria (para chamadas de API de itens) |
EntityDetailsAddress |
uintptr_t |
Endereco da struct de detalhes da entidade |
RenderComponentAddress |
uintptr_t |
Endereco do componente Render (atalho) |
IsValid |
bool |
Flag de validade da entidade |
entityType |
EntityTypes |
Categoria da entidade |
entitySubtype |
EntitySubtypes |
Subcategoria da entidade |
entityState |
EntityStates |
Estado da entidade |
Rarity |
int |
0=Normal, 1=Magico, 2=Raro, 3=Unico |
Reaction |
uint8_t |
0=Hostil, 1=Neutro, 2=Amigavel |
GridPositionX/Y |
float |
Posicao na grade do terreno |
TerrainHeight |
float |
Altura do terreno na posicao da entidade |
WorldX/Y/Z |
float |
Posicao no espaco mundial |
ModelBoundsZ |
float |
Altura do modelo |
Path |
wstring |
Caminho de metadados da entidade |
PlayerName |
wstring |
Nome do jogador (se entidade jogador) |
TgtPath |
string |
Caminho alvo (string estreita) |
CurrentHP/MaxHP |
int |
Saude da entidade |
CurrentES/MaxES |
int |
Escudo de energia da entidade |
IsSleeping |
bool |
Flag de entidade distante |
IsChestOpened |
bool |
Estado de bau aberto |
Zone |
NearbyZone |
Proximidade ao jogador |
ComponentCache |
EntityComponentCache |
Enderecos de componentes |
Importante: Entidades mortas (
EntityState::Useless) sao removidas do snapshot antes de chegar aos plugins. Voce nunca vera o HP de um monstro cair a zero -- ele simplesmente desaparece da lista. Use o campoZonepara distinguir kills (entidade desapareceu de InnerCircle/OuterCircle) de entidades saindo do alcance (entidade estava na zona Far).
Buff/debuff ativo:
| Campo | Tipo | Descricao |
|---|---|---|
Name |
string |
Nome interno do buff (ex., "flask_effect_life") |
TimeLeft |
float |
Segundos restantes |
Charges |
short |
Contagem de cargas |
TotalTime |
float |
Duracao total |
Estado do minimapa/mapa grande:
| Campo | Tipo | Descricao |
|---|---|---|
CenterX/Y |
float |
Centro do mapa |
SizeX/Y |
float |
Dimensoes do mapa |
ShiftX/Y |
float |
Deslocamento atual de pan |
DefaultShiftX/Y |
float |
Valores de deslocamento padrao |
Zoom |
float |
Nivel de zoom |
Scale |
float |
Fator de escala do mapa |
IsVisible |
bool |
O mapa esta atualmente visivel |
| Campo | Tipo | Descricao |
|---|---|---|
Id |
int |
ID do inventario |
TotalBoxesX/Y |
int |
Dimensoes da grade |
Ptr |
uintptr_t |
Endereco de memoria do inventario |
Items |
vector<InventoryItemInfo> |
Itens no inventario |
Campos do item: Address, Name (caminho de metadados), Path (mesmo que Name), BaseTypeName (nome do tipo base, ex. "Divine Orb"), UniqueName (nome unico do item de Words.dat, ex. "Headhunter", vazio para nao unicos), SlotX/Y, Width/Height, StackCount, IsCurrency
Retornado por ReadExtendedItemMods():
| Campo | Tipo | Descricao |
|---|---|---|
ImplicitMods |
vector<ItemModData> |
Modificadores implicitos |
ExplicitMods |
vector<ItemModData> |
Modificadores explicitos |
EnchantMods |
vector<ItemModData> |
Modificadores de encantamento |
HellscapeMods |
vector<ItemModData> |
Modificadores Hellscape |
CrucibleMods |
vector<ItemModData> |
Modificadores Crucible |
Rarity |
int |
0=Normal, 1=Magico, 2=Raro, 3=Unico |
| Campo | Tipo | Descricao |
|---|---|---|
Key |
string |
Chave de stat do mod |
Values |
vector<float> |
Valores do mod |
Enderecos de componentes em cache por entidade (disponiveis 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() |
ActorAddr |
uintptr_t |
HasActor() |
AnimatedAddr |
uintptr_t |
HasAnimated() |
BaseAddr |
uintptr_t |
HasBase() |
ChargesAddr |
uintptr_t |
HasCharges() |
ModsAddr |
uintptr_t |
HasMods() |
StackAddr |
uintptr_t |
HasStack() |
TransitionableAddr |
uintptr_t |
HasTransitionable() |
StateMachineAddr |
uintptr_t |
HasStateMachine() |
Metadados de entidade de GetEntityDebugList():
| Campo | Tipo | Descricao |
|---|---|---|
Id |
uint32_t |
ID da entidade |
Address |
uintptr_t |
Endereco de memoria |
Path |
string |
Caminho de metadados |
EntityType |
int |
Tipo de entidade (converter para EntityTypes) |
EntitySubType |
int |
Subtipo de entidade (converter para EntitySubtypes) |
EntityState |
int |
Estado da entidade (converter para EntityStates) |
Rarity |
int |
0=Normal, 1=Magico, 2=Raro, 3=Unico |
Zone |
NearbyZone |
Proximidade ao jogador |
ComponentAddresses |
vector<pair<string,uintptr_t>> |
Todos os pares nome->endereco de componentes |
Dados completos de componentes de GetWatchedEntityData():
| Campo | Tipo | Descricao |
|---|---|---|
EntityId |
uint32_t |
Para qual entidade sao estes dados |
Valid |
bool |
Se os dados foram lidos com sucesso |
HasLife / Life
|
bool / DebugLifeComp
|
Componente Life -- Life.Health, Life.EnergyShield, Life.Mana (cada um e um DebugVital com .Current, .Total, .Regeneration, .ReservedFlat, .ReservedPercent) |
HasRender / Render
|
bool / DebugRenderComp
|
Posicao (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, MeetsQuestState, MeetsItemRequirements |
HasAnimated / Animated
|
bool / DebugAnimatedComp
|
Path de animacao (string), Id (uint32) |
HasStats / Stats
|
bool / DebugStatsComp
|
CurrentWeaponIndex, IsShapeshifted, StatsItems/StatsBuff (vetores de pares ID de stat->valor) |
HasActor / Actor
|
bool / DebugActorComp
|
AnimationId, AnimationName, ActiveSkills (vetor de DebugActiveSkill), DeployedCounts[256] |
HasBuffs / Buffs
|
bool / vector<DebugBuff>
|
Buffs ativos: Name, TotalTime, TimeLeft, Charges, FlaskSlot, Effectiveness, SourceEntityId |
Detalhes de inventario de GetWatchedInventoryData():
| Campo | Tipo | Descricao |
|---|---|---|
InventoryId |
int |
ID do inventario (-1 se nenhum) |
Address |
uintptr_t |
Endereco do inventario |
TotalBoxesX/Y |
int |
Dimensoes da grade |
ServerRequestCounter |
int |
Contador de sincronizacao do servidor |
GridScreenX / GridScreenY
|
float |
Posicao da grade na tela |
CellSize |
float |
Tamanho da celula da grade em pixels |
GridValid |
bool |
Se os dados da grade sao validos |
SlotOccupied |
vector<bool> |
Ocupacao por slot |
Items |
vector<DebugInventoryItem> |
Itens com caminho, raridade e modificadores |
| Campo | Tipo | Descricao |
|---|---|---|
Address |
uintptr_t |
Endereco da entidade do item |
Path |
string |
Caminho de metadados do item |
BaseTypeName |
string |
Nome do tipo base do item (ex. "Divine Orb") |
UniqueName |
string |
Nome unico do item de Words.dat (vazio para nao unicos) |
SlotX / SlotY
|
int |
Posicao na grade do inventario |
Rarity |
int |
0=Normal, 1=Magico, 2=Raro, 3=Unico |
ItemLevel |
int |
Nivel do item |
RequiredLevel |
int |
Nivel requerido do personagem |
IsIdentified |
bool |
Se o item esta identificado |
IsCorrupted |
bool |
Se o item esta corrompido |
CraftedModCount |
int |
Numero de mods criados |
ImplicitMods |
vector<DebugModInfo> |
Modificadores implicitos |
ExplicitMods |
vector<DebugModInfo> |
Modificadores explicitos |
EnchantMods |
vector<DebugModInfo> |
Modificadores de encantamento |
HellscapeMods |
vector<DebugModInfo> |
Modificadores Hellscape |
| Campo | Tipo | Descricao |
|---|---|---|
Name |
string |
Nome da habilidade |
UseStage |
int |
Estagio de uso atual |
CastType |
int |
Tipo de lancamento |
TotalUses |
int |
Contagem total de usos |
TotalCooldownTimeInMs |
int |
Cooldown em milissegundos |
CanBeUsed |
bool |
Se a habilidade pode ser usada atualmente |
| Campo | Tipo | Descricao |
|---|---|---|
Name |
string |
Nome interno do buff |
TotalTime |
float |
Duracao total |
TimeLeft |
float |
Segundos restantes |
Charges |
short |
Contagem de cargas |
FlaskSlot |
short |
Indice do slot de frasco |
Effectiveness |
short |
Efetividade do buff |
SourceEntityId |
uint32_t |
Entidade que aplicou este buff |
| Campo | Tipo | Descricao |
|---|---|---|
Name |
string |
Nome de exibicao do mod |
StatKey |
string |
Identificador de chave de stat |
AffixName |
string |
Nome do afixo |
GenerationType |
int |
1=Prefixo, 2=Sufixo, 3=Implicito |
Value0 |
float |
Primeiro valor (NaN se nenhum) |
Value1 |
float |
Segundo valor (NaN se nenhum) |
(Translation pending)
| Field | Type | Description |
|---|---|---|
Valid |
bool |
Whether the read succeeded |
X / Y
|
float |
Element position |
Width / Height
|
float |
Element size |
ScaleX / ScaleY
|
float |
Scale factors |
IsVisible |
bool |
Visibility flag |
IsEnabled |
bool |
Enabled flag |
ChildCount |
int |
Number of children |
ParentAddr |
uintptr_t |
Parent element address |
SelfAddr |
uintptr_t |
Self pointer |
All component reader functions return structs with a Valid field. See the English guide for full field listings of all 22 structs (PluginLifeData, PluginRenderData, PluginPositionedData, PluginTargetableData, PluginChestData, PluginShrineData, PluginStackData, PluginChargesData, PluginPlayerData, PluginAnimatedData, PluginTransitionableData, PluginTriggerableBlockageData, PluginMinimapIconData, PluginStateMachineData, PluginBaseData, PluginModsData, PluginStatsData, PluginBuffsData, PluginActorData, PluginNpcData, PluginDiesAfterTimeData).
EntityTypes: Unidentified(0), Chest(1), NPC(2), Player(3), Shrine(4), Monster(5), DeliriumBomb(6), DeliriumSpawner(7), OtherImportantObjects(8), Item(9), Renderable(10), AreaTransition(11), ExpeditionMarker(12), ExpeditionRemnant(13)
EntitySubtypes: _Unidentified(0), _None(1), PlayerSelf(2), PlayerOther(3), ChestWithMagicRarity(4), ChestWithRareRarity(5), ExpeditionChest(6), BreachChest(7), Strongbox(8), SpecialNPC(9), POIMonster(10), PinnacleBoss(11), WorldItem(12), InventoryItem(13)
EntityStates: None(0), Useless(1), PlayerLeader(2), MonsterFriendly(3), PinnacleBossHidden(4)
NearbyZone: None(0), InnerCircle(1, ~60 unidades de grade), OuterCircle(2, ~120 unidades de grade), 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)
O host e o plugin compartilham o mesmo contexto ImGui. Voce deve chamar:
ImGui::SetCurrentContext(static_cast<ImGuiContext*>(m_Context->ImGuiContext));no seu metodo SetContext().
Sempre use IDs de janela unicos para evitar conflitos com o host ou outros plugins:
ImGui::Begin("My Window##MyPluginName", &showWindow);- Janelas, abas, arvores, tabelas, listas de desenho
- Carregamento de texturas via dispositivo D3D11
- Renderizacao de overlay via
ImGui::GetBackgroundDrawList() - Icones FontAwesome 6 via
#include "imgui/IconsFontAwesome6.h"(ex.,ICON_FA_ARROWS_UP_DOWN_LEFT_RIGHT)
Use WorldToScreen() para desenhar rotulos/formas nas posicoes 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));
}Sobrescreva WantsOverlay() para retornar true para solicitar ao host que entre em modo overlay:
bool WantsOverlay() override { return m_OverlayEnabled; }Quando em modo overlay, a janela do host e transparente e posicionada sobre o jogo. Suas chamadas DrawUI() renderizam diretamente na tela do jogo.
O overlay do host usa WS_EX_TRANSPARENT para tornar a janela transparente ao clique quando o menu esta oculto. Isso significa que janelas ImGui nao podem receber entrada do mouse a menos que o menu esteja visivel. Para criar uma janela de overlay arrastavel (como o Vitals Overlay integrado), use este padrao de modo duplo:
#include "imgui/IconsFontAwesome6.h"
void MyPlugin::RenderOverlay() {
bool menuVisible = m_Context->IsMenuVisible ? m_Context->IsMenuVisible() : false;
if (menuVisible) {
// === MODO ARRASTAVEL ===
// Janela com fundo, indicador de arraste, controles interativos
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 NAO INTERATIVO ===
// Overlay estatico — sem arraste, sem interacao de mouse
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();
}
}Pontos-chave:
-
ImGuiCond_Appearingdefine a posicao somente na primeira exibicao; ImGui entao rastreia a posicao de arraste -
NoTitleBar+ semNoMove= a janela e arrastavel de qualquer area vazia (comportamento padrao do ImGui) -
NoInputsno modo nao interativo evita que o overlay roube o foco atraves deWS_EX_TRANSPARENT - Sempre verifique nulo o ponteiro
IsMenuVisiblepara compatibilidade retroativa:m_Context->IsMenuVisible ? m_Context->IsMenuVisible() : false - Salve a posicao no seu arquivo de configuracao para que persista entre sessoes
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
}Armazene configuracoes em <PluginDirectory>/config/:
std::filesystem::path settingsPath =
std::filesystem::path(m_Directory) / "config" / "settings.txt";Para plugins com muitas configuracoes, um formato de texto simples chave=valor funciona bem:
// 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...
}Entidades mortas sao filtradas do snapshot (EntityState::Useless), entao voce nao pode detectar o HP caindo a 0. Em vez disso, rastreie entidades por ID e detecte quando elas desaparecem das zonas proximas:
// 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: Entidades dentro de ~120 unidades de grade que desaparecem repentinamente quase certamente foram mortas (nao apenas sairam do alcance). Entidades na zona Far naturalmente aparecem e desaparecem da lista de entidades -- nao conte essas.
Importante: Limpe m_PrevEntities na mudanca de area (AreaChangeCounter mudou) 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.BaseTypeName.c_str(), item.Path.c_str(),
(int)item.ImplicitMods.size(), (int)item.ExplicitMods.size(),
(int)item.EnchantMods.size(), (int)item.HellscapeMods.size());
}
}
}uintptr_t uiRoot = m_Context->GetGameUiRootAddress();
if (uiRoot) {
PluginSDK::MemoryReader mem(m_Context);
// Read children vector at offset 0x010
auto children = mem.ReadStdVector<uintptr_t>(uiRoot + 0x010);
for (auto childAddr : children) {
// Read StringId at offset 0x448
uintptr_t strPtr = mem.Read<uintptr_t>(childAddr + 0x448);
if (strPtr) {
std::string name = m_Context->ReadString(strPtr);
ImGui::Text("Child: %s (0x%llX)", name.c_str(), childAddr);
}
}
}auto mods = m_Context->ReadExtendedItemMods(item.Address);
ImGui::TextColored(
PluginSDK::GetRarityColor(mods.Rarity),
"Rarity: %s", PluginSDK::GetRarityName(mods.Rarity));
for (auto& mod : mods.ExplicitMods) {
ImGui::BulletText("%s", mod.Key.c_str());
}| Configuracao | Valor |
|---|---|
| Configuration | Release |
| Platform | x64 |
| C++ Standard | /std:c++20 |
| Runtime Library |
/MD (DLL Multi-threaded) |
| Configuration Type | DLL |
- Seu(s) arquivo(s)
.cppdo plugin - Arquivos fonte do ImGui:
imgui.cpp,imgui_draw.cpp,imgui_tables.cpp,imgui_widgets.cpp - Caminho de inclusao para o diretorio raiz do POEFixer (para headers do SDK e ImGui)
- Opcional: Copie
Plugins/ExamplePlugin/sdk/PluginHelpers.hpara o wrapperMemoryReadere funcoes utilitarias
Seu .vcxproj deve ter estes diretorios de inclusao adicionais:
<AdditionalIncludeDirectories>$(SolutionDir)POEFixer;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>Se usar bibliotecas de terceiros locais (ex., SQLite3 em uma subpasta lib/), adicione $(ProjectDir)lib antes do caminho da solucao para que headers locais tenham prioridade:
<AdditionalIncludeDirectories>$(ProjectDir)lib;$(SolutionDir)POEFixer;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>Para usar SQLite3 em um plugin, voce deve compilar o codigo fonte amalgamado diretamente no seu DLL -- Windows LoadLibrary nao busca no proprio diretorio do DLL por dependencias, entao linkar dinamicamente sqlite3.dll falhara com erro 126.
Passos:
- Copie
sqlite3.cesqlite3.hpara o diretoriolib/do seu plugin - Crie
lib/sqlite3-vcpkg-config.hpara sobrescreverSQLITE_API(previne erros 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
- Adicione
sqlite3.cao seu.vcxprojcomo arquivo C com avisos desabilitados:<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 carregar texturas de arquivos de imagem (PNG, JPG), inclua stb_image em um arquivo .cpp:
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"Entao use o dispositivo D3D11 de m_Context->D3DDevice para criar texturas na GPU.
Configure seu diretorio de saida para:
$(SolutionDir)x64\Release\Plugins\YourPlugin\
Copie seu DLL compilado para Plugins/YourPlugin/YourPlugin.dll ao lado do executavel principal.
- Compile seu DLL do plugin em modo Debug
- Inicie a aplicacao host
- No Visual Studio: Depurar -> Anexar ao Processo -> selecione o .exe do host
- Defina breakpoints no codigo fonte do seu plugin
- O depurador parara quando seu codigo for chamado
| Problema | Solucao |
|---|---|
| Plugin nao carrega | Verifique se o nome do DLL corresponde exatamente ao nome da pasta |
| "SDK version mismatch" | Recompile o plugin com os headers mais recentes do SDK (versao atual: 5) |
| Erro 126 do LoadLibrary | O DLL tem dependencias nao resolvidas. Para bibliotecas de terceiros como SQLite3, compile-as estaticamente no DLL (veja Secao 9). Use dumpbin /dependents YourPlugin.dll para verificar. |
| Crash ao carregar | Verifique incompatibilidade de CRT -- ambos devem usar /MD
|
| ImGui nao renderiza | Certifique-se de que ImGui::SetCurrentContext() e chamado em SetContext()
|
| Dados estao vazios/zero | Verifique IsAttached() e IsInGame() antes de ler dados |
| Inventario esta vazio | Chame RequestInventoryScan(-1) -- dados de inventario sao sob demanda |
| Erro "Missing exports" | Certifique-se de que CreatePlugin e DestroyPlugin sao exportados com extern "C"
|
| Plugin causa crash no host | Isso nao deveria acontecer -- todas as chamadas de plugin sao protegidas por SEH. Verifique os logs. |
| Dados obsoletos |
GetSnapshot() retorna os dados do frame mais recente. Nao armazene o ponteiro em cache. |
| Leitura de memoria retorna 0 | Verifique se IsAttached() e verdadeiro e o endereco e valido |
| WorldToScreen retorna falso | A posicao pode estar atras da camera ou fora da tela |
| Janela de overlay nao e clicavel | O host usa WS_EX_TRANSPARENT quando o menu esta oculto. Use IsMenuVisible() para mostrar controles interativos somente quando o menu esta ativo. Veja o Padrao de Overlay Arrastavel na Secao 6. |
| Deteccao de kills nao funciona | Entidades mortas sao removidas do snapshot. Use deteccao baseada em desaparecimento em vez de transicao de HP. Veja Secao 8. |
| Erros C2491 "dllimport function" | Os headers da sua biblioteca de terceiros definem __declspec(dllimport). Crie um header de override local que defina a macro API como vazia (veja exemplo SQLite3 na Secao 9). |