// Copyright (C) 2001-2003 Even Balance, Inc.
//
//
// pbsv.h
//
// EVEN BALANCE - T.RAY
//
// The only file in the game project that needs to include this file is pbsv.cpp
//



#ifndef __PBSV_H__
#define __PBSV_H__



#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "pbcommon.h"



#if defined (__PBWIN32__) && !defined(XENON)
#include <windows.h>
#endif
#ifdef __PBLINUX__
#include <unistd.h>
#include <dlfcn.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#ifdef __PBMAC__
#include <unistd.h>
#include <dlfcn.h>
#include <sys/stat.h>
#include <sys/types.h>
#endif
#ifdef XENON
#include <direct.h>
#endif


#ifdef __WITH_PB__

extern int PbSvLoadFails ;

#define PB_MAX_CLIENTS 64

#ifdef _cplusplus

extern class stPbSdk *pbsdk ;

#if !defined(_WIN32) && !defined(__TRMACH__)
#define _cdecl
//#define stricmp strcasecmp
//#define strnicmp strncasecmp
#ifndef __TRLTOA__
#define __TRLTOA__
//
// ltoa
//
// assumes buffer a is large enough to hold ascii representation of i
//
inline char *ltoa ( long i , char *a , int radix )
{
	if ( a == NULL ) return NULL ;
	strcpy ( a , "0" ) ;
	if ( i && radix > 1 && radix < 37 ) {
		char buf[35] ;
		unsigned long u = i , p = 34 ;
		buf[p] = 0 ;
		if ( i < 0 && radix == 10 ) u = -i ;
		while ( u ) {
			unsigned int d = u % radix ;
			buf[--p] = d < 10 ? '0' + d : 'a' + d - 10 ;
			u /= radix ;
		}
		if ( i < 0 && radix == 10 ) buf[--p] = '-' ;
		strcpy ( a , buf + p ) ;
	}
	return a ;
}
#define itoa ltoa
#endif
#endif //#ifndef _WIN32



#ifndef __PBDLL__
extern char *sa ( void *cp , int ticklimit ) ;
extern char *sb ( void *cp , int event , int clientIndex , int datalen , char *data , int retry ) ;
#endif



//
// Class/Struct Definitions
//
class stPbSv {
public:
	unsigned long m_svId ;
	void *m_Md5 ;
	void *m_SvInstance , *m_ClInstance , *m_AgInstance ;
	char m_msgPrefix[PB_MISCLEN+1] , m_basepath[PB_Q_MAXRESULTLEN+4] , m_homepath[PB_Q_MAXRESULTLEN+4] , m_cwd[PB_Q_MAXRESULTLEN+4] ;
	int m_ReloadServer ;

	//func ptrs
	tdPbGameCommand m_GameCommand ;
	tdPbGameQuery m_GameQuery ;
	tdPbGameMsg m_GameMsg ;
	tdPbSendToClient m_SendToClient ;
	tdPbAddSvEvent m_AddPbEvent ;
	tdPbProcessPbEvents m_ProcessPbEvents ;
	tdPbSendToAddrPort m_SendToAddrPort ;
	tdPbPassConnectString m_PassConnectString ;
	tdPbAuthClient m_AuthClient ;
	tdPbTrapConsole m_TrapConsole ;
	void *m_Agent ;

	inline void copyIfNotExists ( char *fn , char *basepath ) {
		char fromFn[PB_Q_MAXRESULTLEN*2+1] , toFn[PB_Q_MAXRESULTLEN*2+1] ;
		strcpy ( toFn , m_cwd ) ;
		strcat ( toFn , fn ) ;
		FILE *f = fopen ( toFn , "rb" ) ;
		if ( f != NULL ) fclose ( f ) ;
		else {
			strcpy ( fromFn , basepath ) ;
			strcat ( fromFn , fn ) ;
			PbCopyFile ( fromFn , toFn ) ;
		}
	}

	inline char *getBasePath ( char *path ) {
		strncpy ( path , m_basepath , PB_Q_MAXRESULTLEN ) ;
		path[PB_Q_MAXRESULTLEN] = 0 ;
		if ( !(*path) ) getcwd ( path , PB_Q_MAXRESULTLEN - 4 ) ;
		if ( *path && path[strlen(path)-1] != *pbDIRSEP ) strcat ( path , pbDIRSEP ) ;
		strcat ( path , "pb" pbDIRSEP ) ;
		return path ;
	}

	inline char *getHomePath ( char *path ) {
		strncpy ( path , m_homepath , PB_Q_MAXRESULTLEN ) ;
		path[PB_Q_MAXRESULTLEN] = 0 ;
		if ( !(*path) ) getcwd ( path , PB_Q_MAXRESULTLEN - 4 ) ;
		if ( *path && m_cwd[strlen(path)-1] != *pbDIRSEP ) strcat ( path , pbDIRSEP ) ;
		strcat ( path , "pb" pbDIRSEP ) ;
		return path ;
	}

	inline char *makefn ( char *buf , char *fn ) {	//assumes buf is large enough to hold cwd + fn + overhead
		if ( !(*m_cwd) ) {
			getHomePath(m_cwd) ;

			char basepath[PB_Q_MAXRESULTLEN+4] ;
			getBasePath ( basepath ) ;
			if ( stricmp ( basepath , m_cwd ) && *basepath && *m_cwd ) {
#ifdef __PBWIN32__
				mkdir ( m_cwd ) ;
#endif
#ifdef __PBLINUX__
				mkdir ( m_cwd , 511 ) ;
#endif
#ifdef __PBMAC__
				mkdir ( m_cwd , 511 ) ;
#endif
				copyIfNotExists ( "pbsv" pbDLLEXT , basepath ) ;
				copyIfNotExists ( "pbcl" pbDLLEXT , basepath ) ;
				copyIfNotExists ( "pbag" pbDLLEXT , basepath ) ;
			}
		}
		strcpy ( buf , m_cwd ) ;
		strcat ( buf , fn ) ;
		return buf ;
	}

	inline void UnloadClientDll ( void ) {
#ifdef __PBWIN32__
		if ( m_ClInstance != NULL ) FreeLibrary ( (HMODULE) m_ClInstance ) ;
#endif
#ifdef __PBLINUX__
		if ( m_ClInstance != NULL ) dlclose ( m_ClInstance ) ;
#endif
#ifdef __PBMAC__
		if ( m_ClInstance != NULL ) dlclose ( m_ClInstance ) ;
#endif
		m_ClInstance = NULL ;
	}

	inline char *LoadClientDll ( void ) {
		if ( m_ClInstance != NULL ) return NULL ;
		UnloadClientDll() ;

		//check for replacement (updated) dll files and rename if necessary

		char fn[PB_Q_MAXRESULTLEN*2+1] , extrafn[PB_Q_MAXRESULTLEN*2+1] ;

		FILE *f = fopen ( makefn ( fn , "pbclsnew" pbDLLEXT ) , "rb" ) ;
		if ( f != NULL ) {
			fclose ( f ) ;
			setRW ( makefn ( fn , "pbclsold" pbDLLEXT ) ) ;
			remove ( makefn ( fn , "pbclsold" pbDLLEXT ) ) ;
			rename ( makefn ( fn , "pbcls" pbDLLEXT ) , makefn ( extrafn , "pbclsold" pbDLLEXT ) ) ;
			setRW ( makefn ( fn , "pbcls" pbDLLEXT ) ) ;
			remove ( makefn ( fn , "pbcls" pbDLLEXT ) ) ;
			rename ( makefn ( fn , "pbclsnew" pbDLLEXT ) , makefn ( extrafn , "pbcls" pbDLLEXT ) ) ;
		}
		makefn ( fn , "pbcls" pbDLLEXT ) ;
#ifdef __PBWIN32__
		m_ClInstance = LoadLibraryA ( fn ) ;
#endif
#ifdef __PBLINUX__
		m_ClInstance = dlopen ( fn , RTLD_LAZY ) ;
#endif
#ifdef __PBMAC__
		m_ClInstance = dlopen ( fn , RTLD_LAZY ) ;
#endif
		if ( m_ClInstance != NULL ) return NULL ;
		return PbsSvDllLoadFail ;
	}

	inline void UnloadAgentDll ( void ) {
		m_Agent = NULL ;
#ifdef __PBWIN32__
		if ( m_AgInstance != NULL ) FreeLibrary ( (HMODULE) m_AgInstance ) ;
#endif
#ifdef __PBLINUX__
		if ( m_AgInstance != NULL ) dlclose ( m_AgInstance ) ;
#endif
#ifdef __PBMAC__
		if ( m_AgInstance != NULL ) dlclose ( m_AgInstance ) ;
#endif
		m_AgInstance = NULL ;
	}

	inline char *LoadAgentDll ( void ) {
		if ( m_AgInstance != NULL ) return NULL ;
		UnloadAgentDll() ;

		//check for replacement (updated) dll files and rename if necessary

		char fn[PB_Q_MAXRESULTLEN*2+1] , extrafn[PB_Q_MAXRESULTLEN*2+1] ;

		FILE *f = fopen ( makefn ( fn , "pbagsnew" pbDLLEXT ) , "rb" ) ;
		if ( f != NULL ) {
			fclose ( f ) ;
			setRW ( makefn ( fn , "pbagsold" pbDLLEXT ) ) ;
			remove ( makefn ( fn , "pbagsold" pbDLLEXT ) ) ;
			rename ( makefn ( fn , "pbags" pbDLLEXT ) , makefn ( extrafn , "pbagsold" pbDLLEXT ) ) ;
			setRW ( makefn ( fn , "pbags" pbDLLEXT ) ) ;
			remove ( makefn ( fn , "pbags" pbDLLEXT ) ) ;
			rename ( makefn ( fn , "pbagsnew" pbDLLEXT ) , makefn ( extrafn , "pbags" pbDLLEXT ) ) ;
		}
		makefn ( fn , "pbags" pbDLLEXT ) ;
#ifdef __PBWIN32__
		m_AgInstance = LoadLibraryA ( fn ) ;
		if ( m_AgInstance != NULL ) {
			m_Agent = GetProcAddress ( (HMODULE) m_AgInstance , "a" ) ;
			if ( m_Agent != NULL ) return NULL ;
			UnloadAgentDll() ;
		}
#endif
#ifdef __PBLINUX__
		m_AgInstance = dlopen ( fn , RTLD_LAZY ) ;
		if ( m_AgInstance != NULL ) {
			m_Agent = dlsym ( m_AgInstance , "a" ) ;
			if ( m_Agent != NULL ) return NULL ;
			UnloadAgentDll() ;
		}
#endif
#ifdef __PBMAC__
		m_AgInstance = dlopen ( fn , RTLD_LAZY ) ;
		if ( m_AgInstance != NULL ) {
			m_Agent = dlsym ( m_AgInstance , "a" ) ;
			if ( m_Agent != NULL ) return NULL ;
			UnloadAgentDll() ;
		}
#endif
		return PbsSvDllLoadFail ;
	}

#ifndef __PBDLL__ //the following functions are not needed by the PB DLLs

	inline void uninitialize ( void ) {//also initializes game-side func ptrs
		m_GameCommand = NULL ;
		m_GameQuery = NULL ;
		m_GameMsg = NULL ;
		m_SendToClient = NULL ;
		m_SendToAddrPort = NULL ;
		m_PassConnectString = NULL ;
		m_AuthClient = NULL ;
		m_TrapConsole = NULL ;
	}

	inline void initialize ( void ) {
		uninitialize() ;
		m_GameCommand = PbSvGameCommand ;
		m_GameQuery = PbSvGameQuery ;
		m_GameMsg = PbSvGameMsg ;
		m_SendToClient = PbSvSendToClient ;
		m_SendToAddrPort = PbSvSendToAddrPort ;
	}

	inline void UnloadServerDll ( void ) {
		m_ProcessPbEvents = NULL ;
		m_AddPbEvent = NULL ;
#ifdef __PBWIN32__
		if ( m_SvInstance != NULL ) FreeLibrary ( (HMODULE) m_SvInstance ) ;
#endif
#ifdef __PBLINUX__
		if ( m_SvInstance != NULL ) dlclose ( m_SvInstance ) ;
#endif
#ifdef __PBMAC__
		if ( m_SvInstance != NULL ) dlclose ( m_SvInstance ) ;
#endif
		m_SvInstance = NULL ;

		m_PassConnectString = NULL ;
		m_AuthClient = NULL ;
		m_TrapConsole = NULL ;
	}

	inline char *LoadServerDll ( void ) {
		if ( PbSvLoadFails > 2 ) return PbsSvDllLoadFail ;
		++PbSvLoadFails ;

		if ( m_SvInstance != NULL ) return NULL ;
		UnloadServerDll() ;

		char fn[PB_Q_MAXRESULTLEN*2+1] , extrafn[PB_Q_MAXRESULTLEN*2+1] ;

		//check for replacement (updated) dll file and rename if necessary
		//load PB server dll and retrieve exported function pointers
		FILE *f = fopen ( makefn ( fn , "pbsvnew" pbDLLEXT ) , "rb" ) ;
		if ( f != NULL ) {
			fclose ( f ) ;
			setRW ( makefn ( fn , "pbsvold" pbDLLEXT ) ) ;
			remove ( makefn ( fn , "pbsvold" pbDLLEXT ) ) ;
			rename ( makefn ( fn , "pbsv" pbDLLEXT ) , makefn ( extrafn , "pbsvold" pbDLLEXT ) ) ;
			setRW ( makefn ( fn , "pbsv" pbDLLEXT ) ) ;
			remove ( makefn ( fn , "pbsv" pbDLLEXT ) ) ;
			rename ( makefn ( fn , "pbsvnew" pbDLLEXT ) , makefn ( extrafn , "pbsv" pbDLLEXT ) ) ;
		}
#ifdef __PBWIN32__
		m_SvInstance = LoadLibraryA ( makefn ( fn , "pbsv" pbDLLEXT ) ) ;
		if ( m_SvInstance == NULL ) return PbsSvDllLoadFail ;
		FARPROC pProc;
		pProc = GetProcAddress ( (HINSTANCE) m_SvInstance , "sa" ) ;
		m_ProcessPbEvents = (tdPbProcessPbEvents)pProc;
		pProc = GetProcAddress ( (HINSTANCE) m_SvInstance , "sb" ) ;
		m_AddPbEvent = 	(tdPbAddSvEvent)pProc;
#endif
#ifdef __PBLINUX__
		m_SvInstance = dlopen ( makefn ( fn , "pbsv" pbDLLEXT ) , RTLD_LAZY ) ;
		if ( m_SvInstance == NULL ) return PbsSvDllLoadFail ;
		m_ProcessPbEvents = (tdPbProcessPbEvents) dlsym ( m_SvInstance , "sa" ) ;
		m_AddPbEvent = 	(tdPbAddSvEvent) dlsym ( m_SvInstance , "sb" ) ;
#endif
#ifdef __PBMAC__
		m_SvInstance = dlopen ( makefn ( fn , "pbsv" pbDLLEXT ) , RTLD_LAZY ) ;
		if ( m_SvInstance == NULL ) return PbsSvDllLoadFail ;
		m_ProcessPbEvents = (tdPbProcessPbEvents) dlsym ( m_SvInstance , "sa" ) ;
		m_AddPbEvent = 	(tdPbAddSvEvent) dlsym ( m_SvInstance , "sb" ) ;
#endif

		if ( m_ProcessPbEvents == NULL || m_AddPbEvent == NULL ) {
			UnloadServerDll() ;
			return PbsSvDllProcFail ;
		}

		m_ReloadServer = 0 ;
		PbSvLoadFails = 0 ;

		return NULL ;
	}

	inline char *AddPbEvent ( int type , int clientIndex , int datalen , char *data , int retry = 0 ) {
		if ( m_GameCommand == NULL ) return NULL ;//not considered an error, this signifies that PB is disabled

		if ( m_ReloadServer || m_SvInstance == NULL ) {
			if ( m_SvInstance != NULL ) {
				UnloadServerDll() ;
				return NULL ;
			}
			char *res = LoadServerDll() ;
			if ( res != NULL ) {
				if ( type == PB_EV_ISENABLED ) return (char *) 0 ;
				return res ;
			}
		}

		return m_AddPbEvent ( this , type , clientIndex , datalen , data , retry ) ;
	}

	inline char *ProcessPbEvents ( int TickLimit = 0 ) {
		if ( m_GameCommand == NULL ) return NULL ;//not considered an error, this signifies that PB is disabled
		if ( m_SvInstance == NULL ) {
			if ( m_ReloadServer ) AddPbEvent ( PB_EV_CONFIG , -1 , 0 , "" ) ;
			return NULL ;//no events have been successfully added so nothing to process
		}

		if ( m_ReloadServer ) {
			UnloadServerDll() ;
			return NULL ;
		}

		return m_ProcessPbEvents ( this , TickLimit ) ;
	}

	inline void init ( void ) {
		m_svId = PB_SV_ID ;
		strcpy ( m_msgPrefix , "PunkBuster Server" ) ;
		m_SvInstance = NULL ;
		m_ReloadServer = 1 ;
		uninitialize() ;
		m_Md5 = NULL ;
		m_AddPbEvent = NULL ;
		m_ProcessPbEvents = NULL ;
		m_SendToAddrPort = NULL ;
		m_PassConnectString = NULL ;
		m_AuthClient = NULL ;
		m_TrapConsole = NULL ;
	}

	inline stPbSv() {
		init() ;
	}

	void Shutdown( void ) {
		UnloadServerDll() ;
		UnloadClientDll() ;
		UnloadAgentDll() ;
		init() ;
		//pbsdk = NULL ;
	}

	inline ~stPbSv() {
		Shutdown();
	}

#endif //#ifndef __PBDLL__

} ;



#endif //#ifdef _cplusplus
#endif //#ifdef __WITHPB__
#endif //#ifndef __PBSV_H__
