diff --git a/README.CLIENT.md b/README.CLIENT.md index 173535d..993d98e 100644 --- a/README.CLIENT.md +++ b/README.CLIENT.md @@ -58,6 +58,13 @@ This is a compilations of ASM/editions for vSRO (1.188) that you'll need to chan 00797E21 | 3C 0A | cmp al,A | Unlock action ``` +### SERVER_BEGINNER_MARK_LEVEL_MAX + +Fill with NOPs the following instruction to show the beginner mark always. +``` +009DED3D | 0F84 9E000000 | je sro_client.9DEDE1 | +``` + ### RACE_CH_TOTAL_MASTERIES 0x14A = 330, 0xDC = 220 diff --git a/README.md b/README.md index ec8c30d..b0f5bb9 100644 --- a/README.md +++ b/README.md @@ -358,6 +358,24 @@ VALUES ); ``` +19. Reduces health and/or mana points from player +```sql +INSERT INTO [SRO_VT_SHARD].[dbo].[_ExeGameServer] +( + Action_ID, + CharName16, + Param02, -- HP reduced + Param02 -- MP reduced +) +VALUES +( + 19, + 'JellyBitz', + 5000, -- Reducing HP only + 0 +); +``` + ### Action Result Code diff --git a/vSRO-GameServer/AppManager.cpp b/vSRO-GameServer/AppManager.cpp index 25e7f7a..27bb131 100644 --- a/vSRO-GameServer/AppManager.cpp +++ b/vSRO-GameServer/AppManager.cpp @@ -14,6 +14,8 @@ #include "Utils/Memory/Process.h" #include "Utils/Memory/hook.h" #pragma warning(disable:4244) // Bitwise operations warnings +// ASM injection +#include "AsmEdition.h" /// Static stuffs bool AppManager::m_IsInitialized; @@ -50,6 +52,7 @@ void AppManager::InitConfigFile() // Memory ini.SetLongValue("Server", "LEVEL_MAX", 110, "; Maximum level that can be reached on server"); ini.SetLongValue("Server", "STALL_PRICE_LIMIT", 9999999999, "; Maximum price that can be stalled"); + ini.SetLongValue("Server", "PARTY_LEVEL_MIN", 5, "; Minimum level to create a party group"); ini.SetLongValue("Server", "PARTY_MOB_MEMBERS_REQUIRED", 2, "; Party members required to find monsters party type"); ini.SetLongValue("Server", "PARTY_MOB_SPAWN_PROBABILITY", 50, "; % Probability for party mob spawns"); ini.SetLongValue("Server", "PK_LEVEL_REQUIRED", 20, "; Level required to kill other player"); @@ -60,6 +63,7 @@ void AppManager::InitConfigFile() ini.SetLongValue("Server", "BEGINNER_MARK_LEVEL_MAX", 19, "; Maximum level to show the beginner mark"); ini.SetLongValue("Job", "LEVEL_MAX", 7, "; Maximum level that can be reached on job suit"); ini.SetBoolValue("Job", "DISABLE_MOB_SPAWN", false, "; Disable Thief/Hunter monster spawn while trading"); + ini.SetLongValue("Job", "TEMPLE_LEVEL", 105, "; Minimum level to enter the Temple Area"); ini.SetLongValue("Race", "CH_TOTAL_MASTERIES", 330, "; Masteries amount Chinese will obtain"); ini.SetLongValue("Guild", "MEMBERS_LIMIT_LEVEL1", 15, "; Guild members capacity at level 1"); ini.SetLongValue("Guild", "MEMBERS_LIMIT_LEVEL2", 20, "; Guild members capacity at level 2"); @@ -89,6 +93,7 @@ void AppManager::InitConfigFile() ini.SetBoolValue("Fix", "DISABLE_MSGBOX_SILK_GOLD_PRICE", true, "; Disable messages about \"register silk/gold price.\""); ini.SetBoolValue("Fix", "EXCHANGE_ATTACK_CANCEL", true, "; Remove attack cancel when player exchanges"); ini.SetBoolValue("Fix", "EXPLOIT_INVISIBLE_INVINCIBLE", true, "; Cancel exploit sent from client (0x70A7)"); + ini.SetBoolValue("Fix", "GUILD_POINTS", true, "; Prevents negative values on guild points"); // App ini.SetBoolValue("App", "DEBUG_CONSOLE", true, "; Attach debug console"); // Save it @@ -118,7 +123,7 @@ void AppManager::InitHooks() CSimpleIniA ini; ini.LoadFile("vSRO-GameServer.ini"); - // Uniques + // Fixes if (ini.GetBoolValue("Fix","UNIQUE_LOGS",true)) { // Create connection string @@ -131,16 +136,26 @@ void AppManager::InitHooks() if (m_dbUniqueLog.sqlConn.Open((SQLWCHAR*)connString.str().c_str()) && m_dbUniqueLog.sqlCmd.Open(m_dbUniqueLog.sqlConn)) { + printf(" - FIX_UNIQUE_LOGS\r\n"); if (replaceOffset(0x00414DB0, addr_from_this(&AppManager::OnUniqueSpawnMsg))) { - std::cout << " - OnUniqueSpawnMsg" << std::endl; + std::cout << " - OnUniqueSpawnMsg" << std::endl; } if (replaceOffset(0x00414BA9, addr_from_this(&AppManager::OnUniqueKilledMsg))) { - std::cout << " - OnUniqueKilledMsg" << std::endl; + std::cout << " - OnUniqueKilledMsg" << std::endl; } } } + if (ini.GetBoolValue("Fix", "GUILD_POINTS", true)) + { + printf(" - FIX_GUILD_POINTS\r\n"); + // Redirect code flow to DLL + if (placeHook(0x005C4135, addr_from_this(&AsmEdition::OnDonateGuildPoints))) + { + std::cout << " - OnDonateGuildPoints" << std::endl; + } + } } void AppManager::OnUniqueSpawnMsg(uint32_t LogType, const char* Message, const char* UniqueCodeName, uint16_t RegionId, uint32_t unk01, uint32_t unk02) { @@ -216,6 +231,12 @@ void AppManager::InitPatchValues() WriteMemoryValue(0x004F7746 + 4, newValue); } } + if(ReadMemoryValue(0x00513FEC + 1, byteValue)) + { + uint8_t newValue = ini.GetLongValue("Server", "PARTY_LEVEL_MIN", 5); + printf(" - SERVER_PARTY_LEVEL_MIN (%d) -> (%d)\r\n", byteValue, newValue); + WriteMemoryValue(0x00513FEC + 1, newValue); + } if(ReadMemoryValue(0x00558F20 + 4, byteValue)) { uint8_t newValue = ini.GetLongValue("Server", "PARTY_MOB_MEMBERS_REQUIRED", 2); @@ -278,6 +299,13 @@ void AppManager::InitPatchValues() printf(" - JOB_DISABLE_MOB_SPAWN\r\n"); WriteMemoryValue(0x0060C4AB, 0xC031); // mov eax,esi -> xor eax,eax } + if (ReadMemoryValue(0x0051AE71 + 1, byteValue)) + { + uint8_t newValue = ini.GetLongValue("Job", "TEMPLE_LEVEL", 105); + printf(" - JOB_TEMPLE_LEVEL (%d) -> (%d)\r\n", byteValue, newValue); + WriteMemoryValue(0x0051AE71 + 1, newValue); + WriteMemoryValue(0x0051ABE8 + 1, newValue); + } // Race if (ReadMemoryValue(0x0059C5E6 + 1, uintValue)) @@ -457,7 +485,7 @@ void AppManager::InitPatchValues() WriteMemoryValue(0x0066917A + 4, newValue); } - // Fixes + // Fix if (ReadMemoryValue(0x004744BC + 1, uintValue)) { uint32_t newValue = ini.GetLongValue("Fix", "AGENT_SERVER_CAPACITY", 1000); @@ -899,6 +927,19 @@ DWORD WINAPI AppManager::DatabaseFetchThread() actionResult = FETCH_ACTION_STATE::CHARNAME_NOT_FOUND; } } break; + case 19: // Reduce HP/MP from player + { + SQLINTEGER cParam02, cParam03, cParam04; + if (m_dbLink.sqlCmd.GetData(5, SQL_C_LONG, &cParam02, 0, NULL) + && m_dbLink.sqlCmd.GetData(6, SQL_C_LONG, &cParam03, 0, NULL)) + { + CGObjPC* player = CGObjManager::GetObjPCByCharName16(cCharName); + if (player) + player->ReduceHPMP(cParam02, cParam03, true); + else + actionResult = FETCH_ACTION_STATE::CHARNAME_NOT_FOUND; + } + } break; case 3312: // For testing references { CGObjPC* player = CGObjManager::GetObjPCByCharName16(cCharName); diff --git a/vSRO-GameServer/AsmEdition.h b/vSRO-GameServer/AsmEdition.h new file mode 100644 index 0000000..feffad4 --- /dev/null +++ b/vSRO-GameServer/AsmEdition.h @@ -0,0 +1,26 @@ +#include +#pragma once + +// Direct ASM injection +namespace AsmEdition +{ + // Jump back to the code flow from donating guild points + static DWORD jmpAddr_DonateGP = 0x005C413A; + // Handler to catch guild point increasing hook and edit directly with asm + static _declspec(naked) void OnDonateGuildPoints() + { + // Rebuild asm + __asm + { + mov ecx, dword ptr[eax + 0x3c] // rebuild + add ecx, esi // rebuild + cmp ecx, 0x7FFFFFFF // compare ecx with int.MaxValue + jbe _continue // go to _continue if ecx <= int.MaxValue + mov ecx, 0x7FFFFFFF // set ecx as int.MaxValue + jmp _continue // go to _continue + } + // Contine code flow + _continue: + __asm jmp jmpAddr_DonateGP; + } +} \ No newline at end of file diff --git a/vSRO-GameServer/Silkroad/Object/CGObjPC.cpp b/vSRO-GameServer/Silkroad/Object/CGObjPC.cpp index 33c99f7..9e7fd3d 100644 --- a/vSRO-GameServer/Silkroad/Object/CGObjPC.cpp +++ b/vSRO-GameServer/Silkroad/Object/CGObjPC.cpp @@ -42,9 +42,19 @@ void CGObjPC::UpdateSP(int32_t Offset) { CallVirtual(this, 93)(this, Offset, 1); } -void CGObjPC::UpdateHPMP(int32_t Health, int32_t Mana, uint16_t DisplayEffectType) +void CGObjPC::ReduceHPMP(uint32_t Health, uint32_t Mana, bool ShowEffect) { - CallVirtual(this, 194)(this, Health, Mana, DisplayEffectType); + // Check if player will die by health reduction + bool died = Health > m_CInstancePC->Health; + if (died) + { + Health = m_CInstancePC->Health; + Mana = m_CInstancePC->Mana; + } + CallVirtual(this, 194)(this, Health, Mana, ShowEffect ? 1024 : 0); + // Set dead status + if (died) + SetLifeState(false); } void CGObjPC::UpdatePVPCapeType(uint8_t CapeType) { diff --git a/vSRO-GameServer/Silkroad/Object/CGObjPC.h b/vSRO-GameServer/Silkroad/Object/CGObjPC.h index fe1f6ec..b1ae92c 100644 --- a/vSRO-GameServer/Silkroad/Object/CGObjPC.h +++ b/vSRO-GameServer/Silkroad/Object/CGObjPC.h @@ -29,8 +29,8 @@ class CGObjPC : public CGObjChar void UpdateExperience(int64_t ExpOffset); // Add skill experience void AddSPExperience(uint32_t SPExpOffset); - // Updates the HP and MP - void UpdateHPMP(int32_t Health, int32_t Mana, uint16_t DisplayEffectType); + // Reduces health and/or mana points. If health reduced exceeds the current amount, the player will die + void ReduceHPMP(uint32_t Health, uint32_t Mana, bool ShowEffect); // Updates the cape state from PVP void UpdatePVPCapeType(uint8_t CapeType); // Moves the player to the map location. Return success diff --git a/vSRO-GameServer/Silkroad/Object/CInstancePC.h b/vSRO-GameServer/Silkroad/Object/CInstancePC.h index ec94e4c..618b594 100644 --- a/vSRO-GameServer/Silkroad/Object/CInstancePC.h +++ b/vSRO-GameServer/Silkroad/Object/CInstancePC.h @@ -1,6 +1,7 @@ #pragma once #include +// Contains the basic informacion from player character class CInstancePC { public: @@ -9,7 +10,8 @@ class CInstancePC char pad_0014[4]; //0x0014 uint32_t RefObjCharPtr; //0x0018 char pad_001C[4]; //0x001C - uint32_t CharID; //0x0020 + // ID to identify the player from database + uint32_t CharID; char pad_0024[4]; //0x0024 uint32_t ModelID; //0x0028 char pad_002C[4]; //0x002C @@ -27,8 +29,10 @@ class CInstancePC uint32_t RemainSkillPoint; //0x0080 uint32_t RemainStatPoint; //0x0084 char pad_0088[4]; //0x0088 - uint32_t CurHealth; //0x008C - uint32_t CurMana; //0x0090 + // Current health points + uint32_t Health; + // Current mana points + uint32_t Mana; uint32_t RegionID; //0x0094 float PosX; //0x0098 float PosY; //0x009C diff --git a/vSRO-GameServer/vSRO-GameServer.vcxproj b/vSRO-GameServer/vSRO-GameServer.vcxproj index 1d31a5d..fc4eb30 100644 --- a/vSRO-GameServer/vSRO-GameServer.vcxproj +++ b/vSRO-GameServer/vSRO-GameServer.vcxproj @@ -36,6 +36,7 @@ + diff --git a/vSRO-ShardManager/AppManager.cpp b/vSRO-ShardManager/AppManager.cpp index 9cba73d..c3d0c21 100644 --- a/vSRO-ShardManager/AppManager.cpp +++ b/vSRO-ShardManager/AppManager.cpp @@ -4,13 +4,15 @@ #include #include // Utils -#include "Utils/Memory/Process.h" #include "Utils/IO/SimpleIni.h" +#include "Utils/Memory/Process.h" +#include "Utils/Memory/hook.h" #pragma warning(disable:4244) // Bitwise operations warnings +// ASM injection +#include "AsmEdition.h" /// Static stuffs bool AppManager::m_IsInitialized; - void AppManager::Initialize() { if (!m_IsInitialized) @@ -35,6 +37,7 @@ void AppManager::InitConfigFile() ini.SetLongValue("Event", "CTF_PARTICIPANS_MIN", 8, "; Minimum participants required to start Capture The Flag"); ini.SetLongValue("Event", "BA_PARTICIPANS_MIN", 8, "; Minimum participants required to start Battle Arena"); ini.SetBoolValue("Fix", "PARTY_MATCH_1HOUR_DC", true, "; Fix disconnect when party takes more than 1 hour on party match"); + ini.SetBoolValue("Fix", "GUILD_POINTS", true, "; Prevents negative values on guild points"); // App ini.SetBoolValue("App", "DEBUG_CONSOLE", true, "; Attach debug console"); // Save it @@ -58,7 +61,30 @@ void AppManager::InitDebugConsole() } void AppManager::InitHooks() { + std::cout << " * Initializing hooks..." << std::endl; + // Load file + CSimpleIniA ini; + ini.LoadFile("vSRO-GameServer.ini"); + + // Fix + if (ini.GetBoolValue("Fix", "GUILD_POINTS", true)) + { + printf(" - FIX_GUILD_POINTS\r\n"); + // Redirect code flow to DLL + if (placeHook(0x004364EE, addr_from_this(&AsmEdition::OnDonateGuildPoints))) + { + std::cout << " - OnDonateGuildPoints" << std::endl; + } + if (placeHook(0x00438B68, addr_from_this(&AsmEdition::OnDonateGuildPointsErrorCode))) + { + std::cout << " - OnDonateGuildPointsErrorCode" << addr_from_this(&AsmEdition::OnDonateGuildPointsErrorCode) << std::endl; + } + if (placeHook(0x0043A9F6, addr_from_this(&AsmEdition::OnDonateGuildPointsErrorMsg))) + { + std::cout << " - OnDonateGuildPointsErrorMsg" << addr_from_this(&AsmEdition::OnDonateGuildPointsErrorMsg) << std::endl; + } + } } void AppManager::InitPatchValues() { diff --git a/vSRO-ShardManager/AsmEdition.h b/vSRO-ShardManager/AsmEdition.h new file mode 100644 index 0000000..f976556 --- /dev/null +++ b/vSRO-ShardManager/AsmEdition.h @@ -0,0 +1,85 @@ +#include +#pragma once +// Direct ASM injection +namespace AsmEdition +{ + // Jump back to the code flow from donating guild points + static DWORD jmpAddr_DonateGP_01 = 0x004364F3; + static DWORD jmpAddr_DonateGP_02 = 0x004365D2; + // Handler to catch guild point increasing hook and edit directly with asm + static _declspec(naked) void OnDonateGuildPoints() + { + // Rebuild asm + __asm + { + mov ecx, dword ptr[eax + 0x3c] // rebuild + add ecx, edi // rebuild + cmp ecx, 0x7FFFFFFF // compare ecx with int.MaxValue + jbe _continue // go to _continue if ecx <= int.MaxValue + mov ecx, 0x7FFFFFFF // set ecx as int.MaxValue + mov ax, 0x1ABC // set ax with 0x1ABC as custom error code + jmp _skip_code // go to _skip_code + } + // Contine code flow + _continue: + __asm jmp jmpAddr_DonateGP_01; + _skip_code: + __asm jmp jmpAddr_DonateGP_02; + } + // Jump back to the code flow from error code on guild points + static DWORD jmpAddr_DonateErrCode_01 = 0x00438B99; + static DWORD jmpAddr_DonateErrCode_02 = 0x00438BA1; + static DWORD jmpAddr_DonateErrCode_03 = 0x00438B71; + // Handler to catch donating guild point error code hook and edit directly with asm + static _declspec(naked) void OnDonateGuildPointsErrorCode() + { + // Rebuild asm + __asm + { + movzx eax, ax // rebuild + cmp ax, 0x1 // rebuild + je _first_case // rebuild + cmp ax, 0x1ABC // compare ax with 0x1ABC as custom error code + je _skip_message // go to _skip_message + jmp _continue // go to _continue + } + // Contine code flow + _first_case: + __asm jmp jmpAddr_DonateErrCode_01; + _skip_message: + __asm jmp jmpAddr_DonateErrCode_02; + _continue: + __asm jmp jmpAddr_DonateErrCode_03; + } + // Jump back to the code flow from error msg on guild points + static DWORD jmpAddr_DonateErrMsg_01 = 0x0043AA60; + static DWORD jmpAddr_DonateErrMsg_02 = 0x0043AA03; + static DWORD jmpAddr_DonateErrMsg_03 = 0x0043AA26; + static DWORD callAddr_DonateErrMsg_01 = 0x00438010; + // Handler to catch donating guild point error msg hook and edit directly with asm + static _declspec(naked) void OnDonateGuildPointsErrorMsg() + { + // Rebuild asm + __asm + { + movzx eax, ax // rebuild + cmp ax, 0x1 // rebuild + mov dword ptr ss : [esp + 0x1C] , eax // rebuild + je _first_case // rebuild + cmp ax, 0x1ABC // compare ax with 0x1ABC as custom error code + jne _continue // go to _continue + mov eax, dword ptr ss : [esp + 0x10] + push eax + call callAddr_DonateErrMsg_01 + add esp, 0x4 + jmp _skip_stuffs + } + // Contine code flow + _first_case: + __asm jmp jmpAddr_DonateErrMsg_01; + _continue: + __asm jmp jmpAddr_DonateErrMsg_02; + _skip_stuffs: + __asm jmp jmpAddr_DonateErrMsg_03; + } +} \ No newline at end of file diff --git a/vSRO-ShardManager/Utils/Memory/hook.h b/vSRO-ShardManager/Utils/Memory/hook.h new file mode 100644 index 0000000..0ccb2f0 --- /dev/null +++ b/vSRO-ShardManager/Utils/Memory/hook.h @@ -0,0 +1,94 @@ +#pragma once +#include +#include + +template +int addr_from_this(T funptr) { + union { + int addr; + T ptr; + } myu; + + myu.ptr = funptr; + return myu.addr; +} + +template +void placeHook(int trampoline_location, T& target_location) +{ + placeHook(trampoline_location, reinterpret_cast(&target_location)); +} + +static bool placeHook(int trampoline_location, int target_location) +{ + + char jmp_inst[] = { (char)0xE9, 0x00, 0x00, 0x00, 0x00 }; + int distance; + DWORD dwProtect = 0; + + distance = target_location - trampoline_location - 5; + + // Write jump-distance to instruction + memcpy((jmp_inst + 1), &distance, 4); + + if (!VirtualProtect((LPVOID)trampoline_location, sizeof(jmp_inst), PAGE_EXECUTE_READWRITE, &dwProtect)) { + perror("Failed to unprotect memory\n"); + return false; + } + + memcpy((LPVOID)trampoline_location, jmp_inst, sizeof(jmp_inst)); + + VirtualProtect((LPVOID)trampoline_location, sizeof(jmp_inst), dwProtect, NULL); + return true; +} + +static bool replaceOffset(int trampoline_location, int target_location) +{ + + char inst_offset[] = { 0x00, 0x00, 0x00, 0x00 }; + int distance; + DWORD dwProtect = 0; + + int offset_location = trampoline_location + 1; + + distance = target_location - trampoline_location - 5; + + // Write jump-distance to instruction + memcpy(inst_offset, &distance, 4); + + if (!VirtualProtect((LPVOID)offset_location, sizeof(inst_offset), PAGE_EXECUTE_READWRITE, &dwProtect)) { + perror("Failed to unprotect memory\n"); + return false; + } + + memcpy((LPVOID)offset_location, inst_offset, sizeof(inst_offset)); + + VirtualProtect((LPVOID)offset_location, sizeof(inst_offset), dwProtect, NULL); + return true; +} + +static void replaceAddr(int addr, int value) +{ + DWORD dwProtect; + + if (!VirtualProtect((LPVOID)addr, sizeof(int), PAGE_EXECUTE_READWRITE, &dwProtect)) { + perror("Failed to unprotect memory\n"); + return; + } + + *((int*)addr) = value; + + VirtualProtect((LPVOID)addr, sizeof(int), dwProtect, NULL); +} + +static void vftableHook(unsigned int vftable_addr, int offset, int target_func) +{ + replaceAddr(vftable_addr + offset*sizeof(void*), target_func); +} + +// Calls a virtual function from table pointer address +template +T CallVirtual(void* vftable_addr, unsigned index) +{ + return (*static_cast(vftable_addr))[index]; +} \ No newline at end of file diff --git a/vSRO-ShardManager/vSRO-ShardManager.vcxproj b/vSRO-ShardManager/vSRO-ShardManager.vcxproj index 8993a83..77701d6 100644 --- a/vSRO-ShardManager/vSRO-ShardManager.vcxproj +++ b/vSRO-ShardManager/vSRO-ShardManager.vcxproj @@ -153,8 +153,10 @@ + + diff --git a/vSRO-ShardManager/vSRO-ShardManager.vcxproj.filters b/vSRO-ShardManager/vSRO-ShardManager.vcxproj.filters index 4ea5909..f24d766 100644 --- a/vSRO-ShardManager/vSRO-ShardManager.vcxproj.filters +++ b/vSRO-ShardManager/vSRO-ShardManager.vcxproj.filters @@ -27,6 +27,12 @@ Archivos de encabezado + + Archivos de encabezado + + + Archivos de encabezado +