//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================

#ifdef _WIN32
#include <windows.h> 
#else
#include <unistd.h>
#include <string.h>
#include <dlfcn.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/time.h>
#include <malloc.h>
#endif

#include <stdio.h>
#include <stdlib.h>

#include "sys_ded.h"
#include "conproc.h"
#include "dedicated.h"
#include "exefuncs.h"
#include "crc.h"

#include "dll_state.h"
#include "enginecallback.h"
#include "md5.h"
#if defined( _WIN32 )
#include "../utils/procinfo/procinfo.h"
#endif

#ifdef _WIN32
static const char *g_pszengine = "sw.dll";
#else
static const char *g_pszengine = "engine_i386.so";
static char g_szEXEName[ 256 ];
#endif

#define MINIMUM_WIN_MEMORY		0x0c00000 
#define MAXIMUM_WIN_MEMORY		0x2000000 // max 32 mb
#define FIFTEEN_MEGS ( 15 * 1024 * 1024 ) 

#ifdef _WIN32
static	HANDLE	hinput;
static  HANDLE	houtput;
#endif

// System Memory & Size
static unsigned char	*gpMemBase = NULL;
static int				giMemSize = 0x1800000;  // 24 Mb Linux heapsize

exefuncs_t ef;

static char	console_text[256];
static int	console_textlen;

static long hDLLThirdParty = 0L;

char			*gpszCmdLine = NULL;

void Sys_Sleep( int msec )
{
#ifdef _WIN32
	Sleep( msec );
#else
    usleep(msec * 1000);
#endif
}

void *Sys_GetProcAddress( long library, const char *name )
{
#ifdef _WIN32
	return ( void * )GetProcAddress( (HMODULE)library, name );
#else // LINUX
	return dlsym( (void *)library, name );
#endif
}

long Sys_LoadLibrary( char *lib )
{
	void *hDll = NULL;

#ifdef _WIN32
	hDll = ::LoadLibrary( lib );
#else
    char    cwd[1024];
    char    absolute_lib[1024];
    
    if (!getcwd(cwd, sizeof(cwd)))
        Sys_ErrorMessage(1, "Sys_LoadLibrary: Couldn't determine current directory.");
        
    if (cwd[strlen(cwd)-1] == '/')
        cwd[strlen(cwd)-1] = 0;
        
    sprintf(absolute_lib, "%s/%s", cwd, lib);
    
    hDll = dlopen( absolute_lib, RTLD_NOW );
    if ( !hDll )
    {
        Sys_ErrorMessage( 1, dlerror() );
    }   
#endif
	return (long)hDll;
}

void Sys_FreeLibrary( long library )
{
	if ( !library )
		return;

#ifdef _WIN32
	::FreeLibrary( (HMODULE)library );
#else
	dlclose( (void *)library );
#endif
}

int Sys_GetExecutableName( char *out )
{
#ifdef _WIN32
	if ( !::GetModuleFileName( ( HINSTANCE )GetModuleHandle( NULL ), out, 256 ) )
	{
		return 0;
	}
#else
	strcpy( out, g_szEXEName );
#endif
	return 1;
}

/*
==============
Sys_ErrorMessage

Engine is erroring out, display error in message box
==============
*/
void Sys_ErrorMessage( int level, const char *msg )
{
#ifdef _WIN32
	MessageBox( NULL, msg, "Half-Life", MB_OK );
	PostQuitMessage(0);	
#else
	printf( "%s\n", msg );
	exit( -1 );
#endif
}

#ifdef _WIN32
/*
==============
UpdateStatus

Update status line at top of console if engine is running
==============
*/
void UpdateStatus( int force )
{
	static double tLast = 0.0;
	double	tCurrent;
	char	szPrompt[256];
	int		n, spec, nMax;
	char	szMap[32];
	float	fps;

	if ( !engineapi.Host_GetHostInfo )
		return;

	tCurrent = (float)( timeGetTime() / 1000.0f );

	engineapi.Host_GetHostInfo( &fps, &n, &spec, &nMax, szMap );

	if ( !force )
	{
		if ( ( tCurrent - tLast ) < 0.5f )
			return;
	}

	tLast = tCurrent;

	sprintf( szPrompt, "%.1f fps %2i(%2i spec)/%2i on %16s", (float)fps, n, spec, nMax, szMap);

	WriteStatusText( szPrompt );
}
#endif

/*
================
Sys_ConsoleOutput

Print text to the dedicated console
================
*/
void Sys_ConsoleOutput (char *string)
{
#ifdef _WIN32
	unsigned long dummy;
	char	text[256];

	if (console_textlen)
	{
		text[0] = '\r';
		memset(&text[1], ' ', console_textlen);
		text[console_textlen+1] = '\r';
		text[console_textlen+2] = 0;
		WriteFile(houtput, text, console_textlen+2, &dummy, NULL);
	}

	WriteFile(houtput, string, strlen(string), &dummy, NULL);

	if (console_textlen)
	{
		WriteFile(houtput, console_text, console_textlen, &dummy, NULL);
	}
	UpdateStatus( 1 /* force */ );
#else
	printf( "%s", string );
	fflush( stdout );
#endif
}

/*
==============
Sys_Printf

Engine is printing to console
==============
*/
void Sys_Printf(char *fmt, ...)
{
	// Dump text to debugging console.
	va_list argptr;
	char szText[1024];

	va_start (argptr, fmt);
	vsprintf (szText, fmt, argptr);
	va_end (argptr);

	// Get Current text and append it.
	Sys_ConsoleOutput( szText );
}

/*
==============
Load3rdParty

Load support for third party .dlls ( gamehost )
==============
*/
void Load3rdParty( void )
{
	// Only do this if the server operator wants the support.
	// ( In case of malicious code, too )
	if ( CheckParm( "-usegh" ) )   
	{
		hDLLThirdParty = Sys_LoadLibrary( "ghostinj.dll" );
	}
}

/*
============
StripExtension
 
Strips the extension off a filename.  Works backward to insure stripping
of extensions only, and not parts of the path that might contain a
period (i.e. './hlds_run').
============
*/
void StripExtension (char *in, char *out)
{
        char * in_current  = in + strlen(in);
        char * out_current = out + strlen(in);
        int    found_extension = 0;
 
        while (in_current >= in) {
                if ((found_extension == 0) && (*in_current == '.')) {
                        *out_current = 0;
                        found_extension = 1;
                }
                else {
                        if ((*in_current == '/') || (*in_current == '\\'))
                                found_extension = 1;
 
                        *out_current = *in_current;
                }
 
                in_current--;
                out_current--;
        }
}

/*
==============
CheckExeChecksum

Simple self-crc check
==============
*/
int CheckExeChecksum( void )
{
	unsigned char g_MD5[16];
	char szFileName[ 256 ];
	unsigned int newdat = 0;	
	unsigned int olddat;
	char datfile[ 256 ];

	// Get our filename
	if ( !Sys_GetExecutableName( szFileName ) )
	{
		return 0;
	}

	// compute raw 16 byte hash value
	if ( !MD5_Hash_File( g_MD5, szFileName ) )
	{
		return 0;
	}

	StripExtension( szFileName, datfile );

	strcat( datfile, ".dat" );

	// Check .dat file ( or write a new one if running with -newdat )
	FILE *fp = fopen( datfile, "rb" );
	if ( !fp || CheckParm( "-newdat" ) )  // No existing file, or we are asked to create a new one
	{
		if ( fp )
		{
			fclose( fp );
		}

		newdat = *(unsigned int *)&g_MD5[0];
		fp = fopen ( datfile, "wb" );
		if ( fp )
		{
			fwrite( &newdat, sizeof( unsigned int ), 1, fp );
			fclose( fp );
		}
	}
	else
	{
		int bOk = 0;

		if ( fread( &newdat, sizeof( unsigned int ), 1, fp ) == 1 )
			bOk = 1;

		fclose( fp );

		if ( bOk )
		{
			olddat = *(unsigned int *)&g_MD5[0];
			if ( olddat != newdat )
			{
				const char *pmsg = "Your Half-Life executable appears to have been modified.  Please check your system for viruses and then re-install Half-Life.";

#ifdef _WIN32
				MessageBox( NULL, pmsg, "Half-Life Dedicated Server", MB_OK );
#else
				printf( "%s\n", pmsg );
#endif
				return 0;
			}
		}
	}

	return 1;
}

/*
==============
EF_VID_ForceUnlockedAndReturnState

Dummy funcion called by engine
==============
*/
int  EF_VID_ForceUnlockedAndReturnState(void)
{
	return 0;
}

/*
==============
EF_VID_ForceLockState

Dummy funcion called by engine
==============
*/
void EF_VID_ForceLockState(int)
{
}

/*
==============
CheckParm

Search for psz in command line to .exe, if **ppszValue is set, then the pointer is
 directed at the NEXT argument in the command line
==============
*/
char *CheckParm(const char *psz, char **ppszValue)
{
	int i;
	static char sz[128];
	char *pret;

	if (!gpszCmdLine)
		return NULL;

	pret = strstr( gpszCmdLine, psz );

	// should we return a pointer to the value?
	if (pret && ppszValue)
	{
		char *p1 = pret;
		*ppszValue = NULL;

		while ( *p1 && (*p1 != 32))
			p1++;

		if (p1 != 0)
		{
			char *p2 = ++p1;

			for ( i = 0; i < 128; i++ )
			{
				if ( !*p2 || (*p2 == 32))
					break;
				sz[i] = *p2++;
			}

			sz[i] = 0;
			*ppszValue = &sz[0];		
		}	
	}

	return pret;
}

/*
==============
InitInstance

==============
*/
int InitInstance( void )
{
	Load3rdParty();

#if !defined( _DEBUG )
	if ( !CheckExeChecksum() )
		return 0;
#endif

	Eng_SetState( DLL_INACTIVE );

	memset( &ef, 0, sizeof( ef ) );
	
	// Function pointers used by dedicated server
	ef.Sys_Printf							= Sys_Printf;
	ef.ErrorMessage							= Sys_ErrorMessage;

	ef.VID_ForceLockState					= EF_VID_ForceLockState;
	ef.VID_ForceUnlockedAndReturnState		= EF_VID_ForceUnlockedAndReturnState;

#ifdef _WIN32
	// Data
	ef.fMMX									= PROC_IsMMX();
	ef.iCPUMhz								= PROC_GetSpeed();	// in MHz
#endif

	return 1;
}

/*
================
Sys_ConsoleInput

================
*/
#ifdef _WIN32
char *Sys_ConsoleInput (void)
{
	INPUT_RECORD	recs[1024];
	unsigned long	dummy;
	int				ch;
	unsigned long	numread, numevents;

	while ( 1 )
	{
		if (!GetNumberOfConsoleInputEvents (hinput, &numevents))
		{
			exit( -1 );
		}

		if (numevents <= 0)
			break;

		if ( !ReadConsoleInput(hinput, recs, 1, &numread) )
		{
			exit( -1 );
		}

		if (numread != 1)
		{
			exit( -1 );
		}

		if ( recs[0].EventType == KEY_EVENT )
		{
			if ( !recs[0].Event.KeyEvent.bKeyDown )
			{
				ch = recs[0].Event.KeyEvent.uChar.AsciiChar;
				switch (ch)
				{
					case '\r':
						WriteFile(houtput, "\r\n", 2, &dummy, NULL);	
						if (console_textlen)
						{
							console_text[console_textlen] = 0;
							console_textlen = 0;
							return console_text;
						}
						break;

					case '\b':
						if (console_textlen)
						{
							console_textlen--;
							WriteFile(houtput, "\b \b", 3, &dummy, NULL);	
						}
						break;

					default:
						if (ch >= ' ')
						{
							if (console_textlen < sizeof(console_text)-2)
							{
								WriteFile(houtput, &ch, 1, &dummy, NULL);	
								console_text[console_textlen] = ch;
								console_textlen++;
							}
						}

						break;

				}
			}
		}
	}

	return NULL;
}
#else
char *Sys_ConsoleInput(void)
{
	char ch;
    int     len;
	fd_set	fdset;
    struct timeval timeout;
	char szInput[256];
	char iInput = 0;

	FD_ZERO(&fdset);
	FD_SET(0, &fdset); // stdin
	timeout.tv_sec = 0;
	timeout.tv_usec = 0;
	if (select (1, &fdset, NULL, NULL, &timeout) == -1 || !FD_ISSET(0, &fdset))
		return NULL;

	while (read(0,&ch,1))
	{
		if (iInput >= 255)
			continue;

		if (ch == 10) 
		{
			char *pszret = NULL;

			//Null terminate string and return if we have anything
			szInput[iInput] = 0;

			if (iInput > 0)
				pszret = szInput;
			
			iInput = 0;
			return pszret;
		}

		szInput[iInput++] = ch;
	}

	return NULL;
}
#endif

#ifdef _WIN32
/*
==============
WriteStatusText

==============
*/
void WriteStatusText( char *szText )
{
	char szFullLine[81];
	COORD coord;
	DWORD dwWritten = 0;
	WORD wAttrib[80];
	
	int i;
	
	for ( i = 0; i < 80; i++ )
	{
		wAttrib[i] = FOREGROUND_RED | FOREGROUND_INTENSITY;
	}

	memset( szFullLine, 0, 81 );
	strcpy( szFullLine, szText );

	coord.X = 0;
	coord.Y = 0;

	WriteConsoleOutputAttribute( houtput, wAttrib, 80, coord, &dwWritten );
	WriteConsoleOutputCharacter( houtput, szFullLine, 80, coord, &dwWritten );	
}
#endif

/*
==============
CreateConsoleWindow

Create console window ( overridable? )
==============
*/
int CreateConsoleWindow( void )
{
#ifdef _WIN32
	if ( !AllocConsole () )
	{
		return 0;
	}

	hinput	= GetStdHandle (STD_INPUT_HANDLE);
	houtput = GetStdHandle (STD_OUTPUT_HANDLE);
	
	InitConProc();
#endif

	return 1;
}

/*
==============
DestroyConsoleWindow

==============
*/
void DestroyConsoleWindow( void )
{
#ifdef _WIN32
	FreeConsole ();

	// shut down QHOST hooks if necessary
	DeinitConProc ();
#endif
}

/*
==============
ProcessConsoleInput

==============
*/
void ProcessConsoleInput( void )
{
	char *s;

	if ( !engineapi.Cbuf_AddText )
		return;

	do
	{
		s = Sys_ConsoleInput ();
		if (s)
		{
			char szBuf[ 256 ];
			sprintf( szBuf, "%s\n", s );
			engineapi.Cbuf_AddText ( szBuf );
		}
	} while (s);
}

/*
================
GameInit
================
*/
int GameInit(void)
{
	char *p;
#ifdef _WIN32
	MEMORYSTATUS Buffer;

	memset( &Buffer, 0, sizeof( Buffer ) );
	Buffer.dwLength = sizeof( MEMORYSTATUS );
	
	GlobalMemoryStatus ( &Buffer );

	// take the greater of all the available memory or half the total memory,
	// but at least 10 Mb and no more than 32 Mb, unless they explicitly
	// request otherwise
	giMemSize = Buffer.dwTotalPhys;

	if ( giMemSize < FIFTEEN_MEGS )
	{
		return 0;
	}

	if ( giMemSize < (int)( Buffer.dwTotalPhys >> 1 ) )
	{
		giMemSize = (int)( Buffer.dwTotalPhys >> 1 );
	}

	// At least 10 mb, even if we have to swap a lot.
	if (giMemSize <= MINIMUM_WIN_MEMORY)
	{
		giMemSize = MINIMUM_WIN_MEMORY;
	}
	else if (giMemSize > MAXIMUM_WIN_MEMORY)
	{
		giMemSize = MAXIMUM_WIN_MEMORY;
	}
#endif

	// Command line override
	if ( (CheckParm ("-heapsize", &p ) ) && p )
	{
		giMemSize = atoi( p ) * 1024;
	}

	// Command line to force running with minimal memory.
	if (CheckParm ("-minmemory", NULL))
	{
		giMemSize = MINIMUM_WIN_MEMORY;
	}
	
	// Try and allocated it
#ifdef _WIN32
	gpMemBase = (unsigned char *)::GlobalAlloc( GMEM_FIXED, giMemSize );
#else
	gpMemBase = (unsigned char *)malloc( giMemSize );
#endif
	if (!gpMemBase)
	{
		return 0;
	}

#ifdef _WIN32
	// Check that we are running on Win32
	OSVERSIONINFO	vinfo;
	vinfo.dwOSVersionInfoSize = sizeof(vinfo);

	if ( !GetVersionEx ( &vinfo ) )
	{
		return 0;
	}

	if ( vinfo.dwPlatformId == VER_PLATFORM_WIN32s )
	{
		return 0;
	}
	
#endif

	if ( !Eng_Load( gpszCmdLine, &ef, giMemSize, gpMemBase, g_pszengine, DLL_NORMAL ) )
	{
		return 0;
	}

	Eng_SetState( DLL_ACTIVE );

	return 1;
}

/*
==============
GameShutdown

==============
*/
void GameShutdown( void )
{
	Eng_Unload();

	if ( gpMemBase )
	{
#ifdef _WIN32
		::GlobalFree( gpMemBase );
#else
		free( gpMemBase );
#endif
		gpMemBase = NULL;
	}
}

#ifdef _WIN32
/*
==============
WinMain

EXE entry point
==============
*/
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow )
{
	int		iret = -1;

	// Store off command line for argument searching
	gpszCmdLine = strdup( GetCommandLine() );

	if ( !InitInstance() )
	{
		goto cleanup;
	}

	if ( !CreateConsoleWindow() )
	{
		goto cleanup;
	}

	if ( !GameInit() )
	{
		goto cleanup;
	}

	if ( engineapi.SetStartupMode )
	{
		engineapi.SetStartupMode( 1 );
	}

	while ( 1 )
	{
		int bDone = 0;

		static double oldtime = 0.0;

		MSG msg;
		double newtime;
		double dtime;

		// Try to allow other apps to get some CPU
		Sys_Sleep( 1 );

		if ( !engineapi.Sys_FloatTime )
			break;

		while ( 1 )
		{
			newtime = engineapi.Sys_FloatTime();
			if ( newtime < oldtime )
			{
				oldtime = newtime - 0.05;
			}
			
			dtime = newtime - oldtime;

			if ( dtime > 0.001 )
				break;

			// Running really fast, yield some time to other apps
			Sys_Sleep( 1 );
		}
		
		while ( ::PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
		{
			if (!::GetMessage( &msg, NULL, 0, 0))
			{
				bDone = 1;
				break;
			}

			::TranslateMessage( &msg );
			::DispatchMessage( &msg );
		}

		if ( bDone )
			break;

		ProcessConsoleInput();

		if ( engineapi.Host_Frame )
		{
			Eng_Frame( 0, dtime );
		}

		UpdateStatus( 0  /* don't force */ );

		oldtime = newtime;
	}

	GameShutdown();

	DestroyConsoleWindow();

	iret = 1;

cleanup:

	if ( gpszCmdLine )
	{
		free( gpszCmdLine );
	}

	return iret;
}

#else

#define MAX_LINUX_CMDLINE 512 

static char cmdline[ MAX_LINUX_CMDLINE ];

void BuildCmdLine( int argc, char **argv )
{
	int len;
	int i;

	for (len = 0, i = 1; i < argc; i++)
	{
		len += strlen(argv[i]) + 1;
	}

	if ( len > MAX_LINUX_CMDLINE )
	{
		printf( "command line too long, %i max\n", MAX_LINUX_CMDLINE );
		exit(-1);
		return;
	}

	cmdline[0] = '\0';
	for ( i = 1; i < argc; i++ )
	{
		if ( i > 1 )
		{
			strcat( cmdline, " " );
		}
		strcat( cmdline, argv[ i ] );
	}
}

char *GetCommandLine( void )
{
	return cmdline;
}

int main(int argc, char **argv)
{
	int		iret = -1;

#ifdef _DEBUG
	strcpy(g_szEXEName, "hlds_run.dbg" );
#else
	strcpy(g_szEXEName, *argv);
#endif
	// Store off command line for argument searching
	BuildCmdLine(argc, argv);
	gpszCmdLine = strdup( GetCommandLine() );

	if ( !InitInstance() )
	{
		goto cleanup;
	}

	if ( !CreateConsoleWindow() )
	{
		goto cleanup;
	}

	if ( !GameInit() )
	{
		goto cleanup;
	}

	if ( engineapi.SetStartupMode )
	{
		engineapi.SetStartupMode( 1 );
	}

   while ( 1 )
   {
		char *p;
		static double oldtime = 0.0;

		double newtime;
		double dtime;

		// Try to allow other apps to get some CPU
		Sys_Sleep( 1 );

		if ( !engineapi.Sys_FloatTime )
			break;

		while ( 1 )
		{
			newtime = engineapi.Sys_FloatTime();
			if ( newtime < oldtime )
			{
				oldtime = newtime - 0.05;
			}
			
			dtime = newtime - oldtime;

			if ( dtime > 0.001 )
				break;

			// Running really fast, yield some time to other apps
			Sys_Sleep( 1 );
		}
		
		Eng_Frame( 0, dtime );

		p = Sys_ConsoleInput();
		if ( p )
		{
			engineapi.Cbuf_AddText( p );
			engineapi.Cbuf_AddText( "\n" );
		}

		oldtime = newtime;
	}
	
	GameShutdown();

	DestroyConsoleWindow();

	iret = 1;

cleanup:

	if ( gpszCmdLine )
	{
		free( gpszCmdLine );
	}

	if ( hDLLThirdParty )
	{
		Sys_FreeLibrary( hDLLThirdParty );
		hDLLThirdParty = 0L;
	}

	return iret;
}

#endif
