- 52
- 19
Я учусь создавать меню для SA-MP. Как исправить курсор мыши? Он не двигается с середины экрана, когда я открываю меню. Я использую SDK SAMP от DK22Pac.
My Code:
My Code:
C++:
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <d3d9.h>
#include <stdio.h>
#include <time.h>
#pragma comment(lib, "d3d9.lib")
#include "imgui.h"
#include "imgui_impl_dx9.h"
#include "imgui_impl_win32.h"
#include "MinHook.h"
#ifdef _M_X64
#pragma comment(lib, "libMinHook.x64.lib")
#else
#pragma comment(lib, "libMinHook.x86.lib")
#endif
#define LOG_FILE "bode_menu.txt"
#define FRAME_TIME_BUDGET 16.0f // ~60 FPS, em ms
// Estrutura para salvar estado do cursor
struct CursorState {
bool bVisible;
bool clipExists;
RECT clipRect;
HCURSOR hCursor;
};
static bool menu = false;
static bool inicializado = false;
static bool render_paused = false;
static int click_count = 0;
static HWND gameWindow = nullptr;
// Estado do cursor
static CursorState saved_cursor_state = {};
static bool cursor_state_saved = false;
// Ponteiros originais (D3D9)
typedef HRESULT(__stdcall* tEndScene)(IDirect3DDevice9*);
typedef HRESULT(__stdcall* tReset)(IDirect3DDevice9*, D3DPRESENT_PARAMETERS*);
tEndScene oEndScene = nullptr;
tReset oReset = nullptr;
WNDPROC oWndProc = nullptr;
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM);
void Log(const char* fmt, ...) {
FILE* f = fopen(LOG_FILE, "a");
if (f) {
// Timestamp
time_t now = time(nullptr);
struct tm* timeinfo = localtime(&now);
fprintf(f, "[%02d:%02d:%02d] ", timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
va_list args;
va_start(args, fmt);
vfprintf(f, fmt, args);
fprintf(f, "\n");
va_end(args);
fclose(f);
}
}
void SaveCursorState() {
if (cursor_state_saved) return;
Log("[CURSOR] Salvando estado do cursor...");
// Usa CURSORINFO para obter visibilidade e hCursor
CURSORINFO ci;
ci.cbSize = sizeof(ci);
if (GetCursorInfo(&ci)) {
saved_cursor_state.bVisible = (ci.flags & CURSOR_SHOWING) != 0;
saved_cursor_state.hCursor = ci.hCursor;
}
else {
// fallback
saved_cursor_state.bVisible = true;
saved_cursor_state.hCursor = LoadCursor(NULL, IDC_ARROW);
}
// Salva clip region, marcando se existe
RECT rc;
if (GetClipCursor(&rc)) {
saved_cursor_state.clipExists = true;
saved_cursor_state.clipRect = rc;
}
else {
saved_cursor_state.clipExists = false;
}
cursor_state_saved = true;
Log("[CURSOR] Estado salvo: Visible=%d, ClipExists=%d, Cursor=0x%p",
saved_cursor_state.bVisible, saved_cursor_state.clipExists, saved_cursor_state.hCursor);
}
void RestoreCursorState() {
if (!cursor_state_saved) return;
Log("[CURSOR] Restaurando estado do cursor...");
// Restaura clip region somente se existia; caso contrário remove confinamento
if (saved_cursor_state.clipExists) {
ClipCursor(&saved_cursor_state.clipRect);
}
else {
ClipCursor(NULL);
}
// Restaura cursor gráfico
SetCursor(saved_cursor_state.hCursor);
// Restaura visibilidade usando ShowCursor; ajusta contador interno
if (saved_cursor_state.bVisible) {
while (ShowCursor(TRUE) < 0) {}
}
else {
while (ShowCursor(FALSE) >= 0) {}
}
cursor_state_saved = false;
Log("[CURSOR] Estado restaurado: Visible=%d", saved_cursor_state.bVisible);
}
void ShowCursorForMenu() {
Log("[CURSOR] Mostrando cursor para menu...");
// Remove confinamento
ClipCursor(NULL);
// Força cursor sistema com seta
SetCursor(LoadCursor(NULL, IDC_ARROW));
// Garante visibilidade
while (ShowCursor(TRUE) < 0) {}
// Pedir para ImGui não desenhar cursor (usar sistema)
ImGui::GetIO().MouseDrawCursor = false;
Log("[CURSOR] Cursor visível para menu");
}
// ============================================================================
// WNDPROC HOOK - INPUT HANDLING
// ============================================================================
LRESULT CALLBACK WndProcHook(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
// Se menu está fechado, passa tudo normalmente
if (!menu) {
return CallWindowProc(oWndProc, hWnd, uMsg, wParam, lParam);
}
// Menu está aberto - ImGui processa primeiro
if (ImGui_ImplWin32_WndProcHandler(hWnd, uMsg, wParam, lParam))
return TRUE;
ImGuiIO& io = ImGui::GetIO();
// Se ImGui quer capturar, bloqueia do jogo
if (io.WantCaptureMouse) {
if (uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST)
return TRUE;
}
if (io.WantCaptureKeyboard) {
if (uMsg >= WM_KEYFIRST && uMsg <= WM_KEYLAST)
return TRUE;
}
return CallWindowProc(oWndProc, hWnd, uMsg, wParam, lParam);
}
// ============================================================================
// D3D9 HOOKS
// ============================================================================
HRESULT __stdcall HookedReset(IDirect3DDevice9* pDevice, D3DPRESENT_PARAMETERS* pPresentationParameters) {
Log("[D3D] Reset detectado");
if (inicializado) {
ImGui_ImplDX9_InvalidateDeviceObjects();
}
HRESULT hr = oReset(pDevice, pPresentationParameters);
if (SUCCEEDED(hr) && inicializado) {
Log("[D3D] Recriando device objects...");
ImGui_ImplDX9_CreateDeviceObjects();
}
return hr;
}
HRESULT __stdcall HookedEndScene(IDirect3DDevice9* pDevice) {
// Skip frames logic to avoid residual draw immediately after fechar
static int skip_render_frames = 0;
if (skip_render_frames > 0) {
--skip_render_frames;
return oEndScene(pDevice);
}
if (!inicializado) {
Log("[INIT] Inicializando menu ImGui...");
gameWindow = FindWindowA(NULL, "GTA:SA:MP");
if (!gameWindow) gameWindow = FindWindowA("Grand Theft Auto San Andreas", NULL);
if (!gameWindow) {
D3DDEVICE_CREATION_PARAMETERS cp;
if (SUCCEEDED(pDevice->GetCreationParameters(&cp))) {
gameWindow = cp.hFocusWindow;
}
}
if (!gameWindow) {
Log("[INIT] ERRO: Não conseguiu encontrar HWND do jogo!");
return oEndScene(pDevice);
}
Log("[INIT] HWND detectado: 0x%p", gameWindow);
// Inicializa ImGui
ImGui::CreateContext();
ImGui::StyleColorsDark();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.IniFilename = NULL;
io.MouseDrawCursor = false; // Não desenha cursor (usa do sistema)
ImGui_ImplWin32_Init(gameWindow);
ImGui_ImplDX9_Init(pDevice);
// Hook do WndProc para capturar input
oWndProc = (WNDPROC)SetWindowLongPtr(gameWindow, GWLP_WNDPROC, (LONG_PTR)WndProcHook);
inicializado = true;
menu = false;
Log("[INIT] === ImGui PRONTO! ===");
}
// Atualização de tempo local para medir frame
clock_t frame_start_time_local = clock();
ImGuiIO& io = ImGui::GetIO();
if (menu) {
POINT cursorPos;
if (GetCursorPos(&cursorPos)) {
ScreenToClient(gameWindow, &cursorPos);
// Atualiza posição
io.MousePos = ImVec2((float)cursorPos.x, (float)cursorPos.y);
}
else {
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
}
// Atualiza botões do mouse
io.MouseDown[0] = (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0;
io.MouseDown[1] = (GetAsyncKeyState(VK_RBUTTON) & 0x8000) != 0;
io.MouseDown[2] = (GetAsyncKeyState(VK_MBUTTON) & 0x8000) != 0;
}
// ========================================
// TOGGLE DO MENU (F2)
// ========================================
static bool f2_pressed = false;
if (GetAsyncKeyState(VK_F2) & 0x8000) {
if (!f2_pressed) {
f2_pressed = true;
if (!menu) {
// Abrindo menu
menu = true;
SaveCursorState();
ShowCursorForMenu();
// garante que ImGui esteja pronto a receber input
ImGui::GetIO().WantCaptureMouse = true;
ImGui::GetIO().WantCaptureKeyboard = true;
render_paused = false;
Log("[MENU] === MENU ABERTO ===");
}
else {
// Fechando menu
menu = false;
// Restaurar o estado do cursor SALVO (não "ocultar" arbitrariamente)
RestoreCursorState();
// Invalida objetos do ImGui para evitar possíveis draw-residue/artefatos
ImGui_ImplDX9_InvalidateDeviceObjects();
// Limpa flags de captura
ImGui::GetIO().WantCaptureMouse = false;
ImGui::GetIO().WantCaptureKeyboard = false;
// pula alguns frames sem render de menu para garantir limpeza visual
skip_render_frames = 2;
Log("[MENU] === MENU FECHADO ===");
}
}
}
else {
f2_pressed = false;
}
// ========================================
// SE MENU ESTÁ FECHADO, RETORNA RÁPIDO
// ========================================
if (!menu) {
return oEndScene(pDevice);
}
// ========================================
// RENDERIZA MENU (ImGui)
// ========================================
// Inicia frame do ImGui
ImGui_ImplDX9_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
// === JANELA DO MENU ===
ImGui::SetNextWindowPos(ImVec2(100, 100), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(600, 500), ImGuiCond_FirstUseEver);
if (ImGui::Begin("Bode Menu - SA-MP", &menu, ImGuiWindowFlags_NoCollapse)) {
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Menu Stealth - SA-MP");
ImGui::Separator();
// Mostra informações
ImGui::Text("FPS: %.1f", io.Framerate);
ImGui::Text("Posicao do Mouse: X=%.0f Y=%.0f", io.MousePos.x, io.MousePos.y);
ImGui::Text("Cliques registrados: %d", click_count);
ImGui::Spacing();
ImGui::Separator();
// Botão de teste
if (ImGui::Button("TESTE CLIQUE", ImVec2(200, 50))) {
click_count++;
Beep(1000, 100);
Log("[MENU] Botao clicado! Total: %d", click_count);
}
ImGui::Spacing();
// Exemplos de widgets
static bool checkbox_test = false;
ImGui::Checkbox("Opcao de Teste", &checkbox_test);
static float slider_test = 0.5f;
ImGui::SliderFloat("Slider Teste", &slider_test, 0.0f, 1.0f);
ImGui::Spacing();
ImGui::TextColored(ImVec4(1, 1, 0, 1), "Pressione F2 para fechar");
ImGui::End();
}
// Render ImGui
ImGui::EndFrame();
ImGui::Render();
ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData());
// ========================================
// CONTROLE DE FRAME TIME (OTIMIZAÇÃO) - medição local
// ========================================
clock_t frame_end_time_local = clock();
double frame_ms = (double)(frame_end_time_local - frame_start_time_local) * 1000.0 / CLOCKS_PER_SEC;
if (frame_ms > FRAME_TIME_BUDGET * 1.5) {
static int slow_frame_count = 0;
if (++slow_frame_count % 30 == 0) {
Log("[PERF] Frame lento: %.2f ms (budget: %.2f ms)", frame_ms, FRAME_TIME_BUDGET);
}
}
// Debug: detectar se outro módulo mudou o cursor (opcional)
if (cursor_state_saved) {
HCURSOR hCurNow = GetCursor();
if (hCurNow != saved_cursor_state.hCursor) {
Log("[CURSOR] Detectado cursor atual diferente do salvo: now=0x%p saved=0x%p", hCurNow, saved_cursor_state.hCursor);
}
}
return oEndScene(pDevice);
}
// ============================================================================
// THREAD DE INICIALIZAÇÃO DO PLUGIN
// ============================================================================
DWORD WINAPI ThreadInicio(LPVOID) {
Log("=== BODE PLUGIN - INICIANDO ===");
// Aguarda carregamento do SAMP e D3D9
int wait_count = 0;
while (!GetModuleHandleA("samp.dll") && wait_count++ < 200) Sleep(50);
wait_count = 0;
while (!GetModuleHandleA("d3d9.dll") && wait_count++ < 200) Sleep(50);
Log("SAMP e D3D9 carregados!");
Sleep(2000); // Aguarda jogo inicialisar
// Inicializa MinHook
if (MH_Initialize() != MH_OK) {
Log("ERRO: Falha ao inicializar MinHook!");
return 0;
}
// Cria device D3D9 temporário para hooking
HWND hWnd = GetDesktopWindow();
IDirect3D9* pD3D = Direct3DCreate9(D3D_SDK_VERSION);
if (!pD3D) {
Log("ERRO: Falha ao criar IDirect3D9!");
MH_Uninitialize();
return 0;
}
D3DPRESENT_PARAMETERS d3dpp = {};
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hWnd;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
d3dpp.BackBufferWidth = 1;
d3dpp.BackBufferHeight = 1;
IDirect3DDevice9* pDevice = nullptr;
HRESULT hr = pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_NULLREF, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_DISABLE_DRIVER_MANAGEMENT,
&d3dpp, &pDevice);
if (FAILED(hr)) {
hr = pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &pDevice);
}
if (FAILED(hr)) {
Log("ERRO ao criar D3D9 device: 0x%08X", hr);
pD3D->Release();
MH_Uninitialize();
return 0;
}
// Obtém v-table
void** vTable = *(void***)pDevice;
void* pEndScene = vTable[42]; // EndScene index
void* pReset = vTable[16]; // Reset index
// Cria hooks
if (MH_CreateHook(pEndScene, &HookedEndScene, (void**)&oEndScene) != MH_OK) {
Log("ERRO: Falha ao criar hook EndScene!");
pDevice->Release();
pD3D->Release();
MH_Uninitialize();
return 0;
}
if (MH_CreateHook(pReset, &HookedReset, (void**)&oReset) != MH_OK) {
Log("ERRO: Falha ao criar hook Reset!");
pDevice->Release();
pD3D->Release();
MH_Uninitialize();
return 0;
}
// Habilita hooks
if (MH_EnableHook(MH_ALL_HOOKS) != MH_OK) {
Log("ERRO: Falha ao ativar hooks!");
pDevice->Release();
pD3D->Release();
MH_Uninitialize();
return 0;
}
Log("Hooks instalados com sucesso!");
Log("EndScene: 0x%p -> 0x%p", pEndScene, &HookedEndScene);
Log("Reset: 0x%p -> 0x%p", pReset, &HookedReset);
// Libera recursos temporários
pDevice->Release();
pD3D->Release();
Log("=== PLUGIN PRONTO! Pressione F2 para abrir o menu ===");
return 0;
}
// ============================================================================
// DLL MAIN
// ============================================================================
BOOL APIENTRY DllMain(HMODULE hMod, DWORD reason, LPVOID) {
if (reason == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls(hMod);
Log("=== PLUGIN CARREGADO ===");
CreateThread(nullptr, 0, ThreadInicio, nullptr, 0, nullptr);
}
else if (reason == DLL_PROCESS_DETACH) {
Log("=== PLUGIN DESCARREGANDO ===");
// Restaura WndProc se alterado
if (oWndProc && gameWindow) {
SetWindowLongPtr(gameWindow, GWLP_WNDPROC, (LONG_PTR)oWndProc);
oWndProc = nullptr;
}
// Cleanup ImGui se inicializado
if (inicializado) {
ImGui_ImplDX9_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
inicializado = false;
}
MH_DisableHook(MH_ALL_HOOKS);
MH_Uninitialize();
Log("=== PLUGIN DESCARREGADO ===");
}
return TRUE;
}