diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index a098e30d5..ac849bd25 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -29,6 +29,7 @@ SET(common_sources extprofile.cpp faction.cpp file_verify.cpp + file_verify_manager.cpp guild_base.cpp guilds.cpp ipc_mutex.cpp @@ -132,6 +133,7 @@ SET(common_headers faction.h features.h file_verify.h + file_verify_manager.h fixed_memory_hash_set.h fixed_memory_variable_hash_set.h global_define.h diff --git a/common/clientversions.h b/common/clientversions.h index 675429eab..67eb30913 100644 --- a/common/clientversions.h +++ b/common/clientversions.h @@ -55,6 +55,8 @@ enum class ClientVersion MobMerc, MobBot, MobPet, + + MaxClientVersions }; #define CLIENT_VERSION_COUNT 12 diff --git a/common/file_verify.cpp b/common/file_verify.cpp index bd68eb44e..187ee7d28 100644 --- a/common/file_verify.cpp +++ b/common/file_verify.cpp @@ -1,9 +1,11 @@ #include "global_define.h" #include "types.h" +#include "clientversions.h" #include "file_verify.h" #include "crc32.h" #include +#pragma pack(1) struct VerifyFileStruct { uint32 crc; @@ -11,21 +13,33 @@ struct VerifyFileStruct uint32 offset[256]; uint32 data[256]; }; +#pragma pack() +EQEmu::FileVerify::FileVerify() { + buffer = nullptr; + size = 0; +} + +EQEmu::FileVerify::~FileVerify() { + safe_delete_array(buffer); +} + +bool EQEmu::FileVerify::Load(const char *file_name) { + safe_delete_array(buffer); + size = 0; -EQEmu::FileVerify::FileVerify(const char *file_name) { FILE *f = fopen(file_name, "rb"); if(!f) { buffer = nullptr; size = 0; - return; + return false; } fseek(f, 0U, SEEK_END); size = ftell(f); rewind(f); - char *buffer = new char[size]; + buffer = new char[size]; auto result = fread(buffer, 1, size, f); fclose(f); @@ -33,13 +47,11 @@ EQEmu::FileVerify::FileVerify(const char *file_name) { safe_delete_array(buffer); size = 0; } + + return true; } -EQEmu::FileVerify::~FileVerify() { - safe_delete_array(buffer); -} - -bool EQEmu::FileVerify::Verify(const char *data, uint32 size) { +bool EQEmu::FileVerify::Verify(const char *data, uint32 size, ClientVersion version) { if(!buffer) { return true; } @@ -49,11 +61,11 @@ bool EQEmu::FileVerify::Verify(const char *data, uint32 size) { } VerifyFileStruct *vs = (VerifyFileStruct*)data; - if(size != vs->file_size) { + if(this->size != vs->file_size) { return false; } - uint32 crc = CRC32::GenerateNoFlip((uchar*)buffer, size); + uint32 crc = CRC32::GenerateNoFlip((uchar*)buffer, this->size); if(vs->crc != crc) { return false; } @@ -61,7 +73,7 @@ bool EQEmu::FileVerify::Verify(const char *data, uint32 size) { for(int i = 0; i < 256; ++i) { uint32 offset = vs->offset[i] * 4; - if((offset - 4) > size) { + if((offset - 4) > this->size) { return false; } @@ -74,50 +86,3 @@ bool EQEmu::FileVerify::Verify(const char *data, uint32 size) { return true; } -//bool VerifyFile(const EQApplicationPacket *app, const char* filename) { -// FILE *f = fopen(filename, "rb"); -// if(!f) { -// return false; -// } -// -// VerifyFileStruct *vs = (VerifyFileStruct*)app->pBuffer; -// -// fseek(f, 0U, SEEK_END); -// auto size = ftell(f); -// rewind(f); -// -// if(size != vs->file_size || size < 1024) { -// fclose(f); -// return false; -// } -// -// char *buffer = new char[size]; -// std::unique_ptr data(buffer); -// auto result = fread(buffer, 1, size, f); -// fclose(f); -// -// if(result != size) { -// return false; -// } -// -// uint32 crc = CRC32::GenerateNoFlip((uchar*)buffer, size); -// -// Log.Out(Logs::General, Logs::Status, "CRC %u vs %u", crc, vs->crc); -// -// for(int i = 0; i < 256; ++i) { -// uint32 offset = vs->check[i] * 4; -// -// Log.Out(Logs::General, Logs::Status, "Data: %c%c%c%c vs %c%c%c%c", vs->data[i * 4], vs->data[i * 4 + 1], vs->data[i * 4 + 2], vs->data[i * 4 + 3], -// buffer[offset], buffer[offset + 1], buffer[offset + 2], buffer[offset + 3]); -// -// if(buffer[offset] != vs->data[i * 4] || -// buffer[offset + 1] != vs->data[i * 4 + 1] || -// buffer[offset + 2] != vs->data[i * 4 + 2] || -// buffer[offset + 3] != vs->data[i * 4 + 3]) -// { -// return false; -// } -// } -// -// return true; -//} \ No newline at end of file diff --git a/common/file_verify.h b/common/file_verify.h index 8646d3372..0157a0efd 100644 --- a/common/file_verify.h +++ b/common/file_verify.h @@ -6,10 +6,11 @@ namespace EQEmu class FileVerify { public: - FileVerify(const char *file_name); + FileVerify(); ~FileVerify(); - bool Verify(const char *data, uint32 size); + bool Load(const char *file_name); + bool Verify(const char *data, uint32 size, ClientVersion version); private: char *buffer; diff --git a/common/file_verify_manager.cpp b/common/file_verify_manager.cpp new file mode 100644 index 000000000..07dd5a75c --- /dev/null +++ b/common/file_verify_manager.cpp @@ -0,0 +1,87 @@ +#include "global_define.h" +#include "types.h" +#include "clientversions.h" +#include "eq_packet.h" +#include "file_verify_manager.h" +#include "string_util.h" +#include + +struct EQEmu::FileVerifyManager::impl { + std::unique_ptr spell_data; + std::unique_ptr skill_data; + std::unique_ptr base_data; + std::unique_ptr eqgames[(int)ClientVersion::MaxClientVersions]; +}; + +EQEmu::FileVerifyManager::FileVerifyManager() { + impl_ = new impl; + impl_->spell_data.reset(new FileVerify()); + impl_->spell_data->Load("verify/spells_us.txt"); + + impl_->skill_data.reset(new FileVerify()); + impl_->skill_data->Load("verify/SkillCaps.txt"); + + impl_->base_data.reset(new FileVerify()); + impl_->base_data->Load("verify/BaseData.txt"); + + for(int i = 0; i < (int)ClientVersion::MaxClientVersions; ++i) { + impl_->eqgames[i].reset(nullptr); + } +} + +EQEmu::FileVerifyManager::~FileVerifyManager() { + delete impl_; +} + +bool EQEmu::FileVerifyManager::VerifySpellFile(const EQApplicationPacket *app, ClientVersion version) { + if(!impl_->spell_data) { + impl_->spell_data.reset(new FileVerify()); + + if(!impl_->spell_data->Load("verify/spells_us.txt")) { + return true; + } + } + + return impl_->spell_data->Verify((char*)app->pBuffer, app->size, version); +} + +bool EQEmu::FileVerifyManager::VerifySkillFile(const EQApplicationPacket *app, ClientVersion version) { + if(!impl_->skill_data) { + impl_->skill_data.reset(new FileVerify()); + + if(!impl_->skill_data->Load("verify/SkillCaps.txt")) { + return true; + } + } + + return impl_->skill_data->Verify((char*)app->pBuffer, app->size, version); +} + +bool EQEmu::FileVerifyManager::VerifyBaseDataFile(const EQApplicationPacket *app, ClientVersion version) { + if(!impl_->base_data) { + impl_->base_data.reset(new FileVerify()); + + if(!impl_->base_data->Load("verify/BaseData.txt")) { + return true; + } + } + + return impl_->base_data->Verify((char*)app->pBuffer, app->size, version); +} + +bool EQEmu::FileVerifyManager::VerifyEQGame(const EQApplicationPacket *app, ClientVersion version) { + int v = (int)version; + if(v >= (int)ClientVersion::MaxClientVersions) { + return true; + } + + if(!impl_->eqgames[v]) { + impl_->eqgames[v].reset(new FileVerify()); + + if(!impl_->eqgames[v]->Load(StringFormat("verify/%s/eqgame.exe", ClientVersionName(version)).c_str())) { + return true; + } + } + + return impl_->eqgames[v]->Verify((char*)app->pBuffer, app->size, version); +} diff --git a/common/file_verify_manager.h b/common/file_verify_manager.h new file mode 100644 index 000000000..190878c56 --- /dev/null +++ b/common/file_verify_manager.h @@ -0,0 +1,33 @@ +#ifndef EQEMU_COMMON_FILE_VERIFY_MANAGER_H +#define EQEMU_COMMON_FILE_VERIFY_MANAGER_H + +#include "file_verify.h" + +namespace EQEmu +{ + class FileVerifyManager + { + public: + ~FileVerifyManager(); + + static FileVerifyManager& Get() + { + static FileVerifyManager instance; + return instance; + } + + bool VerifySpellFile(const EQApplicationPacket *app, ClientVersion version); + bool VerifySkillFile(const EQApplicationPacket *app, ClientVersion version); + bool VerifyBaseDataFile(const EQApplicationPacket *app, ClientVersion version); + bool VerifyEQGame(const EQApplicationPacket *app, ClientVersion version); + private: + FileVerifyManager(); + FileVerifyManager(FileVerifyManager const&); + void operator=(FileVerifyManager const&); + + struct impl; + impl *impl_; + }; +} + +#endif diff --git a/common/ruletypes.h b/common/ruletypes.h index 1919c7168..bc58e92ca 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -188,6 +188,7 @@ RULE_BOOL (World, IsGMPetitionWindowEnabled, false) RULE_INT (World, FVNoDropFlag, 0) // Sets the Firiona Vie settings on the client. If set to 2, the flag will be set for GMs only, allowing trading of no-drop items. RULE_BOOL (World, IPLimitDisconnectAll, false) RULE_INT (World, TellQueueSize, 20) +RULE_BOOL (World, AllowActionWithBadFiles, true) // if false then the client will be prevented from moving past world if their client files don't match the server's RULE_CATEGORY_END() RULE_CATEGORY(Zone) diff --git a/common/servertalk.h b/common/servertalk.h index af39bfaa4..3be5c7fbd 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -153,6 +153,8 @@ #define ServerOP_GetWorldTime 0x200C #define ServerOP_SyncWorldTime 0x200E +#define ServerOP_ClientFileStatus 0x2020 + #define ServerOP_LSZoneInfo 0x3001 #define ServerOP_LSZoneStart 0x3002 #define ServerOP_LSZoneBoot 0x3003 @@ -1263,6 +1265,22 @@ struct ServerRequestTellQueue_Struct { char name[64]; }; +struct ServerRequestClientFileStatus +{ + int zone_id; + int instance_id; + char name[64]; +}; + +struct ServerResponseClientFileStatus +{ + char name[64]; + bool spells; + bool skills; + bool base_data; + bool eqgame; +}; + #pragma pack() #endif diff --git a/utils/patches/patch_RoF.conf b/utils/patches/patch_RoF.conf index 6e3f0ab1a..8c11c63a2 100644 --- a/utils/patches/patch_RoF.conf +++ b/utils/patches/patch_RoF.conf @@ -22,10 +22,10 @@ OP_ExpansionInfo=0x711a OP_GuildsList=0x2d38 OP_EnterWorld=0x57c3 OP_PostEnterWorld=0x0c3d -OP_World_Client_CRC1=0x0044 -OP_World_Client_CRC2=0x26df -OP_SendSpellChecksum=0x0000 -OP_SendSkillCapsChecksum=0x0000 +OP_World_SpellFileCheck=0x0000 # appears to be removed in RoF, maybe 0x0044? I don't have a client to check +OP_World_SkillFileCheck=0x0000 +OP_World_BaseDataFileCheck=0x0000 +OP_World_ExeFileCheck=0x26df # Character Select Related: OP_SendMaxCharacters=0x5349 diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index 49f5f28b3..105601496 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -22,10 +22,10 @@ OP_ExpansionInfo=0x590d OP_GuildsList=0x507a OP_EnterWorld=0x578f OP_PostEnterWorld=0x6259 -OP_World_Client_CRC1=0x12cc -OP_World_Client_CRC2=0x0f13 -OP_SendSpellChecksum=0x0000 -OP_SendSkillCapsChecksum=0x0000 +OP_World_SpellFileCheck=0x0000 # appears to be removed in RoF +OP_World_SkillFileCheck=0x4b8d +OP_World_BaseDataFileCheck=0x298d +OP_World_ExeFileCheck=0x0f13 # Character Select Related: OP_SendMaxCharacters=0x5475 diff --git a/utils/patches/patch_SoD.conf b/utils/patches/patch_SoD.conf index 2f01ef89b..ec265da01 100644 --- a/utils/patches/patch_SoD.conf +++ b/utils/patches/patch_SoD.conf @@ -26,10 +26,10 @@ OP_ExpansionInfo=0x7519 # C OP_GuildsList=0x5b0b # C OP_EnterWorld=0x1c20 # C OP_PostEnterWorld=0x7c94 # C -OP_World_Client_CRC1=0x0ca5 # C -OP_World_Client_CRC2=0x1cb3 # C -OP_SendSpellChecksum=0x5bad # C -OP_SendSkillCapsChecksum=0x5d24 # C +OP_World_SpellFileCheck=0x0ca5 # C +OP_World_SkillFileCheck=0x5bad # C +OP_World_BaseDataFileCheck=0x5d24 # C +OP_World_ExeFileCheck=0x1cb3 # C # Character Select Related: OP_DeleteCharacter=0x0254 # C diff --git a/utils/patches/patch_SoF.conf b/utils/patches/patch_SoF.conf index 9c58b5684..b860b1ebe 100644 --- a/utils/patches/patch_SoF.conf +++ b/utils/patches/patch_SoF.conf @@ -23,10 +23,10 @@ OP_ExpansionInfo=0x0A1B #SEQ 12/04/08 OP_GuildsList=0x04FB #SEQ 12/04/08 OP_EnterWorld=0x1340 #SEQ 12/04/08 OP_PostEnterWorld=0x1AEE #SEQ 12/04/08 -OP_World_Client_CRC1=0x7A9E #SEQ 12/04/08 -OP_World_Client_CRC2=0x3795 #SEQ 12/04/08 -OP_SendSpellChecksum=0x22CF #SEQ 12/04/08 -OP_SendSkillCapsChecksum=0x43BA #SEQ 12/04/08 +OP_World_SpellFileCheck=0x7A9E #these are guessed but should be right I hope, who even uses SoF though +OP_World_ExeFileCheck=0x3795 +OP_World_BaseDataFileCheck=0x22CF +OP_SendSkillCapsChecksum=0x43BA #Character Select Related: OP_DeleteCharacter=0x789F #SEQ 12/04/08 diff --git a/utils/patches/patch_Titanium.conf b/utils/patches/patch_Titanium.conf index 99ef322a0..24ba4e40a 100644 --- a/utils/patches/patch_Titanium.conf +++ b/utils/patches/patch_Titanium.conf @@ -27,8 +27,6 @@ OP_GuildsList=0x6957 #same as zone guild list afaik OP_ApproveName=0x3ea6 # EQEmu 11/28/05 OP_EnterWorld=0x7cba # ShowEQ 10/27/05 OP_PostEnterWorld=0x52A4 # EQEmu 06/29/05 -OP_World_Client_CRC1=0x5072 # ShowEQ 10/27/05 -OP_World_Client_CRC2=0x5b18 # ShowEQ 10/27/05 OP_SetChatServer=0x00d7 # ShowEQ 10/27/05 OP_SetChatServer2=0x6536 # ShowEQ 10/27/05 OP_ZoneServerInfo=0x61b6 # ShowEQ 10/27/05 @@ -42,6 +40,10 @@ OP_WorldLoginFailed=0x8DA7 # world->client. reject. OP_WorldLogout=0x7718 # client->world OP_WorldLevelTooHigh=0x583b # world->client. Cancels zone in. OP_CharInacessable=0x436A # world->client. Cancels zone in. +OP_World_SpellFileCheck=0x5072 +OP_World_SkillFileCheck=0x0000 +OP_World_BaseDataFileCheck=0x0000 +OP_World_ExeFileCheck=0x5b18 #Zone in opcodes OP_ZoneEntry=0x7213 # ShowEQ 10/27/05 diff --git a/utils/patches/patch_UF.conf b/utils/patches/patch_UF.conf index 6f94c92db..2d8fdab2a 100644 --- a/utils/patches/patch_UF.conf +++ b/utils/patches/patch_UF.conf @@ -26,10 +26,10 @@ OP_ExpansionInfo=0x7e4d # C OP_GuildsList=0x5b0b # C OP_EnterWorld=0x51b9 # C OP_PostEnterWorld=0x5d32 # C -OP_World_Client_CRC1=0x3a18 # C -OP_World_Client_CRC2=0x3e50 # C -OP_SendSpellChecksum=0x46d3 # C -OP_SendSkillCapsChecksum=0x040b # C +OP_World_SpellFileCheck=0x3a18 +OP_World_SkillFileCheck=0x46d3 +OP_World_BaseDataFileCheck=0x040b +OP_World_ExeFileCheck=0x3e50 # Character Select Related: OP_DeleteCharacter=0x5ca5 # C diff --git a/world/client.cpp b/world/client.cpp index 2597b4cbe..7b701908d 100644 --- a/world/client.cpp +++ b/world/client.cpp @@ -17,7 +17,7 @@ #include "../common/clientversions.h" #include "../common/random.h" #include "../common/shareddb.h" -#include "../common/file_verify.h" +#include "../common/file_verify_manager.h" #include "client.h" #include "worlddb.h" @@ -61,11 +61,6 @@ std::vector character_create_allocations; std::vector character_create_race_class_combos; -EQEmu::FileVerify spell_verify("verify/spells_us.txt"); -EQEmu::FileVerify skills_verify("verify/SkillCaps.txt"); -EQEmu::FileVerify basedata_verify("verify/BaseData.txt"); -EQEmu::FileVerify eqgame_verify("verify/eqgame.exe"); - extern ZSList zoneserver_list; extern LoginServerList loginserverlist; extern ClientList client_list; @@ -96,7 +91,7 @@ Client::Client(EQStreamInterface* ieqs) m_ClientVersion = eqs->GetClientVersion(); m_ClientVersionBit = ClientBitFromVersion(m_ClientVersion); - + numclients++; } @@ -611,6 +606,10 @@ bool Client::HandleGenerateRandomNamePacket(const EQApplicationPacket *app) { } bool Client::HandleCharacterCreateRequestPacket(const EQApplicationPacket *app) { + if(!CanTakeAction()) { + return true; + } + // New OpCode in SoF uint32 allocs = character_create_allocations.size(); uint32 combos = character_create_race_class_combos.size(); @@ -658,6 +657,10 @@ bool Client::HandleCharacterCreateRequestPacket(const EQApplicationPacket *app) } bool Client::HandleCharacterCreatePacket(const EQApplicationPacket *app) { + if(!CanTakeAction()) { + return true; + } + if (GetAccountID() == 0) { Log.Out(Logs::Detail, Logs::World_Server,"Account ID not set; unable to create character."); return false; @@ -689,6 +692,11 @@ bool Client::HandleCharacterCreatePacket(const EQApplicationPacket *app) { } bool Client::HandleEnterWorldPacket(const EQApplicationPacket *app) { + if(!CanTakeAction()) { + ZoneUnavail(); + return true; + } + if (GetAccountID() == 0) { Log.Out(Logs::Detail, Logs::World_Server,"Enter world with no logged in account"); eqs->Close(); @@ -908,6 +916,9 @@ bool Client::HandleEnterWorldPacket(const EQApplicationPacket *app) { } bool Client::HandleDeleteCharacterPacket(const EQApplicationPacket *app) { + if(!CanTakeAction()) { + return true; + } uint32 char_acct_id = database.GetAccountIDByChar((char*)app->pBuffer); if(char_acct_id == GetAccountID()) { @@ -960,49 +971,66 @@ bool Client::HandlePacket(const EQApplicationPacket *app) { { case OP_World_SpellFileCheck: { - if(spell_verify.Verify((char*)app->pBuffer, app->Size())) { - Log.Out(Logs::General, Logs::Status, "Spell file verified."); + if(EQEmu::FileVerifyManager::Get().VerifySpellFile(app, eqs->GetClientVersion())) { + if(cle) { + cle->SetSpellFileVerified(true); + } } else { - Log.Out(Logs::General, Logs::Status, "Spell file not verified."); + if(cle) { + cle->SetSpellFileVerified(false); + } } StartInTutorial = false; return true; } - + case OP_World_SkillFileCheck: { - if(skills_verify.Verify((char*)app->pBuffer, app->Size())) { - Log.Out(Logs::General, Logs::Status, "Skill file verified."); + if(EQEmu::FileVerifyManager::Get().VerifySkillFile(app, eqs->GetClientVersion())) { + if(cle) { + cle->SetSkillFileVerified(true); + } } else { - Log.Out(Logs::General, Logs::Status, "Skill file not verified."); + if(cle) { + cle->SetSkillFileVerified(false); + } } StartInTutorial = false; return true; } - + case OP_World_BaseDataFileCheck: { - if(basedata_verify.Verify((char*)app->pBuffer, app->Size())) { - Log.Out(Logs::General, Logs::Status, "BaseData file verified."); + if(EQEmu::FileVerifyManager::Get().VerifyBaseDataFile(app, eqs->GetClientVersion())) { + if(cle) { + cle->SetBaseDataFileVerified(true); + } } else { - Log.Out(Logs::General, Logs::Status, "BaseData file not verified."); + if(cle) { + cle->SetBaseDataFileVerified(false); + } } - + StartInTutorial = false; return true; } - + case OP_World_ExeFileCheck: { - if(eqgame_verify.Verify((char*)app->pBuffer, app->Size())) { - Log.Out(Logs::General, Logs::Status, "eqgame.exe verified."); + if(EQEmu::FileVerifyManager::Get().VerifyEQGame(app, eqs->GetClientVersion())) { + if(cle) { + cle->SetEQGameVerified(true); + } } else { - Log.Out(Logs::General, Logs::Status, "eqgame.exe not verified."); + if(cle) { + cle->SetEQGameVerified(false); + } } - + StartInTutorial = false; return true; } + case OP_SendLoginInfo: { return HandleSendLoginInfoPacket(app); @@ -1048,21 +1076,15 @@ bool Client::HandlePacket(const EQApplicationPacket *app) { case OP_ApproveWorld: case OP_WorldClientReady: { - //char buffer[64]; - //app->build_header_dump(buffer); - //Log.Out(Logs::General, Logs::Status, "%s %s", buffer, DumpPacketToString(app).c_str()); - - // Essentially we are just 'eating' these packets, indicating - // they are handled. return true; } default: { Log.Out(Logs::Detail, Logs::World_Server,"Received unknown EQApplicationPacket"); - //char buffer[64]; - //app->build_header_dump(buffer); - //Log.Out(Logs::General, Logs::Status, "%s %s", buffer, DumpPacketToString(app).c_str()); + char buffer[64]; + app->build_header_dump(buffer); + Log.Out(Logs::General, Logs::Status, "%s %s", buffer, DumpPacketToString(app).c_str()); return true; } } @@ -2035,3 +2057,14 @@ void Client::SetClassLanguages(PlayerProfile_Struct *pp) } } +bool Client::CanTakeAction() { + if(RuleB(World, AllowActionWithBadFiles)) { + return true; + } + + if(!cle) { + return true; + } + + return cle->GetBaseDataFileVerified() && cle->GetEQGameVerified() && cle->GetSkillFileVerified() && cle->GetSpellFileVerified(); +} diff --git a/world/client.h b/world/client.h index d7a42ab28..1509bf928 100644 --- a/world/client.h +++ b/world/client.h @@ -100,6 +100,8 @@ private: bool seencharsel; bool realfirstlogin; + bool CanTakeAction(); + bool HandlePacket(const EQApplicationPacket *app); bool HandleNameApprovalPacket(const EQApplicationPacket *app); bool HandleSendLoginInfoPacket(const EQApplicationPacket *app); diff --git a/world/cliententry.cpp b/world/cliententry.cpp index 5e896dca0..8965d8a87 100644 --- a/world/cliententry.cpp +++ b/world/cliententry.cpp @@ -46,6 +46,11 @@ ClientListEntry::ClientListEntry(uint32 in_id, uint32 iLSID, const char* iLoginN plocal=(local==1); pinstance = 0; + + spell_file_verified = true; + skill_file_verified = true; + base_data_file_verified = true; + eqgame_file_verified = true; } ClientListEntry::ClientListEntry(uint32 in_id, uint32 iAccID, const char* iAccName, MD5& iMD5Pass, int16 iAdmin) @@ -63,6 +68,11 @@ ClientListEntry::ClientListEntry(uint32 in_id, uint32 iAccID, const char* iAccNa padmin = iAdmin; pinstance = 0; + + spell_file_verified = true; + skill_file_verified = true; + base_data_file_verified = true; + eqgame_file_verified = true; } ClientListEntry::ClientListEntry(uint32 in_id, ZoneServer* iZS, ServerClientList_Struct* scl, int8 iOnline) @@ -86,6 +96,11 @@ ClientListEntry::ClientListEntry(uint32 in_id, ZoneServer* iZS, ServerClientList Update(iZS, scl, iOnline); else SetOnline(iOnline); + + spell_file_verified = true; + skill_file_verified = true; + base_data_file_verified = true; + eqgame_file_verified = true; } ClientListEntry::~ClientListEntry() { diff --git a/world/cliententry.h b/world/cliententry.h index cb096950c..25390fcc1 100644 --- a/world/cliententry.h +++ b/world/cliententry.h @@ -87,6 +87,16 @@ public: inline void PushToTellQueue(ServerChannelMessage_Struct *scm) { tell_queue.push_back(scm); } void ProcessTellQueue(); + bool GetSpellFileVerified() { return spell_file_verified; } + bool GetSkillFileVerified() { return skill_file_verified; } + bool GetBaseDataFileVerified() { return base_data_file_verified; } + bool GetEQGameVerified() { return eqgame_file_verified; } + + void SetSpellFileVerified(bool v) { spell_file_verified = v; } + void SetSkillFileVerified(bool v) { skill_file_verified = v; } + void SetBaseDataFileVerified(bool v) { base_data_file_verified = v; } + void SetEQGameVerified(bool v) { eqgame_file_verified = v; } + private: void ClearVars(bool iAll = false); @@ -128,6 +138,11 @@ private: bool pLFGMatchFilter; char pLFGComments[64]; + bool spell_file_verified; + bool skill_file_verified; + bool base_data_file_verified; + bool eqgame_file_verified; + // Tell Queue -- really a vector :D std::vector tell_queue; }; diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 17cac7f62..32eb0a7e6 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1311,6 +1311,25 @@ bool ZoneServer::Process() { cle->ProcessTellQueue(); break; } + case ServerOP_ClientFileStatus: + { + ServerRequestClientFileStatus *req = (ServerRequestClientFileStatus*) pack->pBuffer; + ClientListEntry *cle = client_list.FindCharacter(req->name); + + if(cle) { + ServerPacket pack(ServerOP_ClientFileStatus, sizeof(ServerResponseClientFileStatus)); + ServerResponseClientFileStatus *resp = (ServerResponseClientFileStatus*)pack.pBuffer; + + strcpy(resp->name, req->name); + resp->spells = cle->GetSpellFileVerified(); + resp->skills = cle->GetSkillFileVerified(); + resp->base_data = cle->GetBaseDataFileVerified(); + resp->eqgame = cle->GetEQGameVerified(); + zoneserver_list.SendPacket(req->zone_id, req->instance_id, &pack); + } + + break; + } default: { Log.Out(Logs::Detail, Logs::World_Server, "Unknown ServerOPcode from zone 0x%04x, size %d", pack->opcode, pack->size); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 2ae2babac..02a7a1d33 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -787,6 +787,13 @@ void Client::CompleteConnect() std::string event_desc = StringFormat("Connect :: Logged into zoneid:%i instid:%i", this->GetZoneID(), this->GetInstanceID()); QServ->PlayerLogEvent(Player_Log_Connect_State, this->CharacterID(), event_desc); } + + ServerPacket pack(ServerOP_ClientFileStatus, sizeof(ServerRequestClientFileStatus)); + ServerRequestClientFileStatus *req = (ServerRequestClientFileStatus*)pack.pBuffer; + req->zone_id = zone->GetZoneID(); + req->instance_id = zone->GetInstanceID(); + strn0cpy(req->name, GetName(), 64); + worldserver.SendPacket(&pack); } if (zone) { diff --git a/zone/embparser.cpp b/zone/embparser.cpp index d571dce24..003f4a598 100644 --- a/zone/embparser.cpp +++ b/zone/embparser.cpp @@ -113,7 +113,8 @@ const char *QuestEventSubroutines[_LargestEventID] = { "EVENT_LEAVE_AREA", "EVENT_RESPAWN", "EVENT_DEATH_COMPLETE", - "EVENT_UNHANDLED_OPCODE" + "EVENT_UNHANDLED_OPCODE", + "EVENT_CLIENT_FILE_STATUS" }; PerlembParser::PerlembParser() : perl(nullptr) { diff --git a/zone/event_codes.h b/zone/event_codes.h index f793efaf6..da386e335 100644 --- a/zone/event_codes.h +++ b/zone/event_codes.h @@ -82,6 +82,7 @@ typedef enum { EVENT_RESPAWN, EVENT_DEATH_COMPLETE, EVENT_UNHANDLED_OPCODE, + EVENT_CLIENT_FILE_STATUS, _LargestEventID } QuestEventID; diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index bf65fea7c..44f2e4382 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -1713,7 +1713,8 @@ luabind::scope lua_register_events() { luabind::value("enter_area", static_cast(EVENT_ENTER_AREA)), luabind::value("leave_area", static_cast(EVENT_LEAVE_AREA)), luabind::value("death_complete", static_cast(EVENT_DEATH_COMPLETE)), - luabind::value("unhandled_opcode", static_cast(EVENT_UNHANDLED_OPCODE)) + luabind::value("unhandled_opcode", static_cast(EVENT_UNHANDLED_OPCODE)), + luabind::value("client_file_status", static_cast(EVENT_CLIENT_FILE_STATUS)) ]; } diff --git a/zone/lua_packet.cpp b/zone/lua_packet.cpp index 177b56444..45f236630 100644 --- a/zone/lua_packet.cpp +++ b/zone/lua_packet.cpp @@ -843,7 +843,11 @@ luabind::scope lua_register_packet_opcodes() { luabind::value("OpenContainer", static_cast(OP_OpenContainer)), luabind::value("Marquee", static_cast(OP_Marquee)), luabind::value("ClientTimeStamp", static_cast(OP_ClientTimeStamp)), - luabind::value("GuildPromote", static_cast(OP_GuildPromote)) + luabind::value("GuildPromote", static_cast(OP_GuildPromote)), + luabind::value("World_SpellFileCheck", static_cast(OP_World_SpellFileCheck)), + luabind::value("World_SkillFileCheck", static_cast(OP_World_SkillFileCheck)), + luabind::value("World_BaseDataFileCheck", static_cast(OP_World_BaseDataFileCheck)), + luabind::value("World_ExeFileCheck", static_cast(OP_World_ExeFileCheck)) ]; } diff --git a/zone/lua_parser.cpp b/zone/lua_parser.cpp index 24f25948e..b87adef2c 100644 --- a/zone/lua_parser.cpp +++ b/zone/lua_parser.cpp @@ -116,7 +116,8 @@ const char *LuaEvents[_LargestEventID] = { "event_leave_area", "event_respawn", "event_death_complete", - "event_unhandled_opcode" + "event_unhandled_opcode", + "event_client_file_status" }; extern Zone *zone; @@ -198,6 +199,7 @@ LuaParser::LuaParser() { PlayerArgumentDispatch[EVENT_LEAVE_AREA] = handle_player_area; PlayerArgumentDispatch[EVENT_RESPAWN] = handle_player_respawn; PlayerArgumentDispatch[EVENT_UNHANDLED_OPCODE] = handle_player_packet; + PlayerArgumentDispatch[EVENT_CLIENT_FILE_STATUS] = handle_player_file_status; ItemArgumentDispatch[EVENT_ITEM_CLICK] = handle_item_click; ItemArgumentDispatch[EVENT_ITEM_CLICK_CAST] = handle_item_click; diff --git a/zone/lua_parser_events.cpp b/zone/lua_parser_events.cpp index 840bcb40d..36f02b312 100644 --- a/zone/lua_parser_events.cpp +++ b/zone/lua_parser_events.cpp @@ -11,6 +11,7 @@ #include "masterentity.h" #include "../common/seperator.h" #include "../common/misc_functions.h" +#include "../common/string_util.h" #include "lua_item.h" #include "lua_iteminst.h" #include "lua_entity.h" @@ -501,6 +502,30 @@ void handle_player_packet(QuestInterface *parse, lua_State* L, Client* client, s lua_setfield(L, -2, "connecting"); } +void handle_player_file_status(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, + std::vector *extra_pointers) { + auto values = SplitString(data, ' '); + if(values.size() >= 4) { + auto spells = atoi(values[0].c_str()); + auto skills = atoi(values[1].c_str()); + auto basedata = atoi(values[2].c_str()); + auto eqgame = atoi(values[3].c_str()); + + lua_pushboolean(L, spells == 1 ? true : false); + lua_setfield(L, -2, "spell_file_status"); + + lua_pushboolean(L, skills == 1 ? true : false); + lua_setfield(L, -2, "skills_file_status"); + + lua_pushboolean(L, basedata == 1 ? true : false); + lua_setfield(L, -2, "base_data_file_status"); + + lua_pushboolean(L, eqgame == 1 ? true : false); + lua_setfield(L, -2, "eqgame_file_status"); + } +} + + void handle_player_null(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, std::vector *extra_pointers) { } diff --git a/zone/lua_parser_events.h b/zone/lua_parser_events.h index 0a2ab5ad9..36a66be79 100644 --- a/zone/lua_parser_events.h +++ b/zone/lua_parser_events.h @@ -93,6 +93,8 @@ void handle_player_respawn(QuestInterface *parse, lua_State* L, Client* client, std::vector *extra_pointers); void handle_player_packet(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, std::vector *extra_pointers); +void handle_player_file_status(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, + std::vector *extra_pointers); void handle_player_null(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, std::vector *extra_pointers); diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 517106aca..0a89bbe6e 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -49,7 +49,7 @@ #include "worldserver.h" #include "zone.h" #include "zone_config.h" - +#include "quest_parser_collection.h" extern EntityList entity_list; extern Zone* zone; @@ -1841,6 +1841,20 @@ void WorldServer::Process() { } break; } + case ServerOP_ClientFileStatus: + { + ServerResponseClientFileStatus *resp = (ServerResponseClientFileStatus*)pack->pBuffer; + Client* client = entity_list.GetClientByName(resp->name); + if(client) { + parse->EventPlayer(EVENT_CLIENT_FILE_STATUS, client, StringFormat("%d %d %d %d", + resp->spells ? 1 : 0, + resp->skills ? 1 : 0, + resp->base_data ? 1 : 0, + resp->eqgame ? 1 : 0).c_str(), 0); + } + break; + } + default: { std::cout << " Unknown ZSopcode:" << (int)pack->opcode; std::cout << " size:" << pack->size << std::endl;