2012-11-29 13:37:48 +00:00
/*
Copyright ( C ) 1996 - 1997 Id Software , Inc .
This program is free software ; you can redistribute it and / or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation ; either version 2
of the License , or ( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE .
See the GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 59 Temple Place - Suite 330 , Boston , MA 02111 - 1307 , USA .
*/
# include "quakedef.h"
# include <sys/types.h>
# include <sys/timeb.h>
2014-03-30 08:55:06 +00:00
# include "winquake.h"
2012-11-29 13:37:48 +00:00
# include <conio.h>
2015-01-02 05:57:14 +00:00
# if (defined(_DEBUG) || defined(DEBUG)) && !defined(NPFTE)
2014-05-04 04:14:52 +00:00
# define CATCHCRASH
2015-08-24 15:47:40 +00:00
# ifdef _MSC_VER
# define MSVC_SEH
DWORD CrashExceptionHandler ( qboolean iswatchdog , DWORD exceptionCode , LPEXCEPTION_POINTERS exceptionInfo ) ;
# else
2014-05-04 04:14:52 +00:00
LONG CALLBACK nonmsvc_CrashExceptionHandler ( PEXCEPTION_POINTERS ExceptionInfo ) ;
# endif
# endif
2012-11-29 13:37:48 +00:00
2014-05-14 01:08:36 +00:00
# if defined(_DEBUG) && defined(_MSC_VER)
2012-11-29 13:37:48 +00:00
const DWORD MS_VC_EXCEPTION = 0x406D1388 ;
# pragma pack(push,8)
typedef struct tagTHREADNAME_INFO
{
2014-10-05 20:04:11 +00:00
DWORD dwType ; // Must be 0x1000.
LPCSTR szName ; // Pointer to name (in user addr space).
DWORD dwThreadID ; // Thread ID (-1=caller thread).
DWORD dwFlags ; // Reserved for future use, must be zero.
2012-11-29 13:37:48 +00:00
} THREADNAME_INFO ;
# pragma pack(pop)
2013-05-03 04:28:08 +00:00
void Sys_SetThreadName ( unsigned int dwThreadID , char * threadName )
2012-11-29 13:37:48 +00:00
{
2014-05-14 01:08:36 +00:00
THREADNAME_INFO info ;
info . dwType = 0x1000 ;
info . szName = threadName ;
info . dwThreadID = dwThreadID ;
info . dwFlags = 0 ;
2014-05-04 04:14:52 +00:00
2014-05-14 01:08:36 +00:00
__try
{
RaiseException ( MS_VC_EXCEPTION , 0 , sizeof ( info ) / sizeof ( ULONG_PTR ) , ( ULONG_PTR * ) & info ) ;
}
__except ( EXCEPTION_EXECUTE_HANDLER )
{
}
2012-11-29 13:37:48 +00:00
}
# endif
2014-10-05 20:04:11 +00:00
# if !defined(WINRT) && defined(MULTITHREAD)
# include <process.h>
/* Thread creation calls */
typedef struct threadwrap_s
{
int ( * func ) ( void * ) ;
void * args ;
char name [ 1 ] ;
} threadwrap_t ;
2015-08-02 11:36:46 +00:00
typedef struct
{
HANDLE handle ;
DWORD threadid ;
} threadctx_t ;
2014-10-05 20:04:11 +00:00
// the thread call is wrapped so we don't need WINAPI everywhere
unsigned int WINAPI threadwrapper ( void * args )
{
threadwrap_t tw ;
tw . func = ( ( threadwrap_t * ) args ) - > func ;
tw . args = ( ( threadwrap_t * ) args ) - > args ;
# if defined(_DEBUG) && defined(_MSC_VER)
Sys_SetThreadName ( GetCurrentThreadId ( ) , ( ( threadwrap_t * ) args ) - > name ) ;
# endif
# ifdef CATCHCRASH
2015-08-24 03:54:39 +00:00
if ( strcmp ( ( ( threadwrap_t * ) args ) - > name , " watchdog " ) ) //don't do this for the watchdog timer, as it just breaks the 'no' option.
2015-06-04 06:15:14 +00:00
{
2015-08-24 15:47:40 +00:00
# ifdef MSVC_SEH
__try
{
free ( args ) ;
tw . func ( tw . args ) ;
}
__except ( CrashExceptionHandler ( false , GetExceptionCode ( ) , GetExceptionInformation ( ) ) )
{
}
# else
2015-08-24 16:02:31 +00:00
PVOID ( WINAPI * pAddVectoredExceptionHandler ) ( ULONG FirstHandler , PVECTORED_EXCEPTION_HANDLER VectoredHandler ) ;
dllfunction_t dbgfuncs [ ] = { { ( void * ) & pAddVectoredExceptionHandler , " AddVectoredExceptionHandler " } , { NULL , NULL } } ;
2015-06-04 06:15:14 +00:00
if ( Sys_LoadLibrary ( " kernel32.dll " , dbgfuncs ) & & pAddVectoredExceptionHandler )
pAddVectoredExceptionHandler ( 0 , nonmsvc_CrashExceptionHandler ) ;
2015-08-24 15:47:40 +00:00
# endif
2015-06-04 06:15:14 +00:00
}
2015-08-24 15:47:40 +00:00
else
2014-10-05 20:04:11 +00:00
# endif
2015-08-24 15:47:40 +00:00
{
free ( args ) ;
tw . func ( tw . args ) ;
}
2014-10-05 20:04:11 +00:00
# ifndef WIN32CRTDLL
_endthreadex ( 0 ) ;
# endif
return 0 ;
}
2012-11-29 13:37:48 +00:00
void * Sys_CreateThread ( char * name , int ( * func ) ( void * ) , void * args , int priority , int stacksize )
{
2015-08-02 11:36:46 +00:00
threadctx_t * ctx = ( threadctx_t * ) malloc ( sizeof ( * ctx ) ) ;
2014-10-05 20:04:11 +00:00
threadwrap_t * tw = ( threadwrap_t * ) malloc ( sizeof ( threadwrap_t ) + strlen ( name ) ) ;
2012-11-29 13:37:48 +00:00
2015-08-02 11:36:46 +00:00
if ( ! tw | | ! ctx )
{
free ( tw ) ;
free ( ctx ) ;
2012-11-29 13:37:48 +00:00
return NULL ;
2015-08-02 11:36:46 +00:00
}
2012-11-29 13:37:48 +00:00
stacksize + = 128 ; // wrapper overhead, also prevent default stack size
tw - > func = func ;
tw - > args = args ;
2014-10-05 20:04:11 +00:00
strcpy ( tw - > name , name ) ;
2012-11-29 13:37:48 +00:00
# ifdef WIN32CRTDLL
2015-08-02 11:36:46 +00:00
ctx - > handle = ( HANDLE ) CreateThread ( NULL , stacksize , & threadwrapper , ( void * ) tw , 0 , & ctx - > threadid ) ;
2012-11-29 13:37:48 +00:00
# else
2015-08-02 11:36:46 +00:00
ctx - > handle = ( HANDLE ) _beginthreadex ( NULL , stacksize , & threadwrapper , ( void * ) tw , 0 , & ctx - > threadid ) ;
2012-11-29 13:37:48 +00:00
# endif
2015-08-02 11:36:46 +00:00
if ( ! ctx - > handle )
2012-11-29 13:37:48 +00:00
{
free ( tw ) ;
2015-08-02 11:36:46 +00:00
free ( ctx ) ;
2012-11-29 13:37:48 +00:00
return NULL ;
}
2015-08-02 11:36:46 +00:00
return ( void * ) ctx ;
2012-11-29 13:37:48 +00:00
}
void Sys_DetachThread ( void * thread )
{
2015-08-02 11:36:46 +00:00
threadctx_t * ctx = thread ;
CloseHandle ( ctx - > handle ) ;
free ( ctx ) ;
2012-11-29 13:37:48 +00:00
}
void Sys_WaitOnThread ( void * thread )
{
2015-08-02 11:36:46 +00:00
threadctx_t * ctx = thread ;
WaitForSingleObject ( ctx - > handle , INFINITE ) ;
CloseHandle ( ctx - > handle ) ;
free ( ctx ) ;
2012-11-29 13:37:48 +00:00
}
2014-10-05 20:04:11 +00:00
//used on fatal errors.
void Sys_ThreadAbort ( void )
{
ExitThread ( 0 ) ;
}
static DWORD mainthread ;
void Sys_ThreadsInit ( void )
{
mainthread = GetCurrentThreadId ( ) ;
}
2014-10-14 16:42:48 +00:00
qboolean Sys_IsMainThread ( void )
{
return mainthread = = GetCurrentThreadId ( ) ;
}
2015-06-14 01:28:01 +00:00
2014-10-05 20:04:11 +00:00
qboolean Sys_IsThread ( void * thread )
{
2015-08-02 11:36:46 +00:00
threadctx_t * ctx = thread ;
return ctx - > threadid = = GetCurrentThreadId ( ) ;
2014-10-05 20:04:11 +00:00
}
2015-06-14 01:28:01 +00:00
2014-10-05 20:04:11 +00:00
2012-11-29 13:37:48 +00:00
/* Mutex calls */
2014-10-05 20:04:11 +00:00
/*
Note that a ' mutex ' in win32 terminology is a cross - process / kernel object
A critical section is a single - process object , and thus can be provided more cheaply
*/
2012-11-29 13:37:48 +00:00
void * Sys_CreateMutex ( void )
{
2014-10-05 20:04:11 +00:00
# ifdef _DEBUG
//linux's pthread code doesn't like me recursively locking mutexes, so add some debug-only code to catch that on windows too so that we don't get nasty surprises.
CRITICAL_SECTION * mutex = malloc ( sizeof ( * mutex ) + sizeof ( int ) ) ;
* ( int * ) ( 1 + ( CRITICAL_SECTION * ) mutex ) = 0 ;
# else
CRITICAL_SECTION * mutex = malloc ( sizeof ( * mutex ) ) ;
# endif
InitializeCriticalSection ( mutex ) ;
return ( void * ) mutex ;
2012-11-29 13:37:48 +00:00
}
2015-06-04 06:15:14 +00:00
/*qboolean Sys_TryLockMutex(void *mutex)
2012-11-29 13:37:48 +00:00
{
2014-10-05 20:04:11 +00:00
# ifdef _DEBUG
if ( ! mutex )
{
Con_Printf ( " Invalid mutex \n " ) ;
return false ;
}
# endif
if ( TryEnterCriticalSection ( mutex ) )
{
# ifdef _DEBUG
if ( * ( int * ) ( 1 + ( CRITICAL_SECTION * ) mutex ) )
Con_Printf ( " Double lock \n " ) ;
* ( int * ) ( 1 + ( CRITICAL_SECTION * ) mutex ) + = 1 ;
# endif
return true ;
}
return false ;
2015-06-04 06:15:14 +00:00
} */
2012-11-29 13:37:48 +00:00
qboolean Sys_LockMutex ( void * mutex )
{
2014-10-05 20:04:11 +00:00
# ifdef _DEBUG
if ( ! mutex )
{
Con_Printf ( " Invalid mutex \n " ) ;
return false ;
}
# endif
EnterCriticalSection ( mutex ) ;
# ifdef _DEBUG
if ( * ( int * ) ( 1 + ( CRITICAL_SECTION * ) mutex ) )
Con_Printf ( " Double lock \n " ) ;
* ( int * ) ( 1 + ( CRITICAL_SECTION * ) mutex ) + = 1 ;
# endif
return true ;
2012-11-29 13:37:48 +00:00
}
qboolean Sys_UnlockMutex ( void * mutex )
{
2014-10-05 20:04:11 +00:00
# ifdef _DEBUG
* ( int * ) ( 1 + ( CRITICAL_SECTION * ) mutex ) - = 1 ;
# endif
LeaveCriticalSection ( mutex ) ;
return true ;
2012-11-29 13:37:48 +00:00
}
void Sys_DestroyMutex ( void * mutex )
{
2014-10-05 20:04:11 +00:00
DeleteCriticalSection ( mutex ) ;
free ( mutex ) ;
2012-11-29 13:37:48 +00:00
}
/* Conditional wait calls */
/*
TODO : Windows Vista has condition variables as documented here :
http : //msdn.microsoft.com/en-us/library/ms682052(VS.85).aspx
Note this uses Slim Reader / Writer locks ( Vista + exclusive )
or critical sections .
2014-10-05 20:04:11 +00:00
The condition variable implementation is based on http : //www.cs.wustl.edu/~schmidt/win32-cv-1.html.
( the libsdl - based stuff was too buggy )
2012-11-29 13:37:48 +00:00
*/
typedef struct condvar_s
{
2014-10-05 20:04:11 +00:00
int waiters ;
int release ;
int waitgeneration ;
CRITICAL_SECTION countlock ;
2012-11-29 13:37:48 +00:00
CRITICAL_SECTION mainlock ;
2014-10-05 20:04:11 +00:00
HANDLE evnt ;
2012-11-29 13:37:48 +00:00
} condvar_t ;
void * Sys_CreateConditional ( void )
{
condvar_t * cv ;
cv = ( condvar_t * ) malloc ( sizeof ( condvar_t ) ) ;
if ( ! cv )
return NULL ;
2014-10-05 20:04:11 +00:00
cv - > waiters = 0 ;
cv - > release = 0 ;
cv - > waitgeneration = 0 ;
2012-11-29 13:37:48 +00:00
InitializeCriticalSection ( & cv - > mainlock ) ;
InitializeCriticalSection ( & cv - > countlock ) ;
2014-10-05 20:04:11 +00:00
cv - > evnt = CreateEvent ( NULL , TRUE , FALSE , NULL ) ;
2012-11-29 13:37:48 +00:00
2014-10-05 20:04:11 +00:00
if ( cv - > evnt )
2012-11-29 13:37:48 +00:00
return ( void * ) cv ;
// something failed so deallocate everything
DeleteCriticalSection ( & cv - > countlock ) ;
DeleteCriticalSection ( & cv - > mainlock ) ;
free ( cv ) ;
return NULL ;
}
qboolean Sys_LockConditional ( void * condv )
{
EnterCriticalSection ( & ( ( condvar_t * ) condv ) - > mainlock ) ;
return true ;
}
qboolean Sys_UnlockConditional ( void * condv )
{
LeaveCriticalSection ( & ( ( condvar_t * ) condv ) - > mainlock ) ;
return true ;
}
qboolean Sys_ConditionWait ( void * condv )
{
2014-10-05 20:04:11 +00:00
qboolean done ;
2012-11-29 13:37:48 +00:00
condvar_t * cv = ( condvar_t * ) condv ;
qboolean success ;
2014-10-05 20:04:11 +00:00
int mygen ;
2012-11-29 13:37:48 +00:00
// increase count for non-signaled waiting threads
EnterCriticalSection ( & cv - > countlock ) ;
2014-10-05 20:04:11 +00:00
cv - > waiters + + ;
mygen = cv - > waitgeneration ;
2012-11-29 13:37:48 +00:00
LeaveCriticalSection ( & cv - > countlock ) ;
LeaveCriticalSection ( & cv - > mainlock ) ; // unlock as per condition variable definition
// wait on a signal
2014-10-05 20:04:11 +00:00
for ( ; ; )
2013-04-08 11:27:39 +00:00
{
2014-10-05 20:04:11 +00:00
# if 1
success = ( WaitForSingleObject ( cv - > evnt , INFINITE ) ! = WAIT_FAILED ) ;
# else
do
{
MSG msg ;
while ( PeekMessage ( & msg , NULL , 0 , 0 , PM_REMOVE ) )
DispatchMessage ( & msg ) ;
status = MsgWaitForMultipleObjects ( 1 , & cv - > evnt , FALSE , INFINITE , QS_SENDMESSAGE | QS_POSTMESSAGE ) ;
} while ( status = = ( WAIT_OBJECT_0 + 1 ) ) ;
success = status ! = WAIT_FAILED ;
2013-04-08 11:27:39 +00:00
# endif
2014-10-05 20:04:11 +00:00
EnterCriticalSection ( & cv - > countlock ) ;
done = cv - > release > 0 & & cv - > waitgeneration ! = mygen ;
LeaveCriticalSection ( & cv - > countlock ) ;
if ( done )
break ;
}
EnterCriticalSection ( & cv - > mainlock ) ; // lock as per condition variable definition
2012-11-29 13:37:48 +00:00
// update waiting count and alert signaling thread that we're done to avoid the deadlock condition
EnterCriticalSection ( & cv - > countlock ) ;
2014-10-05 20:04:11 +00:00
cv - > waiters - - ;
cv - > release - - ;
done = cv - > release = = 0 ;
2012-11-29 13:37:48 +00:00
LeaveCriticalSection ( & cv - > countlock ) ;
2014-10-05 20:04:11 +00:00
if ( done )
ResetEvent ( cv - > evnt ) ;
2012-11-29 13:37:48 +00:00
2014-10-05 20:04:11 +00:00
return true ;
2012-11-29 13:37:48 +00:00
}
qboolean Sys_ConditionSignal ( void * condv )
{
condvar_t * cv = ( condvar_t * ) condv ;
2014-10-05 20:04:11 +00:00
EnterCriticalSection ( & cv - > mainlock ) ;
2012-11-29 13:37:48 +00:00
// if there are non-signaled waiting threads, we signal one and wait on the response
EnterCriticalSection ( & cv - > countlock ) ;
2014-10-05 20:04:11 +00:00
if ( cv - > waiters > cv - > release )
2012-11-29 13:37:48 +00:00
{
2014-10-05 20:04:11 +00:00
SetEvent ( cv - > evnt ) ;
cv - > release + + ;
cv - > waitgeneration + + ;
2012-11-29 13:37:48 +00:00
}
2014-10-05 20:04:11 +00:00
LeaveCriticalSection ( & cv - > countlock ) ;
LeaveCriticalSection ( & cv - > mainlock ) ;
2012-11-29 13:37:48 +00:00
2014-10-05 20:04:11 +00:00
return true ;
2012-11-29 13:37:48 +00:00
}
qboolean Sys_ConditionBroadcast ( void * condv )
{
condvar_t * cv = ( condvar_t * ) condv ;
2014-10-05 20:04:11 +00:00
EnterCriticalSection ( & cv - > mainlock ) ;
2012-11-29 13:37:48 +00:00
// if there are non-signaled waiting threads, we signal all of them and wait on all the responses back
EnterCriticalSection ( & cv - > countlock ) ;
2014-10-05 20:04:11 +00:00
if ( cv - > waiters > 0 )
2012-11-29 13:37:48 +00:00
{
2014-10-05 20:04:11 +00:00
SetEvent ( cv - > evnt ) ;
cv - > release = cv - > waiters ;
cv - > waitgeneration + + ;
2012-11-29 13:37:48 +00:00
}
2014-10-05 20:04:11 +00:00
LeaveCriticalSection ( & cv - > countlock ) ;
LeaveCriticalSection ( & cv - > mainlock ) ;
2012-11-29 13:37:48 +00:00
return true ;
}
void Sys_DestroyConditional ( void * condv )
{
condvar_t * cv = ( condvar_t * ) condv ;
2014-10-05 20:04:11 +00:00
//make sure noone is still trying to poke it while shutting down
// Sys_LockConditional(condv);
// Sys_UnlockConditional(condv);
CloseHandle ( cv - > evnt ) ;
2012-11-29 13:37:48 +00:00
DeleteCriticalSection ( & cv - > countlock ) ;
DeleteCriticalSection ( & cv - > mainlock ) ;
free ( cv ) ;
}
2014-03-30 08:55:06 +00:00
# endif
# ifdef SUBSERVERS
typedef struct slaveserver_s
{
pubsubserver_t pub ;
HANDLE inpipe ;
HANDLE outpipe ;
qbyte inbuffer [ 2048 ] ;
int inbufsize ;
} winsubserver_t ;
pubsubserver_t * Sys_ForkServer ( void )
{
char exename [ 256 ] ;
char curdir [ 256 ] ;
char cmdline [ 8192 ] ;
PROCESS_INFORMATION childinfo ;
STARTUPINFO startinfo ;
SECURITY_ATTRIBUTES pipesec = { sizeof ( pipesec ) , NULL , TRUE } ;
winsubserver_t * ctx = Z_Malloc ( sizeof ( * ctx ) ) ;
GetModuleFileName ( NULL , exename , sizeof ( exename ) ) ;
GetCurrentDirectory ( sizeof ( curdir ) , curdir ) ;
2014-09-02 02:44:43 +00:00
Q_snprintfz ( cmdline , sizeof ( cmdline ) , " foo -noreset -clusterslave %s " , FS_GetManifestArgs ( ) ) ; //fixme: include which manifest is in use, so configs get set up the same.
2014-03-30 08:55:06 +00:00
memset ( & startinfo , 0 , sizeof ( startinfo ) ) ;
startinfo . cb = sizeof ( startinfo ) ;
startinfo . hStdInput = NULL ;
startinfo . hStdError = NULL ;
startinfo . hStdOutput = NULL ;
startinfo . dwFlags | = STARTF_USESTDHANDLES ;
//create pipes for the stdin/stdout.
CreatePipe ( & ctx - > inpipe , & startinfo . hStdOutput , & pipesec , 0 ) ;
CreatePipe ( & startinfo . hStdInput , & ctx - > outpipe , & pipesec , 0 ) ;
SetHandleInformation ( ctx - > inpipe , HANDLE_FLAG_INHERIT , 0 ) ;
SetHandleInformation ( ctx - > outpipe , HANDLE_FLAG_INHERIT , 0 ) ;
SetHandleInformation ( startinfo . hStdOutput , HANDLE_FLAG_INHERIT , HANDLE_FLAG_INHERIT ) ;
SetHandleInformation ( startinfo . hStdInput , HANDLE_FLAG_INHERIT , HANDLE_FLAG_INHERIT ) ;
CreateProcess ( exename , cmdline , NULL , NULL , TRUE , 0 , NULL , curdir , & startinfo , & childinfo ) ;
//these ends of the pipes were inherited by now, so we can discard them in the caller.
CloseHandle ( startinfo . hStdOutput ) ;
CloseHandle ( startinfo . hStdInput ) ;
return & ctx - > pub ;
}
void Sys_InstructSlave ( pubsubserver_t * ps , sizebuf_t * cmd )
{
winsubserver_t * s = ( winsubserver_t * ) ps ;
DWORD written = 0 ;
cmd - > data [ 0 ] = cmd - > cursize & 0xff ;
cmd - > data [ 1 ] = ( cmd - > cursize > > 8 ) & 0xff ;
WriteFile ( s - > outpipe , cmd - > data , cmd - > cursize , & written , NULL ) ;
}
void SSV_InstructMaster ( sizebuf_t * cmd )
{
HANDLE output = GetStdHandle ( STD_OUTPUT_HANDLE ) ;
DWORD written = 0 ;
cmd - > data [ 0 ] = cmd - > cursize & 0xff ;
cmd - > data [ 1 ] = ( cmd - > cursize > > 8 ) & 0xff ;
WriteFile ( output , cmd - > data , cmd - > cursize , & written , NULL ) ;
}
int Sys_SubServerRead ( pubsubserver_t * ps )
{
DWORD avail ;
winsubserver_t * s = ( winsubserver_t * ) ps ;
if ( ! PeekNamedPipe ( s - > inpipe , NULL , 0 , NULL , & avail , NULL ) )
{
CloseHandle ( s - > inpipe ) ;
CloseHandle ( s - > outpipe ) ;
Con_Printf ( " %i:%s has died \n " , s - > pub . id , s - > pub . name ) ;
return - 1 ;
}
else if ( avail )
{
if ( avail > sizeof ( s - > inbuffer ) - 1 - s - > inbufsize )
avail = sizeof ( s - > inbuffer ) - 1 - s - > inbufsize ;
if ( ReadFile ( s - > inpipe , s - > inbuffer + s - > inbufsize , avail , & avail , NULL ) )
s - > inbufsize + = avail ;
}
if ( s - > inbufsize > = 2 )
{
unsigned short len = s - > inbuffer [ 0 ] | ( s - > inbuffer [ 1 ] < < 8 ) ;
if ( s - > inbufsize > = len & & len > = 2 )
{
memcpy ( net_message . data , s - > inbuffer + 2 , len - 2 ) ;
net_message . cursize = len - 2 ;
memmove ( s - > inbuffer , s - > inbuffer + len , s - > inbufsize - len ) ;
s - > inbufsize - = len ;
MSG_BeginReading ( msg_nullnetprim ) ;
return 1 ;
}
}
return 0 ;
}
2012-11-29 13:37:48 +00:00
# endif
2014-03-30 08:55:06 +00:00