14c665076b
implement redirectcmd. because I can. add a few more qc extensions that define features already implemented. changing/restarting the map will explicitly flush the worldmodel if the file's modification time is newer. this should make map editing a little nicer. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5106 fc73d0e0-1445-4013-8a0c-d673dee63da5
577 lines
15 KiB
C
577 lines
15 KiB
C
/*
|
|
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>
|
|
|
|
#include "winquake.h"
|
|
#include <conio.h>
|
|
|
|
#if (defined(_DEBUG) || defined(DEBUG)) && !defined(NPFTE)
|
|
#if !defined(_MSC_VER) || _MSC_VER > 1200
|
|
#define CATCHCRASH
|
|
#endif
|
|
#ifdef _MSC_VER
|
|
#define MSVC_SEH
|
|
DWORD CrashExceptionHandler (qboolean iswatchdog, DWORD exceptionCode, LPEXCEPTION_POINTERS exceptionInfo);
|
|
#else
|
|
LONG CALLBACK nonmsvc_CrashExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo);
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
#if defined(_DEBUG) && defined(_MSC_VER)
|
|
const DWORD MS_VC_EXCEPTION=0x406D1388;
|
|
#pragma pack(push,8)
|
|
typedef struct tagTHREADNAME_INFO
|
|
{
|
|
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.
|
|
} THREADNAME_INFO;
|
|
#pragma pack(pop)
|
|
void Sys_SetThreadName(unsigned int dwThreadID, char *threadName)
|
|
{
|
|
THREADNAME_INFO info;
|
|
info.dwType = 0x1000;
|
|
info.szName = threadName;
|
|
info.dwThreadID = dwThreadID;
|
|
info.dwFlags = 0;
|
|
|
|
__try
|
|
{
|
|
RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info );
|
|
}
|
|
__except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#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;
|
|
|
|
typedef struct
|
|
{
|
|
HANDLE handle;
|
|
DWORD threadid;
|
|
} threadctx_t;
|
|
|
|
// 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
|
|
if (strcmp(((threadwrap_t *)args)->name, "watchdog")) //don't do this for the watchdog timer, as it just breaks the 'no' option.
|
|
{
|
|
#ifdef MSVC_SEH
|
|
__try
|
|
{
|
|
free(args);
|
|
tw.func(tw.args);
|
|
}
|
|
__except (CrashExceptionHandler(false, GetExceptionCode(), GetExceptionInformation()))
|
|
{
|
|
}
|
|
#else
|
|
PVOID (WINAPI *pAddVectoredExceptionHandler)(ULONG FirstHandler, PVECTORED_EXCEPTION_HANDLER VectoredHandler);
|
|
dllfunction_t dbgfuncs[] = {{(void*)&pAddVectoredExceptionHandler, "AddVectoredExceptionHandler"}, {NULL,NULL}};
|
|
if (Sys_LoadLibrary("kernel32.dll", dbgfuncs) && pAddVectoredExceptionHandler)
|
|
pAddVectoredExceptionHandler(0, nonmsvc_CrashExceptionHandler);
|
|
free(args);
|
|
tw.func(tw.args);
|
|
#endif
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
free(args);
|
|
tw.func(tw.args);
|
|
}
|
|
|
|
#ifndef WIN32CRTDLL
|
|
_endthreadex(0);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
void *Sys_CreateThread(char *name, int (*func)(void *), void *args, int priority, int stacksize)
|
|
{
|
|
threadctx_t *ctx = (threadctx_t *)malloc(sizeof(*ctx));
|
|
threadwrap_t *tw = (threadwrap_t *)malloc(sizeof(threadwrap_t)+strlen(name));
|
|
|
|
if (!tw || !ctx)
|
|
{
|
|
free(tw);
|
|
free(ctx);
|
|
return NULL;
|
|
}
|
|
|
|
stacksize += 128; // wrapper overhead, also prevent default stack size
|
|
tw->func = func;
|
|
tw->args = args;
|
|
strcpy(tw->name, name);
|
|
#ifdef WIN32CRTDLL
|
|
ctx->handle = (HANDLE)CreateThread(NULL, stacksize, &threadwrapper, (void *)tw, 0, &ctx->threadid);
|
|
#else
|
|
ctx->handle = (HANDLE)_beginthreadex(NULL, stacksize, &threadwrapper, (void *)tw, 0, (unsigned int*)&ctx->threadid);
|
|
#endif
|
|
if (!ctx->handle)
|
|
{
|
|
free(tw);
|
|
free(ctx);
|
|
return NULL;
|
|
}
|
|
|
|
return (void *)ctx;
|
|
}
|
|
|
|
void Sys_DetachThread(void *thread)
|
|
{
|
|
threadctx_t *ctx = thread;
|
|
CloseHandle(ctx->handle);
|
|
free(ctx);
|
|
}
|
|
|
|
void Sys_WaitOnThread(void *thread)
|
|
{
|
|
threadctx_t *ctx = thread;
|
|
#ifdef SERVERONLY
|
|
WaitForSingleObject(ctx->handle, INFINITE);
|
|
#else
|
|
while (WAIT_OBJECT_0+1 == MsgWaitForMultipleObjects(1, &ctx->handle, false, INFINITE, QS_SENDMESSAGE))
|
|
{
|
|
MSG msg;
|
|
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
|
|
DispatchMessage (&msg);
|
|
}
|
|
#endif
|
|
CloseHandle(ctx->handle);
|
|
free(ctx);
|
|
}
|
|
|
|
//used on fatal errors.
|
|
void Sys_ThreadAbort(void)
|
|
{
|
|
ExitThread(0);
|
|
}
|
|
|
|
static DWORD mainthread;
|
|
void Sys_ThreadsInit(void)
|
|
{
|
|
mainthread = GetCurrentThreadId();
|
|
}
|
|
qboolean Sys_IsMainThread(void)
|
|
{
|
|
return mainthread == GetCurrentThreadId();
|
|
}
|
|
|
|
qboolean Sys_IsThread(void *thread)
|
|
{
|
|
threadctx_t *ctx = thread;
|
|
return ctx->threadid == GetCurrentThreadId();
|
|
}
|
|
|
|
|
|
/* Mutex calls */
|
|
/*
|
|
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
|
|
*/
|
|
#ifdef USE_MSVCRT_DEBUG
|
|
void *Sys_CreateMutexNamed(char *file, int line)
|
|
{
|
|
#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_dbg(sizeof(*mutex)+sizeof(int), _NORMAL_BLOCK, file, line);
|
|
*(int*)(1+(CRITICAL_SECTION*)mutex) = 0;
|
|
#else
|
|
CRITICAL_SECTION *mutex = _malloc_dbg(sizeof(*mutex), _NORMAL_BLOCK, file, line);
|
|
#endif
|
|
InitializeCriticalSection(mutex);
|
|
return (void *)mutex;
|
|
}
|
|
#undef Sys_CreateMutex
|
|
#endif
|
|
void *QDECL Sys_CreateMutex(void)
|
|
{
|
|
#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;
|
|
}
|
|
|
|
/*qboolean Sys_TryLockMutex(void *mutex)
|
|
{
|
|
#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;
|
|
}*/
|
|
|
|
qboolean QDECL Sys_LockMutex(void *mutex)
|
|
{
|
|
#if 0//def _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;
|
|
}
|
|
|
|
qboolean QDECL Sys_UnlockMutex(void *mutex)
|
|
{
|
|
#ifdef _DEBUG
|
|
*(int*)(1+(CRITICAL_SECTION*)mutex)-=1;
|
|
#endif
|
|
LeaveCriticalSection(mutex);
|
|
return true;
|
|
}
|
|
|
|
void QDECL Sys_DestroyMutex(void *mutex)
|
|
{
|
|
DeleteCriticalSection(mutex);
|
|
free(mutex);
|
|
}
|
|
|
|
/* 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.
|
|
|
|
The condition variable implementation is based on http://www.cs.wustl.edu/~schmidt/win32-cv-1.html.
|
|
(the libsdl-based stuff was too buggy)
|
|
*/
|
|
typedef struct condvar_s
|
|
{
|
|
int waiters;
|
|
int release;
|
|
int waitgeneration;
|
|
CRITICAL_SECTION countlock;
|
|
CRITICAL_SECTION mainlock;
|
|
HANDLE evnt;
|
|
} condvar_t;
|
|
|
|
void *Sys_CreateConditional(void)
|
|
{
|
|
condvar_t *cv;
|
|
|
|
cv = (condvar_t *)malloc(sizeof(condvar_t));
|
|
if (!cv)
|
|
return NULL;
|
|
|
|
cv->waiters = 0;
|
|
cv->release = 0;
|
|
cv->waitgeneration = 0;
|
|
InitializeCriticalSection (&cv->mainlock);
|
|
InitializeCriticalSection (&cv->countlock);
|
|
cv->evnt = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
|
if (cv->evnt)
|
|
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)
|
|
{
|
|
qboolean done;
|
|
condvar_t *cv = (condvar_t *)condv;
|
|
qboolean success;
|
|
int mygen;
|
|
|
|
// increase count for non-signaled waiting threads
|
|
EnterCriticalSection(&cv->countlock);
|
|
cv->waiters++;
|
|
mygen = cv->waitgeneration;
|
|
LeaveCriticalSection(&cv->countlock);
|
|
|
|
LeaveCriticalSection(&cv->mainlock); // unlock as per condition variable definition
|
|
|
|
// wait on a signal
|
|
for(;;)
|
|
{
|
|
#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;
|
|
#endif
|
|
EnterCriticalSection(&cv->countlock);
|
|
done = cv->release > 0 && cv->waitgeneration != mygen;
|
|
if (done)
|
|
{
|
|
cv->waiters--;
|
|
cv->release--;
|
|
done = cv->release == 0;
|
|
if (done)
|
|
ResetEvent(cv->evnt);
|
|
LeaveCriticalSection(&cv->countlock);
|
|
break;
|
|
}
|
|
LeaveCriticalSection(&cv->countlock);
|
|
}
|
|
|
|
EnterCriticalSection(&cv->mainlock); // lock as per condition variable definition
|
|
return success;
|
|
}
|
|
|
|
qboolean Sys_ConditionSignal(void *condv)
|
|
{
|
|
condvar_t *cv = (condvar_t *)condv;
|
|
|
|
EnterCriticalSection(&cv->mainlock);
|
|
|
|
// if there are non-signaled waiting threads, we signal one and wait on the response
|
|
EnterCriticalSection(&cv->countlock);
|
|
if (cv->waiters > cv->release)
|
|
{
|
|
if (!cv->release)
|
|
SetEvent(cv->evnt);
|
|
cv->release++;
|
|
cv->waitgeneration++;
|
|
}
|
|
LeaveCriticalSection(&cv->countlock);
|
|
|
|
LeaveCriticalSection(&cv->mainlock);
|
|
|
|
return true;
|
|
}
|
|
|
|
qboolean Sys_ConditionBroadcast(void *condv)
|
|
{
|
|
condvar_t *cv = (condvar_t *)condv;
|
|
|
|
EnterCriticalSection(&cv->mainlock);
|
|
|
|
// if there are non-signaled waiting threads, we signal all of them and wait on all the responses back
|
|
EnterCriticalSection(&cv->countlock);
|
|
if (cv->waiters > 0)
|
|
{
|
|
if (!cv->release)
|
|
SetEvent(cv->evnt);
|
|
cv->release = cv->waiters;
|
|
cv->waitgeneration++;
|
|
}
|
|
LeaveCriticalSection(&cv->countlock);
|
|
|
|
LeaveCriticalSection(&cv->mainlock);
|
|
|
|
return true;
|
|
}
|
|
|
|
void Sys_DestroyConditional(void *condv)
|
|
{
|
|
condvar_t *cv = (condvar_t *)condv;
|
|
|
|
//make sure noone is still trying to poke it while shutting down
|
|
// Sys_LockConditional(condv);
|
|
// Sys_UnlockConditional(condv);
|
|
|
|
CloseHandle(cv->evnt);
|
|
DeleteCriticalSection(&cv->countlock);
|
|
DeleteCriticalSection(&cv->mainlock);
|
|
free(cv);
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef SUBSERVERS
|
|
typedef struct slaveserver_s
|
|
{
|
|
pubsubserver_t pub;
|
|
|
|
HANDLE inpipe;
|
|
HANDLE outpipe;
|
|
|
|
qbyte inbuffer[2048];
|
|
int inbufsize;
|
|
} winsubserver_t;
|
|
|
|
static void Sys_InstructSlave(pubsubserver_t *ps, sizebuf_t *cmd)
|
|
{
|
|
//FIXME: this is blocking. this is bad if the target is also blocking while trying to write to us.
|
|
//FIXME: merge buffering logic with SSV_InstructMaster, and allow for failure if full
|
|
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);
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
pubsubserver_t *Sys_ForkServer(void)
|
|
{
|
|
wchar_t exename[256];
|
|
wchar_t curdir[256];
|
|
char cmdline[8192];
|
|
wchar_t wtmp[countof(cmdline)];
|
|
PROCESS_INFORMATION childinfo;
|
|
STARTUPINFOW startinfo;
|
|
SECURITY_ATTRIBUTES pipesec = {sizeof(pipesec), NULL, TRUE};
|
|
winsubserver_t *ctx = Z_Malloc(sizeof(*ctx));
|
|
|
|
GetModuleFileNameW(NULL, exename, countof(exename));
|
|
GetCurrentDirectoryW(countof(curdir), curdir);
|
|
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.
|
|
|
|
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);
|
|
|
|
CreateProcessW(exename, widen(wtmp, sizeof(wtmp), cmdline), NULL, NULL, TRUE, 0, NULL, curdir, &startinfo, &childinfo);
|
|
|
|
//child will close when its pipes are closed. we don't need to hold on to the child process handle.
|
|
CloseHandle(childinfo.hProcess);
|
|
CloseHandle(childinfo.hThread);
|
|
|
|
//these ends of the pipes were inherited by now, so we can discard them in the caller.
|
|
CloseHandle(startinfo.hStdOutput);
|
|
CloseHandle(startinfo.hStdInput);
|
|
|
|
ctx->pub.funcs.InstructSlave = Sys_InstructSlave;
|
|
ctx->pub.funcs.SubServerRead = Sys_SubServerRead;
|
|
return &ctx->pub;
|
|
}
|
|
|
|
void Sys_InstructMaster(sizebuf_t *cmd)
|
|
{
|
|
//FIXME: this is blocking. this is bad if the target is also blocking while trying to write to us.
|
|
HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
DWORD written = 0;
|
|
WriteFile(output, cmd->data, cmd->cursize, &written, NULL);
|
|
}
|
|
|
|
#endif
|
|
|