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;
}
Esempio n. 2
0
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;
}