Um SDK C++ moderno, header-only e de alto nível para o desenvolvimento de plugins e módulos para SA-MP.
- Deutsch: README
- English: README
- Español: README
- Français: README
- Italiano: README
- Polski: README
- Русский: README
- Svenska: README
- Türkçe: README
- SA-MP SDK
- Idiomas
- Índice
- 1. Introdução e Filosofia de Design
- 2. Configuração e Ambiente
- 3. Guia de Uso Abrangente da API
- 3.1. O Ciclo de Vida do Plugin
- 3.2.
Plugin_Public
: Interceptando Eventos do Pawn - 3.3.
Plugin_Native
: Criando Funções Nativas em C++ - 3.4.
Plugin_Native_Hook
: Interceptando Nativas Existentes do SA-MP - 3.5.
Pawn_*
Macros: Chamando Funções do Pawn a Partir do C++ - 3.6.
Plugin_Module
: Gerenciamento de Módulos Dinâmicos - 3.7.
Plugin_Call
: Chamando Nativas Internas do Plugin - 3.8. Funções Utilitárias do SDK
- 4. Anatomia Interna e Arquitetura do SDK
- 4.1.
core.hpp
: A Fundação Minimalista - 4.2.
platform.hpp
eversion.hpp
: Compatibilidade e Metadados - 4.3.
function_hook.hpp
: O Motor de Interceptação x86 - 4.4.
interceptor_manager.hpp
: O Controlador de Hooks do AMX - 4.5.
amx_manager.hpp
: Gerenciando InstânciasAMX*
- 4.6.
public_dispatcher.hpp
: O Roteador de CallbacksPlugin_Public
- 4.7.
native.hpp
: Gerenciando e Chamando Nativas do Plugin - 4.8.
native_hook_manager.hpp
: O Motor de Hooks de Natives - 4.9.
callbacks.hpp
&amx_memory.hpp
: Chamadas C++ -> Pawn e RAII - 4.10.
amx_api.hpp
&amx_helpers.hpp
&amx_defs.h
: Acesso Abstraído à AMX
- 4.1.
- 5. Compilação e Deploy
- Licença
A API de plugins do SA-MP é uma interface de programação em C. Embora funcional e fundamental, ela apresenta os desafios inerentes à programação de baixo nível:
- Gerenciamento Manual de Memória: Funções como
amx_Allot
eamx_Release
exigem que o desenvolvedor aloque e desaloque explicitamente a memória na heap da AMX. Isso é uma fonte comum de vazamentos de memória e falhas de runtime. - Tipagem Fraca e Conversões Manuais: Parâmetros são passados como um array de
cell
s, forçando conversões explícitas (e muitas vezes inseguras) entrecell
,int
,float
, echar*
. - Verbosidade e Boilerplate: Extrair múltiplos parâmetros de um array
cell* params
, lidar com tamanhos de strings, e gerenciar a stack da AMX para chamadas de retorno C++ para Pawn exige código repetitivo. - Fragilidade da Interface: A falta de verificação em tempo de compilação (type safety) significa que erros na passagem de parâmetros ou tipos podem passar despercebidos até a execução, causando crashes ou comportamentos indefinidos.
O SA-MP SDK aborda esses problemas fornecendo uma poderosa camada de abstração em C++:
- RAII (Resource Acquisition Is Initialization): Gerenciamento automático de memória na AMX.
Amx_Scoped_Memory
garante que a memória alocada seja liberada. - Segurança de Tipos: Conversão automática e segura de parâmetros entre C++ e Pawn. Você lida com
int
,float
,std::string
diretamente. - Sintaxe Concisa e Idiomática: Macros e templates fornecem uma API limpa que se assemelha mais ao C++ moderno do que à API C tradicional.
- Interceptação Robusta: Um motor de hooking avançado permite a interceptação transparente de callbacks do Pawn (
Plugin_Public
), a criação de novas nativas (Plugin_Native
), e o hooking e encadeamento de funções nativas existentes do SA-MP (Plugin_Native_Hook
). - Alta Performance: Uso extensivo de hashing em tempo de compilação (FNV1a), caching de funções e otimizações de
thread_local
para minimizar o overhead das abstrações.
O SDK é composto exclusivamente por arquivos de cabeçalho (.hpp
, .h
).
- Vantagens:
- Integração Simplificada: Não há bibliotecas para construir, vincular ou distribuir. Basta incluir os headers.
- Otimizações do Compilador: O compilador tem visibilidade completa do código do SDK e do seu plugin, permitindo inlining agressivo e otimizações de tempo de linkagem, o que pode resultar em binários mais rápidos.
- Implicações:
- Tempo de Compilação: Para projetos muito grandes, a compilação pode levar mais tempo devido à inclusão repetida do código do SDK. Isso é mitigado por guards de inclusão e pela natureza de "incluir apenas o que é necessário" do C++.
- Macros de Implementação: A necessidade da macro
SAMP_SDK_IMPLEMENTATION
é uma consequência do modelo header-only para evitar redefinições de símbolos.
- Compilador C++: Compatível com C++14 ou superior (o SDK utiliza recursos de C++14, C++17 e C++20 para otimizações específicas, mas C++14 é o mínimo).
- GCC (versão 7+)
- Clang (versão 5+)
- MSVC (Visual Studio 2015/2017/2019/2022)
- Arquitetura: x86 (32-bit). O SA-MP opera exclusivamente nesta arquitetura. O SDK inclui verificações em
platform.hpp
que emitirão erros de compilação se a arquitetura incorreta for detectada. - Sistema Operacional: Windows ou Linux.
Para clareza e organização, é comum organizar o SDK em uma subpasta samp-sdk
.
meu_plugin/
├── samp-sdk/
│ ├── // Outros arquivos do SDK
│ └── samp_sdk.hpp // O principal header a ser incluído
│
├── src/
│ ├── main.cpp // Onde SAMP_SDK_IMPLEMENTATION é definido
│ └── my_custom_logic.cpp // Opcional, para organizar código
│
└── CMakeLists.txt (ou .vcxproj, Makefile)
Sempre defina estas macros antes de incluir samp_sdk.hpp
.
- Propósito: Sinaliza ao compilador que este arquivo
.cpp
deve gerar as implementações das funções de exportação do plugin (Supports
,Load
,Unload
,AmxLoad
,AmxUnload
,ProcessTick
). O SDK cuida da exportação dessas funções automaticamente, eliminando a necessidade de arquivos.def
(no Windows) ou declarações__attribute__((visibility("default")))
(no Linux) para estas funções. - Impacto Técnico: Sem esta macro, o linker não encontrará as exportações necessárias, e o servidor SA-MP não conseguirá carregar seu plugin.
- Regra Fundamental: Defina esta macro em APENAS UM arquivo
.cpp
em todo o seu projeto. Definí-la em mais de um arquivo causará um erro de linkagem de "símbolo duplicado".
// main.cpp
#define SAMP_SDK_IMPLEMENTATION // A macro faz o SDK exportar as funções necessárias automaticamente.
#include "samp-sdk/samp_sdk.hpp"
// ... seu código de plugin ...
- Propósito: Habilita os callbacks de ciclo de vida do script Pawn (
OnAmxLoad
,OnAmxUnload
) e a funcionalidade de criar novas nativas em C++ (Plugin_Native
). - Funcionalidades Ativadas:
- Callbacks
OnAmxLoad(AMX* amx)
eOnAmxUnload(AMX* amx)
. - Declaração e registro de novas nativas C++ usando
Plugin_Native
. - A macro
Plugin_Call
para invocar nativas criadas comPlugin_Native
dentro do seu próprio plugin.
- Callbacks
- Impacto Técnico: Quando esta macro é definida, o SDK coleta automaticamente todas as suas
Plugin_Native
s. Na funçãoSupports()
, a flagSUPPORTS_AMX_NATIVES
é automaticamente adicionada se houver qualquerPlugin_Native
no seu projeto. - Flexibilidade: Você pode definir esta macro em múltiplos arquivos
.cpp
. O sistema de registro estático do SDK consolidará todas as nativas de diferentes unidades de compilação em uma única lista global.
// my_natives.cpp (pode ser um arquivo separado do main.cpp)
#define SAMP_SDK_WANT_AMX_EVENTS // Apenas para habilitar Plugin_Native e os callbacks OnAmxLoad/OnAmxUnload
#include "samp-sdk/samp_sdk.hpp"
Plugin_Native(MyCustomNative, AMX* amx, cell* params) {
// ...
return 0;
}
- Propósito: Habilita o callback
OnProcessTick()
, que é invocado regularmente pelo servidor. - Impacto Técnico: Adiciona automaticamente a flag
SUPPORTS_PROCESS_TICK
na funçãoSupports()
.
// main.cpp
#define SAMP_SDK_IMPLEMENTATION
#define SAMP_SDK_WANT_PROCESS_TICK
#include "samp-sdk/samp_sdk.hpp"
void OnProcessTick() {
// Lógica executada a cada "tick" do servidor (ex: 20ms)
// Cuidado com operações pesadas aqui!
}
Este arquivo fornece todas as constantes e limites conhecidos do SA-MP, como MAX_PLAYERS
, INVALID_PLAYER_ID
, PLAYER_STATE_ONFOOT
, WEAPON_DEAGLE
, DIALOG_STYLE_LIST
, etc. É automaticamente incluído por samp_sdk.hpp
e deve ser usado para garantir a compatibilidade e legibilidade do código.
As seguintes funções são os pontos de entrada e saída do seu plugin, exportadas automaticamente pelo SDK.
- Descrição: Primeira função chamada pelo servidor SA-MP após o carregamento bem-sucedido do seu plugin na memória.
- Uso: Ideal para inicializar qualquer sistema, carregar configurações, abrir conexões com banco de dados, ou carregar módulos (
Plugin_Module
). - Retorno:
true
: O plugin foi inicializado com sucesso e o carregamento continua.false
: O plugin falhou ao inicializar. O carregamento será abortado e o plugin será descarregado.
// main.cpp
bool OnLoad() {
Samp_SDK::Log("Inicializando MeuPlugin v1.0...");
// Exemplo: Carregar um módulo (mais detalhes na seção 3.6)
if (!Plugin_Module("my_database_module", "plugins/db_connector", "Módulo de Banco de Dados carregado.")) {
Samp_SDK::Log("ERRO: Falha ao carregar o módulo de banco de dados!");
return false; // Aborta o carregamento do plugin principal
}
return true;
}
- Descrição: Última função chamada pelo servidor SA-MP antes de descarregar seu plugin da memória.
- Uso: Ideal para limpar recursos, fechar conexões, salvar estados, e garantir que nenhum recurso seja vazado. O SDK gerencia o descarregamento de módulos (
Plugin_Module
) automaticamente.
// main.cpp
void OnUnload() {
Samp_SDK::Log("MeuPlugin descarregado. Liberando recursos...");
// Nenhuma ação manual é necessária para módulos carregados via Plugin_Module;
// o SDK cuida disso.
}
- Descrição: Informa ao servidor SA-MP quais recursos seu plugin suporta e deseja usar.
- Uso: Sempre retorne
SUPPORTS_VERSION
(ouSAMP_PLUGIN_VERSION
). As flagsSUPPORTS_AMX_NATIVES
eSUPPORTS_PROCESS_TICK
são adicionadas automaticamente pelo SDK se houverPlugin_Native
s, e/ou se a macroSAMP_SDK_WANT_PROCESS_TICK
for definida, respectivamente. Isso simplifica a manutenção e evita erros.
// main.cpp
unsigned int GetSupportFlags() {
return SUPPORTS_VERSION; // O SDK adiciona as flags necessárias automaticamente.
}
- Requer:
SAMP_SDK_WANT_AMX_EVENTS
- Descrição: Chamada sempre que um novo script Pawn (um Gamemode ou Filterscript) é carregado e inicializado no servidor.
- Uso: Se você precisar de lógica específica para cada script AMX, ou inicializar dados específicos por script.
// main.cpp (com SAMP_SDK_WANT_AMX_EVENTS definido)
void OnAmxLoad(AMX* amx) {
// amx representa a nova instância do script carregado.
Samp_SDK::Log("Script AMX carregado: %p", (void*)amx);
}
- Requer:
SAMP_SDK_WANT_AMX_EVENTS
- Descrição: Chamada quando um script Pawn é descarregado do servidor.
- Uso: Para limpar quaisquer recursos específicos que você alocou ou associou àquele
AMX*
em particular.
// main.cpp (com SAMP_SDK_WANT_AMX_EVENTS definido)
void OnAmxUnload(AMX* amx) {
Samp_SDK::Log("Script AMX descarregado: %p", (void*)amx);
}
- Requer:
SAMP_SDK_WANT_PROCESS_TICK
- Descrição: Chamada repetidamente a cada "tick" do servidor (geralmente 20 vezes por segundo, ou a cada 50ms).
- Uso: Para lógica de fundo contínua, temporizadores, atualizações de estado que não dependem de eventos de jogador, ou sincronização de dados.
- Cuidado: Evite operações de bloqueio ou computacionalmente pesadas aqui, pois podem causar lag no servidor.
// main.cpp (com SAMP_SDK_WANT_PROCESS_TICK definido)
static int tick_count = 0;
void OnProcessTick() {
tick_count++;
if (tick_count % 200 == 0) // A cada 10 segundos (20 ticks/seg * 10 seg = 200 ticks)
Samp_SDK::Log("Servidor ativo por %d segundos.", tick_count / 20);
}
A macro Plugin_Public
é a ponte primária para receber callbacks do Pawn em seu código C++.
Plugin_Public(NomeDaPublic, Tipo1 Param1, Tipo2 Param2, ...)
- O nome da função C++ que você declara deve ser o mesmo do callback Pawn (ex:
OnPlayerConnect
). - Os tipos de parâmetros C++ (
int
,float
,std::string
) são automaticamente convertidos pelo SDK.
// Intercepta OnPlayerText(playerid, text[])
Plugin_Public(OnPlayerText, int playerid, std::string text) {
Samp_SDK::Log("Jogador %d disse: %s", playerid, text.c_str());
return PLUGIN_PUBLIC_CONTINUE;
}
O SDK automaticamente lida com a leitura da cell stack
da AMX e a conversão para os tipos C++ especificados:
int
: Convertido diretamente decell
.float
: Convertido decell
usandoamx::AMX_CTOF
.std::string
: O SDK lê o endereço da string na AMX, aloca umstd::string
em C++ e copia o conteúdo.
O valor retornado pela sua função Plugin_Public
é crucial e determina o fluxo de execução do callback:
return PLUGIN_PUBLIC_CONTINUE;
(valor1
): Indica que a execução do callback deve continuar. Se houver outros plugins que também interceptam este callback, eles serão chamados (em ordem inversa de carregamento). Em seguida, apublic
original no script Pawn será invocada.return PLUGIN_PUBLIC_STOP;
(valor0
): Indica que a execução do callback deve ser interrompida. Nenhum outro plugin (com prioridade menor) ou apublic
original no script Pawn será invocado para este evento. Isso é ideal para implementar um sistema que "sobrescreve" ou "bloqueia" um comportamento padrão do SA-MP.
// main.cpp
Plugin_Public(OnPlayerCommandText, int playerid, std::string cmdtext) {
if (cmdtext == "/freeze") {
Pawn_Native(TogglePlayerControllable, playerid, 0); // Congela o jogador
Pawn_Native(SendClientMessage, playerid, -1, Plugin_Format("Jogador %d congelado.", playerid));
return PLUGIN_PUBLIC_STOP; // Impede que o comando seja processado por outros scripts.
}
return PLUGIN_PUBLIC_CONTINUE; // Permite que outros comandos sejam processados.
}
Uma característica avançada do Plugin_Public
é o suporte a "Ghost Callbacks". Isso significa que você pode interceptar um callback Pawn mesmo que ele não esteja presente no script .amx
do gamemode ou filterscript. O SDK "engana" o servidor para que ele chame seu hook C++ de qualquer forma. Isso é útil para callbacks internos ou para criar novas funcionalidades sem depender da presença de uma public
no script Pawn.
// Você pode definir um callback que o script Pawn não possui, mas seu plugin irá ouvi-lo.
Plugin_Public(OnMyCustomInternalEvent, int data1, float data2) {
Samp_SDK::Log("Evento interno customizado recebido: %d, %.2f", data1, data2);
return PLUGIN_PUBLIC_CONTINUE;
}
// Para "disparar" este evento de outro ponto do seu código C++:
// Pawn_Public(OnMyCustomInternalEvent, 123, 45.67f);
// A chamada irá para o seu Plugin_Public acima, mesmo que não haja OnMyCustomInternalEvent no Pawn.
Plugin_Native
permite que você estenda a funcionalidade do Pawn com código C++ de alta performance.
Plugin_Native(NomeDaNativa, AMX* amx, cell* params)
- A função C++ deve ter exatamente esta assinatura:
cell NomeDaNativa(AMX* amx, cell* params)
. NomeDaNativa
é o nome que os scripts Pawn usarão.
// Pawn: native GetPlayerPingAverage(playerid);
Plugin_Native(GetPlayerPingAverage, AMX* amx, cell* params) {
// ... Implementação ...
return 0;
}
Note
Você não precisa chamar amx_Register
manualmente. O SDK detecta todas as suas Plugin_Native
s (em qualquer arquivo .cpp
que tenha incluído samp_sdk.hpp
e definido SAMP_SDK_WANT_AMX_EVENTS
) e as registra automaticamente em cada script AMX carregado (OnAmxLoad
).
- Descrição: Helper mais simples para extrair múltiplos parâmetros sequencialmente.
- Uso:
Register_Parameters(variavel1, variavel2, ...)
- Limitações: Para parâmetros de entrada. Não lida com parâmetros opcionais ou acesso por índice.
- Tipos Suportados:
int
,float
,std::string
.
// Pawn: native SetPlayerSkinAndMoney(playerid, skinid, money);
Plugin_Native(SetPlayerSkinAndMoney, AMX* amx, cell* params) {
int playerid, skinid, money;
Register_Parameters(playerid, skinid, money); // Extrai os 3 parâmetros
Pawn_Native(SetPlayerSkin, playerid, skinid);
Pawn_Native(GivePlayerMoney, playerid, money);
return 1;
}
- Descrição: Uma classe wrapper que fornece uma interface orientada a objetos para acessar os parâmetros de uma nativa. Mais poderosa para cenários complexos.
- Construção:
Native_Params p(amx, params);
- Descrição: Retorna o número de parâmetros passados para a nativa.
- Uso: Essencial para lidar com parâmetros opcionais.
- Descrição: Extrai um parâmetro de entrada por índice e o converte para o tipo
T
. - Tipos Suportados:
int
,float
,std::string
.
// Pawn: native GetPlayerWeaponAmmo(playerid, weaponid = -1);
Plugin_Native(GetPlayerWeaponAmmo, AMX* amx, cell* params) {
Native_Params p(amx, params);
int playerid = p.Get<int>(0);
int weaponid = (p.Count() > 1) ? p.Get<int>(1) : Pawn_Native(GetPlayerWeapon, playerid);
return Pawn_Native(GetPlayerAmmo, playerid, weaponid);
}
- Descrição: Obtém o valor de um parâmetro de referência (ponteiro Pawn) e o armazena em
out_value
. - Uso: Para ler valores que foram passados por referência do Pawn.
- Retorno:
true
se o endereço AMX for válido,false
caso contrário.
// Pawn: native CheckPlayerStats(playerid, &Float:health, &money);
Plugin_Native(CheckPlayerStats, AMX* amx, cell* params) {
Native_Params p(amx, params);
int playerid = p.Get<int>(0);
float health = 0.0f;
int money = 0;
// Obtém os valores das referências (o Pawn passou endereços)
p.Get_REF(1, health); // Lê o valor de Float:health
p.Get_REF(2, money); // Lê o valor de money
Samp_SDK::Log("Jogador %d, Health: %.1f, Money: %d", playerid, health, money);
// Agora, modifique-os
health = 50.0f;
money += 100;
// E os escreve de volta na memória do Pawn
p.Set_REF(1, health);
p.Set_REF(2, money);
return 1;
}
- Descrição: Retorna um
std::optional<T>
para ler um parâmetro de referência. Mais seguro para C++17 e superior.
- Descrição: Escreve um valor
T
em um parâmetro de referência Pawn (o endereço que o Pawn passou). - Uso: Para modificar valores que foram passados por referência, fazendo com que o Pawn veja a alteração.
- Retorno:
true
se a escrita foi bem-sucedida,false
caso contrário.
- Sua função
Plugin_Native
deve retornar umcell
. - Para retornar um
int
oubool
, use um cast paracell
. - Para retornar um
float
, useamx::AMX_FTOC(meu_float)
.
// Retorna um bool
Plugin_Native(IsPlayerSpawned, AMX* amx, cell* params) {
int playerid;
Register_Parameters(playerid);
return (Pawn_Native(GetPlayerState, playerid) == PLAYER_STATE_SPAWNED) ? 1 : 0;
}
// Retorna um float
Plugin_Native(GetPlayerMaxHealth, AMX* amx, cell* params) {
return amx::AMX_FTOC(100.0f); // Retorna 100.0f
}
A macro Plugin_Native_Hook
permite que você intercepte e modifique o comportamento de qualquer função nativa existente do SA-MP ou de outros plugins. Este é um mecanismo poderoso para estender ou alterar a lógica padrão do servidor.
Plugin_Native_Hook(NomeDaNativa, AMX* amx, cell* params)
- A função C++ deve ter exatamente esta assinatura:
cell NomeDaNativa(AMX* amx, cell* params)
. NomeDaNativa
deve ser o nome exato da native que você deseja hookar (ex:SendClientMessage
,SetPlayerPos
).
// Intercepta a native SendClientMessage
Plugin_Native_Hook(SendClientMessage, AMX* amx, cell* params) {
// ...
return Call_Original_Native(SendClientMessage); // Importante chamar a original
}
Note
Você não precisa chamar amx_Register
manualmente ou definir SAMP_SDK_WANT_AMX_EVENTS
especificamente para Plugin_Native_Hook
. O SDK detecta e registra automaticamente seus hooks. Na primeira execução de um script AMX, o SDK substitui o ponteiro da native na tabela por um "trampoline" que redireciona para sua função Plugin_Native_Hook
. Este processo garante o encadeamento seguro de hooks de múltiplos plugins.
Dentro da sua função Plugin_Native_Hook
, você DEVE usar o macro Call_Original_Native(NomeDaNativa)
para invocar a função original (ou o próximo hook na cadeia). Isso é vital para:
- Preservar a Funcionalidade: Se você não chamar a original, a native hookada simplesmente deixará de funcionar para outros plugins ou para o servidor.
- Encadeamento de Hooks: Permite que múltiplos plugins hookem a mesma native e que todos os hooks sejam executados em uma sequência.
- Manipulação de Retorno: Você pode inspecionar e até mesmo modificar o valor de retorno da
Call_Original_Native
antes de retorná-lo da sua função de hook.
// Exemplo: Bloqueando SendClientMessage se contiver uma palavra específica
Plugin_Native_Hook(SendClientMessage, AMX* amx, cell* params) {
Native_Params p(amx, params);
// Extrai os parâmetros para análise
int playerid = p.Get<int>(0);
// int color = p.Get<int>(1); // Não precisamos da cor para esta lógica
std::string message = p.Get_String(2); // Obtém a string da mensagem
if (message.find("BADWORD") != std::string::npos) {
Samp_SDK::Log("MENSAGEM BLOQUEADA para playerid %d: %s", playerid, message.c_str());
return 0; // Retorna 0 (false) para o Pawn, indicando que a mensagem não foi enviada.
// E mais importante, NÃO chamamos Call_Original_Native, bloqueando a mensagem.
}
// Se a mensagem não contiver a palavra proibida, chamamos a native original
// e retornamos seu valor, garantindo que a mensagem seja enviada normalmente
// e que outros hooks (se existirem) sejam executados.
return Call_Original_Native(SendClientMessage);
}
#define SAMP_SDK_IMPLEMENTATION
// SAMP_SDK_WANT_AMX_EVENTS não é estritamente necessário para hooks, mas é comum para ter OnAmxLoad/Unload
// #define SAMP_SDK_WANT_AMX_EVENTS
#include "samp-sdk/samp_sdk.hpp"
// Hook para a native CreateVehicle
Plugin_Native_Hook(CreateVehicle, AMX* amx, cell* params) {
Native_Params p(amx, params);
// Extrai os parâmetros da native CreateVehicle para inspeção
int modelid = p.Get<int>(0);
float x = p.Get<float>(1);
float y = p.Get<float>(2);
float z = p.Get<float>(3);
float angle = p.Get<float>(4);
int color1 = p.Get<int>(5);
int color2 = p.Get<int>(6);
int respawn_delay = p.Get<int>(7);
bool addsiren = p.Get<bool>(8);
Samp_SDK::Log("HOOK: CreateVehicle chamado! Model: %d, Pos: (%.2f, %.2f, %.2f)", modelid, x, y, z);
// Exemplo de como *modificar* um parâmetro de entrada
// Se o modelo for 400 (Landstalker), mudamos ele para 401 (Bravura)
if (modelid == 400) {
// Modificamos diretamente o array 'params' para a chamada original
params[1] = static_cast<cell>(401); // O modelo está na posição 0 do array de parâmetros (params[1])
Samp_SDK::Log(" -> Model 400 alterado para 401 antes da criação.");
}
// Chamamos a native original (ou o próximo hook na cadeia) com os parâmetros possivelmente modificados
cell original_retval = Call_Original_Native(CreateVehicle);
Samp_SDK::Log("HOOK: CreateVehicle retornou: %d (ID do veículo)", (int)original_retval);
// Você pode modificar o valor de retorno aqui antes de retorná-lo para o Pawn.
// Exemplo: se o veículo falhou na criação, retorne um ID inválido customizado.
if ((int)original_retval == INVALID_VEHICLE_ID) {
Samp_SDK::Log(" -> Criação de veículo falhou na native original.");
return -1; // Retorna um valor diferente para o Pawn.
}
return original_retval; // Retorna o valor que a native original retornou (ou o modificado acima).
}
unsigned int GetSupportFlags() {
return SUPPORTS_VERSION;
}
// Implementações mínimas para o ciclo de vida
bool OnLoad() {
Samp_SDK::Log("Plugin de Exemplo de Native Hook carregado!");
return true;
}
void OnUnload() {
Samp_SDK::Log("Plugin de Exemplo de Native Hook descarregado!");
}
// Estes callbacks só estarão presentes se SAMP_SDK_WANT_AMX_EVENTS for definido
/*void OnAmxLoad(AMX* amx) {
Samp_SDK::Log("AMX Load detectado: %p", (void*)amx);
}
void OnAmxUnload(AMX* amx) {
Samp_SDK::Log("AMX Unload detectado: %p", (void*)amx);
}*/
Warning
A manipulação direta do array cell* params
para alterar parâmetros de entrada exige cautela. Certifique-se de entender a ordem e o tipo dos parâmetros. Para a maioria dos casos de uso, p.Get(...)
para inspecionar e Call_Original_Native(...)
para continuar a cadeia é suficiente. A alteração direta de params
deve ser feita apenas se você souber que o parâmetro é um valor e precisa ser modificado para a chamada original. Para strings e arrays, a modificação é mais complexa e geralmente envolve amx::Set_String
para escrever no endereço existente ou realocar, o que pode ser mais fácil de gerenciar chamando a native via Pawn_Native
com os novos valores e retornando 0
do seu hook para cancelar a chamada original.
Estas macros são o inverso de Plugin_Public
e Plugin_Native
: elas permitem que seu código C++ invoque funções Pawn.
- Propósito: A forma recomendada de chamar funções nativas do SA-MP (ou de outras plugins) a partir do C++.
- Mecanismo: Busca o ponteiro da nativa no cache interno do SDK (preenchido por
Amx_Register_Detour
). Se encontrado, executa a nativa em um ambienteAmx_Sandbox
(uma instância AMX falsa e isolada). - Performance: A mais eficiente, pois evita a busca cara de
publics
e interage diretamente com o ponteiro da nativa.
- Propósito: Chama uma função pública específica em um script Pawn.
- Mecanismo: Percorre as instâncias
AMX*
gerenciadas peloAmx_Manager
, busca apublic
pelo nome, e a executa. - Performance: Menos eficiente que
Pawn_Native
devido à busca e aoamx_Exec
real. Geralmente,publics
são mais lentas quenatives
. - Uso: Ideal para invocar eventos personalizados em seu Gamemode/Filterscripts que não são nativas.
- Propósito: Uma macro de conveniência que tenta adivinhar se a função é uma nativa ou uma pública.
- Mecanismo: Primeiro, tenta chamar como
Pawn_Native
. Se falhar (a nativa não é encontrada), tenta chamar comoPawn_Public
. - Performance: Pode ser um pouco mais lenta que
Pawn_Native
se a função for nativa, devido à tentativa de busca dupla. Parapublics
, o desempenho é o mesmo dePawn_Public
. - Uso: Para funções onde você não tem certeza se são nativas ou públicas, ou para evitar o boilerplate de tentar uma e depois a outra.
- Nome da Função: Sempre use o nome da função Pawn diretamente, sem aspas. O SDK o converterá para string internamente.
- Parâmetros: Passe os parâmetros C++ diretamente.
// Certo:
Pawn_Native(SetPlayerPos, playerid, 100.0f, 200.0f, 300.0f);
// Errado (mas tecnicamente funcionaria devido ao hash, evite):
Pawn_Native("SetPlayerPos", playerid, 100.0f, 200.0f, 300.0f);
O SDK converte seus tipos C++ para o formato cell
da AMX, gerenciando a memória conforme necessário:
int
,bool
,long
,enum
->cell
float
,double
->cell
(usandoamx::AMX_FTOC
)const char*
,std::string
,std::string_view
(C++17+) ->cell
(aloca memória na AMX, copia a string, e passa o endereçoamx_addr
)
void Send_Formatted_Message(int playerid, const std::string& msg) {
Pawn_Native(SendClientMessage, playerid, 0xFFFFFFFF, msg);
}
Esta é uma funcionalidade-chave para conveniência e segurança. Para funções Pawn que esperam um ponteiro (referência), o SDK automatiza todo o processo de alocação/liberação de memória e cópia de dados.
- Como usar: Basta passar sua variável por referência (
&
). - Mecanismo: O SDK aloca memória na heap da AMX, passa o endereço AMX para a função Pawn, espera que a função Pawn preencha esse endereço, lê o valor de volta, e libera a memória da AMX. Tudo de forma transparente.
- Com
std::string&
: O SDK aloca um buffer padrão (256 células) na AMX para a string.
void Get_Player_Location(int playerid) {
float x, y, z;
int interiorid, worldid;
std::string name;
Pawn_Native(GetPlayerPos, playerid, x, y, z);
Pawn_Native(GetPlayerInterior, playerid, interiorid);
Pawn_Native(GetPlayerVirtualWorld, playerid, worldid);
Pawn_Native(GetPlayerName, playerid, name, MAX_PLAYER_NAME);
Samp_SDK::Log("Localização de %s (ID:%d): Pos(%.2f, %.2f, %.2f) Interior:%d World:%d", name.c_str(), playerid, x, y, z, interiorid, worldid);
}
Todas as chamadas Pawn_*
retornam um objeto Callback_Result
. Este objeto é um wrapper seguro para o resultado da chamada Pawn.
Callback_Result() noexcept
: Construtor padrão, indica falha (success_ = false
).Callback_Result(bool success, cell value) noexcept
: Construtor para sucesso ou falha com valor.explicit operator bool() const
: Permite usarif (result)
para verificar se a chamada foi bem-sucedida.operator cell() const
: Permite converter o resultado paracell
para obter o valor.float As_Float() const
: Conveniência para obter o resultado comofloat
.cell Value() const
: Retorna o valor brutocell
.bool Success() const
: Retornatrue
se a chamada Pawn foi bem-sucedida.int Get_Amx_Error() const
: Retorna o código de erro da AMX se a chamada falhou (0 para sucesso).
// Exemplo: Obtendo a vida de um jogador.
// A nativa GetPlayerHealth(playerid, &Float:health) espera um playerid e uma referência para a vida.
int playerid = 0; // Exemplo de ID de jogador
float player_health = 0.0f;
// Chamamos GetPlayerHealth, passando playerid e player_health por referência.
// O SDK cuidará do marshalling para o parâmetro de saída 'health'.
Callback_Result result = Pawn_Native(GetPlayerHealth, playerid, player_health);
if (result) { // Verifica se a chamada foi bem-sucedida (operator bool)
// O valor retornado por result.As_Float() ou result (operator cell)
// seria o valor de retorno da *nativa*, não o parâmetro de saída.
// O valor da vida já foi atualizado em 'player_health' devido ao marshalling do parâmetro de saída.
Samp_SDK::Log("Jogador %d tem %.1f de vida.", playerid, player_health);
}
else {
// A chamada falhou, talvez o jogador não exista ou a nativa não foi encontrada.
Samp_SDK::Log("Erro ao obter vida do jogador %d. Código AMX: %d", playerid, result.Get_Amx_Error());
}
// Para nativas que retornam um valor e usam parâmetros de saída (menos comum, mas possível),
// você usaria ambos:
// Callback_Result other_result = Pawn_Native(SomeNative, param1, output_param, param2);
// if (other_result) {
// cell returned_value = other_result;
// // output_param já está atualizado
// }
A macro Plugin_Module
permite que seu plugin atue como um "carregador" para outros plugins, criando uma arquitetura modular e extensível. Um módulo carregado desta forma é tratado como um plugin de primeira classe, com seu próprio ciclo de vida de eventos gerenciado pelo plugin hospedeiro.
Plugin_Module(const char* nome_do_arquivo_base, const char* diretorio_do_modulo, const char* mensagem_sucesso_opcional)
nome_do_arquivo_base
: O nome base do arquivo do módulo, sem a extensão (ex: paramy_module.dll
oumy_module.so
, use"my_module"
). O SDK adicionará automaticamente a extensão.dll
ou.so
apropriada.diretorio_do_modulo
: O caminho do diretório onde o arquivo do módulo está localizado (ex:"plugins/my_custom_modules"
). Não inclua o nome do arquivo aqui. O SDK fará a concatenação do caminho completo (diretorio_do_modulo/nome_do_arquivo_base.ext
).mensagem_sucesso_opcional
: Uma mensagem opcional para ser logada no console do servidor se o módulo carregar com sucesso.
// main.cpp, dentro de OnLoad()
// Carrega o módulo 'core_logic.dll' (ou 'core_logic.so')
// que está localizado na pasta 'modules/custom/' do servidor.
if (!Plugin_Module("core_logic", "modules/custom", "Módulo de Lógica Core carregado com sucesso!"))
return (Samp_SDK::Log("ERRO FATAL: Falha ao carregar o módulo 'core_logic'!"), false);
// Carrega o módulo 'admin_system.dll' (ou 'admin_system.so')
// que está localizado diretamente na pasta 'plugins/' do servidor.
if (!Plugin_Module("admin_system", "plugins", "Módulo de Administração ativado."))
Samp_SDK::Log("AVISO: Módulo de Administração não pôde ser carregado.");
Um módulo deve exportar as funções Load
, Unload
e Supports
, assim como um plugin normal. O SDK gerencia o ciclo de vida do módulo da seguinte forma:
-
Carregamento: Quando
Plugin_Module
é chamado, o SDK:- Constrói o caminho completo do arquivo (ex:
modules/custom/core_logic.dll
). - Usa
Dynamic_Library
(LoadLibrary
/dlopen
) para carregar o binário. - Obtém os ponteiros para TODAS as funções de ciclo de vida do módulo:
- Obrigatórias:
Load
,Unload
,Supports
. Se alguma faltar, o carregamento do módulo falha. - Opcionais:
AmxLoad
,AmxUnload
,ProcessTick
.
- Obrigatórias:
- Chama a função
Load
do módulo, passandoppData
do plugin principal. - Se
Load
retornartrue
, o módulo é adicionado à lista interna de módulos carregados.
- Constrói o caminho completo do arquivo (ex:
-
Encaminhamento de Eventos: O plugin hospedeiro automaticamente encaminha os eventos para todos os módulos carregados.
Important
Para que os eventos sejam encaminhados corretamente, o plugin hospedeiro (o que chama Plugin_Module
) deve estar configurado para receber esses eventos.
- Para que
AmxLoad
eAmxUnload
funcionem nos módulos, o plugin hospedeiro deve definir a macroSAMP_SDK_WANT_AMX_EVENTS
. - Para que
ProcessTick
funcione nos módulos, o plugin hospedeiro deve definir a macroSAMP_SDK_WANT_PROCESS_TICK
.
- Descarregamento: Durante
OnUnload
do seu plugin principal, o SDK descarrega todos os módulos que foram carregados viaPlugin_Module
. Isso é feito na ordem inversa ao carregamento (o último a ser carregado é o primeiro a ser descarregado), o que é crucial para gerenciar dependências e garantir a liberação correta de recursos.
- Organização do Código: Divida grandes plugins em componentes menores e gerenciáveis, cada um em seu próprio arquivo de módulo.
- Reusabilidade: Crie módulos genéricos (ex: um módulo de banco de dados, um módulo de sistema de log avançado) que podem ser usados por diferentes plugins, promovendo a reutilização de código.
- Componentes Independentes: Crie módulos que são totalmente orientados a eventos e independentes. Um módulo pode ter suas próprias
Plugin_Native
s, interceptarPlugin_Public
s e ter sua própria lógicaOnProcessTick
, operando como um plugin autônomo, mas carregado por um hospedeiro. - Atualizações Dinâmicas: Em cenários controlados, permite a atualização de partes do seu sistema (substituindo um
.dll
ou.so
de módulo) sem a necessidade de recompilar e reiniciar o plugin principal ou o servidor inteiro (embora isso exija um gerenciamento de versão e compatibilidade rigorosos).
Use Plugin_Call
para invocar uma Plugin_Native
definida dentro do seu próprio plugin.
Plugin_Call(NomeDaNativa, Param1, Param2, ...)
- Vantagem: Evita a sobrecarga de procurar a nativa no array de nativas da AMX. O SDK mantém um mapa direto de hashes de nome para ponteiros de função para suas próprias nativas, tornando esta a forma mais rápida de chamá-las internamente.
- Requer:
SAMP_SDK_WANT_AMX_EVENTS
.
// main.cpp
Plugin_Native(InternalCheckPlayerLevel, AMX* amx, cell* params) {
int playerid;
Register_Parameters(playerid);
// Lógica para verificar o nível
return (playerid % 2 == 0) ? 1 : 0; // Exemplo: nível par para IDs pares
}
void Check_All_Players_Level() {
for (int i = 0; i < MAX_PLAYERS; ++i) {
if (Pawn_Native(IsPlayerConnected, i)) {
if (Plugin_Call(InternalCheckPlayerLevel, i)) // Chama sua própria nativa
Samp_SDK::Log("Jogador %d está em um nível alto!", i);
}
}
}
- Descrição: Imprime mensagens no console do servidor e no arquivo
server_log.txt
. Um wrapper seguro paralogprintf
. - Uso: Para depuração, mensagens de status e erros.
- Mecanismo: Internamente, o SDK obtém o ponteiro para
logprintf
através deppData[PLUGIN_DATA_LOGPRINTF]
. A função lida com a formatação da string de forma segura.
// Em qualquer lugar do seu plugin
Samp_SDK::Log("O plugin foi inicializado com um valor %d e uma string '%s'.", 123, "teste");
- Descrição: Formata uma string de forma segura (similar a
sprintf
) e retorna umstd::string
. Esta é a maneira recomendada e mais idiomática de formatar strings para uso dentro do seu plugin. - Uso: Ideal para construir mensagens formatadas antes de passá-las para
Samp_SDK::Log
,Pawn_Native(SendClientMessage, ...)
, ou para qualquer outra necessidade de string dentro do seu código C++. - Mecanismo: Internamente,
Plugin_Format
é uma macro que chamaSamp_SDK::Format
. Ele utilizavsnprintf
para determinar o tamanho exato da string formatada e aloca umstd::string
com capacidade suficiente, evitando estouros de buffer.
int playerid = 0; // Exemplo de ID
int health = 50;
Pawn_Native(SendClientMessage, playerid, 0xFFFFFFFF, Plugin_Format("Jogador %d, sua vida atual é %d.", playerid, health));
// Também pode ser usado para logs internos
Samp_SDK::Log(Plugin_Format("DEBUG: Processando status para o ID %d", playerid));
- Descrição: A função de implementação subjacente para formatação de strings, localizada dentro do namespace
Samp_SDK
. - Uso: Geralmente não é chamada diretamente pelo usuário. A macro
Plugin_Format
é fornecida como uma conveniência para esta função, alinhando-se com a convenção de nomenclatura de outras macros do SDK (Plugin_Public
,Plugin_Native
). Você só a chamaria diretamente se quisesse evitar a macroPlugin_Format
por algum motivo específico.
// Exemplo de como Samp_SDK::Format funciona, mas prefira Plugin_Format
std::string raw_status = Samp_SDK::Format("Apenas para uso interno: %d.", 42);
- Descrição: Converte um endereço de string da AMX (
cell amx_addr
) em umstd::string
C++. - Uso: Principalmente dentro de
Plugin_Native
ePlugin_Native_Hook
quando você precisa acessar strings que não são automaticamente convertidas porRegister_Parameters
ouNative_Params
(ex: se o parâmetro Pawn é umconst
string
e não foi declarado comostd::string
no seuPlugin_Native
ouPlugin_Public
para Marshalling automático).
Plugin_Native(PrintRawAmxString, AMX* amx, cell* params) {
Native_Params p(amx, params);
cell amx_string_addr = p.Get<cell>(0); // Obtém o endereço da string na AMX
std::string cpp_string = Samp_SDK::Get_String(amx, amx_string_addr);
Samp_SDK::Log("String da AMX: %s", cpp_string.c_str());
return 1;
}
Esta seção desvela os mecanismos subjacentes do SA-MP SDK, explorando sua arquitetura, os componentes-chave e como eles interagem para fornecer a abstração de alto nível. Uma compreensão profunda destas entranhas capacita o desenvolvedor a otimizar o uso do SDK, depurar problemas complexos e até mesmo estender suas funcionalidades.
O Samp_SDK::Core
é um singleton
que serve como o ponto de acesso inicial e centralizado aos dados de baixo nível fornecidos pelo ambiente do plugin SA-MP. Sua principal responsabilidade é encapsular e expor as funcionalidades essenciais.
-
Samp_SDK::Core::Instance()
:- Descrição: Retorna a única instância global da classe
Core
. Este é um padrão de designsingleton
para garantir que o acesso aos dados do plugin (ppData
) seja consistente e centralizado. - Mecanismo: A instância é inicializada uma única vez quando
Core::Instance().Load(ppData)
é chamado na funçãoLoad()
do seu plugin.
- Descrição: Retorna a única instância global da classe
-
Get_AMX_Export(PLUGIN_AMX_EXPORT_...)
:- Descrição: Permite acesso seguro e indexado aos ponteiros de função da API AMX. O array
pAMXFunctions
(recebido viappData[PLUGIN_DATA_AMX_EXPORTS]
) contém os endereços de funções críticas comoamx_Exec
,amx_Register
, etc. - Impacto: Em vez de usar
((MyFuncType)((void**)Core::Instance().Get_AMX_Data())[INDEX])(...)
, o SDK oferece wrappers type-safe noamx::
namespace (ex:amx::Exec(...)
), tornando o código mais legível e menos propenso a erros de cast. - Exemplo Interno (
amx_api.hpp
):// Trecho simplificado de amx_api.hpp namespace amx { template <typename Func, int Index, typename... Args> inline auto Call(Args... args) -> decltype(std::declval<Func>()(args...)) { using Return_Type = decltype(std::declval<Func>()(args...)); auto func_ptr = reinterpret_cast<Func>(Core::Instance().Get_AMX_Export(Index)); if (SAMP_SDK_UNLIKELY(!func_ptr)) { Log("[SA-MP SDK] Fatal: Attempted to call AMX export at index %d, but pAMXFunctions was not loaded!", Index); #if defined(SAMP_SDK_CXX_MODERN) return Samp_SDK::amx::Detail::Amx_Call_Error_Handler<Return_Type>(); #elif defined(SAMP_SDK_CXX14) return Samp_SDK::amx::Detail::Amx_Call_Error_Handler<Return_Type>(typename std::is_pointer<Return_Type>::type{}); #endif } return func_ptr(args...); } inline int Exec(AMX* amx, cell* retval, int index) { return Call<Exec_t, PLUGIN_AMX_EXPORT_Exec>(amx, retval, index); } } // namespace amx
- Descrição: Permite acesso seguro e indexado aos ponteiros de função da API AMX. O array
-
logprintf_ptr
:- Descrição: Um ponteiro para a função
logprintf
do SA-MP, que é a interface padrão para imprimir mensagens no console do servidor e noserver_log.txt
. - Mecanismo: O
Samp_SDK::Log
é um wrapper seguro que utiliza este ponteiro, garantindo que suas mensagens sejam exibidas corretamente no ambiente do SA-MP.
- Descrição: Um ponteiro para a função
Estes headers são a base para a portabilidade e otimização do SDK, adaptando-o a diferentes ambientes de compilação e tirando proveito de recursos específicos do C++ moderno.
-
Detecção de Plataforma e Arquitetura:
- Mecanismo: Utiliza macros de pré-processador (
#if defined(WIN32)
,#if defined(__linux__)
, etc.) para identificar o sistema operacional. - Verificação de Arquitetura: Contém
static_assert
ou#error
para garantir que o plugin esteja sendo compilado para x86 (32-bit), uma exigência crítica para a compatibilidade com o SA-MP e o mecanismo de hooking. - Gerenciamento de Exportação de Símbolos:
SAMP_SDK_EXPORT
: Uma macro definida emplatform.hpp
que se expande paraextern "C"
e, no Linux, adiciona__attribute__((visibility("default")))
. No Windows, ela apenas garanteextern "C"
porque o SDK utilizapragma comment(linker, "/EXPORT:...")
(para MSVC) ou o padrão MinGW para exportar as funções principais.- Isso garante que as funções de ciclo de vida do plugin (
Supports
,Load
,Unload
, etc.) sejam corretamente exportadas do seu DLL/SO, independentemente do ambiente de compilação, sem a necessidade de arquivos.def
ou de adicionar__attribute__((visibility("default")))
manualmente na sua implementação.
- Exemplo (
platform.hpp
- fragmento relevante):#if defined(SAMP_SDK_WINDOWS) // Para MSVC, a exportação é gerenciada por pragma linker commands #define SAMP_SDK_EXPORT SAMP_SDK_EXTERN_C #else // Linux // Para GCC/Clang no Linux, usamos o atributo de visibilidade #define SAMP_SDK_EXPORT SAMP_SDK_EXTERN_C __attribute__((visibility("default"))) #endif // ... outras definições ...
- Exemplo (
samp_sdk.hpp
- fragmento relevante da implementação):#if defined(SAMP_SDK_IMPLEMENTATION) #if defined(SAMP_SDK_WINDOWS) // No Windows (MSVC), usamos pragma comment para exportar as funções. // Isso elimina a necessidade de um arquivo .def. #pragma comment(linker, "/EXPORT:Supports=_Supports@0") #pragma comment(linker, "/EXPORT:Load=_Load@4") #pragma comment(linker, "/EXPORT:Unload=_Unload@0") // ... outras exports ... #endif // Implementação das funções exportadas SAMP_SDK_EXPORT unsigned int SAMP_SDK_CALL Supports() { /* ... */ } SAMP_SDK_EXPORT bool SAMP_SDK_CALL Load(void** ppData) { /* ... */ } SAMP_SDK_EXPORT void SAMP_SDK_CALL Unload() { /* ... */ } // ... outras funções ... #endif
- Mecanismo: Utiliza macros de pré-processador (
-
Macros de Otimização e Previsão de Branch:
SAMP_SDK_FORCE_INLINE
:- Mecanismo:
__forceinline
(MSVC) ou__attribute__((always_inline)) inline
(GCC/Clang). Sugere fortemente ao compilador que insira o corpo da função diretamente no local da chamada, eliminando o overhead de uma chamada de função real. - Uso: Aplicado a funções pequenas e críticas para o desempenho dentro do SDK.
- Mecanismo:
SAMP_SDK_LIKELY(x)
/SAMP_SDK_UNLIKELY(x)
:- Mecanismo:
[[likely]]
/[[unlikely]]
(C++20) ou__builtin_expect
(GCC/Clang). Dicas para o compilador sobre qual caminho de umif/else
é mais provável de ser tomado. - Impacto: Ajuda o compilador a gerar código mais eficiente para a previsão de desvios (branch prediction), reduzindo a latência da CPU.
- Exemplo (
platform.hpp
):#if (defined(SAMP_SDK_COMPILER_MSVC) && _MSVC_LANG >= 202002L) || (defined(__cplusplus) && __cplusplus >= 202002L) #define SAMP_SDK_LIKELY(x) (x) [[likely]] #define SAMP_SDK_UNLIKELY(x) (x) [[unlikely]] #elif defined(SAMP_SDK_COMPILER_GCC_OR_CLANG) #define SAMP_SDK_LIKELY(x) __builtin_expect(!!(x), 1) #define SAMP_SDK_UNLIKELY(x) __builtin_expect(!!(x), 0) #else #define SAMP_SDK_LIKELY(x) (x) #define SAMP_SDK_UNLIKELY(x) (x) #endif
- Mecanismo:
SAMP_SDK_USED_BY_ASM
:- Mecanismo:
__attribute__((used))
(GCC/Clang). Informa ao compilador que um símbolo (neste caso, uma função) é utilizado, mesmo que não haja referências a ele no código C++. - Impacto: Crucial para funções C++ que são chamadas a partir de blocos de assembly embutido (
asm volatile
). Sem este atributo, o otimizador do compilador pode remover a função por engano, resultando em um erro de "símbolo indefinido" no linker. - Exemplo (
platform.hpp
):#if defined(SAMP_SDK_COMPILER_GCC_OR_CLANG) #define SAMP_SDK_USED_BY_ASM __attribute__((used)) #else #define SAMP_SDK_USED_BY_ASM #endif
- Mecanismo:
-
Definições C++ Padrão (
SAMP_SDK_CXX14
,SAMP_SDK_CXX_MODERN
):- Mecanismo: Macros definidas com base no valor de
__cplusplus
e_MSVC_LANG
. - Uso: Permitem que o SDK utilize funcionalidades mais recentes do C++ (como
std::apply
eif constexpr
do C++17, oustd::is_same_v
do C++17) quando disponíveis, mantendo a compatibilidade com padrões mais antigos. - Exemplo (
version.hpp
- uso deif constexpr
):// Trecho simplificado de public_dispatcher.hpp template<typename First, typename... Rest> inline void Get_Public_Params_Recursive(AMX* amx, int index, First& first, Rest&... rest) { cell value; if (Get_Stack_Cell(amx, index, value)) { #if defined(SAMP_SDK_CXX_MODERN) if constexpr (std::is_same_v<decay_t<First>, std::string>) first = Samp_SDK::Get_String(amx, value); else if constexpr (std::is_floating_point_v<decay_t<First>>) first = amx::AMX_CTOF(value); else first = static_cast<decay_t<First>>(value); #elif defined(SAMP_SDK_CXX14) // Compatibilidade C++14: usar a função auxiliar Assign_Parameter_By_Type Assign_Parameter_By_Type(amx, &value, first); #endif } Get_Public_Params_Recursive(amx, index + 1, rest...); }
- Mecanismo: Macros definidas com base no valor de
Este header define o mecanismo de baixo nível para realizar hooks (interceptações) de funções, que é fundamental para a operação do SDK. Ele é estritamente dependente da arquitetura x86 (32-bit).
-
X86_Detour
:- Descrição: Uma classe que encapsula a lógica de sobrescrever o início de uma função na memória com uma instrução de salto para uma função de desvio.
JUMP_INSTRUCTION_SIZE = 5
:- Mecanismo: Em x86, a instrução de salto (
JMP
) relativa geralmente tem 5 bytes:0xE9
(opcode para JMP near, relative) seguido de 4 bytes que representam o deslocamento (offset) do endereço do alvo em relação à próxima instrução. - Exemplo de instrução:
E9 XX XX XX XX
(ondeXX
são os bytes do deslocamento).
- Mecanismo: Em x86, a instrução de salto (
Apply(void* target, void* detour)
:- Ação: Instala o hook. Primeiro, armazena os
JUMP_INSTRUCTION_SIZE
bytes originais da funçãotarget
(original_bytes_
). Em seguida, calcula o endereço relativo dodetour
em relação aotarget
e sobrescreve o início detarget
com a instruçãoJMP
. - Exemplo do cálculo do endereço relativo:
// (uintptr_t)detour_func_ - ((uintptr_t)target_func_ + JUMP_INSTRUCTION_SIZE) // Endereço do desvio - (Endereço do alvo + Tamanho da instrução de JMP)
- Ação: Instala o hook. Primeiro, armazena os
Revert()
:- Ação: Desinstala o hook, restaurando os
original_bytes_
na funçãotarget
.
- Ação: Desinstala o hook, restaurando os
Unprotect_Memory(void* address, size_t size)
:- Mecanismo: No Windows, usa
VirtualProtect
; no Linux,mprotect
. Estas chamadas de sistema alteram as permissões da página de memória onde a função reside paraEXECUTE_READWRITE
(Windows) ouPROT_WRITE | PROT_EXEC
(Linux), permitindo que o código seja modificado em tempo de execução. - Exemplo (
Unprotect_Memory
):// Trecho simplificado de function_hook.hpp #if defined(SAMP_SDK_WINDOWS) DWORD old_protect; VirtualProtect(address, size, PAGE_EXECUTE_READWRITE, &old_protect); #elif defined(SAMP_SDK_LINUX) long pagesize = sysconf(_SC_PAGESIZE); uintptr_t page_start = reinterpret_cast<uintptr_t>(address) & -pagesize; mprotect(reinterpret_cast<void*>(page_start), size + (reinterpret_cast<uintptr_t>(address) - page_start), PROT_READ | PROT_WRITE | PROT_EXEC); #endif
- Mecanismo: No Windows, usa
-
Function_Hook<FuncPtr>
:- Descrição: Um wrapper C++
type-safe
para aX86_Detour
, garantindo que os tipos de ponteiro de função sejam corretos. Install(void* target, void* detour)
: Encapsula a chamadaX86_Detour::Apply
.Uninstall()
: Encapsula a chamadaX86_Detour::Revert
.Call_Original(Args... args)
:- Mecanismo de Segurança (Guarda de Recursão): Esta função é crítica para evitar loops infinitos quando o desvio precisa chamar a função original. Ela temporariamente desinstala o hook (
detour_.Revert()
), chama a função original (Get_Original()(args...)
), e então reinstala o hook (detour_.Reapply()
). static thread_local int recursion_guard
: Um contador que garante que o hook só seja reinstalado quando a chamada original de mais alto nível for concluída, permitindo chamadas recursivas seguras da função original (se a função original for recursiva, por exemplo). Othread_local
garante que orecursion_guard
seja isolado para cada thread, importante em ambientes multi-threaded.- Exemplo (
Call_Original
com Scope_Guard):// Trecho simplificado de function_hook.hpp template<typename... Args> auto Call_Original(Args... args) -> decltype(Get_Original()(args...)) { static thread_local int recursion_guard = 0; if (recursion_guard == 0) detour_.Revert(); recursion_guard++; struct Scope_Guard { int& guard_ref; X86_Detour& detour_ref; ~Scope_Guard() { guard_ref--; if (guard_ref == 0) detour_ref.Reapply(); } }; Scope_Guard guard{recursion_guard, detour_}; return Get_Original()(args...); }
- Mecanismo de Segurança (Guarda de Recursão): Esta função é crítica para evitar loops infinitos quando o desvio precisa chamar a função original. Ela temporariamente desinstala o hook (
- Descrição: Um wrapper C++
Este singleton
é o centro nervoso da interação do SDK com a máquina virtual AMX do SA-MP. Ele coordena a instalação dos hooks fornecidos por function_hook.hpp
nas funções da API AMX expostas pelo servidor, redirecionando o fluxo de execução para a lógica do SDK.
Activate()
/Deactivate()
:- Descrição: Métodos públicos para instalar e desinstalar todos os hooks necessários. Chamados em
OnLoad()
eOnUnload()
do seu plugin, respectivamente. - Mecanismo: Obtém os ponteiros das funções AMX (como
amx_Register
,amx_Exec
, etc.) usandoCore::Instance().Get_AMX_Export(...)
e instala os desvios.
- Descrição: Métodos públicos para instalar e desinstalar todos os hooks necessários. Chamados em
int SAMP_SDK_AMX_API Amx_Register_Detour(...)
:- Função Hooked:
amx_Register
- Propósito: Intercepta o registro de todas as nativas (pelo SA-MP, outros plugins ou gamemode).
- Ação: Para cada nativa registrada, o
Interceptor_Manager
a adiciona a um cache interno (Cache_Data::native_cache
). - Impacto: Este cache é fundamental para a performance de
Pawn_Native
, permitindo uma busca extremamente rápida do ponteiro da nativa em vez de uma busca cara na AMX. - Função adicional para
Plugin_Native_Hook
: Este detour também é responsável por modificar a lista de nativas antes que elas sejam registradas. Se uma nativa tiver umPlugin_Native_Hook
associado, o ponteiro da função na lista de registro é substituído pelo trampoline gerado peloNative_Hook_Manager
. Isso permite que seu hook seja o primeiro a ser chamado.
- Função Hooked:
int SAMP_SDK_AMX_API Amx_Exec_Detour(...)
:- Função Hooked:
amx_Exec
- Propósito: Este é o hook mais crítico. Ele intercepta qualquer execução de código na AMX, incluindo a chamada de
publics
do Pawn. - Mecanismo de Interceptação de
Plugin_Public
:- Quando
amx_Exec
é chamado para umapublic
(ouAMX_EXEC_MAIN
), oAmx_Exec_Detour
é executado. - Ele obtém o nome da
public
(usandotl_public_name
que foi preenchido porAmx_Find_Public_Detour
). - Consulta o
Public_Dispatcher::Instance().Dispatch()
para verificar se há handlers C++ registrados para este nome. - Se houver handlers, ele os executa. O
Public_Dispatcher
lida com omarshalling
dos parâmetros da AMX para os tipos C++. - Com base no valor de retorno do
Public_Dispatcher
(PLUGIN_PUBLIC_STOP
/PLUGIN_PUBLIC_CONTINUE
), ele decide se chama aamx_Exec
original (Get_Amx_Exec_Hook().Call_Original(...)
) ou se encerra a execução dapublic
Pawn. - Manipulação da Stack: Se a execução da
public
Pawn for interrompida (PLUGIN_PUBLIC_STOP
), oAmx_Exec_Detour
corrige a stack da AMX (amx->stk += amx->paramcount * sizeof(cell); amx->paramcount = 0;
) para evitar inconsistências.
- Quando
- Ativação de
Plugin_Native_Hook
(Patching AMX):- Na primeira vez que um
AMX*
é executado, este detour verifica se a AMX já foi "patchada" (!manager.Is_Amx_Patched(amx)
). - Se não foi, ele percorre a tabela de nativas daquela instância AMX específica na memória.
- Para cada nativa que tem um
Plugin_Native_Hook
registrado noNative_Hook_Manager
, ele substitui o endereço da native na tabela por um trampoline gerado peloNative_Hook_Manager
. O endereço original (ou do hook anterior) é salvo no objetoNative_Hook
. - A AMX é então marcada como "patchada" para evitar reprocessamento desnecessário.
- Na primeira vez que um
- Exemplo (
Amx_Exec_Detour
- com detalhes dePlugin_Native_Hook
):// Trecho simplificado de interceptor_manager.hpp inline int SAMP_SDK_AMX_API Amx_Exec_Detour(AMX* amx, cell* retval, int index) { auto& manager = Interceptor_Manager::Instance(); std::unique_ptr<std::string> public_name_ptr; // Lógica de interceptação de Plugin_Public (conforme descrito acima) // ... int exec_result = Get_Amx_Exec_Hook().Call_Original(amx, retval, index); // Lógica de Patching para Plugin_Native_Hook if (SAMP_SDK_UNLIKELY(!manager.Is_Amx_Patched(amx))) { auto& hook_manager = Native_Hook_Manager::Instance(); auto& hooks_to_apply = hook_manager.Get_All_Hooks(); if (!hooks_to_apply.empty()) { AMX_HEADER* hdr = reinterpret_cast<AMX_HEADER*>(amx->base); AMX_FUNCSTUBNT* natives = reinterpret_cast<AMX_FUNCSTUBNT*>(reinterpret_cast<unsigned char*>(hdr) + hdr->natives); int num_natives; amx::Num_Natives(amx, &num_natives); for (auto& hook_to_apply : hooks_to_apply) { // Percorre todos os Plugin_Native_Hook registrados uint32_t hook_hash = hook_to_apply.Get_Hash(); for (int i = 0; i < num_natives; ++i) { // Percorre as nativas da AMX const char* native_name = reinterpret_cast<const char*>(reinterpret_cast<unsigned char*>(hdr) + natives[i].nameofs); if (FNV1a_Hash(native_name) == hook_hash) { // Se o nome da nativa da AMX corresponde a um hook AMX_NATIVE current_func = reinterpret_cast<AMX_NATIVE>(natives[i].address); hook_to_apply.Set_Next_In_Chain(current_func); // Salva o ponteiro da função original/anterior AMX_NATIVE trampoline = hook_manager.Get_Trampoline(hook_hash); // Obtém o trampoline para este hook if (trampoline) natives[i].address = reinterpret_cast<ucell>(trampoline); // Substitui na tabela da AMX break; } } } } manager.On_Amx_Patched(amx); // Marca a AMX como patchada } return exec_result; }
- Função Hooked:
int SAMP_SDK_AMX_API Amx_Find_Public_Detour(...)
:- Função Hooked:
amx_FindPublic
- Propósito: Intercepta a busca por
publics
pelo nome. - Mecanismo de "Ghost Publics": Se
amx_FindPublic
original não encontrar umapublic
no Pawn, mas oPublic_Dispatcher
tiver um handler C++ registrado para aquele nome, este hook retornaAMX_ERR_NONE
e umindex
especial (PLUGIN_EXEC_GHOST_PUBLIC
). Isso faz com que a API do SA-MP "pense" que apublic
existe, permitindo que a chamadaamx_Exec
subsequente (para este índice especial) seja interceptada peloAmx_Exec_Detour
, que então redireciona para o handler C++. static thread_local std::unique_ptr<std::string> tl_public_name
: Usada para passar o nome dapublic
para oAmx_Exec_Detour
quando um "ghost public" é detectado, pois oamx_Exec
só recebe o índice, não o nome.
- Função Hooked:
int SAMP_SDK_AMX_API Amx_Init_Detour(...)
/Amx_Cleanup_Detour(...)
:- Funções Hooked:
amx_Init
/amx_Cleanup
- Propósito: Gerenciar a lista de instâncias
AMX*
ativas. - Ação:
Amx_Init_Detour
chamaAmx_Manager::Instance().Add_Amx()
, eAmx_Cleanup_Detour
chamaAmx_Manager::Instance().Remove_Amx()
.
- Funções Hooked:
Este singleton
mantém um registro dinâmico de todas as máquinas virtuais AMX atualmente carregadas no servidor. É essencial para funções que precisam interagir com "todos os scripts" ou encontrar um script específico.
std::vector<AMX*> loaded_amx_
:- Descrição: Uma lista de ponteiros para todas as instâncias
AMX*
que foram inicializadas (gamemode e filterscripts). - Gerenciamento: Populada pelos hooks
Amx_Init_Detour
e esvaziada porAmx_Cleanup_Detour
.
- Descrição: Uma lista de ponteiros para todas as instâncias
std::shared_mutex mtx_
(C++17+) /std::mutex mtx_
(C++14):- Propósito: Protege a
loaded_amx_
contra acesso concorrente em ambientes multi-threaded (embora o SA-MP seja majoritariamente single-thread, esta é uma boa prática de segurança).std::shared_mutex
permite múltiplos leitores simultaneamente, mas apenas um escritor.
- Propósito: Protege a
std::atomic<uint32_t> generation_
:- Propósito: Um contador que incrementa a cada vez que uma AMX é adicionada ou removida.
- Uso: É utilizado pelo
Caller_Cache
emcallbacks.hpp
para detectar quando a lista de AMXs mudou, invalidando caches de busca depublics
e garantindo que as chamadasPawn_Public
operem sempre com informações atualizadas. Isso otimiza o desempenho ao evitar buscas repetitivas em um estado que não mudou.
AMX* Find_Public(const char* name, int& index)
:- Descrição: Percorre
loaded_amx_
(do mais recente para o mais antigo, o que geralmente coloca o gamemode ou o filterscript mais relevante primeiro) para encontrar apublic
com o nome especificado. - Mecanismo: Usa
amx::Find_Public
para cada instânciaAMX*
. - Impacto: É a base para
Pawn_Public
.
- Descrição: Percorre
Este singleton
é o componente que mapeia nomes de publics
do Pawn para as suas funções C++ Plugin_Public
.
-
std::unordered_map<uint32_t, std::vector<Amx_Handler_Func>> handlers_
:- Chave: O hash FNV1a do nome da
public
(ex:FNV1a_Hash_Const("OnPlayerConnect")
). - Valor: Uma
std::vector
destd::function<cell(AMX*)>
, onde cadastd::function
é um handler C++ registrado para aquelapublic
. - Mecanismo: A
std::vector
permite que múltiplosPlugin_Public
s sejam registrados para o mesmo callback (ex: vários plugins querendo interceptarOnPlayerCommandText
). Os handlers são executados em ordem inversa de registro.
- Chave: O hash FNV1a do nome da
-
Public_Register
:- Mecanismo: Esta é uma classe template cuja macro
PLUGIN_PUBLIC_REGISTRATION
cria uma instância estática global. No construtor estático (static bool registered = [...]
), ela registra seu handlerPlugin_Public
noPublic_Dispatcher
. Este é um padrão de "registro estático em tempo de compilação/inicialização". - Exemplo (
public_dispatcher.hpp
):#define PLUGIN_PUBLIC_REGISTRATION(name) \ constexpr uint32_t hash_##name = Samp_SDK::Detail::FNV1a_Hash_Const(#name); \ Samp_SDK::Detail::Public_Register<decltype(&name), &name, hash_##name> register_##name;
- Mecanismo: Esta é uma classe template cuja macro
-
Public_Traits
eWrapper()
:- Mecanismo:
Public_Traits
é um template de trait que, usando metaprogramação, gera uma funçãostatic cell Wrapper(AMX* amx)
. - Propósito: Esta
Wrapper
é aAmx_Handler_Func
que oPublic_Dispatcher
realmente armazena e chama. Ela é responsável por:- Chamar
Public_Param_Reader::Get_Public_Params(amx, args...)
para extrair os parâmetros da stack da AMX. - Chamar a sua função
Plugin_Public
C++ real (func_ptr
) com os parâmetros já convertidos para os tipos C++ corretos.
- Chamar
- Mecanismo:
-
Public_Param_Reader::Get_Public_Params(...)
:- Descrição: Um conjunto de funções template recursivas que lêem os valores da stack da AMX e os convertem para os tipos C++ especificados na declaração do
Plugin_Public
. - Mecanismo: Usa
Get_Stack_Cell()
para acessar oscell
s na stack. Utilizaif constexpr
(C++17+) oustd::is_same<T>::value
(C++14) para aplicar a conversão correta (amx::AMX_CTOF
para float,Samp_SDK::Get_String
para string, cast direto para int).
- Descrição: Um conjunto de funções template recursivas que lêem os valores da stack da AMX e os convertem para os tipos C++ especificados na declaração do
Este header é dedicado à criação e ao gerenciamento de nativas C++ que seu plugin expõe ao Pawn.
Native_List_Holder
:- Descrição: Um
singleton
global que armazena todas asPlugin_Native
s declaradas no seu plugin (de todos os arquivos.cpp
que usamSAMP_SDK_WANT_AMX_EVENTS
). std::vector<Native> natives_
: Contém objetosNative
(que armazenam o nome da nativa e o ponteiro para a função C++Native_Handler
).std::unordered_map<uint32_t, Native_Handler> plugin_natives_
: Um mapa otimizado por hash para buscas rápidas dePlugin_Native
s internas (usado porPlugin_Call
).
- Descrição: Um
Native_Register
:- Mecanismo: Assim como o
Public_Register
, esta é uma classe template cuja macroPlugin_Native
cria uma instância estática global. No seu construtor, ela adiciona a nativa aoNative_List_Holder
. - Impacto: Permite que você declare
Plugin_Native
s em múltiplos arquivos.cpp
sem se preocupar com o registro manual. Todas serão coletadas automaticamente.
- Mecanismo: Assim como o
Native_Registry
:- Descrição: Uma classe auxiliar que, no
OnAmxLoad
, pega a lista completa deNative
s doNative_List_Holder
e as formata em um arrayAMX_NATIVE_INFO
. - Mecanismo: Chama
amx::Register(amx, amx_natives_info_.data(), -1)
para registrar todas as suas nativas na instância AMX que acabou de ser carregada.
- Descrição: Uma classe auxiliar que, no
Plugin_Call_Impl(...)
:- Descrição: A implementação subjacente da macro
Plugin_Call
. - Mecanismo: Utiliza
Native_List_Holder::Instance().Find_Plugin_Native(native_hash)
para obter diretamente o ponteiro da função C++. - Ambiente: Executa a nativa em um ambiente
Amx_Sandbox
(isolado) para gerenciar a stack e heap temporárias, de forma semelhante a comoPawn_Native
funciona.
- Descrição: A implementação subjacente da macro
Este é o robusto sistema de hooking de nativas, projetado para gerenciar o encadeamento de hooks de múltiplos plugins para a mesma native.
Native_Hook
:- Descrição: Uma classe que representa um único hook de native. Armazena o hash do nome da native, a função handler C++ fornecida pelo usuário (
user_handler_
) e umstd::atomic<AMX_NATIVE> next_in_chain_
. user_handler_
: Sua funçãoPlugin_Native_Hook
C++.next_in_chain_
: O ponteiro para a native original ou para o hook de um plugin com prioridade mais baixa. É umstd::atomic
para garantir thread-safety em leitura/escrita.Dispatch(AMX* amx, cell* params)
: Chamada pelo trampoline para executar seuuser_handler_
.Call_Original(AMX* amx, cell* params)
: Método crucial que chamanext_in_chain_
, permitindo que seu hook invoque a funcionalidade original ou o próximo hook na cadeia.
- Descrição: Uma classe que representa um único hook de native. Armazena o hash do nome da native, a função handler C++ fornecida pelo usuário (
Trampoline_Allocator
:- Descrição: Uma classe responsável por alocar blocos de memória executável e gerar o código assembly "trampoline" nesses blocos.
Generate_Trampoline_Code(unsigned char* memory, int hook_id)
: Escreve 10 bytes de assembly:B8 XX XX XX XX
:MOV EAX, hook_id
(coloca o ID único do hook no registrador EAX).E9 XX XX XX XX
:JMP relative_address_to_Dispatch_Wrapper_Asm
(salta para a função de despacho genérica do SDK).
Allocation_Size = 4096
: Aloca memória em páginas para eficiência e alinhamento de cache.- Permissões de Memória: Usa
VirtualAlloc
(Windows) oummap
(Linux) com permissõesEXECUTE_READWRITE
para garantir que o código gerado possa ser executado.
Dispatch_Wrapper_Asm()
:- Descrição: Uma pequena função em assembly (definida com
__declspec(naked)
ouasm volatile
) que serve como o destino de todos os trampolines. - Ação: Salva os registradores, move
EAX
(que contém ohook_id
) para a stack, e chama a funçãoDispatch_Hook
em C++. Após o retorno deDispatch_Hook
, restaura os registradores e retorna.
- Descrição: Uma pequena função em assembly (definida com
cell SAMP_SDK_CDECL Dispatch_Hook(int hook_id, AMX* amx, cell* params)
:- Descrição: A função C++ genérica chamada por
Dispatch_Wrapper_Asm
. - Ação: Usa
hook_id
para encontrar oNative_Hook
correspondente noNative_Hook_Manager
e chama seu métodoDispatch()
, que por sua vez invoca o handlerPlugin_Native_Hook
do usuário. - Considerações de Linkagem: Esta função é um ponto crítico de interoperabilidade entre C++ e assembly. Para garantir que ela seja corretamente exportada e encontrada pelo linker no Linux (GCC/Clang), ela é definida com três características importantes:
extern "C"
: Impede o C++ Name Mangling, garantindo que o símbolo tenha o nome C puroDispatch_Hook
, que é o que o código assembly procura.inline
: Permite que a definição da função resida no arquivo de cabeçalho (necessário para uma biblioteca header-only) sem causar erros de "definição múltipla" (ODR - One Definition Rule).SAMP_SDK_USED_BY_ASM
(__attribute__((used))
no GCC/Clang): Força o compilador a emitir o código para a função, mesmo que ele não encontre nenhuma chamada para ela a partir de outro código C++. Isso evita que o otimizador a remova por engano.
- Descrição: A função C++ genérica chamada por
Native_Hook_Manager
:- Descrição: O
singleton
central que gerencia todos osNative_Hook
s registrados e seus trampolines. std::list<Native_Hook> hooks_
: Armazena a lista de hooks em ordem.std::unordered_map<uint32_t, Trampoline_Func> hash_to_trampoline_
: Mapeia o hash do nome da native para o ponteiro do trampoline gerado.std::vector<uint32_t> hook_id_to_hash_
: Mapeia o ID inteiro do hook (usado no trampoline) de volta para o hash do nome da native.Get_Trampoline(uint32_t hash)
: Retorna (ou cria e aloca) um ponteiro de trampoline para um determinado hash de native.
- Descrição: O
PLUGIN_NATIVE_HOOK_REGISTRATION
:- Mecanismo: Uma macro que cria uma classe estática global (
Native_Hook_Register_##name
) para cadaPlugin_Native_Hook
. No construtor estático dessa classe, ela registra ohandler
do usuário noNative_Hook_Manager
.
- Mecanismo: Uma macro que cria uma classe estática global (
Estes headers formam a espinha dorsal para chamar funções Pawn a partir do C++ (Pawn_*
macros) e garantem a segurança da memória.
-
Amx_Sandbox
:- Descrição: Uma estrutura
thread_local
que simula um ambienteAMX
minimalista e isolado para chamadasPawn_Native
ePlugin_Call
. - Mecanismo: Possui sua própria
AMX
struct,AMX_HEADER
e umstd::vector<unsigned char> heap
para simular a memória de um script. Isso permite que aamx::Push
,amx::Allot
, etc., sejam chamadas sem interferir com o estado de scripts Pawn reais em execução. thread_local
: Garante que cada thread tenha sua própriaAmx_Sandbox
, prevenindo condições de corrida se o SDK for usado em um contexto multi-threaded (ex: um futuro pool de threads para operações não-Pawn).- Exemplo (
Amx_Sandbox
):// Trecho simplificado de callbacks.hpp struct Amx_Sandbox { AMX amx; AMX_HEADER amx_header; std::vector<unsigned char> heap; // Memória simulada para stack/heap Amx_Sandbox(size_t heap_size = 64 * 1024) : heap(heap_size) { Reset(); // Inicializa a AMX e a header } void Reset() { memset(&amx, 0, sizeof(amx)); memset(&amx_header, 0, sizeof(amx_header)); amx_header.magic = AMX_MAGIC; amx_header.file_version = MIN_FILE_VERSION; amx_header.amx_version = MIN_AMX_VERSION; amx_header.dat = reinterpret_cast<ucell>(heap.data()) - reinterpret_cast<ucell>(&amx_header); amx.base = reinterpret_cast<unsigned char*>(&amx_header); amx.data = heap.data(); amx.callback = amx::Callback; amx.stp = heap.size(); amx.stk = heap.size(); amx.hea = 0; amx.flags = AMX_FLAG_NTVREG | AMX_FLAG_RELOC; } };
- Descrição: Uma estrutura
-
Parameter_Processor
:- Descrição: Um conjunto de funções template sobrecarregadas que gerencia o
marshalling
de cada parâmetro C++ para o formatocell
esperado pela AMX, e vice-versa para parâmetros de saída. - Processamento de Entrada:
- Para
int
,float
,bool
: Converte diretamente paracell
. - Para
const char*
,std::string
: Aloca memória na heap daAmx_Sandbox
(ou AMX real paraPawn_Public
), copia a string, e empurra o endereço AMX na stack.
- Para
- Processamento de Saída (
is_output_arg
):- Mecanismo: Quando um argumento é uma referência de lvalue não-const (detectado pelo trait
is_output_arg
), oParameter_Processor
não empurra o valor, mas sim um endereço AMX para umacell
alocada temporariamente na heap. std::vector<std::function<void()>> post_call_updaters
: Após a chamada da nativa Pawn, uma lista de lambdas (post_call_updaters
) é executada. Cada lambda é responsável por ler o valor final dacell
alocada na AMX e atribuí-lo de volta à variável C++ original (ex:x = amx::AMX_CTOF(*phys_addr)
).
- Mecanismo: Quando um argumento é uma referência de lvalue não-const (detectado pelo trait
- Descrição: Um conjunto de funções template sobrecarregadas que gerencia o
-
is_output_arg
:- Mecanismo: Um
std::integral_constant
(trait de tipo) que, em tempo de compilação, avalia se um tipo de parâmetro C++ é uma referência modificável (ex:int&
,float&
,std::string&
). Isso permite que oParameter_Processor
diferencie parâmetros de entrada de saída. - Exemplo (
is_output_arg
):// Trecho simplificado de callbacks.hpp template <typename T> struct is_output_arg : std::integral_constant<bool, std::is_lvalue_reference<T>::value && !std::is_const<typename std::remove_reference<T>::type>::value> {};
- Mecanismo: Um
-
Amx_Scoped_Memory
:- Descrição: Uma classe RAII (
Resource Acquisition Is Initialization
) que encapsula a alocação e desalocação de memória na AMX. - Mecanismo: No construtor, ela chama
amx::Allot
para obter umamx_addr
e umphys_addr
. No destrutor, ela chamaamx::Release
para liberar essa memória. - Impacto: Crucial para prevenir vazamentos de memória na heap da AMX. Garante que a memória temporária usada para strings ou parâmetros de saída seja sempre liberada, mesmo que ocorram exceções ou retornos antecipados.
- Descrição: Uma classe RAII (
Estes headers fornecem as definições fundamentais e as ferramentas de alto nível para interagir com o Pawn.
amx_defs.h
:- Conteúdo: Contém as definições brutas das estruturas da AMX (
AMX
,AMX_HEADER
), tipos (cell
,ucell
), e enums de erro (AmxError
). Também defineAMX_NATIVE
eAMX_CALLBACK
. SAMP_SDK_PACKED
: Utiliza atributos de empacotamento (#pragma pack(push, 1)
/__attribute__((packed))
) para garantir que as estruturas da AMX tenham o layout de memória correto, fundamental para a interoperabilidade.
- Conteúdo: Contém as definições brutas das estruturas da AMX (
Samp_SDK::amx::Call<Func, Index, ...>
:- Descrição: A função template principal para invocar as funções da API AMX expostas pelo servidor.
- Mecanismo: Obtém o ponteiro da função através do
Core::Instance().Get_AMX_Export(Index)
e o chama. Centraliza o tratamento de erros se o ponteiro da função não estiver disponível. - Impacto: Converte chamadas de baixo nível (
Core::Instance().Get_AMX_Export(PLUGIN_AMX_EXPORT_Exec)
) em chamadas C++ idiomáticas e type-safe (amx::Exec
).
Samp_SDK::amx::AMX_CTOF(cell c)
/AMX_FTOC(float f)
:- Descrição: Funções essenciais para converter valores
cell
parafloat
e vice-versa, realizando uma reinterpretação bitwise da memória. static_assert
: Incluemstatic_assert
para garantir quesizeof(cell) == sizeof(float)
em tempo de compilação, prevenindo erros em plataformas com tamanhos de tipo diferentes.
- Descrição: Funções essenciais para converter valores
Samp_SDK::Get_String(AMX* amx, cell amx_addr)
:- Descrição: Helper para converter um endereço AMX de string em
std::string
. - Mecanismo: Primeiro, obtém o endereço físico (
cell* phys_addr
) da string na AMX usandoamx::Get_Addr
. Em seguida, usaamx::STR_Len
para determinar o comprimento eamx::Get_String
para copiar os bytes para umstd::string
.
- Descrição: Helper para converter um endereço AMX de string em
std::string Samp_SDK::Format(const char* format, ...)
:- Descrição: A função base de formatação de strings (
printf
-like) para o SDK. - Mecanismo: Utiliza
vsnprintf
em duas passagens: primeiro para determinar o tamanho necessário da string, e depois para formatar a string nostd::string
alocado dinamicamente. Isso evita estouros de buffer.
- Descrição: A função base de formatação de strings (
- Seu plugin DEVE ser compilado para a arquitetura x86 (32-bit).
- Plataformas Suportadas: Windows (.dll) e Linux (.so).
- Crie um novo projeto de "Dynamic-Link Library (DLL)".
- Nas configurações do projeto, defina a "Plataforma de Solução" para x86.
- Garanta que o C++ Language Standard seja pelo menos C++14.
# Para um plugin chamado 'my_plugin.so' a partir de 'main.cpp'
g++ -m32 -shared -std=c++17 -O2 -fPIC -Wall -Wextra -Wl,--no-undefined main.cpp -o my_plugin.so
-m32
: Compila para 32-bit.-shared
: Cria uma biblioteca compartilhada (.so
).-std=c++17
: Define o padrão C++ para C++17 (pode serc++14
ouc++20
).-O2
: Nível de otimização 2.-fPIC
: Gera código independente de posição, necessário para bibliotecas compartilhadas.-Wall -Wextra
: Ativa avisos adicionais para ajudar a pegar erros.-Wl,--no-undefined
: Impede a criação da biblioteca se houver símbolos indefinidos.
# Para um plugin chamado 'my_plugin.dll' a partir de 'main.cpp'
g++ -m32 -shared -std=c++17 -O2 -static-libstdc++ -static-libgcc -Wl,--no-undefined main.cpp -o my_plugin.dll
-static-libstdc++
: Linka a biblioteca padrão C++ estaticamente. Essencial para evitar que seu plugin dependa de DLLs de runtime específicas do compilador que podem não estar presentes no sistema do usuário.-static-libgcc
: Linka a biblioteca GCC estaticamente.
- Nome do Arquivo: Seu plugin deve ter a extensão
.dll
(Windows) ou.so
(Linux). Ex:my_plugin.dll
. - Localização: Coloque o arquivo compilado na pasta
plugins/
do seu servidor SA-MP. - server.cfg: Adicione o nome do seu plugin (se for Windows, sem a extensão) à linha
plugins
noserver.cfg
.plugins my_plugin (se for Linux, my_plugin.so)
Copyright © SA-MP Programming Community
Este software é licenciado sob os termos da Licença MIT ("Licença"); você pode utilizar este software de acordo com as condições da Licença. Uma cópia da Licença pode ser obtida em: MIT License
A presente licença concede, gratuitamente, a qualquer pessoa que obtenha uma cópia deste software e arquivos de documentação associados, os seguintes direitos:
- Utilizar, copiar, modificar, mesclar, publicar, distribuir, sublicenciar e/ou vender cópias do software sem restrições
- Permitir que pessoas para as quais o software é fornecido façam o mesmo, desde que sujeitas às condições a seguir
Todas as cópias ou partes substanciais do software devem incluir:
- O aviso de direitos autorais acima
- Este aviso de permissão
- O aviso de isenção de responsabilidade abaixo
O software e toda a documentação associada são protegidos por leis de direitos autorais. A SA-MP Programming Community mantém a titularidade dos direitos autorais originais do software.
O SOFTWARE É FORNECIDO "COMO ESTÁ", SEM GARANTIA DE QUALQUER TIPO, EXPRESSA OU IMPLÍCITA, INCLUINDO, MAS NÃO SE LIMITANDO ÀS GARANTIAS DE COMERCIALIZAÇÃO, ADEQUAÇÃO A UM DETERMINADO FIM E NÃO VIOLAÇÃO.
EM NENHUMA CIRCUNSTÂNCIA OS AUTORES OU TITULARES DOS DIREITOS AUTORAIS SERÃO RESPONSÁVEIS POR QUALQUER REIVINDICAÇÃO, DANOS OU OUTRA RESPONSABILIDADE, SEJA EM AÇÃO DE CONTRATO, DELITO OU DE OUTRA FORMA, DECORRENTE DE, FORA DE OU EM CONEXÃO COM O SOFTWARE OU O USO OU OUTRAS NEGOCIAÇÕES NO SOFTWARE.
Para informações detalhadas sobre a Licença MIT, consulte: https://opensource.org/licenses/MIT