bool MMG_TrackableServer::ConnectServer(MMG_TrackableServer::Server *aServer, uint aSourceIp, MMG_ServerStartupVariables *aStartupVars) { // // Protocol validation // //if (aStartupVars->m_ProtocolVersion != MassgateProtocolVersion) // return false; if (aStartupVars->m_GameVersion != WIC_DS_CURRENT_VERSION) return false; // License validation if (aStartupVars->somebits.Ranked) { // If ranked and mod, drop if (aStartupVars->m_ModId != 0) return false; // If ranked and password, drop if (aStartupVars->somebits.Passworded) return false; // If ranked and has no license, drop if (!aServer->m_KeyAuthenticated) return false; } // Resolve IP address if UseFireWallSettingsFlag is set in_addr serverAddr; if (strlen(aStartupVars->m_PublicIp) > 0) serverAddr.s_addr = inet_addr(aStartupVars->m_PublicIp); else serverAddr.s_addr = htonl(aSourceIp); // Generate a psuedo-random public ID by hashing a string containing // various server fields char data[1024]; sprintf_s(data, "%ws%s%d%d%X%X", aStartupVars->m_ServerName, inet_ntoa(serverAddr), aStartupVars->m_MassgateCommPort, aStartupVars->m_ServerReliablePort, aServer->m_KeySequence, MC_MTwister().Random()); // Mark server list entry as valid and copy information over aServer->m_PublicId = MC_Misc::HashSDBM(data); aServer->m_Info = *aStartupVars; aServer->m_Info.m_Ip = serverAddr.s_addr; DebugLog(L_INFO, "ConnectServer: %ws %s %s %d %d %d", aStartupVars->m_ServerName, aStartupVars->m_PublicIp, inet_ntoa(serverAddr), aStartupVars->m_MassgateCommPort, aStartupVars->m_ServerReliablePort, aServer->m_PublicId); return true; }
bool MMG_Messaging::HandleMessage(SvClient *aClient, MN_ReadMessage *aMessage, MMG_ProtocolDelimiters::Delimiter aDelimiter) { switch(aDelimiter) { case MMG_ProtocolDelimiters::MESSAGING_RETRIEVE_PROFILENAME: { DebugLog(L_INFO, "MESSAGING_RETRIEVE_PROFILENAME:"); MN_WriteMessage responseMessage(4096); ushort count; if (!aMessage->ReadUShort(count)) return false; #ifndef USING_MYSQL_DATABASE //handle profiles (count). uint profileId1, profileId2; if (!aMessage->ReadUInt(profileId2) || !aMessage->ReadUInt(profileId1)) return false; MMG_Profile friends[2]; friends[0].m_ProfileId = profileId1; friends[1].m_ProfileId = profileId2; wcscpy_s(friends[0].m_Name, L"tenerefis"); wcscpy_s(friends[1].m_Name, L"HouseBee"); friends[0].m_OnlineStatus = 1; friends[0].m_Rank = 18; friends[1].m_Rank = 18; //friends[0].m_ClanId = 4321; //friends[1].m_ClanId = 4321; //friends[0].m_RankInClan = 2; //friends[1].m_RankInClan = 3; responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_RESPOND_PROFILENAME); friends[0].ToStream(&responseMessage); responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_RESPOND_PROFILENAME); friends[1].ToStream(&responseMessage); if (!aClient->SendData(&responseMessage)) return false; #else // TODO: limit may be 128, (MMG_Messaging::Update) if (count > 128) count = 128; uint *profildIds = NULL; MMG_Profile *profileList = NULL; profildIds = (uint*)malloc(count * sizeof(uint)); memset(profildIds, 0, count * sizeof(uint)); profileList = new MMG_Profile[count]; // read profile ids from message for (ushort i = 0; i < count; i++) { if (!aMessage->ReadUInt(profildIds[i])) return false; } // query database MySQLDatabase::ourInstance->QueryProfileList(count, profildIds, profileList); DebugLog(L_INFO, "MESSAGING_RESPOND_PROFILENAME: sending %d profile name/s", count); // write profiles to stream for (ushort i = 0; i < count; i++) { // determine profiles' online status SvClient *player = MMG_AccountProxy::ourInstance->GetClientByProfileId(profileList[i].m_ProfileId); if (player) profileList[i].m_OnlineStatus = player->GetProfile()->m_OnlineStatus; responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_RESPOND_PROFILENAME); profileList[i].ToStream(&responseMessage); if (responseMessage.GetDataLength() + sizeof(MMG_Profile) >= 4096) { if (!aClient->SendData(&responseMessage)) break; } } delete [] profileList; profileList = NULL; free(profildIds); profildIds = NULL; // send packet if (responseMessage.GetDataLength() > 0 && !aClient->SendData(&responseMessage)) return false; #endif } break; case MMG_ProtocolDelimiters::MESSAGING_GET_FRIENDS_AND_ACQUAINTANCES_REQUEST: { DebugLog(L_INFO, "MESSAGING_GET_FRIENDS_AND_ACQUAINTANCES_REQUEST:"); MN_WriteMessage responseMessage(2048); /*MMG_Profile *myProfile = aClient->GetProfile(); this->SendProfileName(aClient, &responseMessage, myProfile); this->SendPingsPerSecond(aClient, &responseMessage); if (myProfile->m_ClanId) { MMG_ProtocolDelimiters::Delimiter delim; if (!aMessage->ReadDelimiter((ushort &)delim)) return false; uint clanId; uint unkZero; if (!aMessage->ReadUInt(clanId) || !aMessage->ReadUInt(unkZero)) return false; if (myProfile->m_ClanId != clanId || unkZero != 0) return false; // TODO: Write clan information } this->SendCommOptions(aClient, &responseMessage); this->SendStartupSequenceComplete(aClient, &responseMessage); */ //send acquaintances first, otherwise it screws up the contacts list if (!this->SendAcquaintance(aClient, &responseMessage)) return false; if (!this->SendFriend(aClient, &responseMessage)) return false; } break; case MMG_ProtocolDelimiters::MESSAGING_ADD_FRIEND_REQUEST: { DebugLog(L_INFO, "MESSAGING_ADD_FRIEND_REQUEST:"); uint profileId; if (!aMessage->ReadUInt(profileId)) return false; #ifdef USING_MYSQL_DATABASE MySQLDatabase::ourInstance->AddFriend(aClient->GetProfile()->m_ProfileId, profileId); #endif // NOTE: // there doesnt seem to be a response, there is a lot of client side validation, making it unnecessary // if the client behaves weird, use MESSAGING_GET_FRIENDS_RESPONSE } break; case MMG_ProtocolDelimiters::MESSAGING_REMOVE_FRIEND_REQUEST: { DebugLog(L_INFO, "MESSAGING_REMOVE_FRIEND_REQUEST:"); MN_WriteMessage responseMessage(2048); uint profileId; if (!aMessage->ReadUInt(profileId)) return false; #ifdef USING_MYSQL_DATABASE MySQLDatabase::ourInstance->RemoveFriend(aClient->GetProfile()->m_ProfileId, profileId); #endif DebugLog(L_INFO, "MESSAGING_REMOVE_FRIEND_RESPONSE:"); responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_REMOVE_FRIEND_RESPONSE); responseMessage.WriteUInt(profileId); if (!aClient->SendData(&responseMessage)) return false; } break; case MMG_ProtocolDelimiters::MESSAGING_IM_CHECK_PENDING_MESSAGES: { DebugLog(L_INFO, "MESSAGING_IM_CHECK_PENDING_MESSAGES:"); MN_WriteMessage responseMessage(4096); uint msgCount = 0; MMG_InstantMessageListener::InstantMessage *myMsgs = NULL; #ifdef USING_MYSQL_DATABASE MySQLDatabase::ourInstance->QueryPendingMessages(aClient->GetProfile()->m_ProfileId, &msgCount, &myMsgs); #endif // if there are messages, send them // pending messages will be removed once they have been acknowledged. if (myMsgs) { for (uint i = 0; i < msgCount; i++) { // check the online status of the sender SvClient *sender = MMG_AccountProxy::ourInstance->GetClientByProfileId(myMsgs->m_SenderProfile.m_ProfileId); if (sender) myMsgs->m_SenderProfile.m_OnlineStatus = sender->GetProfile()->m_OnlineStatus; //DebugLog(L_INFO, "MESSAGING_IM_RECEIVE"); responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_IM_RECEIVE); myMsgs[i].ToStream(&responseMessage); if (responseMessage.GetDataLength() + sizeof(MMG_InstantMessageListener::InstantMessage) >= 4096) { if (!aClient->SendData(&responseMessage)) break; } } delete [] myMsgs; myMsgs = NULL; if (responseMessage.GetDataLength() > 0 && !aClient->SendData(&responseMessage)) return false; } } break; case MMG_ProtocolDelimiters::MESSAGING_IM_SEND: { DebugLog(L_INFO, "MESSAGING_IM_SEND"); MMG_InstantMessageListener::InstantMessage myInstantMessage; if(!myInstantMessage.FromStream(aMessage)) return false; //handle padding uint padZero; if (!aMessage->ReadUInt(padZero)) return false; //check to see if recipient is online SvClient *recipient = MMG_AccountProxy::ourInstance->GetClientByProfileId(myInstantMessage.m_RecipientProfile); if (!recipient) { // intended recipient is offline #ifdef USING_MYSQL_DATABASE MySQLDatabase::ourInstance->AddInstantMessage(aClient->GetProfile()->m_ProfileId, &myInstantMessage); #endif } else { MN_WriteMessage responseMessage(2048); // if recipient does not ack, the message is lost, un-read messages are NOT saved client side #ifdef USING_MYSQL_DATABASE MySQLDatabase::ourInstance->AddInstantMessage(aClient->GetProfile()->m_ProfileId, &myInstantMessage); #endif //DebugLog(L_INFO, "MESSAGING_IM_RECEIVE"); responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_IM_RECEIVE); myInstantMessage.ToStream(&responseMessage); if (!recipient->SendData(&responseMessage)) return false; } } break; case MMG_ProtocolDelimiters::MESSAGING_IM_ACK: { DebugLog(L_INFO, "MESSAGING_IM_ACK:"); uint messageId; if (!aMessage->ReadUInt(messageId)) return false; // message has been acked, remove it from queue #ifdef USING_MYSQL_DATABASE MySQLDatabase::ourInstance->RemoveInstantMessage(aClient->GetProfile()->m_ProfileId, messageId); #endif } break; case MMG_ProtocolDelimiters::MESSAGING_SET_COMMUNICATION_OPTIONS_REQ: { DebugLog(L_INFO, "MESSAGING_SET_COMMUNICATION_OPTIONS_REQ:"); uint commOptions; if (!aMessage->ReadUInt(commOptions)) return false; // TODO //MMG_Options myOptions; //myOptions.FromUInt(commOptions); aClient->GetOptions()->FromUInt(commOptions); #ifdef USING_MYSQL_DATABASE MySQLDatabase::ourInstance->SaveUserOptions(aClient->GetProfile()->m_ProfileId, commOptions); #endif } break; case MMG_ProtocolDelimiters::MESSAGING_GET_COMMUNICATION_OPTIONS_REQ: { DebugLog(L_INFO, "MESSAGING_GET_COMMUNICATION_OPTIONS_REQ:"); MN_WriteMessage responseMessage(2048); #ifndef USING_MYSQL_DATABASE aClient->GetOptions()->FromUInt(992); // i dont remember the default values #else uint commOptions; MySQLDatabase::ourInstance->QueryUserOptions(aClient->GetProfile()->m_ProfileId, &commOptions); aClient->GetOptions()->FromUInt(commOptions); #endif if (!this->SendCommOptions(aClient, &responseMessage)) return false; } break; case MMG_ProtocolDelimiters::MESSAGING_GET_IM_SETTINGS: { DebugLog(L_INFO, "MESSAGING_GET_IM_SETTINGS:"); MN_WriteMessage responseMessage(2048); #ifndef USING_MYSQL_DATABASE aClient->GetOptions()->FromUInt(992); // i dont remember the default values #else uint commOptions; MySQLDatabase::ourInstance->QueryUserOptions(aClient->GetProfile()->m_ProfileId, &commOptions); aClient->GetOptions()->FromUInt(commOptions); #endif if (!this->SendIMSettings(aClient, &responseMessage)) return false; } break; case MMG_ProtocolDelimiters::MESSAGING_GET_PPS_SETTINGS_REQ: { DebugLog(L_INFO, "MESSAGING_GET_PPS_SETTINGS_REQ:"); MN_WriteMessage responseMessage(2048); this->SendPingsPerSecond(aClient, &responseMessage); } break; case MMG_ProtocolDelimiters::MESSAGING_CLAN_CREATE_REQUEST: { DebugLog(L_INFO, "MESSAGING_CLAN_CREATE_REQUEST:"); MN_WriteMessage responseMessage(2048); wchar_t clanName[WIC_CLANNAME_MAX_LENGTH]; wchar_t clanTag[WIC_CLANTAG_MAX_LENGTH]; char displayTag[8]; uint padZero; memset(clanName, 0, sizeof(clanName)); memset(clanTag, 0, sizeof(clanTag)); memset(displayTag, 0, sizeof(displayTag)); if (!aMessage->ReadString(clanName, ARRAYSIZE(clanName))) return false; if (!aMessage->ReadString(clanTag, ARRAYSIZE(clanTag))) return false; if (!aMessage->ReadString(displayTag, ARRAYSIZE(displayTag))) return false; if (!aMessage->ReadUInt(padZero)) return false; #ifndef USING_MYSQL_DATABASE DebugLog(L_INFO, "MESSAGING_CLAN_CREATE_RESPONSE:"); responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_CLAN_CREATE_RESPONSE); responseMessage.WriteUChar(myClanStrings::InternalMassgateError); responseMessage.WriteUInt(0); if (!aClient->SendData(&responseMessage)) return false; #else uchar myStatusCode; uint myClanId; uint nameId = 0, tagId = 0; bool ClanNameQueryOK = MySQLDatabase::ourInstance->CheckIfClanNameExists(clanName, &nameId); bool ClanTagQueryOK = MySQLDatabase::ourInstance->CheckIfClanTagExists(clanTag, &tagId); if (nameId > 0 && ClanNameQueryOK) { myStatusCode = myClanStrings::FAIL_INVALID_NAME; myClanId = 0; } else if (tagId > 0 && ClanTagQueryOK) { myStatusCode = myClanStrings::FAIL_TAG_TAKEN; myClanId = 0; } //else if(wcsncmp(clanName, clanTag, WIC_CLANTAG_MAX_LENGTH) == 0) //{ // myStatusCode = myClanStrings::FAIL_OTHER; // myClanId = 0; //} //else if (checkvalidchars) //{ // myStatusCode = myClanStrings::FAIL_OTHER; // myClanId = 0; //} else if (!ClanNameQueryOK || !ClanTagQueryOK) { myStatusCode = myClanStrings::InternalMassgateError; myClanId = 0; } else { uint newClanId=0; bool CreateClanQueryOk = MySQLDatabase::ourInstance->CreateClan(aClient->GetProfile()->m_ProfileId, clanName, clanTag, displayTag, &newClanId); if (CreateClanQueryOk) { myStatusCode = 1; myClanId = newClanId; // update database profile MySQLDatabase::ourInstance->UpdatePlayerClanId(aClient->GetProfile()->m_ProfileId, newClanId); MySQLDatabase::ourInstance->UpdatePlayerClanRank(aClient->GetProfile()->m_ProfileId, 1); // update the logged in client object MySQLDatabase::ourInstance->QueryProfileName(aClient->GetProfile()->m_ProfileId, aClient->GetProfile()); aClient->GetProfile()->m_OnlineStatus = 1; } else { myStatusCode = myClanStrings::InternalMassgateError; myClanId = 0; } } DebugLog(L_INFO, "MESSAGING_CLAN_CREATE_RESPONSE:"); responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_CLAN_CREATE_RESPONSE); responseMessage.WriteUChar(myStatusCode); responseMessage.WriteUInt(myClanId); if (!aClient->SendData(&responseMessage)) return false; // TODO update friends and acquaintances MMG_AccountProxy::ourInstance->SendPlayerJoinedClan(aClient->GetProfile()); #endif } break; #ifdef USING_MYSQL_DATABASE case MMG_ProtocolDelimiters::MESSAGING_CLAN_MODIFY_REQUEST: { DebugLog(L_INFO, "MESSAGING_CLAN_MODIFY_REQUEST:"); wchar_t ClanMotto[WIC_MOTTO_MAX_LENGTH]; wchar_t ClanMessageoftheday[WIC_MOTD_MAX_LENGTH]; wchar_t ClanHomepage[WIC_HOMEPAGE_MAX_LENGTH]; memset(ClanMotto, 0, sizeof(ClanMotto)); memset(ClanMessageoftheday, 0, sizeof(ClanMessageoftheday)); memset(ClanHomepage, 0, sizeof(ClanHomepage)); if (!aMessage->ReadString(ClanMotto, ARRAYSIZE(ClanMotto))) return false; if (!aMessage->ReadString(ClanMessageoftheday, ARRAYSIZE(ClanMessageoftheday))) return false; if (!aMessage->ReadString(ClanHomepage, ARRAYSIZE(ClanHomepage))) return false; uint profileId; if (!aMessage->ReadUInt(profileId)) return false; MySQLDatabase::ourInstance->SaveClanEditableVariables(aClient->GetProfile()->m_ClanId, profileId, ClanMotto, ClanMessageoftheday, ClanHomepage); // no response, rest of packet at this point is another delimiter (sub_7A0FE0, MESSAGING_CLAN_FULL_INFO_REQUEST) } break; case MMG_ProtocolDelimiters::MESSAGING_CLAN_MODIFY_RANKS_REQUEST: { DebugLog(L_INFO, "MESSAGING_CLAN_MODIFY_RANKS_REQUEST:"); MN_WriteMessage responseMessage(2048); uint profileId, option; if (!aMessage->ReadUInt(profileId) || !aMessage->ReadUInt(option)) return false; bool myProfileDirty = false; bool requestedProfileDirty = false; uint memberCount = 0; MMG_Clan::FullInfo myClan; MMG_Profile profile; MySQLDatabase::ourInstance->QueryProfileName(profileId, &profile); MySQLDatabase::ourInstance->QueryClanFullInfo(profile.m_ClanId, &memberCount, &myClan); switch (option) { // if client->profileid = profileId then leave, otherwise kick case 0: { // if leaving/kicked profile is playerofweek, set potw 0 in clans table if (myClan.m_PlayerOfWeek == profileId) MySQLDatabase::ourInstance->UpdateClanPlayerOfWeek(myClan.m_ClanId, 0); if (profile.m_RankInClan < 3) MySQLDatabase::ourInstance->DeleteProfileClanInvites(profileId, myClan.m_ClanId); MySQLDatabase::ourInstance->DeleteProfileClanMessages(profileId); MySQLDatabase::ourInstance->UpdatePlayerClanId(profileId, 0); MySQLDatabase::ourInstance->UpdatePlayerClanRank(profileId, 0); if (aClient->GetProfile()->m_ProfileId == profileId) { // randomly assign new clan leader if (aClient->GetProfile()->m_RankInClan == 1 && memberCount > 1) { MMG_Profile p[512]; uint officers[512], grunts[512]; int i=0, j=0, k=0; memset(officers, 0, sizeof(officers)); memset(grunts, 0, sizeof(grunts)); MySQLDatabase::ourInstance->QueryProfileList(memberCount, myClan.m_ClanMembers, p); while (myClan.m_ClanMembers[i] > 0) { if (myClan.m_ClanMembers[i] != profileId) { if (p[i].m_RankInClan == 2) officers[j++] = p[i].m_ProfileId; if (p[i].m_RankInClan == 3) grunts[k++] = p[i].m_ProfileId; } i++; } int pos = 0; uint ldrId = 0; if (j>0) { pos = MC_MTwister().Random(0, j-1); ldrId = officers[pos]; } else { pos = MC_MTwister().Random(0, k-1); ldrId = grunts[pos]; } MySQLDatabase::ourInstance->UpdatePlayerClanRank(ldrId, 1); MMG_Profile modifiedProfile; MySQLDatabase::ourInstance->QueryProfileName(ldrId, &modifiedProfile); // if profile/player is online, update the logged in client object, preserve online status SvClient *player = MMG_AccountProxy::ourInstance->GetClientByProfileId(ldrId); if (player) { modifiedProfile.m_OnlineStatus = player->GetProfile()->m_OnlineStatus; MySQLDatabase::ourInstance->QueryProfileName(ldrId, player->GetProfile()); player->GetProfile()->m_OnlineStatus = modifiedProfile.m_OnlineStatus; } // send the profile to all online players MMG_AccountProxy::ourInstance->UpdateClients(&modifiedProfile); } else if (aClient->GetProfile()->m_RankInClan == 1 && memberCount == 1) { MySQLDatabase::ourInstance->DeleteClan(myClan.m_ClanId); } myProfileDirty = true; } else requestedProfileDirty = true; } break; // assign clan leader case 1: { // delete any clan message the previous leader sent MySQLDatabase::ourInstance->DeleteProfileClanMessages(aClient->GetProfile()->m_ProfileId); // update my profile MySQLDatabase::ourInstance->UpdatePlayerClanRank(aClient->GetProfile()->m_ProfileId, 2); myProfileDirty = true; // update new leader profile MySQLDatabase::ourInstance->UpdatePlayerClanRank(profileId, 1); requestedProfileDirty = true; } break; // promote case 2: { MySQLDatabase::ourInstance->UpdatePlayerClanRank(profileId, 2); requestedProfileDirty = true; } break; // demote case 3: { MySQLDatabase::ourInstance->DeleteProfileClanInvites(profileId, myClan.m_ClanId); MySQLDatabase::ourInstance->UpdatePlayerClanRank(profileId, 3); requestedProfileDirty = true; } break; default: assert(false); } if (requestedProfileDirty) { MMG_Profile modifiedProfile; MySQLDatabase::ourInstance->QueryProfileName(profileId, &modifiedProfile); // if profile/player is online, update the logged in client object, preserve online status SvClient *player = MMG_AccountProxy::ourInstance->GetClientByProfileId(profileId); if (player) { modifiedProfile.m_OnlineStatus = player->GetProfile()->m_OnlineStatus; MySQLDatabase::ourInstance->QueryProfileName(profileId, player->GetProfile()); player->GetProfile()->m_OnlineStatus = modifiedProfile.m_OnlineStatus; } // send the profile to all online players MMG_AccountProxy::ourInstance->UpdateClients(&modifiedProfile); } if (myProfileDirty) { uint myOnlineStatus = aClient->GetProfile()->m_OnlineStatus; MySQLDatabase::ourInstance->QueryProfileName(aClient->GetProfile()->m_ProfileId, aClient->GetProfile()); aClient->GetProfile()->m_OnlineStatus = myOnlineStatus; MMG_AccountProxy::ourInstance->UpdateClients(aClient->GetProfile()); } } break; case MMG_ProtocolDelimiters::MESSAGING_CLAN_FULL_INFO_REQUEST: { DebugLog(L_INFO, "MESSAGING_CLAN_FULL_INFO_REQUEST:"); MN_WriteMessage responseMessage(2048); uint clanId, padZero; if (!aMessage->ReadUInt(clanId) || !aMessage->ReadUInt(padZero)) return false; uint memberCount; MMG_Clan::FullInfo clanFullInfo; // request clan info from database MySQLDatabase::ourInstance->QueryClanFullInfo(clanId, &memberCount, &clanFullInfo); DebugLog(L_INFO, "MESSAGING_CLAN_FULL_INFO_RESPONSE:"); responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_CLAN_FULL_INFO_RESPONSE); responseMessage.WriteString(clanFullInfo.m_FullClanName); responseMessage.WriteString(clanFullInfo.m_ShortClanName); responseMessage.WriteString(clanFullInfo.m_Motto); responseMessage.WriteString(clanFullInfo.m_LeaderSays); responseMessage.WriteString(clanFullInfo.m_Homepage); responseMessage.WriteUInt(memberCount); for (int i = 0; i < memberCount; i++) responseMessage.WriteUInt(clanFullInfo.m_ClanMembers[i]); responseMessage.WriteUInt(clanFullInfo.m_ClanId); responseMessage.WriteUInt(clanFullInfo.m_PlayerOfWeek); if (!aClient->SendData(&responseMessage)) return false; } break; case MMG_ProtocolDelimiters::MESSAGING_CLAN_INVITE_PLAYER_REQUEST: { DebugLog(L_INFO, "MESSAGING_CLAN_INVITE_PLAYER_REQUEST:"); MN_WriteMessage responseMessage(2048); uint profileId, padZero; if (!aMessage->ReadUInt(profileId) || !aMessage->ReadUInt(padZero)) return false; uchar myStatusCode; MMG_Clan::Description myClan; MMG_Profile invitedProfile; uint msgId = 0; MySQLDatabase::ourInstance->QueryProfileName(profileId, &invitedProfile); MySQLDatabase::ourInstance->CheckIfInvitedAlready(aClient->GetProfile()->m_ClanId, profileId, &msgId); if (invitedProfile.m_ClanId > 0) myStatusCode = myClanStrings::FAIL_ALREADY_IN_CLAN; else if (msgId > 0) myStatusCode = myClanStrings::FAIL_DUPLICATE; //else if (aClient->GetProfile()->m_RankInClan > 2) // myStatusCode = myClanStrings::FAIL_INVALID_PRIVILIGES; //else if (!querysuccess) // myStatusCode = myClanStrings::InternalMassgateError; //else if (checkimsettings) // myStatusCode = myClanStrings::INVITE_FAIL_PLAYER_IGNORE_MESSAGES; else { MySQLDatabase::ourInstance->QueryClanDescription(aClient->GetProfile()->m_ClanId, &myClan); MMG_InstantMessageListener::InstantMessage im; wchar_t msgBuffer[WIC_INSTANTMSG_MAX_LENGTH]; memset(msgBuffer, 0, sizeof(msgBuffer)); swprintf(msgBuffer, WIC_INSTANTMSG_MAX_LENGTH, L"|clan|%u|%ws|%ws|", myClan.m_ClanId, aClient->GetProfile()->m_Name, myClan.m_FullName); wcsncpy(im.m_Message, msgBuffer, WIC_INSTANTMSG_MAX_LENGTH); im.m_RecipientProfile = profileId; im.m_SenderProfile = *aClient->GetProfile(); // todo // messageid and writtenat are generated in the AddInstantMessage function MySQLDatabase::ourInstance->AddInstantMessage(aClient->GetProfile()->m_ProfileId, &im); myStatusCode = myClanStrings::InviteSent; // if client is online send them the invite directly SvClient *invitee = MMG_AccountProxy::ourInstance->GetClientByProfileId(profileId); if (invitee) { MN_WriteMessage inviteMsg(2048); inviteMsg.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_IM_RECEIVE); im.ToStream(&inviteMsg); if (!invitee->SendData(&inviteMsg)) return false; } } responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_MASSGATE_GENERIC_STATUS_RESPONSE); responseMessage.WriteUChar(myStatusCode); if (!aClient->SendData(&responseMessage)) return false; } break; case MMG_ProtocolDelimiters::MESSAGING_CLAN_INVITE_PLAYER_RESPONSE: { DebugLog(L_INFO, "MESSAGING_CLAN_INVITE_PLAYER_RESPONSE:"); MN_WriteMessage responseMessage(2048); uint clanId, padZero; uchar acceptOrNot; if (!aMessage->ReadUInt(clanId) || !aMessage->ReadUChar(acceptOrNot) || !aMessage->ReadUInt(padZero)) return false; if (acceptOrNot) { MMG_Clan::Description theClan; // check to see if the clan exists MySQLDatabase::ourInstance->QueryClanDescription(clanId, &theClan); // clan found if (theClan.m_ClanId > 0) { // update database profile MySQLDatabase::ourInstance->UpdatePlayerClanId(aClient->GetProfile()->m_ProfileId, clanId); MySQLDatabase::ourInstance->UpdatePlayerClanRank(aClient->GetProfile()->m_ProfileId, 3); // reload local client->profile object MySQLDatabase::ourInstance->QueryProfileName(aClient->GetProfile()->m_ProfileId, aClient->GetProfile()); aClient->GetProfile()->m_OnlineStatus = 1; MMG_AccountProxy::ourInstance->SendPlayerJoinedClan(aClient->GetProfile()); } else //clan was deleted already { responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_MASSGATE_GENERIC_STATUS_RESPONSE); responseMessage.WriteUChar(myClanStrings::MODIFY_FAIL_MASSGATE); if (!aClient->SendData(&responseMessage)) return false; } } // TODO clan member list is not updated after joining clan } break; case MMG_ProtocolDelimiters::MESSAGING_CLAN_GUESTBOOK_POST_REQ: { DebugLog(L_INFO, "MESSAGING_CLAN_GUESTBOOK_POST_REQ:"); uint clanId, messageId, requestId; MMG_ClanGuestbookProtocol::GetRsp::GuestbookEntry entry; if (!aMessage->ReadUInt(clanId) || !aMessage->ReadUInt(messageId) || !aMessage->ReadUInt(requestId)) return false; if (!aMessage->ReadString(entry.m_Message, ARRAYSIZE(entry.m_Message))) return false; entry.m_ProfileId = aClient->GetProfile()->m_ProfileId; MySQLDatabase::ourInstance->AddClanGuestbookEntry(clanId, requestId, &entry); if (messageId) { // refresh the guestbook (todo: temporary) MN_WriteMessage responseMessage(2048); responseMessage.WriteUInt(requestId); responseMessage.WriteUInt(clanId); MN_ReadMessage temp(2048); temp.BuildMessage(responseMessage.GetDataStream(), responseMessage.GetDataLength()); this->HandleMessage(aClient, &temp, MMG_ProtocolDelimiters::MESSAGING_CLAN_GUESTBOOK_GET_REQ); } } break; case MMG_ProtocolDelimiters::MESSAGING_CLAN_GUESTBOOK_GET_REQ: { DebugLog(L_INFO, "MESSAGING_CLAN_GUESTBOOK_GET_REQ:"); MN_WriteMessage responseMessage(8192); uint requestId, clanId; if (!aMessage->ReadUInt(requestId) || !aMessage->ReadUInt(clanId)) return false; uint entryCount = 0; MMG_ClanGuestbookProtocol::GetRsp myResponse; MySQLDatabase::ourInstance->QueryClanGuestbook(clanId, &entryCount, &myResponse); MMG_Profile poster[32]; // todo MN_WriteMessage profileMsg(2048); for (uint i = 0; i < entryCount; i++) { MySQLDatabase::ourInstance->QueryProfileName(myResponse.m_Entries[i].m_ProfileId, &poster[i]); SvClient *player = MMG_AccountProxy::ourInstance->GetClientByProfileId(poster[i].m_ProfileId); if (player) poster[i].m_OnlineStatus = player->GetProfile()->m_OnlineStatus; profileMsg.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_RESPOND_PROFILENAME); poster[i].ToStream(&profileMsg); } if (entryCount > 0) { if (!aClient->SendData(&profileMsg)) return false; } responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_CLAN_GUESTBOOK_GET_RSP); responseMessage.WriteUInt(requestId); responseMessage.WriteUInt(entryCount); for (uint i = 0; i < entryCount; i++) { responseMessage.WriteString(myResponse.m_Entries[i].m_Message); responseMessage.WriteUInt(myResponse.m_Entries[i].m_Timestamp); responseMessage.WriteUInt(myResponse.m_Entries[i].m_ProfileId); responseMessage.WriteUInt(myResponse.m_Entries[i].m_MessageId); } if (!aClient->SendData(&responseMessage)) return false; } break; case MMG_ProtocolDelimiters::MESSAGING_CLAN_GUESTBOOK_DELETE_REQ: { DebugLog(L_INFO, "MESSAGING_CLAN_GUESTBOOK_DELETE_REQ:"); uint messageId; uchar deleteAll; if (!aMessage->ReadUInt(messageId) || !aMessage->ReadUChar(deleteAll)) return false; MMG_Profile *myProfile = aClient->GetProfile(); bool QueryOK = MySQLDatabase::ourInstance->DeleteClanGuestbookEntry(myProfile->m_ClanId, messageId, deleteAll); /*if (!QueryOK) { MN_WriteMessage responseMessage(2048); responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_MASSGATE_GENERIC_STATUS_RESPONSE); responseMessage.WriteUChar(myClanStrings::MODIFY_FAIL_INVALID_PRIVILIGES); if (!aClient->SendData(&responseMessage)) return false; } */ } break; case MMG_ProtocolDelimiters::MESSAGING_CLAN_MESSAGE_SEND_REQ: { DebugLog(L_INFO, "MESSAGING_CLAN_MESSAGE_SEND_REQ:"); MN_WriteMessage responseMessage(2048); wchar_t myMessage[WIC_INSTANTMSG_MAX_LENGTH]; memset(myMessage, 0, sizeof(myMessage)); if (!aMessage->ReadString(myMessage, ARRAYSIZE(myMessage))) return false; uint memberCount = 0; MMG_Clan::FullInfo myClan; MySQLDatabase::ourInstance->QueryClanFullInfo(aClient->GetProfile()->m_ClanId, &memberCount, &myClan); for (uint i = 0; i < memberCount; i++) { if (myClan.m_ClanMembers[i] > 0 && myClan.m_ClanMembers[i] != aClient->GetProfile()->m_ProfileId) { MMG_InstantMessageListener::InstantMessage im; wchar_t msgBuffer[WIC_INSTANTMSG_MAX_LENGTH]; memset(msgBuffer, 0, sizeof(msgBuffer)); swprintf(msgBuffer, WIC_INSTANTMSG_MAX_LENGTH, L"|clms|%ws", myMessage); wcsncpy(im.m_Message, msgBuffer, WIC_INSTANTMSG_MAX_LENGTH); im.m_RecipientProfile = myClan.m_ClanMembers[i]; im.m_SenderProfile = *aClient->GetProfile(); // todo MySQLDatabase::ourInstance->AddInstantMessage(aClient->GetProfile()->m_ProfileId, &im); // if player is online send them the message instantly SvClient *player = MMG_AccountProxy::ourInstance->GetClientByProfileId(im.m_RecipientProfile); if (player) { MN_WriteMessage clanMsg(2048); clanMsg.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_IM_RECEIVE); im.ToStream(&clanMsg); if (!player->SendData(&clanMsg)) continue; } } } } break; #endif case MMG_ProtocolDelimiters::MESSAGING_SET_STATUS_ONLINE: { DebugLog(L_INFO, "MESSAGING_SET_STATUS_ONLINE:"); /* IDA wic.exe sub_7A1360 MMG_Messaging::SetStatusOnline after WriteDelimiter, the client appends a Uint ( 0, some sort of padding? ) to the packet handling this random 0 is necessary otherwise "message from client delimiter 0 type 0" will show up in the debug log. Massgate will crash if these 0's arent handled since the message reader will treat it as the next delimiter. the same thing happens for cases: - MESSAGING_GET_CLIENT_METRICS -UChar, (IDA sub_7A1200 MMG_Messaging::GetClientMetrics) - MESSAGING_STARTUP_SEQUENCE_COMPLETE -2 x UInt, (IDA sub_7A1B00, see EXMASS_Client::ReceiveNotification lines 41 and 42) remove the handle padding code in the 3 cases to reproduce the crash */ //handle padding uint padZero; if (!aMessage->ReadUInt(padZero)) return false; // not sure if this is right aClient->GetProfile()->m_OnlineStatus = 1; MMG_AccountProxy::ourInstance->SetClientOnline(aClient); MMG_AccountProxy::ourInstance->UpdateClients(aClient->GetProfile()); // response maybe MESSAGING_MASSGATE_GENERIC_STATUS_RESPONSE } break; case MMG_ProtocolDelimiters::MESSAGING_SET_STATUS_PLAYING: { DebugLog(L_INFO, "MESSAGING_SET_STATUS_PLAYING:"); //IDA wic.exe sub_7A1290 MMG_Messaging::SetStatusPlaying // either MMG_AccountProxy::State_t or MMG_MasterConnection::State uint serverId; if (!aMessage->ReadUInt(serverId)) return false; //handle padding uint padZero; if (!aMessage->ReadUInt(padZero)) return false; aClient->GetProfile()->m_OnlineStatus = serverId; MMG_AccountProxy::ourInstance->UpdateClients(aClient->GetProfile()); // response maybe MESSAGING_MASSGATE_GENERIC_STATUS_RESPONSE } break; case MMG_ProtocolDelimiters::MESSAGING_FIND_PROFILE_REQUEST: { DebugLog(L_INFO, "MESSAGING_FIND_PROFILE_REQUEST:"); MN_WriteMessage responseMessage(4096); uint maxResults; // hardcoded to 100 if (!aMessage->ReadUInt(maxResults)) return false; wchar_t search[WIC_NAME_MAX_LENGTH]; memset(search, 0, sizeof(search)); if (!aMessage->ReadString(search, ARRAYSIZE(search))) return false; uint padZero; if (!aMessage->ReadUInt(padZero)) return false; uint resultCount=0; uint profileIds[100]; memset(profileIds, 0, sizeof(profileIds)); MySQLDatabase::ourInstance->SearchProfileName(search, &resultCount, profileIds); DebugLog(L_INFO, "MESSAGING_FIND_PROFILE_RESPONSE:"); responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_FIND_PROFILE_RESPONSE); responseMessage.WriteUInt(resultCount); for(uint i = 0; i < resultCount; i++) responseMessage.WriteUInt(profileIds[i]); responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_FIND_PROFILE_SEARCH_COMPLETE); if (!aClient->SendData(&responseMessage)) return false; } break; case MMG_ProtocolDelimiters::MESSAGING_FIND_CLAN_REQUEST: { DebugLog(L_INFO, "MESSAGING_FIND_CLAN_REQUEST:"); MN_WriteMessage responseMessage(4096); uint maxResults; // hardcoded to 100 if (!aMessage->ReadUInt(maxResults)) return false; wchar_t search[WIC_CLANNAME_MAX_LENGTH]; memset(search, 0, sizeof(search)); if (!aMessage->ReadString(search, ARRAYSIZE(search))) return false; uint padZero; if (!aMessage->ReadUInt(padZero)) return false; uint resultCount=0; MMG_Clan::Description clans[100]; MySQLDatabase::ourInstance->SearchClanName(search, &resultCount, clans); DebugLog(L_INFO, "MESSAGING_FIND_CLAN_RESPONSE:"); responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_FIND_CLAN_RESPONSE); responseMessage.WriteUInt(resultCount); for (uint i = 0; i < resultCount; i++) { clans[i].ToStream(&responseMessage); if (responseMessage.GetDataLength() + sizeof(MMG_Clan::Description) >= 4096) { if (!aClient->SendData(&responseMessage)) break; } } responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_FIND_CLAN_SEARCH_COMPLETE); if (responseMessage.GetDataLength() > 0 && !aClient->SendData(&responseMessage)) return false; } break; case MMG_ProtocolDelimiters::MESSAGING_GET_CLIENT_METRICS: { DebugLog(L_INFO, "MESSAGING_GET_CLIENT_METRICS:"); MN_WriteMessage responseMessage(2048); //handle padding uchar padZero; if (!aMessage->ReadUChar(padZero)) return false; //DebugLog(L_INFO, "MESSAGING_GET_CLIENT_METRICS:"); //responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_GET_CLIENT_METRICS); //char key[16] = ""; //char value[96] = ""; // TODO // this has something to do with EXMASS_Client::ReceiveNotification case 27) // // enum MMG_ClientMetric::Context // number of options // key // value //responseMessage.WriteUChar(1); //responseMessage.WriteUInt(0); //responseMessage.WriteString(key, ARRAYSIZE(key)); //responseMessage.WriteString(value, ARRAYSIZE(value)); //if (!aClient->SendData(&responseMessage)) // return false; } break; case MMG_ProtocolDelimiters::MESSAGING_STARTUP_SEQUENCE_COMPLETE: { DebugLog(L_INFO, "MESSAGING_STARTUP_SEQUENCE_COMPLETE:"); MN_WriteMessage responseMessage(2048); //handle padding uint padZero; if (!aMessage->ReadUInt(padZero) || !aMessage->ReadUInt(padZero)) return false; this->SendStartupSequenceComplete(aClient, &responseMessage); } break; case MMG_ProtocolDelimiters::MESSAGING_CLIENT_REQ_GET_PCC: { DebugLog(L_INFO, "MESSAGING_CLIENT_REQ_GET_PCC:"); MN_WriteMessage responseMessage(2048); // This is called when the clan profile page is opened, player created content uint requestCount; if (!aMessage->ReadUInt(requestCount)) return false; responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_CLIENT_RSP_GET_PCC); responseMessage.WriteUInt(requestCount);// Number of responses for (uint i = 0; i < requestCount; i++) { uint requestId; uchar requestType; aMessage->ReadUInt(requestId); aMessage->ReadUChar(requestType); // Write these back to the outgoing messages responseMessage.WriteUInt(requestId); // Content id responseMessage.WriteUInt(1); // Content sequence number responseMessage.WriteUChar(requestType); // Content type (0 is profile image, 1 is clan image) responseMessage.WriteString("https://my_domain_here.abc/my_image.dds"); // Image url } if (!aClient->SendData(&responseMessage)) return false; } break; case MMG_ProtocolDelimiters::MESSAGING_ABUSE_REPORT: { DebugLog(L_INFO, "MESSAGING_ABUSE_REPORT:"); uint profileIdReported; if (!aMessage->ReadUInt(profileIdReported)) return false; wchar_t anAbuseReport[256]; memset(anAbuseReport, 0, sizeof(anAbuseReport)); if (!aMessage->ReadString(anAbuseReport, ARRAYSIZE(anAbuseReport))) return false; //handle padding uchar aZero; if (!aMessage->ReadUChar(aZero)) return false; #ifdef USING_MYSQL_DATABASE MMG_Profile myProfile = *aClient->GetProfile(); MySQLDatabase::ourInstance->AddAbuseReport(myProfile.m_ProfileId, myProfile, profileIdReported, anAbuseReport); #endif } break; case MMG_ProtocolDelimiters::MESSAGING_OPTIONAL_CONTENT_GET_REQ: { MN_WriteMessage responseMessage(2048); char langCode[3]; // "EN" aMessage->ReadString(langCode, ARRAYSIZE(langCode)); DebugLog(L_INFO, "MESSAGING_OPTIONAL_CONTENT_GET_REQ: %s", langCode); // Optional content (I.E maps) this->SendOptionalContent(aClient, &responseMessage); } break; case MMG_ProtocolDelimiters::MESSAGING_OPTIONAL_CONTENT_RETRY_REQ: { DebugLog(L_INFO, "MESSAGING_OPTIONAL_CONTENT_RETRY_REQ:"); MN_WriteMessage responseMessage(2048); // TODO // Is this used? // Retry // this->SendOptionalContent(aClient, &responseMessage); } break; #ifdef USING_MYSQL_DATABASE case MMG_ProtocolDelimiters::MESSAGING_PROFILE_GUESTBOOK_POST_REQ: { DebugLog(L_INFO, "MESSAGING_PROFILE_GUESTBOOK_POST_REQ:"); uint profileId, messageId, requestId; MMG_ProfileGuestBookProtocol::GetRsp::GuestbookEntry entry; if (!aMessage->ReadUInt(profileId) || !aMessage->ReadUInt(messageId) || !aMessage->ReadUInt(requestId)) return false; if (!aMessage->ReadString(entry.m_Message, ARRAYSIZE(entry.m_Message))) return false; entry.m_ProfileId = aClient->GetProfile()->m_ProfileId; MySQLDatabase::ourInstance->AddProfileGuestbookEntry(profileId, requestId, &entry); if (messageId) { // refresh the guestbook (todo: temporary) MN_WriteMessage responseMessage(2048); responseMessage.WriteUInt(requestId); responseMessage.WriteUInt(profileId); MN_ReadMessage temp(2048); temp.BuildMessage(responseMessage.GetDataStream(), responseMessage.GetDataLength()); this->HandleMessage(aClient, &temp, MMG_ProtocolDelimiters::MESSAGING_PROFILE_GUESTBOOK_GET_REQ); } } break; case MMG_ProtocolDelimiters::MESSAGING_PROFILE_GUESTBOOK_GET_REQ: { DebugLog(L_INFO, "MESSAGING_PROFILE_GUESTBOOK_GET_REQ:"); MN_WriteMessage responseMessage(8192); uint requestId, profileId; if (!aMessage->ReadUInt(requestId) || !aMessage->ReadUInt(profileId)) return false; uint entryCount = 0; MMG_ProfileGuestBookProtocol::GetRsp myResponse; MySQLDatabase::ourInstance->QueryProfileGuestbook(profileId, &entryCount, &myResponse); MMG_Profile poster[32]; // todo MN_WriteMessage profileMsg(2048); for (uint i = 0; i < entryCount; i++) { MySQLDatabase::ourInstance->QueryProfileName(myResponse.m_Entries[i].m_ProfileId, &poster[i]); SvClient *player = MMG_AccountProxy::ourInstance->GetClientByProfileId(poster[i].m_ProfileId); if (player) poster[i].m_OnlineStatus = player->GetProfile()->m_OnlineStatus; profileMsg.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_RESPOND_PROFILENAME); poster[i].ToStream(&profileMsg); } if (entryCount > 0) { if (!aClient->SendData(&profileMsg)) return false; } responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_PROFILE_GUESTBOOK_GET_RSP); responseMessage.WriteUInt(requestId); responseMessage.WriteUChar(0); //TODO: IgnoresGettingProfile? responseMessage.WriteUInt(entryCount); for (uint i = 0; i < entryCount; i++) { responseMessage.WriteString(myResponse.m_Entries[i].m_Message); responseMessage.WriteUInt(myResponse.m_Entries[i].m_Timestamp); responseMessage.WriteUInt(myResponse.m_Entries[i].m_ProfileId); responseMessage.WriteUInt(myResponse.m_Entries[i].m_MessageId); } if (!aClient->SendData(&responseMessage)) return false; } break; case MMG_ProtocolDelimiters::MESSAGING_PROFILE_GUESTBOOK_DELETE_REQ: { DebugLog(L_INFO, "MESSAGING_PROFILE_GUESTBOOK_DELETE_REQ:"); uint messageId; uchar deleteAll; //deleteAllByThisProfile if (!aMessage->ReadUInt(messageId) || !aMessage->ReadUChar(deleteAll)) return false; MySQLDatabase::ourInstance->DeleteProfileGuestbookEntry(aClient->GetProfile()->m_ProfileId, messageId, deleteAll); // no response, rest of packet is MESSAGING_PROFILE_GUESTBOOK_GET_REQ } break; #endif case MMG_ProtocolDelimiters::MESSAGING_PROFILE_SET_EDITABLES_REQ: { DebugLog(L_INFO, "MESSAGING_PROFILE_SET_EDITABLES_REQ:"); MMG_ProfileEditableVariablesProtocol::GetRsp myResponse; if (!myResponse.FromStream(aMessage)) return false; #ifdef USING_MYSQL_DATABASE MySQLDatabase::ourInstance->SaveEditableVariables(aClient->GetProfile()->m_ProfileId, myResponse.motto, myResponse.homepage); #endif // no response required } break; case MMG_ProtocolDelimiters::MESSAGING_PROFILE_GET_EDITABLES_REQ: { DebugLog(L_INFO, "MESSAGING_PROFILE_GET_EDITABLES_REQ:"); MN_WriteMessage responseMessage(2048); uint profileId; if (!aMessage->ReadUInt(profileId)) return false; MMG_ProfileEditableVariablesProtocol::GetRsp myResponse; #ifndef USING_MYSQL_DATABASE wcscpy_s(myResponse.motto, L""); wcscpy_s(myResponse.homepage, L""); #else MySQLDatabase::ourInstance->QueryEditableVariables(profileId, myResponse.motto, myResponse.homepage); #endif DebugLog(L_INFO, "MESSAGING_PROFILE_GET_EDITABLES_RSP:"); responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_PROFILE_GET_EDITABLES_RSP); myResponse.ToStream(&responseMessage); if (!aClient->SendData(&responseMessage)) return false; } break; case MMG_ProtocolDelimiters::MESSAGING_IGNORELIST_ADD_REMOVE_REQ: { DebugLog(L_INFO, "MESSAGING_IGNORELIST_ADD_REMOVE_REQ:"); uint profileId; if (!aMessage->ReadUInt(profileId)) return false; uchar operation; if (!aMessage->ReadUChar(operation)) return false; #ifndef USING_MYSQL_DATABASE if (operation == 1) printf("add\n"); else printf("remove\n"); #else if (operation == 1) MySQLDatabase::ourInstance->AddIgnoredProfile(aClient->GetProfile()->m_ProfileId, profileId); else MySQLDatabase::ourInstance->RemoveIgnoredProfile(aClient->GetProfile()->m_ProfileId, profileId); #endif // wic.exe IDA sub_BD7C90 (called from WICMASS_ContactsScreenHandler) // NOTE: // there doesnt seem to be a response, there is a lot of client side validation, making it unnecessary // if the client behaves weird, use MESSAGING_IGNORELIST_GET_RSP } break; case MMG_ProtocolDelimiters::MESSAGING_IGNORELIST_GET_REQ: { DebugLog(L_INFO, "MESSAGING_IGNORELIST_GET_REQ:"); MN_WriteMessage responseMessage(2048); DebugLog(L_INFO, "MESSAGING_IGNORELIST_GET_RSP:"); responseMessage.WriteDelimiter(MMG_ProtocolDelimiters::MESSAGING_IGNORELIST_GET_RSP); #ifndef USING_MYSQL_DATABASE //write uint (num ignored profiles) responseMessage.WriteUInt(0); //for each ignored profile //write uint (profile id) #else uint ignoredCount = 0; uint *myIgnoreList = NULL; bool QueryOK = MySQLDatabase::ourInstance->QueryIgnoredProfiles(aClient->GetProfile()->m_ProfileId, &ignoredCount, &myIgnoreList); if (!QueryOK) responseMessage.WriteUInt(0); else { responseMessage.WriteUInt(ignoredCount); for (uint i=0; i < ignoredCount; i++) responseMessage.WriteUInt(myIgnoreList[i]); } delete [] myIgnoreList; myIgnoreList = NULL; #endif if (!aClient->SendData(&responseMessage)) return false; } break; case MMG_ProtocolDelimiters::MESSAGING_CLAN_COLOSSEUM_GET_FILTER_WEIGHTS_REQ: { DebugLog(L_INFO, "MESSAGING_CLAN_COLOSSEUM_GET_FILTER_WEIGHTS_REQ:"); MN_WriteMessage responseMessage(2048); this->SendClanWarsFilterWeights(aClient, &responseMessage); } break; default: DebugLog(L_WARN, "Unknown delimiter %i", aDelimiter); return false; } return true; }
bool MMG_AccountProtocol::HandleMessage(SvClient *aClient, MN_ReadMessage *aMessage, MMG_ProtocolDelimiters::Delimiter aDelimiter) { Query myQuery; if (!myQuery.FromStream(aDelimiter, aMessage)) return false; // Copy over the data to the main client manager class aClient->m_CipherIdentifier = myQuery.m_CipherIdentifier; aClient->m_EncryptionKeySequenceNumber = myQuery.m_EncryptionKeySequenceNumber; memcpy(aClient->m_CipherKeys, myQuery.m_CipherKeys, sizeof(myQuery.m_CipherKeys)); MN_WriteMessage cryptMessage(1024); MMG_ProtocolDelimiters::Delimiter responseDelimiter; // Write the base identifier (random number, only a marker) cryptMessage.WriteUInt(myQuery.m_RandomKey); if (myQuery.m_StatusCode == IncorrectProtocolVersion || !myQuery.VerifyProductId()) { // Invalid game version // This takes priority over maintenance due to potential protocol differences DebugLog(L_INFO, "ACCOUNT_PATCH_INFORMATION: Client has an old version [%d vs %d]", myQuery.m_Protocol, MassgateProtocolVersion); responseDelimiter = MMG_ProtocolDelimiters::ACCOUNT_PATCH_INFORMATION; this->WritePatchInformation(&cryptMessage, &myQuery); } else if (this->m_MaintenanceMode) { // Massgate/Server maintenance notice DebugLog(L_INFO, "ACCOUNT_NOT_CONNECTED: Sending Maintenance Notice"); responseDelimiter = MMG_ProtocolDelimiters::ACCOUNT_NOT_CONNECTED; this->WriteMaintenanceInformation(&cryptMessage); } else { switch(myQuery.m_Delimiter) { // Account authorization: Login case MMG_ProtocolDelimiters::ACCOUNT_AUTH_ACCOUNT_REQ: { #ifndef USING_MYSQL_DATABASE // Query the database and determine if credentials were valid uint accProfileId = 0;//Database::AuthUserAccount(myQuery.m_Authenticate.m_Email, myQuery.m_Authenticate.m_Password); DebugLog(L_INFO, "ACCOUNT_AUTH_ACCOUNT_RSP: Sending login response (id %i)", accProfileId); responseDelimiter = MMG_ProtocolDelimiters::ACCOUNT_AUTH_ACCOUNT_RSP; if (accProfileId == WIC_INVALID_ACCOUNT) { cryptMessage.WriteUChar(AuthFailed_BadCredentials); cryptMessage.WriteUChar(0); // auth.mySuccessFlag cryptMessage.WriteUShort(0);// auth.myLatestVersion } else { // Update the maximum client timeout aClient->SetLoginStatus(true); aClient->SetIsPlayer(true); aClient->SetTimeout(WIC_LOGGEDIN_NET_TIMEOUT); cryptMessage.WriteUChar(AuthSuccess); cryptMessage.WriteUChar(1); // auth.mySuccessFlag cryptMessage.WriteUShort(0);// auth.myLatestVersion // Query profile //if (!Database::QueryUserProfile(accProfileId, aClient->GetProfile())) // DebugLog(L_ERROR, "Failed to retrieve profile for valid account "); wcscpy_s(aClient->GetProfile()->m_Name, L"Nukem"); aClient->GetProfile()->m_OnlineStatus = 1; aClient->GetProfile()->m_Rank = 18; aClient->GetProfile()->m_ProfileId = 1234; //aClient->GetProfile()->m_ClanId = 4321; //aClient->GetProfile()->m_RankInClan = 1; // Write the profile info stream aClient->GetProfile()->ToStream(&cryptMessage); MMG_AuthToken *myAuthToken = aClient->GetToken(); //sync client->authtoken.profileid to client->profile.profileid myAuthToken->m_ProfileId = 1234; //myAuthToken->m_ProfileId = aClient->GetProfile()->m_ProfileId; myAuthToken->m_AccountId = 69; // TigerMD5 of ...? (possibly crypt keys) /*myAuthToken->m_Hash.m_Hash[0] = 0x558C0A1C; myAuthToken->m_Hash.m_Hash[1] = 0xA59C9FCA; myAuthToken->m_Hash.m_Hash[2] = 0x6566857D; myAuthToken->m_Hash.m_Hash[3] = 0x8A3FF551; myAuthToken->m_Hash.m_Hash[4] = 0xB69D17E5; myAuthToken->m_Hash.m_Hash[5] = 0xD7BBF74D;*/ memset(&myAuthToken->m_Hash.m_Hash, 0, 6 * sizeof(ulong)); myAuthToken->m_Hash.m_HashLength = 6 * sizeof(ulong); myAuthToken->m_Hash.m_GeneratedFromHashAlgorithm = HASH_ALGORITHM_TIGER; myAuthToken->ToStream(&cryptMessage);// Write the authorization token info stream cryptMessage.WriteUInt(WIC_CREDAUTH_RESEND_S); // periodicityOfCredentialsRequests (How long until the first is sent) cryptMessage.WriteUInt(0); // myLeaseTimeLeft (Limited access key) cryptMessage.WriteUInt(45523626); // myAntiSpoofToken (Random number) } #else //DebugLog(L_INFO, "ACCOUNT_AUTH_ACCOUNT_RSP: Sending login response (id %i)", AccountId); DebugLog(L_INFO, "ACCOUNT_AUTH_ACCOUNT_RSP:"); responseDelimiter = MMG_ProtocolDelimiters::ACCOUNT_AUTH_ACCOUNT_RSP; MMG_AuthToken *myAuthToken = aClient->GetToken(); MMG_Profile *myProfile = aClient->GetProfile(); //password check should be done by massgate server, not by database PasswordHash hasher(8, false); char myPasswordHash[WIC_PASSWORDHASH_MAX_LENGTH]; memset(myPasswordHash, 0, sizeof(myPasswordHash)); char myPasswordMD5[64]; memset(myPasswordMD5, 0, sizeof(myPasswordMD5)); MC_Misc::MD5String(myQuery.m_Authenticate.m_Password, myPasswordMD5); uchar isBanned = 0; uint myStatusCode = 0; uint mySuccessFlag = 0; // Query the database bool AuthQueryOK = MySQLDatabase::ourInstance->AuthUserAccount(myQuery.m_Authenticate.m_Email, myPasswordHash, &isBanned, myAuthToken); // TODO: generate a better authtoken myAuthToken->m_TokenId = MC_MTwister().Random(); //determine if credentials were valid if(myAuthToken->m_AccountId == 0 && AuthQueryOK) //account doesnt exist { myStatusCode = AuthFailed_NoSuchAccount; mySuccessFlag = 0; } else if(!hasher.CheckPassword(myPasswordMD5, myPasswordHash) && AuthQueryOK) //wrong password { myStatusCode = AuthFailed_BadCredentials; mySuccessFlag = 0; } else if(isBanned && AuthQueryOK) //account has been banned { myStatusCode = AuthFailed_AccountBanned; mySuccessFlag = 0; } else if(myAuthToken->m_ProfileId == 0 && AuthQueryOK) //no profiles exist. bring up add profile box { myStatusCode = AuthFailed_RequestedProfileNotFound; mySuccessFlag = 0; } else if(SvClientManager::ourInstance->AccountInUse(myAuthToken->m_AccountId)) { myStatusCode = AuthFailed_AccountInUse; mySuccessFlag = 0; } else if(!AuthQueryOK) // something went wrong executing the query { myStatusCode = AuthFailed_General; //ServerError mySuccessFlag = 0; } else //should be ok to retrieve a profile { // TODO insert missing data if (myAuthToken->m_AccountId > 0) { // update geoip info MySQLDatabase::ourInstance->UpdateRealCountry(myAuthToken->m_AccountId, aClient->GetIPAddress()); // update missing sequence number & cipherkeys for handmade accounts MySQLDatabase::ourInstance->UpdateCDKeyInfo(myAuthToken->m_AccountId, myQuery.m_EncryptionKeySequenceNumber, myQuery.m_CipherKeys); // if current password hash is md5 based, update to blowfish if (!strncmp(myPasswordHash, "$H$", 3)) { memset(myPasswordHash, 0, sizeof(myPasswordHash)); hasher.HashPassword(myPasswordHash, myPasswordMD5); MySQLDatabase::ourInstance->UpdatePassword(myAuthToken->m_AccountId, myPasswordHash); } } bool ProfileQueryOK; //AuthFailed_AccountInUse, AuthFailed_ProfileInUse, AuthFailed_CdKeyInUse, AuthFailed_IllegalCDKey(not using) //profile selection box was used if(myQuery.m_Authenticate.m_UseProfile) { ProfileQueryOK = MySQLDatabase::ourInstance->QueryUserProfile(myAuthToken->m_AccountId, myQuery.m_Authenticate.m_UseProfile, myProfile); //if(myQuery.m_Authenticate.m_UseProfile > 0 && ProfileQueryOK) if(ProfileQueryOK) //ok to login, set active profile { myAuthToken->m_ProfileId = myProfile->m_ProfileId; myStatusCode = AuthSuccess; mySuccessFlag = 1; } else //!ProfileQueryOK // something went wrong executing the query { myStatusCode = AuthFailed_General; //ServerError mySuccessFlag = 0; } } //login button was used else { //if(myQuery.m_Authenticate.m_HasOldCredentials) //myQuery.m_Authenticate.m_Credentials, myQuery.m_Authenticate.m_Profile, myQuery.m_Authenticate.m_UseProfile //myAuthToken, myProfile ProfileQueryOK = MySQLDatabase::ourInstance->QueryUserProfile(myAuthToken->m_AccountId, myAuthToken->m_ProfileId, myProfile); if(myAuthToken->m_ProfileId > 0 && ProfileQueryOK) // ok to login, set active profile { myAuthToken->m_ProfileId = myProfile->m_ProfileId; myStatusCode = AuthSuccess; mySuccessFlag = 1; } else //!ProfileQueryOK // something went wrong executing the query { myStatusCode = AuthFailed_General; //ServerError mySuccessFlag = 0; } } //TODO: use SetLoginStatus() + SetTimeout(), we need //a way to determine if a profile or account is currently in use //auth.myLatestVersion?? // Update the maximum client timeout aClient->SetLoginStatus(true); aClient->SetIsPlayer(true); aClient->SetTimeout(WIC_LOGGEDIN_NET_TIMEOUT); } //write response message to the stream; cryptMessage.WriteUChar(myStatusCode); cryptMessage.WriteUChar(mySuccessFlag); // auth.mySuccessFlag cryptMessage.WriteUShort(0); // auth.myLatestVersion // Write the profile info stream myProfile->ToStream(&cryptMessage); // TigerMD5 of ...? (possibly crypt keys) /*myAuthToken->m_Hash.m_Hash[0] = 0x558C0A1C; myAuthToken->m_Hash.m_Hash[1] = 0xA59C9FCA; myAuthToken->m_Hash.m_Hash[2] = 0x6566857D; myAuthToken->m_Hash.m_Hash[3] = 0x8A3FF551; myAuthToken->m_Hash.m_Hash[4] = 0xB69D17E5; myAuthToken->m_Hash.m_Hash[5] = 0xD7BBF74D; memset(&myAuthToken->m_Hash.m_Hash, 0, 6 * sizeof(ulong));*/ myAuthToken->m_Hash.m_HashLength = 6 * sizeof(ulong); myAuthToken->m_Hash.m_GeneratedFromHashAlgorithm = HASH_ALGORITHM_TIGER; // Write the authorization token info stream myAuthToken->ToStream(&cryptMessage); cryptMessage.WriteUInt(WIC_CREDAUTH_RESEND_S); // periodicityOfCredentialsRequests (How long until the first is sent) cryptMessage.WriteUInt(0); // myLeaseTimeLeft (Limited access key) //cryptMessage.WriteUInt(45523626); // myAntiSpoofToken (Random number) cryptMessage.WriteUInt(myAuthToken->m_TokenId); // TODO #endif } break; // Client request to create a new account case MMG_ProtocolDelimiters::ACCOUNT_CREATE_ACCOUNT_REQ: { DebugLog(L_INFO, "ACCOUNT_CREATE_ACCOUNT_RSP:"); responseDelimiter = MMG_ProtocolDelimiters::ACCOUNT_CREATE_ACCOUNT_RSP; #ifdef USING_MYSQL_DATABASE bool isPrivateKeyUser = false; uint myAccountId = 0; uint myCdkeyId = 0; uint myStatusCode = 0; uint mySuccessFlag = 0; bool CheckEmailQueryOK = MySQLDatabase::ourInstance->CheckIfEmailExists(myQuery.m_Create.m_Email, &myAccountId); bool CheckCDKeyQueryOK = MySQLDatabase::ourInstance->CheckIfCDKeyExists(myQuery.m_EncryptionKeySequenceNumber, &myCdkeyId); if (myAccountId > 0 && CheckEmailQueryOK) { myStatusCode = CreateFailed_EmailExists; mySuccessFlag = 0; } else if (myCdkeyId > 0 && CheckCDKeyQueryOK) { myStatusCode = CreateFailed_CdKeyExhausted; mySuccessFlag = 0; } else if (!CheckEmailQueryOK || !CheckCDKeyQueryOK) { myStatusCode = CreateFailed_General; //ServerError mySuccessFlag = 0; } else { uint checkId = 0; uchar checkValidated = 0; char checkEmail[WIC_EMAIL_MAX_LENGTH]; memset(checkEmail, 0, sizeof(checkEmail)); bool CheckPrivateCDKeyQueryOk = MySQLDatabase::ourInstance->CheckIfPrivateCDKeyUser(myQuery.m_EncryptionKeySequenceNumber, &checkId, checkEmail, &checkValidated); if (checkId > 0 && CheckPrivateCDKeyQueryOk) { // private isPrivateKeyUser = true; uint myPrivateCDKeyId = 0; uint checkAccountId = 0; // todo: extra checks, ip address, cipherkeys? etc bool AuthPrivateCDKeyQueryOk = MySQLDatabase::ourInstance->AuthPrivateCDKey(myQuery.m_EncryptionKeySequenceNumber, myQuery.m_Create.m_Email, &myPrivateCDKeyId, &checkAccountId); if (myPrivateCDKeyId == 0 && AuthPrivateCDKeyQueryOk) { myStatusCode = CreateFailed_General; mySuccessFlag = 0; } else if (checkAccountId > 0 && AuthPrivateCDKeyQueryOk) { myStatusCode = CreateFailed_CdKeyExhausted; mySuccessFlag = 0; } else if (!AuthPrivateCDKeyQueryOk) { myStatusCode = CreateFailed_General; mySuccessFlag = 0; } else { myStatusCode = ActionStatusCodes::Creating; mySuccessFlag = 1; } } else { // purchased key from before the shutdown myStatusCode = ActionStatusCodes::Creating; mySuccessFlag = 1; } if (myStatusCode == ActionStatusCodes::Creating && mySuccessFlag == 1) { char realcountry[WIC_COUNTRY_MAX_LENGTH]; memset(realcountry, 0, sizeof(realcountry)); strcpy_s(realcountry, GeoIP::ClientLocateIP(aClient->GetIPAddress())); PasswordHash hasher(8, false); char myPasswordHash[WIC_PASSWORDHASH_MAX_LENGTH]; memset(myPasswordHash, 0, sizeof(myPasswordHash)); char myPasswordMD5[64]; memset(myPasswordMD5, 0, sizeof(myPasswordMD5)); MC_Misc::MD5String(myQuery.m_Create.m_Password, myPasswordMD5); hasher.HashPassword(myPasswordHash, myPasswordMD5); bool CreateQueryOK = MySQLDatabase::ourInstance->CreateUserAccount(isPrivateKeyUser, myQuery.m_Create.m_Email, myPasswordHash, myQuery.m_Create.m_Country, realcountry, &myQuery.m_Create.m_EmailMeGameRelated, &myQuery.m_Create.m_AcceptsEmail, myQuery.m_EncryptionKeySequenceNumber, myQuery.m_CipherKeys); if (CreateQueryOK) { myStatusCode = CreateSuccess; mySuccessFlag = 1; } else { myStatusCode = CreateFailed_General; //ServerError mySuccessFlag = 0; } } } cryptMessage.WriteUChar(myStatusCode); cryptMessage.WriteUChar(mySuccessFlag); #endif } break; // Prepare (sent before ACCOUNT_CREATE_ACCOUNT_REQ) authorization for cd-key case MMG_ProtocolDelimiters::ACCOUNT_PREPARE_CREATE_ACCOUNT_REQ: { DebugLog(L_INFO, "ACCOUNT_PREPARE_CREATE_ACCOUNT_RSP:"); responseDelimiter = MMG_ProtocolDelimiters::ACCOUNT_PREPARE_CREATE_ACCOUNT_RSP; char country[WIC_COUNTRY_MAX_LENGTH]; // Guessed by IPv4 geolocation information #ifndef USING_MYSQL_DATABASE strcpy_s(country, "US"); #else char* countrycode = GeoIP::ClientLocateIP(aClient->GetIPAddress()); strcpy_s(country, countrycode); #endif cryptMessage.WriteUChar(AuthSuccess); // Otherwise AuthFailed_CdKeyExpired cryptMessage.WriteUChar(1); // mySuccessFlag cryptMessage.WriteString(country); // yourCountry } break; // Client requests a session update to prevent dropping case MMG_ProtocolDelimiters::ACCOUNT_NEW_CREDENTIALS_REQ: { DebugLog(L_INFO, "ACCOUNT_NEW_CREDENTIALS_RSP:"); responseDelimiter = MMG_ProtocolDelimiters::ACCOUNT_NEW_CREDENTIALS_RSP; // Default to success until it's actually implemented (if ever) cryptMessage.WriteUChar(AuthSuccess); // Write the authorization token info stream aClient->GetToken()->ToStream(&cryptMessage); // doCredentialsRequestAgain (in seconds) cryptMessage.WriteUInt(WIC_CREDAUTH_RESEND_S); } break; // Retrieve account profiles list (maximum 5) case MMG_ProtocolDelimiters::ACCOUNT_RETRIEVE_PROFILES_REQ: { DebugLog(L_INFO, "ACCOUNT_RETRIEVE_PROFILES_RSP: getting profiles for %s", myQuery.m_RetrieveProfiles.m_Email); responseDelimiter = MMG_ProtocolDelimiters::ACCOUNT_RETRIEVE_PROFILES_RSP; #ifndef USING_MYSQL_DATABASE cryptMessage.WriteUChar(AuthSuccess); cryptMessage.WriteUChar(1); // mySuccessFlag cryptMessage.WriteUInt(1); // numUserProfiles cryptMessage.WriteUInt(1234); // lastUsedProfileId MMG_Profile *myProfile = aClient->GetProfile(); wcscpy_s(myProfile->m_Name, L"Nukem"); myProfile->m_ProfileId = 1234; //myProfile->m_ClanId = 4321; myProfile->m_OnlineStatus = 0; myProfile->m_Rank = 18; //myProfile->m_RankInClan = 1; // Write the profile info stream myProfile->ToStream(&cryptMessage); #else MMG_AuthToken *myAuthToken = aClient->GetToken(); MMG_Profile myProfiles[5]; PasswordHash hasher(8, false); char myPasswordHash[WIC_PASSWORDHASH_MAX_LENGTH]; memset(myPasswordHash, 0, sizeof(myPasswordHash)); char myPasswordMD5[64]; memset(myPasswordMD5, 0, sizeof(myPasswordMD5)); MC_Misc::MD5String(myQuery.m_RetrieveProfiles.m_Password, myPasswordMD5); uchar isBanned = 0; ulong myProfileCount = 0; uint lastUsedId = 0; uint myStatusCode = 0; uint mySuccessFlag = 0; bool AuthQueryOK = MySQLDatabase::ourInstance->AuthUserAccount(myQuery.m_RetrieveProfiles.m_Email, myPasswordHash, &isBanned, myAuthToken); //determine if credentials were valid if(myAuthToken->m_AccountId == 0 && AuthQueryOK) //account doesnt exist { myStatusCode = AuthFailed_BadCredentials; //AuthFailed_NoSuchAccount mySuccessFlag = 0; } else if(!hasher.CheckPassword(myPasswordMD5, myPasswordHash) && AuthQueryOK) //wrong password { myStatusCode = AuthFailed_BadCredentials; mySuccessFlag = 0; } else if(isBanned && AuthQueryOK) //account has been banned { myStatusCode = AuthFailed_AccountBanned; mySuccessFlag = 0; } else if(SvClientManager::ourInstance->AccountInUse(myAuthToken->m_AccountId)) { myStatusCode = AuthFailed_AccountInUse; mySuccessFlag = 0; } else if(!AuthQueryOK) // something went wrong executing the query { myStatusCode = AuthFailed_General; //ServerError mySuccessFlag = 0; } else //should be ok to retrieve profile list { // TODO insert missing data if (myAuthToken->m_AccountId > 0) { // update geoip info MySQLDatabase::ourInstance->UpdateRealCountry(myAuthToken->m_AccountId, aClient->GetIPAddress()); // update missing sequence number & cipherkeys for handmade accounts MySQLDatabase::ourInstance->UpdateCDKeyInfo(myAuthToken->m_AccountId, myQuery.m_EncryptionKeySequenceNumber, myQuery.m_CipherKeys); // if current password hash is md5 based, update to blowfish if (!strncmp(myPasswordHash, "$H$", 3)) { memset(myPasswordHash, 0, sizeof(myPasswordHash)); hasher.HashPassword(myPasswordHash, myPasswordMD5); MySQLDatabase::ourInstance->UpdatePassword(myAuthToken->m_AccountId, myPasswordHash); } } bool RetrieveProfilesQueryOK = MySQLDatabase::ourInstance->RetrieveUserProfiles(myAuthToken->m_AccountId, &myProfileCount, myProfiles); if(RetrieveProfilesQueryOK) { myStatusCode = AuthSuccess; mySuccessFlag = 1; lastUsedId = myProfiles[0].m_ProfileId; //myAuthToken->m_ProfileId } else { myStatusCode = AuthFailed_General; //ServerError mySuccessFlag = 0; myProfileCount = 0; lastUsedId = 0; } } cryptMessage.WriteUChar(myStatusCode); cryptMessage.WriteUChar(mySuccessFlag); // mySuccessFlag cryptMessage.WriteUInt(myProfileCount); // numUserProfiles cryptMessage.WriteUInt(lastUsedId); // lastUsedProfileId //write profile/s to stream for(uint i=0; i < myProfileCount; i++) myProfiles[i].ToStream(&cryptMessage); #endif } break; case MMG_ProtocolDelimiters::ACCOUNT_MODIFY_PROFILE_REQ: { #ifdef USING_MYSQL_DATABASE //DebugLog(L_INFO, "ACCOUNT_MODIFY_PROFILE_RSP: modify profiles"); //responseDelimiter = MMG_ProtocolDelimiters::ACCOUNT_MODIFY_PROFILE_RSP; responseDelimiter = MMG_ProtocolDelimiters::ACCOUNT_RETRIEVE_PROFILES_RSP; MMG_AuthToken *myAuthToken = aClient->GetToken(); MMG_Profile myProfiles[5]; uchar isBanned = 0; ulong myProfileCount = 0; uint lastUsedId = 0; uint myProfileId = 0; uint myStatusCode = 0; uint mySuccessFlag = 0; if (myQuery.m_ModifyProfile.m_Operation == 'add') { DebugLog(L_INFO, "ACCOUNT_MODIFY_PROFILE_RSP: add new profile for %s", myQuery.m_ModifyProfile.m_Email); bool CheckProfileQueryOK = MySQLDatabase::ourInstance->CheckIfProfileExists(myQuery.m_ModifyProfile.m_Name, &myProfileId); if (myProfileId > 0 && CheckProfileQueryOK) //profile exists with that name { myStatusCode = ModifyFailed_ProfileNameTaken; mySuccessFlag = 1; } else if (!CheckProfileQueryOK) { myStatusCode = ModifyFailed_General; //server / database error mySuccessFlag = 1; } else //should be ok to create profile { bool CreateProfileQueryOK = MySQLDatabase::ourInstance->CreateUserProfile(myAuthToken->m_AccountId, myQuery.m_ModifyProfile.m_Name, myQuery.m_ModifyProfile.m_Email); if(CreateProfileQueryOK) //create profile success { myStatusCode = ModifySuccess; mySuccessFlag = 1; } else //!CreateProfileQueryOK // something went wrong executing the query { myStatusCode = ModifyFailed_General; //ServerError mySuccessFlag = 1; } } } else if (myQuery.m_ModifyProfile.m_Operation == 'del') { DebugLog(L_INFO, "ACCOUNT_MODIFY_PROFILE_RSP: delete profile %d for %s", myQuery.m_ModifyProfile.m_ProfileId, myQuery.m_ModifyProfile.m_Email); MMG_Profile myProfile; bool ProfileQueryOK = MySQLDatabase::ourInstance->QueryProfileName(myQuery.m_ModifyProfile.m_ProfileId, &myProfile); if (!ProfileQueryOK) { myStatusCode = ModifyFailed_General; mySuccessFlag = 0; } else if (ProfileQueryOK && myProfile.m_ClanId > 0) { myStatusCode = DeleteProfile_Failed_Clan; mySuccessFlag = 1; } else { bool DeleteProfileQueryOK = MySQLDatabase::ourInstance->DeleteUserProfile(myAuthToken->m_AccountId, myQuery.m_ModifyProfile.m_ProfileId, myQuery.m_ModifyProfile.m_Email); if(DeleteProfileQueryOK) //delete profile success { myStatusCode = ModifySuccess; mySuccessFlag = 1; } else //!DeleteProfileQueryOK // something went wrong executing the query { myStatusCode = ModifyFailed_General; //ServerError mySuccessFlag = 1; } } } else { DebugLog(L_INFO, "ACCOUNT_MODIFY_PROFILE_RSP: unknown operation"); } // retrieve and send profile list bool RetrieveProfilesQueryOK = MySQLDatabase::ourInstance->RetrieveUserProfiles(myAuthToken->m_AccountId, &myProfileCount, myProfiles); if (RetrieveProfilesQueryOK && mySuccessFlag) { lastUsedId = myProfiles[0].m_ProfileId; //myAuthToken->m_ProfileId } else { myStatusCode = ModifyFailed_General; //ServerError mySuccessFlag = 0; myProfileCount = 0; lastUsedId = 0; } cryptMessage.WriteUChar(myStatusCode); cryptMessage.WriteUChar(mySuccessFlag); // mySuccessFlag cryptMessage.WriteUInt(myProfileCount); // numUserProfiles cryptMessage.WriteUInt(lastUsedId); // lastUsedProfileId //write profile/s to stream for(uint i=0; i < myProfileCount; i++) myProfiles[i].ToStream(&cryptMessage); #endif } break; default: DebugLog(L_WARN, "Unknown delimiter %i", aDelimiter); return false; } } // Write the main message header MN_WriteMessage responseMsg(1024); responseMsg.WriteDelimiter(responseDelimiter); responseMsg.WriteUShort(MassgateProtocolVersion); responseMsg.WriteUChar(aClient->m_CipherIdentifier); responseMsg.WriteUInt(aClient->m_EncryptionKeySequenceNumber); // Encrypt and write the data to the main (outgoing) packet // Packet buffer can be modified because it is no longer used sizeptr_t dataLength = cryptMessage.GetDataLength(); voidptr_t dataStream = cryptMessage.GetDataStream(); if (!MMG_ICipher::EncryptWith(aClient->m_CipherIdentifier, aClient->m_CipherKeys, (uint *)dataStream, dataLength)) return false; responseMsg.WriteUShort(dataLength); responseMsg.WriteRawData(dataStream, dataLength); // Finally send the message if (!aClient->SendData(&responseMsg)) return false; return true; }