
#include "StdAfx.h"

#include "Network.h"
#include "Client.h"
#include "World.h"

// Database access
#include "Database.h"
#include "AccountDatabase.h"
#include "CharacterDatabase.h"

// Network access
#include "crc.h"
#include "BinaryWriter.h"
#include "BinaryReader.h"
#include "PacketController.h"

// NOTES:
// A client can easily perform denial of service attacks by issuing a large number of connection requests if there ever comes a time this matters to fix

CNetwork::CNetwork(SOCKET *sockets, int socketCount)
{
	m_sockets = sockets;
	m_socketCount = socketCount;

	//
	m_wID = 0x0B; // 0x005;

	//
	m_freeslot = 1;
	m_slotrange = 0;

	//
	memset(m_clients, 0, sizeof(m_clients));
}

CNetwork::~CNetwork()
{
	for (int i = 0; i < 400; i++)
	{
		SafeDelete(m_clients[i]);
	}
}

WORD CNetwork::GetServerID(void)
{
	return m_wID;
}

void CNetwork::SendConnectlessBlob(SOCKADDR_IN *peer, BlobPacket_s *blob,
	DWORD dwFlags, DWORD dwSequence = 0, WORD wTime = 0)
{
	BlobHeader_s *header = &blob->header;

	header->dwSequence = dwSequence;
	header->dwFlags = dwFlags;
	header->dwCRC = 0;
	header->wRecID = GetServerID();
	header->wTime = wTime;
	header->wTable = 0x01;

	GenericCRC(blob);

	SendPacket(peer, blob, BLOBLEN(blob));
}

void CNetwork::SendPacket(SOCKADDR_IN *peer, void *data, DWORD len)
{
	SOCKET socket = m_sockets[0];
	if (socket == INVALID_SOCKET) return;

#ifdef _DEBUG
	LOG(Network, Verbose, "Sent:\n");
	LOG_BYTES(Network, Verbose, data, len);
#endif

	sendto(socket, (char *)data, len, 0, (sockaddr *)peer, sizeof(SOCKADDR_IN));
	g_pGlobals->PacketSent(len);
}

void CNetwork::ThinkSocket(SOCKET socket)
{
	if (socket == INVALID_SOCKET) return;

	static BYTE			buffer[0x1E4];
	static int			bloblen;
	static int			clientaddrlen = { sizeof(sockaddr_in) };
	static sockaddr_in	clientaddr;

	while (TRUE)
	{
		// Doing it similar to AC..
		int bloblen = recvfrom(socket, (char *)buffer, 0x1E4, NULL, (sockaddr *)&clientaddr, &clientaddrlen);

		if (bloblen == SOCKET_ERROR)
		{
			DWORD dwCode = WSAGetLastError();

			if (dwCode != 10035)
				LOG(Temp, Normal, "Winsock Error %lu\n", dwCode);
			break;
		}
		else if (!bloblen)
			break;
		else if (bloblen < sizeof(BlobHeader_s))
			continue;

		g_pGlobals->PacketRecv(bloblen);

		BlobPacket_s *blob = reinterpret_cast<BlobPacket_s*>(buffer);
		bloblen -= sizeof(BlobHeader_s);

		WORD wSize = blob->header.wSize;
		WORD wRecID = blob->header.wRecID;

		if (bloblen != wSize)
			continue;

		blob->header.dwCRC -= CalcTransportCRC((DWORD *)blob);

#ifdef _DEBUG
		SOCKADDR_IN addr;
		memset(&addr, 0, sizeof(addr));
		int namelen = sizeof(addr);
		getsockname(socket, (sockaddr *)&addr, &namelen);
		LOG(Network, Verbose, "Received on port %d:\n", ntohs(addr.sin_port));
		LOG_BYTES(Network, Verbose, &blob->header, blob->header.wSize + sizeof(blob->header));
#endif

		if (!wRecID)
		{
			ProcessConnectionless(&clientaddr, blob);
		}
		else
		{
			CClient *client = ValidateClient(wRecID, &clientaddr);
			if (client)
				client->IncomingBlob(blob);
		}
	}

}

void CNetwork::Think()
{
	for (int i = 0; i < m_socketCount; i++)
		ThinkSocket(m_sockets[i]);

	if (m_slotrange >= 400)
		m_slotrange = 399;

	for (WORD index = 1; index <= m_slotrange; index++)
	{
		CClient *client = m_clients[index];
		if (client)
		{
			client->Think();

			if (!client->IsAlive())
				KillClient(index);
		}
	}
}

CClient* CNetwork::GetClient(WORD index)
{
	if (!index || index >= 400)
		return NULL;

	return m_clients[index];
}

void CNetwork::KickClient(CClient *pClient)
{
	if (!pClient)
		return;

	LOG(Temp, Normal, "Client #%u (%s) is being kicked.\n", pClient->GetIndex(), pClient->GetAccount());
	BinaryWriter KC;
	KC.WriteLong(0xF7DC);
	KC.WriteLong(0);

	pClient->SendNetMessage(KC.GetData(), KC.GetSize(), PRIVATE_MSG);
	pClient->ThinkOutbound();
	pClient->Kill(NULL, NULL);
}

void CNetwork::KickClient(WORD index)
{
	KickClient(GetClient(index));
}

CClient* CNetwork::ValidateClient(WORD index, sockaddr_in *peer)
{
	CClient* pClient = GetClient(index);

	if (!pClient)
		return NULL;

	if (!pClient->CheckAddress(peer))
		return NULL;

	return pClient;
}

void CNetwork::KillClient(WORD index)
{

	CClient *pClient = GetClient(index);
	if (!pClient)
		return;

	LOG(Temp, Normal, "Client(%s, %s) disconnected. (%s)\n", pClient->GetAccount(), inet_ntoa(pClient->GetHostAddress()->sin_addr), timestamp());

	delete pClient;
	m_clients[index] = NULL;

	if (index == m_slotrange)	m_slotrange--;
	if (index < m_freeslot)		m_freeslot = index;

	UpdateClientsHUD(m_clients, m_slotrange);
}

CClient* CNetwork::FindClientByAccount(const char* account)
{
	for (WORD index = 1; index <= m_slotrange; index++)
	{
		CClient *client = m_clients[index];
		if (client)
		{
			if (client->CheckAccount(account))
				return client;
		}
	}

	return NULL;
}

void CNetwork::ConnectionRequest(sockaddr_in *addr, BlobPacket_s *p)
{
	BinaryReader cr(p->data, p->header.wSize);

	char *szVersion = cr.ReadString();	//client version stamp

	cr.ReadDWORD(); // 0x20 ?

	enum eAuthMethod {
		ePhatAC = 1,
		eUnk = 2,
		eLiveAC = 3
	};

	DWORD dwAuthMethod = cr.ReadDWORD();

	cr.ReadDWORD(); // 0x0 ?

	DWORD dwUnixTime = cr.ReadDWORD();	//client unix timestamp

	char AC2DLove[20];
	char *szAccount;

	switch (dwAuthMethod)
	{
	case ePhatAC:
		szAccount = cr.ReadString();
		break;
	case eLiveAC:
	{
		DWORD dwTicketLength = cr.ReadDWORD();
		if (dwTicketLength >= 256)
			return;
		cr.ReadArray(dwTicketLength);

		strcpy(AC2DLove, "actwod:love");
		szAccount = AC2DLove;
	}
	break;
	default:
		return;
	}

	DWORD dwPortalStamp = cr.ReadDWORD();
	DWORD dwCellStamp = cr.ReadDWORD();

	if (cr.GetLastError()) return;

	char *szPassword = strstr(szAccount, ":");
	if (!szPassword) return;

	*(szPassword) = '\0';
	szPassword++;

	int accessLevel;
	std::string actualAccount;

	if (!g_pDB->AccountDB()->CheckAccount(szAccount, szPassword, &accessLevel, actualAccount))
	{
		szAccount = (char *)actualAccount.c_str();

		//Bad login.
		CREATEBLOB(BadLogin, sizeof(DWORD));
		*((DWORD *)BadLogin->data) = 0x00000000;

		SendConnectlessBlob(addr, BadLogin, BT_ERROR, NULL);

		DELETEBLOB(BadLogin);
		LOG(Temp, Normal, "Invalid login from %s, used %s:%s\n", inet_ntoa(addr->sin_addr), szAccount, szPassword);
	}
	else
	{
		szAccount = (char *)actualAccount.c_str();

		CClient *pExistingClient = FindClientByAccount(szAccount);

		if (pExistingClient)
		{
			if (_stricmp(pExistingClient->GetAccount(), " admin"))
			{
				KickClient(pExistingClient);
				// TODO don't allow this player to login for a few seconds while the world handles the other player
			}
			else
			{
				return;
			}
		}

		WORD index = GetClientSlot();

		if (index == NULL)
		{
			//Server unavailable.
			CREATEBLOB(ServerFull, sizeof(DWORD));
			*((DWORD *)ServerFull->data) = 0x00000005;

			SendConnectlessBlob(addr, ServerFull, BT_ERROR, NULL);

			DELETEBLOB(ServerFull);
		}

		LOG(Temp, Normal, "Client(%s, %s) connected on slot #%u (%s)\n", szAccount, inet_ntoa(addr->sin_addr), index, timestamp());

		CClient *client = m_clients[index] = new CClient(addr, index, szAccount, accessLevel);
		client->SetLoginData(dwUnixTime, dwPortalStamp, dwCellStamp);

		if (index > m_slotrange) m_slotrange = index;
		if (index == m_freeslot) m_freeslot++;

		//Add the client to the HUD
		UpdateClientsHUD(m_clients, m_slotrange);

		BinaryWriter AcceptConnect;

		//Some server variables.
		AcceptConnect.WriteDouble(g_pGlobals->Time());

		/*
		AcceptConnect.WriteDWORD( 0 );
		AcceptConnect.WriteDWORD( 0 );
		AcceptConnect.WriteWORD( index );

		//Mock the client's version.
		DWORD dwVersionLen = (DWORD)strlen( szVersion );
		AcceptConnect.WriteDWORD( dwVersionLen );
		AcceptConnect.AppendData( szVersion, dwVersionLen );
		*/

		/*
		AcceptConnect.WriteDWORD(0x587335ff);
		AcceptConnect.WriteDWORD(0xbd1ab10b);
		AcceptConnect.WriteWORD(0);
		AcceptConnect.WriteWORD(0);
		*/

		/*
		//Now for the CRC information
		AcceptConnect.WriteDWORD(0x20002000);

		//This CRC information could be expanded, but it would be useless to do so.
		static DWORD CRCData[] =
		{
			//The seeds will be 'AC2D' =)
			0x33667788,
			0x00000008,
			0x08C563FB, 0x3180C716, 0xFEE543FF,
			0x69E3F38A, 0x82F8CF23, 0x8059C4B9,
			0x08B63099, 0xDD80C2E5,	0xADCDADBB,

			0x00000003,
			//AC2D:
			0x8E3E7E34, 0x8E3E7E34, 0x8E3E7E34
			//ACE:
			//0x74EFCAF8, 0x4BFC9E54, 0xBBDF4885
		};

		AcceptConnect.WriteDWORD( sizeof(CRCData) );
		AcceptConnect.AppendData( CRCData, sizeof(CRCData) );
		*/

		/*
		AcceptConnect.WriteDWORD(0xAC2DAC2D);
		AcceptConnect.WriteDWORD(0xAC2DAC2D);
		*/

		/*
		AcceptConnect.WriteDWORD(0);
		AcceptConnect.WriteDWORD(0);
		AcceptConnect.WriteDWORD(0x587335ff);
		AcceptConnect.WriteDWORD(0xbd1ab10b);
		AcceptConnect.WriteWORD(0);
		AcceptConnect.WriteWORD(0);
		AcceptConnect.WriteDWORD(0x45EBD7CD);
		AcceptConnect.WriteDWORD(0x6D54AF60);
		AcceptConnect.WriteDWORD(0);
		*/

		BYTE canned[] = {
			0xbe, 0xc8, 0x8a, 0x58, 0x0b, 0x1e, 0x99, 0x43
		};
		AcceptConnect.WriteData(canned, sizeof(canned));

		/*
		BYTE canned[] = {
			0x13, 0x24, 0x46, 0x80, 0x96, 0x03, 0xc4, 0x41,
			0xbe, 0xc8, 0x8a, 0x58, 0x0b, 0x1e, 0x99, 0x43,
			0x2c, 0x00, 0x00, 0x00, 0x5e, 0x77, 0xb2, 0x33,
			0x0c, 0x76, 0x08, 0x76, 0x02, 0x00, 0x00, 0x00
		};
		AcceptConnect.WriteData(canned, sizeof(canned));
		*/

		AcceptConnect.WriteDWORD(index);
		AcceptConnect.WriteDWORD(0xAC2DAC2D);
		AcceptConnect.WriteDWORD(0xAC2DAC2D);
		AcceptConnect.WriteDWORD(2);

		DWORD dwLength = AcceptConnect.GetSize();

		if (dwLength <= 0x1D0)
		{
			CREATEBLOB(Woot, (WORD)dwLength);
			memcpy(Woot->data, AcceptConnect.GetData(), dwLength);

			SendConnectlessBlob(addr, Woot, BT_LOGINREPLY, 0x00000000);

			DELETEBLOB(Woot);
		}
		else
		{
			LOG(Temp, Normal, "AcceptConnect.GetSize() > 0x1D0");
		}
	}
}

WORD CNetwork::GetClientSlot()
{
	//Find an available slot for a connecting client.

	WORD index = m_freeslot;
	while (index < 400)
	{
		if (!m_clients[index])
			return index;

		index++;
	}

	return NULL;
}

void CNetwork::ProcessConnectionless(sockaddr_in *peer, BlobPacket_s *blob)
{
	DWORD dwFlags = blob->header.dwFlags;

	if (dwFlags == BT_LOGIN)
	{
		//if (blob->header.dwSequence != 1)
		//	LOG(Temp, Normal, "Client connecting with bad sequence?\n");

		if (!IsBannedIP(peer->sin_addr))
		{
			ConnectionRequest(peer, blob);
		}
		return;
	}

	LOG(Network, Verbose, "Unhandled connectionless packet received: 0x%08X Look into this\n", dwFlags);
}

BOOL CNetwork::IsBannedIP(in_addr ip)
{
	return FALSE;
}






