diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index 131a6182..7c98a367 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -81,6 +81,7 @@ namespace Components Loader::Register(new Gametypes()); Loader::Register(new Materials()); Loader::Register(new Threading()); + Loader::Register(new CardTitles()); Loader::Register(new FileSystem()); Loader::Register(new ModelSurfs()); Loader::Register(new PlayerName()); diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index d41085d2..d09d22f1 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -91,6 +91,7 @@ namespace Components #include "Modules/Materials.hpp" #include "Modules/Singleton.hpp" #include "Modules/Threading.hpp" +#include "Modules/CardTitles.hpp" #include "Modules/FileSystem.hpp" #include "Modules/ModelSurfs.hpp" #include "Modules/PlayerName.hpp" diff --git a/src/Components/Modules/CardTitles.cpp b/src/Components/Modules/CardTitles.cpp new file mode 100644 index 00000000..36ce2f96 --- /dev/null +++ b/src/Components/Modules/CardTitles.cpp @@ -0,0 +1,188 @@ +#include "STDInclude.hpp" + +namespace Components +{ + std::vector < std::string > CardTitles::CustomTitles; + Game::dvar_t* CardTitles::CustomTitleDvar; + + CClient* CardTitles::GetClientByIndex(std::uint32_t index) + { + return reinterpret_cast(0x8E77B0 + (sizeof CClient * index)); + } + + std::int32_t CardTitles::GetPlayerCardClientInfo(std::int32_t lookupResult, playercarddata_s* data) + { + std::int32_t returnResult = lookupResult; + + CClient* c; + std::string username = Dvar::Var("name").get(); + + if (data->name == username) + { + returnResult += 0xFE000000; + } + else + { + for (auto clientNum = 0; clientNum < 18; clientNum++) + { + c = GetClientByIndex(clientNum); + + if (c != nullptr) + { + if (!strcmp(data->name, c->Name)) + { + // Since a 4 byte integer is overkill for a row num: We can use it to store the customprefix + clientNum and use a 2 byte integer for the row number + returnResult += 0xFF000000; + returnResult += clientNum * 0x10000; + break; + } + } + } + } + + return returnResult; + } + void __declspec(naked) CardTitles::GetPlayerCardClientInfoStub() + { + __asm + { + push esi; + push eax; + call GetPlayerCardClientInfo; + add esp, 0x08; + + pop esi; + pop ebp; + mov[ebx + 0x4], eax; + pop ebx; + + push 0x62EB2C; + retn; + } + } + + void __declspec(naked) CardTitles::LocalizationSkipHookStub() + { + __asm + { + cmp byte ptr[edi + 0x01], 0xFF; // Check if skip prefix exists (edi + 0x00 = @) + jne back; + add edi, 0x02; // Ignore the 0x40 and 0xFF prefix (Localize and Skip prefix) + jmp jumpback; + + back: + add edi, 0x01; + push edi; + + mov eax, 0x4F1700; + call eax; + + add esp, 0x04; + mov edi, eax; + + jumpback: + push 0x63A2E3; + retn; + } + } + + void __declspec(naked) CardTitles::TableLookupByRowHookStub() + { + static tablelookuprequest_s* currentLookupRequest; + static tablelookupresult_s* currentLookupResult; + static std::int32_t prefix; + static std::int32_t data; + static const char* title; + + __asm + { + mov currentLookupRequest, esi; + mov currentLookupResult, ebx; + } + + prefix = (currentLookupRequest->tableRow >> (8 * 3)) & 0xff; + data = (currentLookupRequest->tableRow >> (8 * 2)) & 0xff; + + //So we don't accidentally mess up other tables that might have a ridiculous amount of rows. Who knows. + if (!strcmp(currentLookupRequest->tablename, "mp/cardTitleTable.csv")) + { + if (prefix != 0x00) + { + // Column 1 = CardTitle + if (currentLookupRequest->tableColumn == 1) + { + if (prefix == 0xFE) + { + // 0xFF in front of the title to skip localization. Or else it will wait for a couple of seconds for the asset of type localize + if ((BYTE)*CustomTitleDvar->current.string) + { + title = Utils::String::VA("\xFF%s", CustomTitleDvar->current.string); + currentLookupResult->result = title; + currentLookupResult->dunno = 2; // Seems to be nessecary. Don't ask me why. + + __asm + { + mov eax, title; + pop ecx; + mov ecx, currentLookupRequest; + mov ecx, DWORD ptr[ecx + 0x04]; + retn; + } + } + } + else if (prefix == 0xFF) + { + if (!CustomTitles[data].empty()) + { + title = Utils::String::VA("\xFF%s", CustomTitles[data].data()); + currentLookupResult->result = title; + currentLookupResult->dunno = 2; + + __asm + { + mov eax, title; + pop ecx; + mov ecx, currentLookupRequest; + mov ecx, DWORD ptr[ecx + 0x04]; + retn; + } + } + } + } + // If the title was changed it already returned at this point so... + // Remove prefix and data to make being readable to the normal lookuprequest + currentLookupRequest->tableRow = static_cast(*(reinterpret_cast(¤tLookupRequest->tableRow))); + } + } + + // Continue with the normal lookup request because we did not use our custom result + __asm + { + mov eax, [esi + 0x50]; + cmp eax, 0x3; + + push 0x62DCC7; + retn; + } + } + + CardTitles::CardTitles() + { + Dvar::OnInit([]() { + CardTitles::CustomTitleDvar = Game::Dvar_RegisterString("cardtitle", "", Game::dvar_flag::DVAR_FLAG_SAVED, "Custom card title"); + }); + + CardTitles::CustomTitles.resize(18); + + Utils::Hook(0x62EB26, GetPlayerCardClientInfoStub).install()->quick(); + Utils::Hook(0x63A2D5, LocalizationSkipHookStub).install()->quick(); + Utils::Hook(0x62DCC1, TableLookupByRowHookStub).install()->quick(); + + Utils::Hook::Nop(0x62DCC6, 1); + } + + CardTitles::~CardTitles() + { + + } +} \ No newline at end of file diff --git a/src/Components/Modules/CardTitles.hpp b/src/Components/Modules/CardTitles.hpp new file mode 100644 index 00000000..97b13602 --- /dev/null +++ b/src/Components/Modules/CardTitles.hpp @@ -0,0 +1,76 @@ +#pragma once + +namespace Components +{ + struct tablelookuprequest_s + { + std::uint8_t padding[4]; + char* tablename; + std::uint8_t padding2[4]; + std::int32_t tableRow; + std::uint8_t padding3[4]; + std::int32_t tableColumn; + }; + struct tablelookupresult_s + { + std::uint32_t dunno; + const char* result; + }; + struct playercarddata_s + { + std::uint32_t padding; + std::uint32_t playercardNumber; + std::uint32_t unknown; + std::uint32_t unknown2; + std::uint32_t level; //Level is counted from 0 -> Value 69 is Level 70 + std::uint32_t prestige; + std::uint32_t padding2; + char name[40]; + }; + + struct CClient + { + std::uint32_t IsValid; // 0x0000 + std::uint32_t IsValid2; // 0x0004 + std::uint32_t ClientNumber; // 0x0008 + char Name[16]; // 0x000C + std::uint32_t Team; // 0x001C + std::uint32_t Team2; // 0x0020 + std::uint32_t Rank; // 0x0024 (rank - 1) + std::uint32_t Prestige; // 0x0028 + std::uint32_t Perks; // 0x002C + std::uint32_t Kills; // 0x0030 + std::uint32_t Score; // 0x0034 + std::uint8_t _0x0038[968]; + std::uint32_t ViewAngles; // 0x0400 + std::uint8_t _0x040C[136]; + std::uint32_t IsShooting; // 0x0494 + std::uint8_t _0x0498[4]; + std::uint32_t IsZoomed; // 0x049C + std::uint8_t _0x04A0[68]; + std::uint32_t weaponID; // 0x04E4 + std::uint8_t _0x04E8[24]; + std::uint32_t weaponID2; // 0x0500 + std::uint8_t _0x0504[40]; + std::uint8_t _padding[8]; + }; + + class CardTitles : public Component + { + public: + static Game::dvar_t* CustomTitleDvar; + + CardTitles(); + ~CardTitles(); + + private: + static std::vector < std::string > CustomTitles; + + static CClient * GetClientByIndex(std::uint32_t index); + static std::int32_t GetPlayerCardClientInfo(std::int32_t lookupResult, playercarddata_s * data); + static void GetPlayerCardClientInfoStub(); + static void LocalizationSkipHookStub(); + static void TableLookupByRowHookStub(); + + }; +}