diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index f847957db..a098e30d5 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -28,6 +28,7 @@ SET(common_sources eqtime.cpp extprofile.cpp faction.cpp + file_verify.cpp guild_base.cpp guilds.cpp ipc_mutex.cpp @@ -130,6 +131,7 @@ SET(common_headers extprofile.h faction.h features.h + file_verify.h fixed_memory_hash_set.h fixed_memory_variable_hash_set.h global_define.h diff --git a/common/emu_oplist.h b/common/emu_oplist.h index e4793826e..0468ff704 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -292,8 +292,6 @@ N(OP_LockoutTimerInfo), N(OP_Login), N(OP_LoginAccepted), N(OP_LoginComplete), -N(OP_LoginUnknown1), -N(OP_LoginUnknown2), N(OP_Logout), N(OP_LogoutReply), N(OP_LogServer), @@ -527,8 +525,10 @@ N(OP_Weather), N(OP_Weblink), N(OP_WhoAllRequest), N(OP_WhoAllResponse), -N(OP_World_Client_CRC1), -N(OP_World_Client_CRC2), +N(OP_World_SpellFileCheck), +N(OP_World_SkillFileCheck), +N(OP_World_BaseDataFileCheck), +N(OP_World_ExeFileCheck), N(OP_WorldClientReady), N(OP_WorldComplete), N(OP_WorldLogout), diff --git a/common/file_verify.cpp b/common/file_verify.cpp new file mode 100644 index 000000000..bd68eb44e --- /dev/null +++ b/common/file_verify.cpp @@ -0,0 +1,123 @@ +#include "global_define.h" +#include "types.h" +#include "file_verify.h" +#include "crc32.h" +#include + +struct VerifyFileStruct +{ + uint32 crc; + uint32 file_size; + uint32 offset[256]; + uint32 data[256]; +}; + + +EQEmu::FileVerify::FileVerify(const char *file_name) { + FILE *f = fopen(file_name, "rb"); + if(!f) { + buffer = nullptr; + size = 0; + return; + } + + fseek(f, 0U, SEEK_END); + size = ftell(f); + rewind(f); + + char *buffer = new char[size]; + auto result = fread(buffer, 1, size, f); + fclose(f); + + if(result != size) { + safe_delete_array(buffer); + size = 0; + } +} + +EQEmu::FileVerify::~FileVerify() { + safe_delete_array(buffer); +} + +bool EQEmu::FileVerify::Verify(const char *data, uint32 size) { + if(!buffer) { + return true; + } + + if(size != sizeof(VerifyFileStruct)) { + return false; + } + + VerifyFileStruct *vs = (VerifyFileStruct*)data; + if(size != vs->file_size) { + return false; + } + + uint32 crc = CRC32::GenerateNoFlip((uchar*)buffer, size); + if(vs->crc != crc) { + return false; + } + + for(int i = 0; i < 256; ++i) { + uint32 offset = vs->offset[i] * 4; + + if((offset - 4) > size) { + return false; + } + + uint32 check = *(uint32*)(buffer + offset); + if(check != vs->data[i]) { + return false; + } + } + + 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 new file mode 100644 index 000000000..8646d3372 --- /dev/null +++ b/common/file_verify.h @@ -0,0 +1,20 @@ +#ifndef EQEMU_COMMON_FILE_VERIFY_H +#define EQEMU_COMMON_FILE_VERIFY_H + +namespace EQEmu +{ + class FileVerify + { + public: + FileVerify(const char *file_name); + ~FileVerify(); + + bool Verify(const char *data, uint32 size); + + private: + char *buffer; + uint32 size; + }; +} + +#endif diff --git a/world/client.cpp b/world/client.cpp index 26a69222b..2597b4cbe 100644 --- a/world/client.cpp +++ b/world/client.cpp @@ -17,6 +17,7 @@ #include "../common/clientversions.h" #include "../common/random.h" #include "../common/shareddb.h" +#include "../common/file_verify.h" #include "client.h" #include "worlddb.h" @@ -60,6 +61,11 @@ 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; @@ -952,13 +958,48 @@ bool Client::HandlePacket(const EQApplicationPacket *app) { switch(opcode) { - case OP_World_Client_CRC1: - case OP_World_Client_CRC2: + case OP_World_SpellFileCheck: { - // There is no obvious entry in the CC struct to indicate that the 'Start Tutorial button - // is selected when a character is created. I have observed that in this case, OP_EnterWorld is sent - // before OP_World_Client_CRC1. Therefore, if we receive OP_World_Client_CRC1 before OP_EnterWorld, - // then 'Start Tutorial' was not chosen. + if(spell_verify.Verify((char*)app->pBuffer, app->Size())) { + Log.Out(Logs::General, Logs::Status, "Spell file verified."); + } else { + Log.Out(Logs::General, Logs::Status, "Spell file not verified."); + } + 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."); + } else { + Log.Out(Logs::General, Logs::Status, "Skill file not verified."); + } + 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."); + } else { + Log.Out(Logs::General, Logs::Status, "BaseData file not verified."); + } + + 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."); + } else { + Log.Out(Logs::General, Logs::Status, "eqgame.exe not verified."); + } + StartInTutorial = false; return true; } @@ -1001,14 +1042,16 @@ bool Client::HandlePacket(const EQApplicationPacket *app) { // HoT sends this to world while zoning and wants it echoed back. return HandleZoneChangePacket(app); } - case OP_LoginUnknown1: - case OP_LoginUnknown2: case OP_CrashDump: case OP_WearChange: case OP_LoginComplete: 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; @@ -1016,6 +1059,10 @@ bool Client::HandlePacket(const EQApplicationPacket *app) { 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()); return true; } } diff --git a/zone/lua_packet.cpp b/zone/lua_packet.cpp index 1eef7220d..177b56444 100644 --- a/zone/lua_packet.cpp +++ b/zone/lua_packet.cpp @@ -575,8 +575,6 @@ luabind::scope lua_register_packet_opcodes() { luabind::value("EnterWorld", static_cast(OP_EnterWorld)), luabind::value("PostEnterWorld ", static_cast(OP_PostEnterWorld )), luabind::value("SendSystemStats", static_cast(OP_SendSystemStats)), - luabind::value("World_Client_CRC1", static_cast(OP_World_Client_CRC1)), - luabind::value("World_Client_CRC2", static_cast(OP_World_Client_CRC2)), luabind::value("SetChatServer", static_cast(OP_SetChatServer)), luabind::value("SetChatServer2", static_cast(OP_SetChatServer2)), luabind::value("ZoneServerInfo", static_cast(OP_ZoneServerInfo)), @@ -758,8 +756,6 @@ luabind::scope lua_register_packet_opcodes() { luabind::value("AdventureLeaderboardRequest", static_cast(OP_AdventureLeaderboardRequest)), luabind::value("AdventureLeaderboardReply", static_cast(OP_AdventureLeaderboardReply)), luabind::value("SetStartCity", static_cast(OP_SetStartCity)), - luabind::value("LoginUnknown1", static_cast(OP_LoginUnknown1)), - luabind::value("LoginUnknown2", static_cast(OP_LoginUnknown2)), luabind::value("ItemViewUnknown", static_cast(OP_ItemViewUnknown)), luabind::value("GetGuildMOTDReply", static_cast(OP_GetGuildMOTDReply)), luabind::value("SetGuildRank", static_cast(OP_SetGuildRank)),