// Include
#include "loginsrv.h"
#include <time.h>

// Local definitions

/* macro for SQL Class checking */
#define SQL_ACCOUNT_CHECK(sqlAccount)           \
	sqlAccount = g_loginSrv->GetSQLAccount( );  \
	if ( sqlAccount == NULL )                   \
		return false;

#define SENDER_CHECK(sender)                    \
	sender = g_loginSrv->GetSender( );          \
	if ( sender == NULL )                       \
		return false;


#pragma pack(1) // 1Byte 정렬 설정

struct Login2Game : Packet
{
	unsigned long  ipv4;
	unsigned short port;
};

#pragma pack( ) // 1Byte 정렬 해제


struct ClientInfo
{
	union
	{
		struct
		{
			// 0 off / 1 on
			bool login      : 1;
			bool logout     : 1;
			bool comeinGame : 1;
		} db;
		long dbData;
	};
	union
	{
		struct
		{
			// 0 off / 1 on
			bool login        : 1;
			bool serverList   : 1;
			bool comeInGame   : 1;
		} request;
		long requestData;
	};
	union
	{
		struct
		{
			// 0 off / 1 on
			bool login        : 1;
			bool comeInGame   : 1;
		} complete;
		long completeData;
	};
	struct
	{
		DWORD login;
		DWORD serverList;
		DWORD comeinGame;
	} delay;

	long memberIdx;
	long loginIdx;

	long serverNum;
	long channelNum;

	char uid[ 17 ];
	char pwd[ 17 ];
};

// Global data
#define MAX_RC5_KEY				16

RC5_32_KEY RC5_32_KEY_TABLE[MAX_RC5_KEY];

BYTE SY_KEY_TABLE[16][4]=
{
	{ 0x17,0xb3,0x67,0xaa },	//0
	{ 0x53,0xf2,0x74,0xb1 },	//1
	{ 0x8c,0x72,0x59,0x32 },	//2
	{ 0x98,0xea,0x55,0x29 },	//3
	{ 0x4B,0x1C,0x15,0x11 },	//4
	{ 0xE8,0x9B,0x3A,0x8A },	//5
	{ 0x0F,0xAD,0x37,0x6C },	//6
	{ 0xD9,0x64,0x28,0x5C },	//7
	{ 0xB3,0xB7,0xEC,0x7B },	//8
	{ 0x9C,0x13,0xFE,0xF5 },	//9
	{ 0x5C,0x55,0x6A,0xD8 },	//10
	{ 0x6C,0xA9,0xDB,0x64 },	//11
	{ 0x5A,0xBE,0xF2,0xF6 },	//12
	{ 0xA6,0x4D,0x8F,0xB9 },	//13
	{ 0x67,0x9F,0x7D,0xF6 },	//14
	{ 0xCA,0x6A,0xA9,0xEF },	//15
};

BYTE SY_RKEY_TABLE[16][4]=
{
	{ 0xC0,0x3C,0x06,0xEA },	//0
	{ 0xB9,0x78,0xB3,0x45 },	//1
	{ 0x88,0x48,0xC0,0x71 },	//2
	{ 0x85,0x4C,0xB6,0xE7 },	//3
	{ 0x98,0xAE,0xD9,0xBF },	//4
	{ 0x2D,0x83,0x06,0x55 },	//5
	{ 0x44,0x95,0xAF,0x99 },	//6
	{ 0x43,0xC8,0x53,0x63 },	//7
	{ 0x3B,0xB1,0x60,0x0B },	//8
	{ 0x19,0x2D,0x57,0xC6 },	//9
	{ 0x68,0xCC,0x56,0x3A },	//10
	{ 0x5C,0xF8,0x90,0x4A },	//11
	{ 0xA1,0x8A,0x0B,0x70 },	//12
	{ 0x28,0x45,0x80,0xB1 },	//13
	{ 0x71,0x9D,0x4A,0x80 },	//14
	{ 0x54,0x1B,0x0F,0x9C },	//15
};

// cLoginProcess Constructor.
cLoginProcess::cLoginProcess(void) : mServerManager(NULL)
{
	RC5_32_KEY*   rc532Key  = RC5_32_KEY_TABLE;		//
	unsigned char tiv1[ 4 ] = {'I','R','I','S'};	//
	unsigned char tiv2[ 4 ] = {'S','I','R','I'};	//
	DWORD         skey[ 2 ];						// 4Byte * 2  = 64bit

	memset( RC5_32_KEY_TABLE, 0, sizeof(RC5_32_KEY_TABLE) );

	for ( int i = 0; i < MAX_RC5_KEY; i++, rc532Key++ )
	{
		skey[ 0 ] = (*(DWORD*)(SY_RKEY_TABLE + i)) ^ (*(DWORD*)tiv1);
		skey[ 1 ] = (*(DWORD*)(SY_RKEY_TABLE + i)) ^ (*(DWORD*)tiv2);

		RC5_32_set_key( rc532Key, sizeof(skey), (unsigned char*)&skey, RC5_8_ROUNDS );
	}

	mRequestData        = 0;
	mCompleteData       = 0;

	mFrame              = 0;
	mFpsTick            = 0;

	mThreadPriority     = false;
	mThreadPriorityTick = 0;

	mServerManager   = new cServerManager( );
	ZeroMemory( &mSystemTime, sizeof(mSystemTime) );
	mConcurrentUser = 0;
}

// ~cLoginProcess Destructor.
cLoginProcess::~cLoginProcess(void)
{
	delete mServerManager;
	mServerManager = NULL;
}

// Initialize Method
bool cLoginProcess::Initialize(char* ipAddr, unsigned short port, unsigned short numWorkerThreads)
{
	// IocpServer 초기화.
	return cIocpServer::Initialize( ipAddr, port, numWorkerThreads );
}

// Shutdown Method
void cLoginProcess::Shutdown(DWORD maxWait)
{
	// IocpServer 종료.
	cIocpServer::Shutdown( maxWait );
}

// PostServerEvent Method
bool cLoginProcess::PostServerEvent(LPCTSTR format, ...)
{
	bool retvalue = false;
	if ( g_loginSrv != NULL )
	{
		LPVOID  msgBuf = NULL;
		DWORD   bufferLength;

		va_list args;

		va_start( args, format );

		bufferLength = _vscprintf( format, args ) + 1;
		msgBuf       = malloc( bufferLength );

		vsprintf( (char*)msgBuf, format, args );

		va_end( args );

		if ( msgBuf != NULL )
		{
			retvalue = g_loginSrv->PostServerEvent( (char*)msgBuf );
			free( msgBuf );
		}
	}
	return retvalue;
}

// PostConcurrentEvent Method
bool cLoginProcess::PostConcurrentEvent(long serverIdx, long minimum, long maximum, LPCTSTR format, ...)
{
	cSender* sender   = g_loginSrv->GetSender( );
	bool     retvalue = false;
	SENDER_CHECK( sender );

	LPVOID  msgBuf = NULL;
	DWORD   bufferLength;

	va_list args;

	va_start( args, format );

	bufferLength = _vscprintf( format, args ) + 1;
	msgBuf       = malloc( bufferLength );

	vsprintf( (char*)msgBuf, format, args );

	va_end( args );

	if ( msgBuf != NULL )
	{
		retvalue = sender->PostConcurrentEvent( serverIdx, minimum, maximum, (char*)msgBuf );
		free( msgBuf );
	}

	return retvalue;
}

// PostMemberEvent Method
bool cLoginProcess::PostMemberEvent(char type, char category, long memberIdx, char* message)
{
	cSender* sender = g_loginSrv->GetSender( );
	SENDER_CHECK( sender );
	return sender->PostMemberEvent( type, category, memberIdx, message );
}

// RecvChStatus Method
void cLoginProcess::ChannelStatus(long cid, BYTE status)
{
	cCSLock lock( &mCs );
	bool    eventLog = true;

	switch ( status )
	{
	case _E_STATUS_RUNABLE_:
		if ( mServerManager->GetChannel( cid, status ) != NULL )
			ChannelUpdate( HIWORD(cid), LOWORD(cid), status );
		break;

	case _E_STATUS_RUNNING_S1_:
	case _E_STATUS_RUNNING_S2_:
	case _E_STATUS_RUNNING_S3_:
		if ( mServerManager->CompareChannel( cid, status ) == true )
			eventLog = false;
		else if ( mServerManager->UpdateChannel( cid, status ) == true )
			ChannelUpdate( HIWORD(cid), LOWORD(cid), status );
		else if ( mServerManager->GetChannel( cid, status ) != NULL )
			ChannelUpdate( HIWORD(cid), LOWORD(cid), status );
		break;

	case _E_STATUS_CLOSING_:
	case _E_STATUS_ERROR_:
		mServerManager->ReleaseChannel( cid );
		break;

	default:
		eventLog = false;
		break;
	}

	if ( eventLog )
		PostServerEvent( "ChannelStatus - ServerNum(=%d):ChannelNum(=%d):Status(=%d)", HIWORD(cid), LOWORD(cid), status );
}

// GetMsgRoot Method
MSGROOT* cLoginProcess::GetMsgRoot(void** handle, PerSocketContext* perSocketContext)
{
	PerIoContext*     perIoContext     = NULL;
	Packet*           packet           = NULL;
	char*             buffer           = NULL;

	perIoContext = mIoContextPool->GetIoContext( perSocketContext->socket, IOCP_REQUEST_WRITE );
	if ( perIoContext != NULL )
	{
		packet       = (Packet*)perIoContext->buffer;

		packet->ver  = PHVer;
		packet->hlen = PHLen;
		packet->tos  = TOS_LOGIN;
		packet->tlen = packet->hlen;

		(*handle) = perIoContext;
		buffer = (perIoContext->buffer + packet->hlen);
		return (MSGROOT*)buffer;
	}

	return NULL;
}

// SendMsgRoot Method
bool cLoginProcess::SendMsgRoot(void* handle, unsigned long length)
{
	PerIoContext* perIoContext = (PerIoContext*)handle;
	Packet*       packet       = (Packet*)perIoContext->buffer;

	packet->tlen = packet->hlen + (u_short)length;

	perIoContext->offset = packet->tlen;
	return SendPost( perIoContext );
}

// SendMsgError Method
bool cLoginProcess::SendMsgError(PerSocketContext* perSocketContext, char category, char protocol, int result )
{
	HANDLE     handle  = NULL;
	MSG_ERROR* sendMsg = (MSG_ERROR*)GetMsgRoot( &handle, perSocketContext );

	if ( sendMsg != NULL )
	{
		sendMsg->Category  = category;
		sendMsg->Protocol  = protocol;
		sendMsg->ErrorCode = result;

		return SendMsgRoot( handle, sizeof(MSG_ERROR) );
	}

	return false;
}

// MatrixCreate Method
bool cLoginProcess::MatrixCreate(long maxServer, long maxChannel)
{
	cSQLAccount* sqlAccount = NULL;
	SQL_ACCOUNT_CHECK( sqlAccount );

	PerIoContext*  cbIoContext  = mIoContextPool->GetIoContext( NULL, IOCP_REQUEST_CALLBACK );
	MATRIX_CREATE* matrixCreate = (MATRIX_CREATE*)cbIoContext->buffer;
	BOOL           retvalue;

	matrixCreate->maxServer  = maxServer;
	matrixCreate->maxChannel = maxChannel;

	cbIoContext->offset = sizeof(MATRIX_CREATE);
	cbIoContext->iParam = SQL_SERVER_PROC_MATRIX_CREATE;

	// cSQLAccount를 통해 SQLServer로 요청.
	retvalue = sqlAccount->QueueRequest( NULL, (LPOVERLAPPED)cbIoContext, cbIoContext->offset );
	return (retvalue == TRUE) ? true : false;
}

// ServerList Method
bool cLoginProcess::ServerList( )
{
	cSQLAccount* sqlAccount = NULL;
	SQL_ACCOUNT_CHECK( sqlAccount );

	PerIoContext* cbIoContext  = mIoContextPool->GetIoContext( NULL, IOCP_REQUEST_CALLBACK );
	SERVER_LIST*  serverList = (SERVER_LIST*)cbIoContext->buffer;
	BOOL          retvalue;

	cbIoContext->offset = sizeof(SERVER_LIST) - sizeof(serverList->table);
	cbIoContext->iParam = SQL_SERVER_PROC_SERVER_LIST;

	// SQLAccount를 통해 SQLServer로 요청.
	retvalue = sqlAccount->QueueRequest( NULL, (LPOVERLAPPED)cbIoContext, cbIoContext->offset );
	return (retvalue == TRUE) ? true : false;
}

// ServerDown Method
bool cLoginProcess::ServerDown(long serverNum, long channelNum)
{
	cSQLAccount* sqlAccount = NULL;
	SQL_ACCOUNT_CHECK( sqlAccount );

	PerIoContext* cbIoContext = mIoContextPool->GetIoContext( NULL, IOCP_REQUEST_CALLBACK );
	SERVER_DOWN*  serverDown  = (SERVER_DOWN*)cbIoContext->buffer;
	BOOL          retvalue;

	serverDown->serverNum  = serverNum;
	serverDown->channelNum = channelNum;

	cbIoContext->offset = sizeof(SERVER_DOWN);
	cbIoContext->iParam = SQL_SERVER_PROC_SERVER_DOWN;

	// SQLAccount를 통해 SQLServer로 요청.
	retvalue = sqlAccount->QueueRequest( NULL, (LPOVERLAPPED)cbIoContext, cbIoContext->offset );
	return (retvalue == TRUE) ? true : false;
}

// ServerUpdate Method
bool cLoginProcess::ChannelUpdate(long serverNum, long channelNum, BYTE status)
{
	cSQLAccount* sqlAccount = NULL;
	SQL_ACCOUNT_CHECK( sqlAccount );

	PerIoContext*   cbIoContext  = mIoContextPool->GetIoContext( NULL, IOCP_REQUEST_CALLBACK );
	CHANNEL_UPDATE* channelUpdate = (CHANNEL_UPDATE*)cbIoContext->buffer;
	BOOL            retvalue;

	channelUpdate->serverNum  = serverNum;
	channelUpdate->channelNum = channelNum;
	channelUpdate->status     = status;
	channelUpdate->retvalue   = 0;

	cbIoContext->offset = sizeof(CHANNEL_UPDATE);
	cbIoContext->iParam = SQL_SERVER_PROC_CHANNEL_UPDATE;

	// cSQLAccount를 통해 SQLServer로 요청.
	retvalue = sqlAccount->QueueRequest( NULL, (LPOVERLAPPED)cbIoContext, cbIoContext->offset );
	return (retvalue == TRUE) ? true : false;
}

// ConcurrentUser Method
bool cLoginProcess::ConcurrentUser(long serverNum)
{
	cSQLAccount* sqlAccount = NULL;
	SQL_ACCOUNT_CHECK( sqlAccount );

	PerIoContext*    cbIoContext    = mIoContextPool->GetIoContext( NULL, IOCP_REQUEST_CALLBACK );
	CONCURRENT_USER* concurrentUser = (CONCURRENT_USER*)cbIoContext->buffer;
	BOOL             retvalue;

	concurrentUser->serverIdx = serverNum;

	cbIoContext->offset = sizeof(CONCURRENT_USER);
	cbIoContext->iParam = SQL_SERVER_PROC_CONCURRENT_USER;

	// cSQLAccount를 통해 SQLServer로 요청.
	retvalue = sqlAccount->QueueRequest( NULL, (LPOVERLAPPED)cbIoContext, cbIoContext->offset );
	return (retvalue == TRUE) ? true : false;
}

// MemberLogin Method
bool cLoginProcess::MemberLogin(PerSocketContext* perSocketContext, char* uid, char* pwd)
{
	cSQLAccount* sqlAccount = NULL;
	SQL_ACCOUNT_CHECK( sqlAccount );

	SOCKADDR_IN&  addr        = perSocketContext->addr;

	PerIoContext* cbIoContext = mIoContextPool->GetIoContext( perSocketContext->socket, IOCP_REQUEST_CALLBACK );
	MEMBER_LOGIN* memberLogin = (MEMBER_LOGIN*)cbIoContext->buffer;
	char*         inetnum     = inet_ntoa( perSocketContext->addr.sin_addr );
	BOOL          retvalue;

	memberLogin->memberIdx = 0;
	memberLogin->loginIdx  = 0;
	strcpy( memberLogin->loginid,  uid );
	strcpy( memberLogin->password, pwd );
	strcpy( memberLogin->inetnum, inetnum );
	memberLogin->slevel    = GetServerSec( );
	memberLogin->retvalue  = 0;

	cbIoContext->offset = sizeof(MEMBER_LOGIN);
	cbIoContext->iParam = SQL_CLIENT_PROC_MEMBER_LOGIN;

	// cSQLAccount를 통해 SQLServer로 요청.
	retvalue = sqlAccount->QueueRequest( (ULONG_PTR)perSocketContext, (LPOVERLAPPED)cbIoContext, cbIoContext->offset );
	return (retvalue == TRUE) ? true : false;
}

// MemberLogout Method
bool cLoginProcess::MemberLogout(PerSocketContext* perSocketContext, long loginIdx, long memberIdx)
{
	cSQLAccount* sqlAccount = NULL;
	SQL_ACCOUNT_CHECK( sqlAccount );

	PerIoContext*  cbIoContext = mIoContextPool->GetIoContext( perSocketContext->socket, IOCP_REQUEST_CALLBACK );
	MEMBER_LOGOUT* memberLogout = (MEMBER_LOGOUT*)cbIoContext->buffer;
	BOOL           retvalue;

	memberLogout->loginIdx  = loginIdx;
	memberLogout->memberIdx = memberIdx;
	memberLogout->retvalue  = 0;

	cbIoContext->offset = sizeof(MEMBER_LOGOUT);
	cbIoContext->iParam = SQL_CLIENT_PROC_MEMBER_LOGOUT;

	// cSQLAccount를 통해 SQLServer로 요청.
	retvalue = sqlAccount->QueueRequest( (ULONG_PTR)perSocketContext, (LPOVERLAPPED)cbIoContext, cbIoContext->offset );
	return (retvalue == TRUE) ? true : false;
}

// ComeinGame Method
bool cLoginProcess::ComeinGame(PerSocketContext* perSocketContext, long loginIdx, short serverNum, short channelNum, short lastChannel)
{
	cSQLAccount* sqlAccount = NULL;
	SQL_ACCOUNT_CHECK( sqlAccount );

	PerIoContext*  cbIoContext = mIoContextPool->GetIoContext( perSocketContext->socket, IOCP_REQUEST_CALLBACK );
	COMEIN_GAME*   comeinGame  = (COMEIN_GAME*)cbIoContext->buffer;
	BOOL           retvalue;

	comeinGame->loginIdx    = loginIdx;
	comeinGame->serverNum   = serverNum;
	comeinGame->channelNum  = channelNum;
	comeinGame->lastChannel = lastChannel;

	cbIoContext->offset = sizeof(COMEIN_GAME);
	cbIoContext->iParam = SQL_CLIENT_PROC_COMEIN_GAME;

	// cSQLAccount를 통해 SQLServer로 요청.
	retvalue = sqlAccount->QueueRequest( (ULONG_PTR)perSocketContext, (LPOVERLAPPED)cbIoContext, cbIoContext->offset );
	return (retvalue == TRUE) ? true : false;
}

// LoginSuccess Method
bool cLoginProcess::LoginSuccess(PerSocketContext* perSocketContext, long result, long memberIdx, long loginIdx)
{
	HANDLE             handle  = NULL;
	MSG_DIST_USERINFO* sendMsg = (MSG_DIST_USERINFO*)GetMsgRoot( &handle, perSocketContext );

	if ( sendMsg != NULL )
	{
		sendMsg->Category      = NM_USER;
		sendMsg->Protocol      = NM_USER_LOGIN_RES;
		sendMsg->ErrorCode     = ERROR_DIST_USERINFO_SUCCESS;
		sendMsg->connectionIdx = (u_long)perSocketContext->cid;
		sendMsg->UserIdx       = memberIdx;
		sendMsg->LoginIdx      = loginIdx;

		return SendMsgRoot( handle, sizeof(MSG_DIST_USERINFO) );
	}

	return false;
}

// ServerList Method
bool cLoginProcess::ServerList(PerSocketContext* perSocketContext, ServerTable* serverTable, char category, char protocol)
{
	HANDLE              handle  = NULL;
	MSG_RES_SERVERLIST* sendMsg = (MSG_RES_SERVERLIST*)GetMsgRoot( &handle, perSocketContext );

	if ( sendMsg != NULL )
	{
		ClientInfo*    clientInfo = (ClientInfo*)perSocketContext->buffer;
		unsigned long  length     = sizeof(MSG_RES_SERVERLIST) - sizeof(sendMsg->Servers);
		unsigned long& rowCount   = sendMsg->RowCount;

		sendMsg->Category = category;
		sendMsg->Protocol = protocol;
		sendMsg->ServerNo = clientInfo->serverNum;

		for ( int i = 0; i < MAX_SERVER; i++, serverTable++ )
		{
			if ( serverTable->idx > 0 && serverTable->channelCounter > 0 )
			{
				sendMsg->Servers[ rowCount ].NumOfServer = serverTable->idx;
				wcscpy( sendMsg->Servers[ rowCount ].Name, serverTable->name );
				sendMsg->Servers[ rowCount ].Status = serverTable->status;
				rowCount++;
			}
		}

		length += (rowCount * sizeof(sendMsg->Servers) );
		return SendMsgRoot( handle, length );
	}

	return false;
}

// ChannelList Method
bool cLoginProcess::ChannelList(PerSocketContext* perSocketContext, PerChannel* perChannel, char category, char protocol)
{
	HANDLE                   handle  = NULL;
	MSG_RES_SERVTOOL_CHLIST* sendMsg = (MSG_RES_SERVTOOL_CHLIST*)GetMsgRoot( &handle, perSocketContext );

	if ( sendMsg != NULL )
	{
		long length = sizeof(MSG_RES_SERVTOOL_CHLIST) - sizeof(sendMsg->mChannels);

		sendMsg->Category  = NM_SERVERTOOL;
		sendMsg->Protocol  = NM_SERVERTOOL_CHANNEL_LIST_RES;

		for ( int i = 0; i < MAX_CHANNEL && perChannel != NULL; i++ )
		{
			// 채널정보 복사.
			sendMsg->mChannels[ i ].mNumOfChannel = LOWORD( perChannel->cid );
			strcpy( sendMsg->mChannels[ i ].mInetnum, "0.0.0.0" );
			sendMsg->mChannels[ i ].mStatus = perChannel->status;
			sendMsg->mRowCount++;

			perChannel = perChannel->next;
		}

		length += (sendMsg->mRowCount* sizeof(sendMsg->mChannels));
		return SendMsgRoot( handle, length );
	}

	return false;
}

// ComeinGame Method
bool cLoginProcess::ComeinGame(PerSocketContext* perSocketContext, long ipv4, short port)
{
	PerIoContext* sendIoContext = mIoContextPool->GetIoContext( perSocketContext->socket, IOCP_REQUEST_WRITE );
	Login2Game*   packet        = (Login2Game*)sendIoContext->buffer;

	packet->ver  = PHVer;
	packet->hlen = PHLen;
	packet->tos  = TOS_LOGIN_2_GAME;
	packet->tlen = sizeof(Login2Game);
	packet->ipv4 = ipv4;
	packet->port = port;

	sendIoContext->offset = packet->tlen;
	return SendPost( sendIoContext );
}

// Decrypt Method
void cLoginProcess::Decrypt(BYTE* ptr, RC5_32_KEY key)
{
	unsigned char tiv[8] = {'I','R','I','S','I','R','I','S'};
	int           num    = 0;

	LONG  decryptlen = ((Packet*)ptr)->tlen;
	BYTE* decryptptr = ptr;

	decryptlen -= PHLen;
	decryptptr += PHLen;

	if ( decryptlen > 0 )
	{
		RC5_32_cfb64_encrypt( decryptptr, decryptptr, decryptlen, &key, tiv, &num, RC5_DECRYPT );
	}
}

// Encrypt Method
void cLoginProcess::Encrypt(BYTE* ptr, RC5_32_KEY key)
{
	unsigned char tiv[8] = {'I','R','I','S','I','R','I','S'};
	int           num    = 0;

	LONG  encryptlen = ((Packet*)ptr)->tlen;
	BYTE* encryptptr = ptr;

	encryptlen -= PHLen;
	encryptptr += PHLen;

	if ( encryptlen > 0 )
	{
		RC5_32_cfb64_encrypt( encryptptr, encryptptr, encryptlen, &key, tiv, &num, RC5_ENCRYPT);
	}
}

// BatchComplete Method
bool cLoginProcess::BatchComplete(PerSocketContext* perSocketContext, void* ptr)
{
	ClientInfo* clientInfo = (ClientInfo*)perSocketContext->buffer;

	Packet*     packet     = (Packet*)ptr;
	MSGROOT*    msgRoot    = (MSGROOT*)((char*)ptr + packet->hlen);

	if ( g_packet == true )
	{
		// 패킷해더(Packet Header) 정보 출력 - 버전/헤더길이/서비스 종류/전체길이.
		printf( "CLIENTPROCESS::RECV COMPLETE::VER:%d/HLEN:%d/TOS:%d/TLEN:%d\n",
				packet->ver,
				packet->hlen,
				packet->tos,
				packet->tlen );

		printf( "\tMSGROOT::Category:%d/Protocol:%d\n", msgRoot->Category, msgRoot->Protocol );
	}

	switch( msgRoot->Category )
	{
	default:
		switch ( msgRoot->Protocol )
		{
		case NM_USER_LOGIN_REQ:
			try
			{
				MSG_IDPASS* msg = (MSG_IDPASS*)msgRoot;

				if ( clientInfo->complete.login || clientInfo->request.login || clientInfo->db.login )
					throw ERROR_DIST_USERINFO_FAIL;

				msg->uid[ MAX_ID_SIZE   ] = 0;
				msg->pwd[ MAX_PASS_SIZE ] = 0;

				strcpy( clientInfo->uid, msg->uid );
				strcpy( clientInfo->pwd, msg->pwd );

				clientInfo->request.login = true;
				clientInfo->delay.login = GetTickCount( ) + 500;
			}
			catch ( int error )
			{
				SendMsgError( perSocketContext, NM_USER, NM_USER_LOGIN_RES, error );
			}
			break;

		case NM_USER_SERVER_LIST_REQ:
			if ( clientInfo->complete.login && !clientInfo->request.serverList )
			{
				clientInfo->request.serverList = true;
				clientInfo->delay.serverList = GetTickCount( ) + 500;
			}
			break;

		case NM_USER_COME_IN_GAME_REQ:
			try
			{
				if ( clientInfo->complete.login && !clientInfo->request.comeInGame )
				{
					MSG_REQ_COMEIN_GAME* msg = (MSG_REQ_COMEIN_GAME*)msgRoot;
					long                 cid;
					long                 result;

					if ( clientInfo->serverNum == msg->NumOfServer )
						cid = MAKECID( (WORD)msg->NumOfServer, (WORD)clientInfo->channelNum );
					else
						cid = MAKECID( (WORD)msg->NumOfServer, 0);

					result = mServerManager->FindChannel( cid );

					if ( result != 0 )
					{
						clientInfo->serverNum  = HIWORD( result );
						clientInfo->channelNum = LOWORD( result );

						clientInfo->request.comeInGame = true;
						clientInfo->delay.comeinGame = GetTickCount( ) + 500;
					}
					else
						throw ERROR_COMEIN_GAME_FAIL;
				}
			}
			catch ( int error )
			{
				SendMsgError( perSocketContext, NM_USER, NM_USER_COME_IN_GAME_RES, error );
			}
			break;
		default:
			return false;
		}
		break;
	case NM_SERVERTOOL:
		/*-- TOS_SERVERTOOL 메세지는 암호화 체크를 하지 않아 tos검사를 해준다.
		*/
		if( packet->tos != TOS_SERVERTOOL )
			return false;

		switch( msgRoot->Protocol )
		{
		case NM_SERVERTOOL_PING_REQ:
			{
				MSG_REQ_SRVTOOL_PING* reqMsg = (MSG_REQ_SRVTOOL_PING*)msgRoot;

				HANDLE     handle  = NULL;
				MSG_RES_SRVTOOL_PING* sendMsg = (MSG_RES_SRVTOOL_PING*)GetMsgRoot( &handle, perSocketContext );

				if ( sendMsg != NULL )
				{
					sendMsg->Category = NM_SERVERTOOL;
					sendMsg->Protocol = NM_SERVERTOOL_PING_RES;
					sendMsg->mToolSendTime = reqMsg->mToolSendTime;
					sendMsg->mUserIn = mComplete.serverSec;

					return SendMsgRoot( handle, sizeof(MSG_RES_SRVTOOL_PING) );
				}

				return false;
			}
			break;
		case NM_SERVERTOOL_SERVER_LIST_REQ:
			{
				ServerTable* serverTable = mServerManager->GetServerTable( );
				ServerList( perSocketContext, serverTable, NM_SERVERTOOL, NM_SERVERTOOL_SERVER_LIST_RES );
			}
			break;
		case NM_SERVERTOOL_CHANNEL_LIST_REQ:
			{
				MSG_REQ_SERVTOOL_CHLIST* reqMsg      = (MSG_REQ_SERVTOOL_CHLIST*)msgRoot;
				ServerTable*             serverTable = mServerManager->GetServerTable( MAKECID( (BYTE)reqMsg->mServerNum, 0 ) );
				ChannelList( perSocketContext, serverTable->pool, NM_SERVERTOOL, NM_SERVERTOOL_CHANNEL_LIST_RES );
			}
			break;
		case NM_SERVERTOOL_USERIN_REQ:
			{
				mComplete.serverSec = true;
			}
			break;
		default:
			return false;
		}
		break;
	}

	return true;
}

// SendExec Method
bool cLoginProcess::SendExec(PerIoContext* perIoContext)
{
	WSABUF wsaBuf;
	DWORD  sendBytes = 0;
	DWORD  flags     = 0;
	int    retcode;

	wsaBuf.len = perIoContext->offset;
	wsaBuf.buf = perIoContext->buffer;

	if( *(perIoContext->buffer + PHLen) != NM_SERVERTOOL )
	{
		BYTE key = (BYTE)(rand( ) & 0x0f);
		(*wsaBuf.buf) = ((*wsaBuf.buf) & 0xf0) | key;

		Encrypt( (BYTE*)wsaBuf.buf, RC5_32_KEY_TABLE[ ((*wsaBuf.buf) & 0x0f) ] );
	}

	retcode = WSASend( perIoContext->socket,
					   &wsaBuf,
					   1,
					   &sendBytes,
					   flags,
					   &(perIoContext->wsaOverlapped),
					   NULL );

	if ( (retcode == SOCKET_ERROR) && (WSAGetLastError() != WSA_IO_PENDING) )
	{
		mIoContextPool->ReleaseIoContext( perIoContext );
		return false;
	}
	return true;
}

// AcceptComplete Method
bool cLoginProcess::AcceptComplete(PerSocketContext* perSocketContext)
{
	cCSLock lock( &mCs );

	if ( g_verbose == true )
	{
		char* inetnum = inet_ntoa( perSocketContext->addr.sin_addr );

		printf( "cLoginProcess::AcceptComplete\n" );
		printf( "\t[SOCKET:%d] %s (에서) 연결되었습니다.\n", perSocketContext->socket, inetnum );
	}

	// 클라이언트 메모리(버퍼) 사용량 설정.
	perSocketContext->offset = (long)sizeof(ClientInfo);

	// 로그인서버 패킷정보.
	PerIoContext* perIoContext = mIoContextPool->GetIoContext( perSocketContext->socket, IOCP_REQUEST_WRITE );
	Packet*       packet       = (Packet*)perIoContext->buffer;

	packet->ver  = PHVer;
	packet->hlen = PHLen;
	packet->tos  = TOS_LOGIN_ACCEPT;
	packet->tlen = packet->hlen;

	perIoContext->offset = packet->tlen;
	return SendPost( perIoContext );
}

// RecvComplete Method
bool cLoginProcess::RecvComplete(PerSocketContext* perSocketContext, PerIoContext* perIoContext, DWORD bytesTransferred)
{
	cCSLock lock( &mCs );
	char*   recvBuf    = (perSocketContext->buffer + (perSocketContext->offset + perSocketContext->InternalHigh));
	DWORD   recvBufLen = (perSocketContext->length - (perSocketContext->offset + perSocketContext->InternalHigh));

	// 오프셋의 크기를 누적한다.
	perIoContext->offset += bytesTransferred;

	DWORD length = perIoContext->offset; // 수신된 누적버퍼의 크기
	DWORD offset = 0;                    // 수신된 누적버퍼의 오프셋

	while ( length >= PHLen )
	{
		Packet* packet    = (Packet*)(perIoContext->buffer + offset);
		DWORD   packetLen = packet->tlen;

		// 패킷의 헤더정보를 확인하여 클라이언트가 아닐경우 오류처리 한다.
		if ( !(packet->tos < TOS_MAX) || packet->hlen != PHLen )
		{
			Close( perSocketContext, perIoContext );
			return false;
		}

		// 데이터의 길이가 최소 단위보다 작을 경우 오류 처리해야 한다.
		if ( packetLen < PHLen )
		{
			Close( perSocketContext, perIoContext );
			return false;
		}

		// 데이터의 길이가 완전할때까지 이어 받기를 해야 한다.
		if ( packetLen > length )
			break;

		// Type of service를 검사한다.
		switch ( packet->tos )
		{
		case TOS_CLIENT:
			Decrypt( (BYTE*)packet, RC5_32_KEY_TABLE[ packet->ver ] );
		case TOS_SERVERTOOL:
			// Receive Data 복사.
			if ( packetLen <= recvBufLen )
			{
				memcpy( recvBuf, packet, packetLen );

				recvBuf                        += packetLen;
				recvBufLen                     -= packetLen;
				perSocketContext->InternalHigh += packetLen;
			}
			else
			{
				// Buffer Overflow (버퍼 오버플로).
				Close( perSocketContext, perIoContext );
				return false;
			}
			break;
		case TOS_TTL:
			perSocketContext->timeToLive = (GetTickCount( ) + MAX_TTL);     // 최대 03분
//			perSocketContext->timeToLive = (GetTickCount( ) + 0x1B7740);    // 최대 30분
			break;
		default:
			// Error.
			Close( perSocketContext, perIoContext );
			return false;
		}

		// 처리후 Offset 및 Length를 다시 계산한다.
		length -= packetLen;
		offset += packetLen;
	}

	// 이어 받기를 위해 데이터를 보관한한다.
	if ( offset > 0 )
	{
		PerIoContext* recvIoContext = mIoContextPool->GetIoContext( perSocketContext->socket, IOCP_REQUEST_READ );

		if ( length > 0 )
		{
			memcpy( recvIoContext->buffer, (perIoContext->buffer + offset), length );
			recvIoContext->offset = length;
		}

		mIoContextPool->ReleaseIoContext( perIoContext );
		perIoContext = recvIoContext;
	}

	// 수신을 위해 I/O Context를 준비한다.
	if ( RecvPost( perIoContext ) == false )
	{
		Close( perSocketContext );
		return false;
	}
	return true;
}

// CallbackComplete Method - cCSLock lock( &mCs )는 개별 사용한다.
bool cLoginProcess::CallbackComplete(PerSocketContext* perSocketContext, PerIoContext* perIoContext, DWORD bytesTransferred)
{
	switch ( perIoContext->iParam )
	{
	case SQL_SERVER_PROC_MATRIX_CREATE:
		if ( mRequest.serverMatrix )
		{
			MATRIX_CREATE* matrixCreate = (MATRIX_CREATE*)perIoContext->buffer;
			if ( g_verbose == true )
			{
				cCSLock lock( &mCs );
				printf( "SQL_SERVER_PROC_MATRIX_CREATE - %s\n", (perIoContext->requestResult == 0) ? "SUCCESS" : "FAILED" );
				printf( "\tMax Server:.....%d\n", matrixCreate->maxServer );
				printf( "\tMax Channel:....%d\n", matrixCreate->maxChannel );
			}
			mRequest.serverMatrix = false;
			mComplete.serverMatrix = (perIoContext->requestResult == 0) ? true : false;
		}
		break;
	case SQL_SERVER_PROC_SERVER_LIST:
		if ( mRequest.serverTable )
		{
			cCSLock      lock( &mCs );
			SERVER_LIST* serverList = (SERVER_LIST*)perIoContext->buffer;

			if ( g_verbose == true )
			{
				char serverName[51];

				printf( "SQL_SERVER_PROC_SERVER_LIST - %s\n", (perIoContext->requestResult == 0) ? "SUCCESS" : "FAILED" );
				printf( "\tRow Count:...%d\n", serverList->rowCount );

				TB_SERVER* table = serverList->table;
				for ( int i = 0; i < serverList->rowCount; i++, table++ )
				{
					WideCharToMultiByte( CP_ACP,
										 0,
										 table->name,
										 -1,
										 serverName,
										 sizeof(serverName)/sizeof(char),
										 NULL,
										 NULL );

					printf( "\t\tIDX:.........%d\n", table->idx         );
					printf( "\t\tNAME:..     .%s\n", serverName         );
					printf( "\t\tDESCRIPTION:.%s\n", table->description );
					printf( "\t\tSTATUS:......%d\n", table->status      );
				}
			}

			if ( perIoContext->requestResult == IOCP_REQUEST_SUCCESS )
			{
				TB_SERVER* table = serverList->table;
				for ( int i = 0; i < serverList->rowCount; i++, table++ )
				{
					long         cid         = MAKECID( (WORD)table->idx, 0 );
					ServerTable* serverTable = mServerManager->GetServerTable( cid );
					if ( serverTable != NULL )
					{
						serverTable->idx = table->idx;
						wcscpy( serverTable->name, table->name );
						serverTable->status = table->status;
					}
				}
			}

			mRequest.serverTable  = false;
			mComplete.serverTable = (perIoContext->requestResult == 0) ? true : false;
		}
		break;
	case SQL_SERVER_PROC_SERVER_DOWN:
		{
			SERVER_DOWN* serverDown = (SERVER_DOWN*)perIoContext->buffer;
			if ( g_verbose == true )
			{
				printf( "SQL_SERVER_PROC_SERVER_DOWN - %s\n", (perIoContext->requestResult == 0) ? "SUCCESS" : "FAILED" );
				printf( "\tServer Number:....%d\n", serverDown->serverNum );
				printf( "\tChannel Number:...%d\n", serverDown->channelNum );
			}
		}
		break;
	case SQL_SERVER_PROC_CHANNEL_UPDATE:
		{
			CHANNEL_UPDATE* channelUpdate = (CHANNEL_UPDATE*)perIoContext->buffer;
			if ( g_verbose == true )
			{
				cCSLock lock( &mCs );
				printf( "SQL_SERVER_PROC_CHANNEL_UPDATE - %s\n", (perIoContext->requestResult == 0) ? "SUCCESS" : "FAILED" );
				printf( "\tServer Num:.....%d\n", channelUpdate->serverNum );
				printf( "\tChannel Num:....%d\n", channelUpdate->channelNum );
				printf( "\tStatus:.........%d\n", channelUpdate->status );
				printf( "\tRetvalue:.......%d\n", channelUpdate->retvalue );
			}
		}
		break;
	case SQL_SERVER_PROC_CONCURRENT_USER:
		{
			cCSLock          lock( &mCs );
			CONCURRENT_USER* concurrentUser = (CONCURRENT_USER*)perIoContext->buffer;
			ServerTable*     serverInfo     = mServerManager->GetServerTable( MAKECID( (WORD)concurrentUser->serverIdx, 0 ) );

			if ( serverInfo != NULL )
			{
				serverInfo->concurrentUser = concurrentUser->value;
			}

			if ( g_verbose == true )
			{
				printf( "SQL_SERVER_PROC_CONCURRENT_USER - %s\n", (perIoContext->requestResult == 0) ? "SUCCESS" : "FAILED" );
				printf( "\tServer Num:.....%d\n", concurrentUser->serverIdx );
				printf( "\tValue:..........%d\n", concurrentUser->value     );
			}
		}
		break;
	case SQL_CLIENT_PROC_MEMBER_LOGIN:
		if ( perSocketContext->socket == perIoContext->socket )
		{
			cCSLock       lock( &mCs );
			ClientInfo*   clientInfo  = (ClientInfo*)perSocketContext->buffer;
			MEMBER_LOGIN* memberLogin = (MEMBER_LOGIN*)perIoContext->buffer;
			char          buffer[1024];

			if ( (perIoContext->requestResult == 0) && (memberLogin->retvalue == 0) )
			{
				// 로그인성공.
				clientInfo->complete.login = true;

				clientInfo->memberIdx = memberLogin->memberIdx;
				clientInfo->loginIdx  = memberLogin->loginIdx;

				strcpy( clientInfo->uid, memberLogin->loginid );
				strcpy( clientInfo->pwd, memberLogin->password );

				clientInfo->serverNum  = (short)memberLogin->lastServerNum;
				clientInfo->channelNum = (short)memberLogin->lastChannelNum;

				sprintf( buffer, "Type (=Success Audit), Category (=Logon), Inetnum (=%s), Error Code (=%d)", memberLogin->inetnum, memberLogin->retvalue );
				PostMemberEvent( EVENT_MEMBER_SUCCESS_AUDIT, EVENT_MEMBER_LOGIN, memberLogin->memberIdx, buffer );

				LoginSuccess( perSocketContext, memberLogin->retvalue, memberLogin->memberIdx, memberLogin->loginIdx );
			}
			else
			{
				clientInfo->complete.login = false;
				// Begin - MemberLogout
				// 로그인실패 - 강제 로그아웃 처리.
				// if ( memberLogin->retvalue == 3 )
				// {
				// clientInfo->db.logout = (MemberLogout( perSocketContext, memberLogin->memberIdx, 0, 0 ) == TRUE);
				// }
				// End
				sprintf( buffer, "Type (=Failure Audit), Category (=Logon), Inetnum (=%s), Error Code (=%d)", memberLogin->inetnum, memberLogin->retvalue );
				PostMemberEvent( EVENT_MEMBER_FAILURE_AUDIT, EVENT_MEMBER_LOGIN, memberLogin->memberIdx, buffer );

				SendMsgError( perSocketContext, NM_USER, NM_USER_LOGIN_RES, memberLogin->retvalue );
			}

			if ( g_verbose == true )
			{
				printf( "SQL_CLIENT_PROC_MEMBER_LOGIN  - %s\n", (perIoContext->requestResult == 0) ? "SUCCESS" : "FAILED" );
				printf( "\tMEMBER_IDX:       %d\n", memberLogin->memberIdx      );
				printf( "\tLOGIN_IDX:        %d\n", memberLogin->loginIdx       );
				printf( "\tLOGINID:          %s\n", memberLogin->loginid        );
				printf( "\tPASSWORD:         %s\n", memberLogin->password       );
				printf( "\tINETNUM:          %s\n", memberLogin->inetnum        );
				printf( "\tLAST SERVER NUM:  %d\n", memberLogin->lastServerNum  );
				printf( "\tLAST CHANNEL NUM: %d\n", memberLogin->lastChannelNum );
				printf( "\tERROR CODE:       %d\n", memberLogin->retvalue       );
			}

			// DB;사용종료
			clientInfo->db.login = false;
		}
		break;
	case SQL_CLIENT_PROC_MEMBER_LOGOUT:
		if ( perSocketContext->socket == perIoContext->socket )
		{
			cCSLock        lock( &mCs );
			ClientInfo*    clientInfo   = (ClientInfo*)perSocketContext->buffer;
			MEMBER_LOGOUT* memberLogout = (MEMBER_LOGOUT*)perIoContext->buffer;
			char*          inetnum      = inet_ntoa( perSocketContext->addr.sin_addr );
			TCHAR          message[1024];

			sprintf( message, "Type (=Success Audit), Category (=Logoff), Inetnum (=%s), Error Code (=%d)", inetnum, memberLogout->retvalue );
			PostMemberEvent( EVENT_MEMBER_SUCCESS_AUDIT, EVENT_MEMBER_LOGOUT, memberLogout->memberIdx, message );

			if ( g_verbose == true )
			{
				printf( "SQL_CLIENT_PROC_MEMBER_LOGOUT - %s\n", (perIoContext->requestResult == 0) ? "SUCCESS" : "FAILED" );
				printf( "\tLOGIN_IDX:  %d\n", memberLogout->loginIdx  );
				printf( "\tMEMBER_IDX: %d\n", memberLogout->memberIdx );
				printf( "\tERROR CODE: %d\n", memberLogout->retvalue  );
			}

			clientInfo->db.logout = false;
			clientInfo->complete.login = false;
		}
		break;
	case SQL_CLIENT_PROC_COMEIN_GAME:
		if ( perSocketContext->socket == perIoContext->socket )
		{
			cCSLock lock( &mCs );

			ClientInfo*  clientInfo = (ClientInfo*)perSocketContext->buffer;
			COMEIN_GAME* comeinGame = (COMEIN_GAME*)perIoContext->buffer;

			SendMsgError( perSocketContext, NM_USER, NM_USER_COME_IN_GAME_RES, comeinGame->retvalue );

			if ( comeinGame->retvalue == 0 )
			{
				// 게임서버 이동이므로, 로그아웃 처리를 무시.
				clientInfo->complete.login      = false;
				clientInfo->complete.comeInGame = true;

				ComeinGame( perSocketContext, comeinGame->ipv4, comeinGame->port );
			}

			if ( g_verbose == true )
			{
				in_addr addr;
				addr.S_un.S_addr = comeinGame->ipv4;
				printf( "SQL_CLIENT_PROC_COMEIN_GAME  - %s\n", (perIoContext->requestResult == 0) ? "SUCCESS" : "FAILED" );
				printf( "\tLOGIN IDX:   %d\n", comeinGame->loginIdx   );
				printf( "\tSERVER NUM:  %d\n", comeinGame->serverNum  );
				printf( "\tCHANNEL NUM: %d\n", comeinGame->channelNum );
				printf( "\tINETNUM:     %d.%d.%d.%d:%d\n",
						addr.S_un.S_un_b.s_b1,
						addr.S_un.S_un_b.s_b2,
						addr.S_un.S_un_b.s_b3,
						addr.S_un.S_un_b.s_b4,
						comeinGame->port );
				printf( "\tERROR CODE:  %d\n", comeinGame->retvalue   );
			}

			clientInfo->db.comeinGame = false;
		}
		break;
	}

	// 사용이 완료된 I/O Context는 회수한다.
	mIoContextPool->ReleaseIoContext( perIoContext );
	return true;
}

// BackendThreadBegin Method
void cLoginProcess::BackendThreadBegin(void)
{
	DWORD currTickCount = 0;
	while ( true )
	{
		currTickCount = GetTickCount( );
		{
			cCSLock lock( &mCs );

			// 클라이언트 종료, Thread를 나간다.
			if ( mEndServer == true )
				break; // while ( true )

			if ( mComplete.startLog == false )
			{
				mComplete.startLog = PostServerEvent( "LoginProcess가 '%s:%d'에서 시작합니다.", inet_ntoa( mAddr.sin_addr ), mPort );
			}
			else if ( mComplete.serverMatrix == false )
			{
				if ( mRequest.serverMatrix == false )
				{
					mRequest.serverMatrix = MatrixCreate( MAX_SERVER, MAX_CHANNEL );
				}
			}
			else if ( mComplete.serverTable == false )
			{
				if ( mRequest.serverTable == false )
				{
					mRequest.serverTable = ServerList( );
				}
			}
			else if ( mComplete.serverSync == false )
			{
				mComplete.serverSync = g_loginSrv->GetSender( )->PostChSync( );
			}
			else
				break; // while ( true )

		}

		// I/O Context 처리.
		IoContextPresent( );

		// FPS 처리.
		BackendThreadFps( currTickCount );
	} // while ( true )
}

// BackendThreadEnd Method
void cLoginProcess::BackendThreadEnd(void)
{
	PostServerEvent( "Begin - cLoginProcess::BackendThreadEnd Close And Update" );
	Sleep( 100 );

	// 1. 업데이트.
	if ( mSocketContextPool != NULL )
	{
		PerSocketContext* socketContext = NULL;
		PerSocketContext* next;
		ClientInfo*       clientInfo;
		DWORD             currTickCount;

		// 1-1. 통신차단
		EnterCriticalSection( &mCs );

			socketContext = mSocketContextPool->GetPagedPoolUsage( );

			while ( socketContext != NULL )
			{
				Close( socketContext );
				socketContext = socketContext->next;
			}

		LeaveCriticalSection( &mCs );

		// 1-2. 안전종료.
		do {
			currTickCount = GetTickCount( );
			EnterCriticalSection( &mCs );

				socketContext = mSocketContextPool->GetPagedPoolUsage( );
				while ( socketContext != NULL )
				{
					next       = socketContext->next;
					clientInfo = (ClientInfo*)socketContext->buffer;

					// DB 사용중.
					if ( clientInfo->dbData != 0 )
						;
					// 로그아웃 처리.
					else if ( clientInfo->complete.login )
						clientInfo->db.logout = MemberLogout( socketContext, clientInfo->loginIdx, clientInfo->memberIdx );
					// 종료
					else
						socketContext->status.closeSocket = 1;

					if ( socketContext->status.closeSocket )
						mSocketContextPool->ReleasePerSocketContext( socketContext );

					socketContext = next;
				}

			LeaveCriticalSection( &mCs );
			BackendThreadFps( currTickCount );
		} while ( mSocketContextPool->GetPagedPoolUsage( ) );
	}

	PostServerEvent( "End - cLoginProcess::BackendThreadEnd Close And Update" );
	Sleep( 100 );

	// 2. 서버종료.
	PostServerEvent( "관리자로부터 '중지' 요청을 받았으므로 LoginProcess '%s:%d'를 종료하는 중입니다.", inet_ntoa( mAddr.sin_addr ), mPort );
	Sleep( 1000 );
}

// BackendThreadProc Method
void cLoginProcess::BackendThreadProc(void)
{
	DWORD currTickCount = 0;
	while ( true )
	{
		currTickCount = GetTickCount( );

		// 클라이언트 종료, Thread를 나간다.
		if ( mEndServer == true )
			break; // while ( true )

		// 클라이언트의 소켓 관리.
		if ( mSocketContextPool != NULL )
		{
			cCSLock           lock( &mCs );
			PerSocketContext* socketContext = mSocketContextPool->GetPagedPoolUsage( );
			PerSocketContext* next          = NULL;

			ClientInfo*       clientInfo    = NULL;
			char*             recvBuf       = NULL;

			while ( socketContext != NULL )
			{
				next       = socketContext->next;
				clientInfo = (ClientInfo*)socketContext->buffer;
				recvBuf    = (socketContext->buffer + socketContext->offset);

				// Receive 데이터 처리를 위한 코드.
				if ( socketContext->InternalHigh )
				{
					do {
						Packet* packet = (Packet*)(recvBuf + socketContext->Internal);

						if ( BatchComplete( socketContext, packet ) == false )
						{
							Close( socketContext );
							break;
						}

						socketContext->Internal += packet->tlen;
					} while ( socketContext->Internal < socketContext->InternalHigh );

					memset( recvBuf, 0, socketContext->InternalHigh );
					socketContext->Internal     = 0;
					socketContext->InternalHigh = 0;
				}

				// Close Socket On.
				if ( !socketContext->status.connectionDead )
				{
					// Time To Live.
					if ( socketContext->timeToLive > currTickCount )
					{
						// 로그인인증 요청 처리.
						if ( clientInfo->request.login && clientInfo->delay.login < currTickCount )
						{
							int  uidLen = (int)strlen( clientInfo->uid );
							int  pwdLen = (int)strlen( clientInfo->pwd );
							bool result = true;

							// SQL Injection를 방지 하기 위해 특수문자나 예외문자를 반드시 제어.
							for ( int i = 0; i < uidLen && result == true; i++ )
							{
								result  = (isalnum( clientInfo->uid[ i ] ) ? true : false);
							}

							// 결과확인
							if ( result == true )
							{
								clientInfo->db.login = MemberLogin( socketContext, clientInfo->uid, clientInfo->pwd );
								clientInfo->request.login = !clientInfo->db.login;
							}
							else
							{
								clientInfo->request.login = false;
								// 특수문자 및 예외문자로 인하여 인증실패.
								SendMsgError( socketContext, NM_USER, NM_USER_LOGIN_RES, 1 );
							}
						}
						// 서버리스트 요청 처리.
						if ( clientInfo->request.serverList && clientInfo->delay.serverList < currTickCount )
						{
							ServerTable* serverTable = mServerManager->GetServerTable( );
							ServerList( socketContext, serverTable, NM_USER, NM_USER_SERVER_LIST_RES );
							clientInfo->request.serverList = false;
						}
						// 게임서버 접속요청 처리.
						if ( clientInfo->request.comeInGame && clientInfo->delay.comeinGame < currTickCount )
						{
							// 채널 자동검색.
							long cid    = MAKECID( (WORD)clientInfo->serverNum, (WORD)clientInfo->channelNum );
							long result = mServerManager->FindChannel( cid );
							if ( result > 0 )
							{
								clientInfo->serverNum  = HIWORD( result );
								clientInfo->channelNum = LOWORD( result );
								clientInfo->db.comeinGame = ComeinGame( socketContext,
																		clientInfo->loginIdx,
																		(short)clientInfo->serverNum,
																		(short)clientInfo->channelNum,
																		0 );
							}
							else
							{
								SendMsgError( socketContext, NM_USER, NM_USER_COME_IN_GAME_RES, ERROR_COMEIN_GAME_FAIL );
							}

							clientInfo->request.comeInGame = false;
						}
					}
					else
						Close( socketContext );
				}
				/*-- DB 사용중     --*/
				else if ( clientInfo->dbData != 0 )
					;
				/*-- 로그아웃 처리 --*/
				else if ( clientInfo->complete.login )
					clientInfo->db.logout = (MemberLogout( socketContext, clientInfo->loginIdx, clientInfo->memberIdx ) == TRUE);
				/*-- 종료          --*/
				else
					socketContext->status.closeSocket = 1;

				// SocketContext 회수.
				if ( socketContext->status.closeSocket )
				{
					if ( g_verbose == true )
					{
						char* inetnum = inet_ntoa( socketContext->addr.sin_addr );

						printf( "cLoginProcess::BackendThread\n" );
						printf( "\t[SOCKET:%d] %s (에서) 연결이 종료되었습니다.\n", socketContext->socket, inetnum );
					}

					mSocketContextPool->ReleasePerSocketContext( socketContext );
				}

				socketContext = next;
			} // while ( socketContext != NULL )
		}

		// 동시접속자 처리.
		if ( mComplete.serverTable )
		{
			SYSTEMTIME st;
			GetLocalTime( &st );

			if ( st.wMinute != mSystemTime.wMinute )
			{
				cCSLock      lock( &mCs );
				ServerTable* serverTable    = mServerManager->GetServerTable( );;
				DWORD        concurrentUser = 0;

				for ( u_long i = 0; i < MAX_SERVER; i++, serverTable++ )
				{
					if ( serverTable->channelCounter > 0 )
					{
						concurrentUser += serverTable->concurrentUser;
						serverTable->maxConcurrentUser = max( serverTable->maxConcurrentUser, serverTable->concurrentUser );
						serverTable->minConcurrentUser = min( serverTable->minConcurrentUser, serverTable->concurrentUser );

						if ( st.wHour != serverTable->lastUpdate )
						{
							PostConcurrentEvent( serverTable->idx,
												 serverTable->minConcurrentUser,
												 serverTable->maxConcurrentUser,
												 "Current User(=%d)/Total Current User(=%d)/Current Channel(=%d).",
												 serverTable->concurrentUser,
												 mConcurrentUser,
												 serverTable->channelCounter );

							serverTable->maxConcurrentUser = serverTable->concurrentUser;
							serverTable->minConcurrentUser = serverTable->concurrentUser;

							serverTable->lastUpdate = st.wHour;
						}

						ConcurrentUser( serverTable->idx );
					}
				}

				mSystemTime     = st;
				mConcurrentUser = concurrentUser;
			}
		}

		// 서버관리(채널)
		if ( mComplete.serverSync )
			mServerManager->UpdateProcess( currTickCount );

		// I/O Context 처리.
		IoContextPresent( );

		// FPS 처리.
		BackendThreadFps( currTickCount );
	} // while ( true )
}

// BackendThread Method
DWORD cLoginProcess::BackendThread( )
{
	// 1. 로그인 서버 시작중.
	try {
		BackendThreadBegin( );
	} catch ( char* str ) {
		PostServerEvent( "Caught 'BackendThreadBegin' exception type: '%s'. Throwing 'cLoginProcess::BackendThreadBegin' exception", str );
		Sleep( 100 );
		throw;
	} catch ( ... ) {
		PostServerEvent( "In BackendThreadBegin Method. Throwing 'cLoginProcess::BackendThread' exception." );
		Sleep( 100 );
		throw;
	}

	// 2. 로그인 서버 처리중.
	try {
		BackendThreadProc( );
	} catch ( char* str ) {
		PostServerEvent( "Caught 'BackendThreadBegin' exception type: '%s'. Throwing 'cLoginProcess::BackendThreadProc' exception", str );
		Sleep( 100 );
		throw;
	} catch ( ... ) {
		PostServerEvent( "In BackendThreadProc Method. Throwing 'cLoginProcess::BackendThread' exception." );
		Sleep( 100 );
		throw;
	}

	// 3. 로그인 서버 종료중.
	try {
		BackendThreadEnd( );
	} catch ( char* str ) {
		PostServerEvent( "Caught 'BackendThreadBegin' exception type: '%s'. Throwing 'cLoginProcess::BackendThreadEnd' exception", str );
		Sleep( 100 );
		throw;
	} catch ( ... ) {
		PostServerEvent( "In BackendThreadEnd Method. Throwing 'cLoginProcess::BackendThread' exception." );
		Sleep( 100 );
		throw;
	}
	return 0;
}

// BackendThreadFps Method
void cLoginProcess::BackendThreadFps(DWORD currentTick)
{
	if ( mFpsTick < currentTick )
	{
		if ( g_fps == true )
		{
			float fps = (float)mFrame / (float)(currentTick - (mFpsTick - 1000)) * 1000;
			printf( "FPS:%6.2f / FRAME:%d\tClientProcess Thread\n", fps, mFrame );
		}

		if ( mFrame < 30 )
		{
			if ( mThreadPriority == false )
			{
				if ( SetThreadPriority( mIocpBackendThread, THREAD_PRIORITY_HIGHEST ) == TRUE )
				{
					mThreadPriority = true;
					if ( g_verbose == true )
					{
						printf( "cLoginProcess\n" );
						printf( "\tSetThreadPriority - THREAD_PRIORITY_HIGHEST\n" );
					}
				}
			}
			// 1분간 지속
			mThreadPriorityTick = currentTick + 60000;
		}
		if ( mThreadPriority == true && mThreadPriorityTick < currentTick )
		{
			if ( SetThreadPriority( mIocpBackendThread, THREAD_PRIORITY_NORMAL ) == TRUE )
			{
				mThreadPriority = false;
				if ( g_verbose == true )
				{
					printf( "cLoginProcess\n" );
					printf( "\tSetThreadPriority - THREAD_PRIORITY_NORMAL\n" );
				}
			}
		}

		mFpsTick = currentTick + 1000;
		mFrame   = 0;
	}
	else
		mFrame++;

	// 지연시간 약16(ms) - CPU 과부화 방지 & 내부 동기화.
	DWORD elapsedTick = GetTickCount( ) - currentTick;

	if ( elapsedTick < 10 )
	{
		Sleep( (10 - elapsedTick) );
	}
}
