diff --git a/CMakeLists.txt b/CMakeLists.txt index 34dde32..c46f98c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required (VERSION 3.15) project ("MiTurboFix" - VERSION 0.0.3 + VERSION 0.0.4 ) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") diff --git a/src/Plugin.cpp b/src/Plugin.cpp index 37dd3b0..e7429aa 100644 --- a/src/Plugin.cpp +++ b/src/Plugin.cpp @@ -3,22 +3,14 @@ #include #include "RPCEnumerations.h" +#include +#include -void alloc_console() { -#ifdef _DEBUG - AllocConsole(); - SetConsoleOutputCP(1251); - - FILE* fDummy; - freopen_s(&fDummy, "CONIN$", "r", stdin); - freopen_s(&fDummy, "CONOUT$", "w", stderr); - freopen_s(&fDummy, "CONOUT$", "w", stdout); -#endif -} Plugin::Plugin(HMODULE hndl) : hModule(hndl) { - alloc_console(); + InitConsole(); + using namespace std::placeholders; hookCTimerUpdate.set_cb(std::bind(&Plugin::mainloop, this, _1)); hookCTimerUpdate.install(); @@ -41,6 +33,8 @@ void Plugin::mainloop(const decltype(hookCTimerUpdate)& hook) { rakhook::on_receive_rpc += std::bind(&PluginRPC::ApplyAnimation, &RPC, _1, _2); rakhook::on_receive_rpc += std::bind(&PluginRPC::ApplyActorAnimation, &RPC, _1, _2); rakhook::on_receive_rpc += std::bind(&PluginRPC::ShowPlayerDialog, &RPC, _1, _2); + rakhook::on_receive_rpc += std::bind(&PluginRPC::InitMenu, &RPC, _1, _2); + inited = true; } @@ -51,17 +45,60 @@ void Plugin::CMessages_AddBigMessageHooked(const decltype(hookCMessages_AddBigMe char* text, uint32_t duration, uint16_t style) { if (style > 6) { -#ifdef _DEBUG - Plugin::AddChatMessage(0xFFFFFFFF, __FUNCTION__ ": bad style = %d, strlen = %d", style, strlen(text)); -#endif + Plugin::AddChatMessageDebug(0xFFFFFFFF, __FUNCTION__ ": GameText bad style = %d, strlen = %d", style, strlen(text)); + return; } return hook.get_trampoline()(text, duration, style); } -void Plugin::AddChatMessage(std::uint32_t dwColor, std::string sFmrMessage, ...) + +/** +* SetSpawnInfo buffer overflow fix (not installing until SAMP is unavailable, for maybe not break single-player mode) +* +* int __cdecl CRestart::AddHospitalRestartPoint(RwV3D *point, float angle, int town) +* mov CRestarts__m_dwHospitalRestartCount, cx +* +* => +* +* nop (7 bytes) +*/ +void Plugin::InstallPatchAddHospitalRestartPoint() +{ + MemSet(reinterpret_cast(0x460773), 0x90, 0x7); +} + +void Plugin::MemSet(LPVOID lpAddr, int iVal, size_t dwSize) { + DWORD oldProtection = PAGE_EXECUTE_READWRITE; + VirtualProtect(lpAddr, dwSize, oldProtection, &oldProtection); + memset(lpAddr, iVal, dwSize); + VirtualProtect(lpAddr, dwSize, oldProtection, &oldProtection); +} + +/// +/// DEBUG Helpers +/// + +void Plugin::PrintBin(std::string str) +{ +#ifdef _DEBUG + for (char c : str) { std::cout << std::hex << std::setfill('0') << std::setw(2) << int(c); } + + std::cout << std::endl; +#endif +} + +/// +/// Add a debug chat message + WinDbg Message. Displayed ONLY IF you compile a DEBUG build +/// +/// Message Color +/// Message to format +/// Args... +void Plugin::AddChatMessageDebug(std::uint32_t dwColor, std::string sFmtMessage, ...) +{ +#ifdef _DEBUG if (!rakhook::initialized) return; @@ -70,8 +107,8 @@ void Plugin::AddChatMessage(std::uint32_t dwColor, std::string sFmrMessage, ...) memset(szBuffer, 0x0, sizeof(szBuffer)); va_list args; - va_start(args, sFmrMessage); - vsnprintf(szBuffer, 144, sFmrMessage.c_str(), args); + va_start(args, sFmtMessage); + vsnprintf(szBuffer, 144, sFmtMessage.c_str(), args); va_end(args); std::string sMessage = std::string(szBuffer); @@ -81,24 +118,7 @@ void Plugin::AddChatMessage(std::uint32_t dwColor, std::string sFmrMessage, ...) bs.Write(sMessage.size()); bs.Write(sMessage.data(), sMessage.size()); rakhook::emul_rpc(ScriptRPC::ClientMessage, bs); -} -/** -* SetSpawnInfo buffer overflow fix (not installing until SAMP is unavailable, for maybe not break single-player mode) -* -* int __cdecl CRestart::AddHospitalRestartPoint(RwV3D *point, float angle, int town) -* mov CRestarts__m_dwHospitalRestartCount, cx -* -* => -* -* nop (7 bytes) -*/ -void Plugin::InstallPatchAddHospitalRestartPoint() -{ - auto address = reinterpret_cast(0x460773); - auto size = 0x7; - DWORD oldProtection = PAGE_EXECUTE_READWRITE; - VirtualProtect(address, size, oldProtection, &oldProtection); - memset(address, 0x90, size); - VirtualProtect(address, size, oldProtection, &oldProtection); + OutputDebugStringA(std::string(std::string("[MiTurboFix] ") + sMessage).data()); +#endif } diff --git a/src/Plugin.h b/src/Plugin.h index 8786412..82b08e1 100644 --- a/src/Plugin.h +++ b/src/Plugin.h @@ -13,8 +13,11 @@ class Plugin { Plugin(HMODULE hModule); HMODULE hModule; - static void AddChatMessage(std::uint32_t dwColor, std::string sFmrMessage, ...); void InstallPatchAddHospitalRestartPoint(); + + void MemSet(LPVOID lpAddr, int iVal, size_t dwSize); + + static void AddChatMessageDebug(std::uint32_t dwColor, std::string sFmrMessage, ...); // DEBUG private: PluginRPC RPC; kthook::kthook_simple hookCTimerUpdate{ reinterpret_cast(0x561B10) }; @@ -23,4 +26,24 @@ class Plugin { void mainloop(const decltype(hookCTimerUpdate)& hook); void CMessages_AddBigMessageHooked(const decltype(hookCMessages_AddBigMessageHooked)& hook, char* text, uint32_t duration, uint16_t style); + +/// +/// Debug helpers +/// + + void PrintBin(std::string str); + + inline void InitConsole() + { +#ifdef _DEBUG + AllocConsole(); + SetConsoleOutputCP(1251); + + FILE* fDummy; + freopen_s(&fDummy, "CONIN$", "r", stdin); + freopen_s(&fDummy, "CONOUT$", "w", stderr); + freopen_s(&fDummy, "CONOUT$", "w", stdout); +#endif + } + }; diff --git a/src/PluginRPC.cpp b/src/PluginRPC.cpp index 27de2d3..51eb9e4 100644 --- a/src/PluginRPC.cpp +++ b/src/PluginRPC.cpp @@ -1,29 +1,18 @@ #include "PluginRPC.h" #include "Plugin.h" -#include "misc.h" - #include #include #include -#include #include -#include #include #include #include "RPCEnumerations.h" +#include "misc.h" -#ifdef _DEBUG -void PrintBin(std::string str) -{ - for (char c : str) { std::cout << std::hex << std::setfill('0') << std::setw(2) << int(c); } - - std::cout << std::endl; -} -#endif template std::string readWithSize(RakNet::BitStream& bs, size_t max = 0) { @@ -56,11 +45,9 @@ bool BitStreamIgnoreString(RakNet::BitStream& bs, size_t max = 0) { inline std::string PluginRPC::DialogSaintizeListItem(std::string token, size_t maxLineLen) { size_t tokenLen = (maxLineLen > 0 && token.length() > maxLineLen) ? maxLineLen : token.length(); -#ifdef _DEBUG if (maxLineLen > 0 && token.length() > maxLineLen) { - Plugin::AddChatMessage(0xFFFFFFFF, __FUNCTION__ ": bad listitem len = %d", token.length()); + Plugin::AddChatMessageDebug(0xFFFFFFFF, __FUNCTION__ ": bad listitem len = %d", token.length()); } -#endif return token.substr(0, tokenLen); } @@ -100,6 +87,8 @@ bool PluginRPC::ApplyAnimation(unsigned char& id, RakNet::BitStream* bs) { if (id != ScriptRPC::ApplyAnimation) // RPC_ApplyPlayerAnimation return true; + bs->ResetReadPointer(); + bs->IgnoreBits(16); // wPlayerId; size_t iAnimLibMaxLen = 15; @@ -108,9 +97,7 @@ bool PluginRPC::ApplyAnimation(unsigned char& id, RakNet::BitStream* bs) { std::string sAnimLib = readWithSize(*bs, iAnimLibMaxLen); if (sAnimLib.length() < 1 || sAnimLib.length() > iAnimLibMaxLen) { -#ifdef _DEBUG - Plugin::AddChatMessage(0xFFFFFFFF, __FUNCTION__ ": bad bAnimLibLength = %d", iAnimLibMaxLen); -#endif + Plugin::AddChatMessageDebug(0xFFFFFFFF, __FUNCTION__ ": bad bAnimLibLength = %d", iAnimLibMaxLen); return false; } @@ -118,9 +105,7 @@ bool PluginRPC::ApplyAnimation(unsigned char& id, RakNet::BitStream* bs) { std::string sAnimName = readWithSize(*bs, iAnimNameMaxLen); if (sAnimName.length() < 1 || sAnimName.length() > iAnimNameMaxLen) { -#ifdef _DEBUG - Plugin::AddChatMessage(0xFFFFFFFF, __FUNCTION__ ": bad bAnimNameLength = %d", iAnimNameMaxLen); -#endif + Plugin::AddChatMessageDebug(0xFFFFFFFF, __FUNCTION__ ": bad bAnimNameLength = %d", iAnimNameMaxLen); return false; } @@ -133,6 +118,8 @@ bool PluginRPC::ApplyActorAnimation(unsigned char& id, RakNet::BitStream* bs) { if (id != ScriptRPC::ApplyActorAnimation) return true; + bs->ResetReadPointer(); + bs->IgnoreBits(16); // wActorID; size_t iAnimLibMaxLen = 15; @@ -141,9 +128,7 @@ bool PluginRPC::ApplyActorAnimation(unsigned char& id, RakNet::BitStream* bs) { std::string sAnimLib = readWithSize(*bs, iAnimLibMaxLen); if (sAnimLib.length() < 1 || sAnimLib.length() > iAnimLibMaxLen) { -#ifdef _DEBUG - Plugin::AddChatMessage(0xFFFFFFFF, __FUNCTION__ ": bad bAnimLibLength = %d", iAnimLibMaxLen); -#endif + Plugin::AddChatMessageDebug(0xFFFFFFFF, __FUNCTION__ ": bad bAnimLibLength = %d", iAnimLibMaxLen); return false; } @@ -151,9 +136,7 @@ bool PluginRPC::ApplyActorAnimation(unsigned char& id, RakNet::BitStream* bs) { std::string sAnimName = readWithSize(*bs, iAnimNameMaxLen); if (sAnimName.length() < 1 || sAnimName.length() > iAnimNameMaxLen) { -#ifdef _DEBUG - Plugin::AddChatMessage(0xFFFFFFFF, __FUNCTION__ ": bad bAnimNameLength = %d", iAnimNameMaxLen); -#endif + Plugin::AddChatMessageDebug(0xFFFFFFFF, __FUNCTION__ ": bad bAnimNameLength = %d", iAnimNameMaxLen); return false; } @@ -169,6 +152,8 @@ bool PluginRPC::ShowPlayerDialog(unsigned char& id, RakNet::BitStream* bs) { unsigned char bDialogStyle; std::string title, but1, but2, text(4096, 0); + bs->ResetReadPointer(); + // read bs->Read(sDialogID); bs->Read(bDialogStyle); @@ -194,4 +179,118 @@ bool PluginRPC::ShowPlayerDialog(unsigned char& id, RakNet::BitStream* bs) { StringCompressor::Instance()->EncodeString(text.data(), 4096, bs); return true; -} \ No newline at end of file +} + +bool PluginRPC::InitMenu(unsigned char& id, RakNet::BitStream* bs) { + if (id != ScriptRPC::InitMenu) + return true; + + unsigned char bMenuID; + unsigned char bIsTwoColumns; + + char szText[Menu::MAX_MENU_LINE]; + + float fX, fY; + float fColWidth[2] = { 0.0, 0.0 }; + + Menu::Interaction MenuInteraction; // enabled/disabled + + memset(&MenuInteraction, 0x0, sizeof(Menu::Interaction)); + + bs->ResetReadPointer(); + + bs->Read(bMenuID); + + if (bMenuID >= Menu::MAX_MENUS) { + Plugin::AddChatMessageDebug(0xFFFFFFFF, __FUNCTION__ ": bad bMenuID = %d", bMenuID); + + return false; + } + + bs->Read(bIsTwoColumns); + bs->Read(szText, Menu::MAX_MENU_LINE); + + if (!String::ValidateLen(szText, Menu::MAX_MENU_LINE)) { + Plugin::AddChatMessageDebug(0xFFFFFFFF, __FUNCTION__ ": bad MenuTitle length = %d", strnlen_s(szText, Menu::MAX_MENU_LINE)); + + return false; + } + + bs->Read(fX); + bs->Read(fY); + + bs->Read(fColWidth[0]); + if (bIsTwoColumns) bs->Read(fColWidth[1]); + + bs->Read(MenuInteraction.bMenu); + + for (unsigned char i = 0; i < Menu::MAX_MENU_ITEMS; i++) + bs->Read(MenuInteraction.bRow[i]); + + + bs->Read(szText, Menu::MAX_MENU_LINE); + + if (!String::ValidateLen(szText, Menu::MAX_MENU_LINE)) { + Plugin::AddChatMessageDebug(0xFFFFFFFF, __FUNCTION__ ": bad MenuHeaderCol1 length = %d", strnlen_s(szText, Menu::MAX_MENU_LINE)); + + return false; + } + + unsigned char byteRowCount; + + if (!bs->Read(byteRowCount)) { + Plugin::AddChatMessageDebug(0xFFFFFFFF, __FUNCTION__ ": bad can't read MenuRowCountCol1"); + + return false; + } + + if (byteRowCount > Menu::MAX_MENU_ITEMS) { + Plugin::AddChatMessageDebug(0xFFFFFFFF, __FUNCTION__ ": bad MenuRowCountCol1 = %d", byteRowCount); + + return false; + } + + for (unsigned char i = 0; i < byteRowCount; i++) { + bs->Read(szText, Menu::MAX_MENU_LINE); + + if (!String::ValidateLen(szText, Menu::MAX_MENU_LINE)) { + Plugin::AddChatMessageDebug(0xFFFFFFFF, __FUNCTION__ ": bad MenuRowCol1(%d) length = %d", i, strnlen_s(szText, Menu::MAX_MENU_LINE)); + + return false; + } + } + + if (bIsTwoColumns) { + bs->Read(szText, Menu::MAX_MENU_LINE); + + if (!String::ValidateLen(szText, Menu::MAX_MENU_LINE)) { + Plugin::AddChatMessageDebug(0xFFFFFFFF, __FUNCTION__ ": bad MenuHeaderCol2 length = %d", strnlen_s(szText, Menu::MAX_MENU_LINE)); + + return false; + } + + if (!bs->Read(byteRowCount)) { + Plugin::AddChatMessageDebug(0xFFFFFFFF, __FUNCTION__ ": bad can't read MenuRowCountCol1"); + + return false; + } + + if (byteRowCount > Menu::MAX_MENU_ITEMS) { + Plugin::AddChatMessageDebug(0xFFFFFFFF, __FUNCTION__ ": bad MenuRowCountCol2 = %d", byteRowCount); + + return false; + } + + for (unsigned char i = 0; i < byteRowCount; i++) { + bs->Read(szText, Menu::MAX_MENU_LINE); + + if (!String::ValidateLen(szText, Menu::MAX_MENU_LINE)) { + Plugin::AddChatMessageDebug(0xFFFFFFFF, __FUNCTION__ ": bad MenuRowCol2(%d) length = %d", i, strnlen_s(szText, Menu::MAX_MENU_LINE)); + + return false; + } + } + } + + return true; +} diff --git a/src/PluginRPC.h b/src/PluginRPC.h index 5e37da7..b807c2a 100644 --- a/src/PluginRPC.h +++ b/src/PluginRPC.h @@ -12,4 +12,5 @@ class PluginRPC { bool ApplyAnimation(unsigned char& id, RakNet::BitStream* bs); bool ApplyActorAnimation(unsigned char& id, RakNet::BitStream* bs); bool ShowPlayerDialog(unsigned char& id, RakNet::BitStream* bs); + bool InitMenu(unsigned char& id, RakNet::BitStream* bs); }; \ No newline at end of file diff --git a/src/Resource.rc b/src/Resource.rc index 5f469ec..07be688 100644 Binary files a/src/Resource.rc and b/src/Resource.rc differ diff --git a/src/misc.h b/src/misc.h index fcd9b86..ac8875d 100644 --- a/src/misc.h +++ b/src/misc.h @@ -24,3 +24,30 @@ namespace DialogType { } }; +/// +/// Menu (from RakSAMP) +/// + +namespace Menu { + constexpr auto MAX_MENUS = 128; + constexpr auto MAX_MENU_ITEMS = 12; + constexpr auto MAX_MENU_LINE = 32; + + struct Interaction + { + unsigned char bMenu; + unsigned char bRow[MAX_MENU_ITEMS]; + unsigned char bPadding[8 - ((MAX_MENU_ITEMS + 1) % 8)]; + }; +}; + +/// +/// String helpers +/// + +namespace String { + inline bool ValidateLen(char* szBuffer, size_t maxSize) { + return strnlen_s(szBuffer, maxSize) < maxSize; + } +}; +