mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-30 07:31:13 +00:00
d248ce223f
tweaked shutdown of sql to not make sql unusable after 'disconnect' on a listen server, fixed a couple of mem leaks. audio no longer starts up twice in windows. removed specular permutation. its either globally active or globally inactive. Use oblique near-plane clipping instead of a clipplane, fixing our rtlighting+water warping. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4156 fc73d0e0-1445-4013-8a0c-d673dee63da5
1592 lines
36 KiB
C
1592 lines
36 KiB
C
#include "quakedef.h"
|
|
#include "winquake.h"
|
|
#include "sys_plugfte.h"
|
|
#include "../http/iweb.h"
|
|
|
|
static void UnpackAndExtractPakFiles_Complete(struct dl_download *dl);
|
|
static void pscript_property_splash_sets(struct context *ctx, const char *val);
|
|
|
|
|
|
#ifdef _MSC_VER
|
|
# ifdef _WIN64
|
|
# pragma comment(lib, MSVCLIBSPATH "zlib64.lib")
|
|
# else
|
|
# pragma comment(lib, MSVCLIBSPATH "zlib.lib")
|
|
# endif
|
|
#endif
|
|
|
|
void BZ_Free(void *ptr)
|
|
{
|
|
free(ptr);
|
|
}
|
|
void *BZF_Malloc(int size)
|
|
{
|
|
return malloc(size);
|
|
}
|
|
//FIXME: we can't use this.
|
|
void *BZ_Malloc(int size)
|
|
{
|
|
return BZF_Malloc(size);
|
|
}
|
|
|
|
void QDECL Q_strncpyz(char *d, const char *s, int n)
|
|
{
|
|
int i;
|
|
n--;
|
|
if (n < 0)
|
|
return; //this could be an error
|
|
|
|
for (i=0; *s; i++)
|
|
{
|
|
if (i == n)
|
|
break;
|
|
*d++ = *s++;
|
|
}
|
|
*d='\0';
|
|
}
|
|
char *COM_SkipPath (const char *pathname)
|
|
{
|
|
const char *last;
|
|
|
|
last = pathname;
|
|
while (*pathname)
|
|
{
|
|
if (*pathname=='/' || *pathname == '\\')
|
|
last = pathname+1;
|
|
pathname++;
|
|
}
|
|
return (char *)last;
|
|
}
|
|
void VARGS Q_vsnprintfz (char *dest, size_t size, const char *fmt, va_list argptr)
|
|
{
|
|
vsnprintf (dest, size, fmt, argptr);
|
|
dest[size-1] = 0;
|
|
}
|
|
void VARGS Q_snprintfz (char *dest, size_t size, const char *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
|
|
va_start (argptr, fmt);
|
|
Q_vsnprintfz(dest, size, fmt, argptr);
|
|
va_end (argptr);
|
|
}
|
|
char *COM_TrimString(char *str)
|
|
{
|
|
int i;
|
|
static char buffer[256];
|
|
while (*str <= ' ' && *str>'\0')
|
|
str++;
|
|
|
|
for (i = 0; i < 255; i++)
|
|
{
|
|
if (*str <= ' ')
|
|
break;
|
|
buffer[i] = *str++;
|
|
}
|
|
buffer[i] = '\0';
|
|
return buffer;
|
|
}
|
|
void VARGS Con_Printf (const char *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
char dest[256];
|
|
|
|
va_start (argptr, fmt);
|
|
Q_vsnprintfz(dest, sizeof(dest), fmt, argptr);
|
|
va_end (argptr);
|
|
|
|
OutputDebugString(dest);
|
|
}
|
|
|
|
#include "netinc.h"
|
|
#ifdef _WIN32
|
|
#include <Wspiapi.h>
|
|
#endif
|
|
qboolean NET_StringToSockaddr (const char *s, int defaultport, struct sockaddr_qstorage *sadr, int *addrfamily, int *addrsize)
|
|
{
|
|
struct addrinfo *addrinfo = NULL;
|
|
struct addrinfo *pos;
|
|
struct addrinfo udp6hint;
|
|
int error;
|
|
char *port;
|
|
char dupbase[256];
|
|
int len;
|
|
|
|
if (!(*s))
|
|
return false;
|
|
|
|
memset (sadr, 0, sizeof(*sadr));
|
|
|
|
|
|
memset(&udp6hint, 0, sizeof(udp6hint));
|
|
udp6hint.ai_family = 0;//Any... we check for AF_INET6 or 4
|
|
udp6hint.ai_socktype = SOCK_DGRAM;
|
|
udp6hint.ai_protocol = IPPROTO_UDP;
|
|
|
|
//handle parsing of ipv6 literal addresses
|
|
if (*s == '[')
|
|
{
|
|
port = strstr(s, "]");
|
|
if (!port)
|
|
error = EAI_NONAME;
|
|
else
|
|
{
|
|
len = port - (s+1);
|
|
if (len >= sizeof(dupbase))
|
|
len = sizeof(dupbase)-1;
|
|
strncpy(dupbase, s+1, len);
|
|
dupbase[len] = '\0';
|
|
error = getaddrinfo(dupbase, (port[1] == ':')?port+2:NULL, &udp6hint, &addrinfo);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
port = strrchr(s, ':');
|
|
|
|
if (port)
|
|
{
|
|
len = port - s;
|
|
if (len >= sizeof(dupbase))
|
|
len = sizeof(dupbase)-1;
|
|
strncpy(dupbase, s, len);
|
|
dupbase[len] = '\0';
|
|
error = getaddrinfo(dupbase, port+1, &udp6hint, &addrinfo);
|
|
}
|
|
else
|
|
error = EAI_NONAME;
|
|
if (error) //failed, try string with no port.
|
|
error = getaddrinfo(s, NULL, &udp6hint, &addrinfo); //remember, this func will return any address family that could be using the udp protocol... (ip4 or ip6)
|
|
}
|
|
if (error)
|
|
{
|
|
return false;
|
|
}
|
|
((struct sockaddr*)sadr)->sa_family = 0;
|
|
for (pos = addrinfo; pos; pos = pos->ai_next)
|
|
{
|
|
switch(pos->ai_family)
|
|
{
|
|
default:
|
|
//unrecognised address families are ignored.
|
|
break;
|
|
case AF_INET6:
|
|
if (((struct sockaddr_in *)sadr)->sin_family == AF_INET6)
|
|
break; //first one should be best...
|
|
//fallthrough
|
|
case AF_INET:
|
|
memcpy(sadr, pos->ai_addr, pos->ai_addrlen);
|
|
if (pos->ai_family == AF_INET)
|
|
goto dblbreak; //don't try finding any more, this is quake, they probably prefer ip4...
|
|
break;
|
|
}
|
|
}
|
|
dblbreak:
|
|
freeaddrinfo (addrinfo);
|
|
if (!((struct sockaddr*)sadr)->sa_family) //none suitablefound
|
|
return false;
|
|
|
|
if (addrfamily)
|
|
*addrfamily = ((struct sockaddr*)sadr)->sa_family;
|
|
if (addrsize)
|
|
{
|
|
if (((struct sockaddr*)sadr)->sa_family == AF_INET)
|
|
*addrsize = sizeof(struct sockaddr_in);
|
|
else
|
|
*addrsize = sizeof(struct sockaddr_in6);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
char *COM_ParseOut (const char *data, char *out, int outlen)
|
|
{
|
|
int c;
|
|
int len;
|
|
|
|
len = 0;
|
|
out[0] = 0;
|
|
|
|
if (!data)
|
|
return NULL;
|
|
|
|
// skip whitespace
|
|
skipwhite:
|
|
while ( (c = *data) <= ' ')
|
|
{
|
|
if (c == 0)
|
|
return NULL; // end of file;
|
|
data++;
|
|
}
|
|
|
|
// skip // comments
|
|
if (c=='/')
|
|
{
|
|
if (data[1] == '/')
|
|
{
|
|
while (*data && *data != '\n')
|
|
data++;
|
|
goto skipwhite;
|
|
}
|
|
}
|
|
|
|
//skip / * comments
|
|
if (c == '/' && data[1] == '*')
|
|
{
|
|
data+=2;
|
|
while(*data)
|
|
{
|
|
if (*data == '*' && data[1] == '/')
|
|
{
|
|
data+=2;
|
|
goto skipwhite;
|
|
}
|
|
data++;
|
|
}
|
|
goto skipwhite;
|
|
}
|
|
|
|
// handle quoted strings specially
|
|
if (c == '\"')
|
|
{
|
|
data++;
|
|
while (1)
|
|
{
|
|
if (len >= outlen-1)
|
|
{
|
|
out[len] = 0;
|
|
return (char*)data;
|
|
}
|
|
|
|
c = *data++;
|
|
if (c=='\"' || !c)
|
|
{
|
|
out[len] = 0;
|
|
return (char*)data;
|
|
}
|
|
out[len] = c;
|
|
len++;
|
|
}
|
|
}
|
|
|
|
// parse a regular word
|
|
do
|
|
{
|
|
if (len >= outlen-1)
|
|
{
|
|
out[len] = 0;
|
|
return (char*)data;
|
|
}
|
|
|
|
out[len] = c;
|
|
data++;
|
|
len++;
|
|
c = *data;
|
|
} while (c>32);
|
|
|
|
out[len] = 0;
|
|
return (char*)data;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
vfsfile_t funcs;
|
|
|
|
char *data;
|
|
int maxlen;
|
|
int writepos;
|
|
int readpos;
|
|
} vfspipe_t;
|
|
|
|
void VFSPIPE_Close(vfsfile_t *f)
|
|
{
|
|
vfspipe_t *p = (vfspipe_t*)f;
|
|
free(p->data);
|
|
free(p);
|
|
}
|
|
unsigned long VFSPIPE_GetLen(vfsfile_t *f)
|
|
{
|
|
vfspipe_t *p = (vfspipe_t*)f;
|
|
return p->writepos - p->readpos;
|
|
}
|
|
unsigned long VFSPIPE_Tell(vfsfile_t *f)
|
|
{
|
|
return 0;
|
|
}
|
|
qboolean VFSPIPE_Seek(vfsfile_t *f, unsigned long offset)
|
|
{
|
|
OutputDebugStringA("Seeking is a bad plan, mmkay?\n");
|
|
return false;
|
|
}
|
|
int VFSPIPE_ReadBytes(vfsfile_t *f, void *buffer, int len)
|
|
{
|
|
vfspipe_t *p = (vfspipe_t*)f;
|
|
if (len > p->writepos - p->readpos)
|
|
len = p->writepos - p->readpos;
|
|
memcpy(buffer, p->data+p->readpos, len);
|
|
p->readpos += len;
|
|
|
|
if (p->readpos > 8192)
|
|
{
|
|
//shift the memory down periodically
|
|
//fixme: use cyclic buffer? max size, etc?
|
|
memmove(p->data, p->data+p->readpos, p->writepos-p->readpos);
|
|
|
|
p->writepos -= p->readpos;
|
|
p->readpos = 0;
|
|
}
|
|
return len;
|
|
}
|
|
int VFSPIPE_WriteBytes(vfsfile_t *f, const void *buffer, int len)
|
|
{
|
|
vfspipe_t *p = (vfspipe_t*)f;
|
|
if (p->writepos + len > p->maxlen)
|
|
{
|
|
p->maxlen = p->writepos + len;
|
|
p->data = realloc(p->data, p->maxlen);
|
|
}
|
|
memcpy(p->data+p->writepos, buffer, len);
|
|
p->writepos += len;
|
|
return len;
|
|
}
|
|
|
|
vfsfile_t *VFSPIPE_Open(void)
|
|
{
|
|
vfspipe_t *newf;
|
|
newf = malloc(sizeof(*newf));
|
|
newf->data = NULL;
|
|
newf->maxlen = 0;
|
|
newf->readpos = 0;
|
|
newf->writepos = 0;
|
|
newf->funcs.Close = VFSPIPE_Close;
|
|
newf->funcs.Flush = NULL;
|
|
newf->funcs.GetLen = VFSPIPE_GetLen;
|
|
newf->funcs.ReadBytes = VFSPIPE_ReadBytes;
|
|
newf->funcs.Seek = VFSPIPE_Seek;
|
|
newf->funcs.Tell = VFSPIPE_Tell;
|
|
newf->funcs.WriteBytes = VFSPIPE_WriteBytes;
|
|
newf->funcs.seekingisabadplan = true;
|
|
|
|
return &newf->funcs;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct context
|
|
{
|
|
struct contextpublic pub;
|
|
|
|
void *windowhnd;
|
|
int windowleft;
|
|
int windowtop;
|
|
int windowwidth;
|
|
int windowheight;
|
|
|
|
int waitingfordatafiles;
|
|
|
|
char *datadownload;
|
|
char *gamename;
|
|
char *password;
|
|
char *onstart;
|
|
char *onend;
|
|
char *ondemoend;
|
|
char *curserver; //updated by engine
|
|
|
|
void *hostinstance;
|
|
|
|
int read;
|
|
int written;
|
|
|
|
qtvfile_t qtvf;
|
|
|
|
unsigned char *splashdata;
|
|
int splashwidth;
|
|
int splashheight;
|
|
struct dl_download *splashdownload;
|
|
struct dl_download *packagelist;
|
|
|
|
void *mutex;
|
|
void *thread;
|
|
char resetvideo;
|
|
qboolean shutdown;
|
|
qboolean multiplecontexts;
|
|
|
|
struct context *next;
|
|
|
|
struct browserfuncs bfuncs;
|
|
|
|
#ifdef _WIN32
|
|
HANDLE pipetoengine;
|
|
HANDLE pipefromengine;
|
|
HANDLE engineprocess;
|
|
#endif
|
|
};
|
|
|
|
#ifdef _WIN32
|
|
|
|
extern HWND sys_parentwindow;
|
|
extern unsigned int sys_parentwidth;
|
|
extern unsigned int sys_parentheight;
|
|
HINSTANCE global_hInstance;
|
|
static char binarypath[MAX_PATH];
|
|
|
|
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
|
|
{
|
|
char *bp;
|
|
switch (fdwReason)
|
|
{
|
|
case DLL_PROCESS_ATTACH:
|
|
global_hInstance = hinstDLL;
|
|
GetModuleFileName(global_hInstance, binarypath, sizeof(binarypath));
|
|
bp = COM_SkipPath(binarypath);
|
|
if (bp)
|
|
*bp = 0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
struct context *activecontext;
|
|
struct context *contextlist;
|
|
|
|
#define ADDRARG(x) do {if (argc < maxargs) argv[argc++] = strdup(x);} while(0) //for strings that are safe
|
|
#define ADDCARG(x) do {if (argc < maxargs) argv[argc++] = cleanarg(x);} while(0) //for arguments that we don't trust.
|
|
char *cleanarg(char *arg)
|
|
{
|
|
unsigned char *c;
|
|
//skip over any leading spaces.
|
|
while (*arg <= ' ')
|
|
arg++;
|
|
|
|
//reject anything with a leading + or -
|
|
if (*arg == '-' || *arg == '+')
|
|
return strdup("badarg");
|
|
|
|
//clean up the argument
|
|
for (c = (unsigned char *)arg; *c; c++)
|
|
{
|
|
//remove special chars... we automagically add quotes so any that exist are someone trying to get past us.
|
|
if (*c == ';' || *c == '\n' || *c == '\"')
|
|
*c = '?';
|
|
//remove other control chars.
|
|
//we allow spaces
|
|
if (*c < ' ')
|
|
*c = '?';
|
|
}
|
|
|
|
if (*arg)
|
|
{
|
|
char *out = malloc(strlen(arg)+3);
|
|
strcpy(out+1, arg);
|
|
out[0] = '\"';
|
|
strcat(out, "\"");
|
|
return out;
|
|
}
|
|
return strdup("\"\"");
|
|
}
|
|
|
|
void Plug_GetBinaryName(char *exe, int exelen,
|
|
char *basedir, int basedirlen)
|
|
{
|
|
char buffer[1024];
|
|
char cmd[64];
|
|
char value[1024];
|
|
FILE *f;
|
|
Q_snprintfz(buffer, sizeof(buffer), "%s%s", binarypath, "npfte.txt");
|
|
f = fopen(buffer, "rt");
|
|
if (f)
|
|
{
|
|
while(fgets(buffer, sizeof(buffer), f))
|
|
{
|
|
*cmd = 0;
|
|
*value = 0;
|
|
COM_ParseOut(COM_ParseOut(buffer, cmd, sizeof(cmd)), value, sizeof(value));
|
|
if (!strcmp(cmd, "relexe"))
|
|
Q_snprintfz(buffer, sizeof(buffer), "%s%s", binarypath, value);
|
|
else if (!strcmp(cmd, "absexe"))
|
|
Q_strncpyz(exe, value, exelen);
|
|
else if (!strcmp(cmd, "basedir"))
|
|
Q_strncpyz(basedir, value, basedirlen);
|
|
}
|
|
fclose(f);
|
|
}
|
|
}
|
|
|
|
int Plug_GenCommandline(struct context *ctx, char **argv, int maxargs)
|
|
{
|
|
char *s;
|
|
int argc;
|
|
char tok[256];
|
|
char exe[1024];
|
|
char basedir[1024];
|
|
|
|
Q_snprintfz(exe, sizeof(exe), "%s%s", binarypath, "fteqw");
|
|
*basedir = 0;
|
|
|
|
Plug_GetBinaryName(exe, sizeof(exe), basedir, sizeof(basedir));
|
|
|
|
argv[0] = strdup(exe);
|
|
argc = 1;
|
|
|
|
ADDRARG("-plugin");
|
|
|
|
if (*basedir)
|
|
{
|
|
ADDRARG("-basedir");
|
|
ADDCARG(basedir);
|
|
}
|
|
|
|
switch(ctx->qtvf.connectiontype)
|
|
{
|
|
default:
|
|
break;
|
|
case QTVCT_STREAM:
|
|
ADDRARG("+qtvplay");
|
|
ADDCARG(ctx->qtvf.server);
|
|
break;
|
|
case QTVCT_CONNECT:
|
|
ADDRARG("+connect");
|
|
ADDCARG(ctx->qtvf.server);
|
|
break;
|
|
case QTVCT_JOIN:
|
|
ADDRARG("+join");
|
|
ADDCARG(ctx->qtvf.server);
|
|
break;
|
|
case QTVCT_OBSERVE:
|
|
ADDRARG("+observe");
|
|
ADDCARG(ctx->qtvf.server);
|
|
break;
|
|
case QTVCT_MAP:
|
|
ADDRARG("+map");
|
|
ADDCARG(ctx->qtvf.server);
|
|
break;
|
|
}
|
|
|
|
if (ctx->password)
|
|
{
|
|
ADDRARG("+password");
|
|
ADDCARG(ctx->password);
|
|
}
|
|
|
|
//figure out the game dirs (first token is the base game)
|
|
s = ctx->gamename;
|
|
s = COM_ParseOut(s, tok, sizeof(tok));
|
|
if (!*tok || !strcmp(tok, "q1") || !strcmp(tok, "qw") || !strcmp(tok, "quake"))
|
|
ADDRARG("-quake");
|
|
else if (!strcmp(tok, "q2") || !strcmp(tok, "quake2"))
|
|
ADDRARG("-q2");
|
|
else if (!strcmp(tok, "q3") || !strcmp(tok, "quake3"))
|
|
ADDRARG("-q3");
|
|
else if (!strcmp(tok, "hl") || !strcmp(tok, "halflife"))
|
|
ADDRARG("-halflife");
|
|
else if (!strcmp(tok, "h2") || !strcmp(tok, "hexen2"))
|
|
ADDRARG("-hexen2");
|
|
else if (!strcmp(tok, "nex") || !strcmp(tok, "nexuiz"))
|
|
ADDRARG("-nexuiz");
|
|
else
|
|
{
|
|
ADDRARG("-basegame");
|
|
ADDCARG(tok);
|
|
}
|
|
//later options are additions to that
|
|
while ((s = COM_ParseOut(s, tok, sizeof(tok))))
|
|
{
|
|
if (argc == sizeof(argv)/sizeof(argv[0]))
|
|
break;
|
|
ADDRARG("-addbasegame");
|
|
ADDCARG(tok);
|
|
}
|
|
return argc;
|
|
}
|
|
qboolean Plug_GenCommandlineString(struct context *ctx, char *cmdline, int cmdlinelen)
|
|
{
|
|
char *argv[64];
|
|
int argc, i;
|
|
argc = Plug_GenCommandline(ctx, argv, 64);
|
|
for (i = 0; i < argc; i++)
|
|
{
|
|
//add quotes for any arguments with spaces
|
|
if (strchr(argv[i], ' '))
|
|
{
|
|
Q_strncatz(cmdline, "\"", cmdlinelen);
|
|
Q_strncatz(cmdline, argv[i], cmdlinelen);
|
|
Q_strncatz(cmdline, "\"", cmdlinelen);
|
|
}
|
|
else
|
|
Q_strncatz(cmdline, argv[i], cmdlinelen);
|
|
Q_strncatz(cmdline, " ", cmdlinelen);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Plug_ExecuteCommand(struct context *ctx, char *message, ...)
|
|
{
|
|
va_list va;
|
|
|
|
char finalmessage[1024];
|
|
DWORD written = 0;
|
|
|
|
va_start (va, message);
|
|
vsnprintf (finalmessage, sizeof(finalmessage)-1, message, va);
|
|
va_end (va);
|
|
|
|
WriteFile(ctx->pipetoengine, finalmessage, strlen(finalmessage), &written, NULL);
|
|
}
|
|
|
|
void Plug_CreatePluginProcess(struct context *ctx)
|
|
{
|
|
char cmdline[8192];
|
|
PROCESS_INFORMATION childinfo;
|
|
STARTUPINFO startinfo;
|
|
SECURITY_ATTRIBUTES pipesec = {sizeof(pipesec), NULL, TRUE};
|
|
if (!Plug_GenCommandlineString(ctx, cmdline, sizeof(cmdline)))
|
|
return;
|
|
|
|
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->pipefromengine, &startinfo.hStdOutput, &pipesec, 0);
|
|
CreatePipe(&startinfo.hStdInput, &ctx->pipetoengine, &pipesec, 0);
|
|
|
|
SetHandleInformation(ctx->pipefromengine, HANDLE_FLAG_INHERIT, 0);
|
|
SetHandleInformation(ctx->pipetoengine, HANDLE_FLAG_INHERIT, 0);
|
|
|
|
Plug_ExecuteCommand(ctx, "vid_recenter %i %i %i %i %#llx\n", ctx->windowleft, ctx->windowtop, ctx->windowwidth, ctx->windowheight, (long long)ctx->windowhnd);
|
|
|
|
CreateProcess(NULL, cmdline, NULL, NULL, TRUE, 0, NULL, binarypath, &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);
|
|
}
|
|
|
|
int Plug_PluginThread(void *ctxptr)
|
|
{
|
|
char buffer[1024];
|
|
char *nl;
|
|
int bufoffs = 0;
|
|
struct context *ctx = ctxptr;
|
|
|
|
#if 0
|
|
//I really don't know what to do about multiple active clients downloading at once.
|
|
//maybe just depricate this feature. android ports have the same issue with missing files.
|
|
//we should probably just have the engine take charge of the downloads.
|
|
if (ctx->datadownload)
|
|
{
|
|
char token[1024];
|
|
struct dl_download *dl;
|
|
char *s = ctx->datadownload;
|
|
char *c;
|
|
vfsfile_t *f;
|
|
while ((s = COM_ParseOut(s, token, sizeof(token))))
|
|
{
|
|
//FIXME: do we want to add some sort of file size indicator?
|
|
c = strchr(token, ':');
|
|
if (!c)
|
|
continue;
|
|
*c++ = 0;
|
|
f = VFSSTDIO_Open(va("%s/%s", basedir, token), "rb", NULL);
|
|
if (f)
|
|
{
|
|
Plug_ExecuteCommand(ctx, "echo " "Already have %s\n", token);
|
|
VFS_CLOSE(f);
|
|
continue;
|
|
}
|
|
|
|
Plug_ExecuteCommand(ctx, "echo " "Attempting to download %s\n", c);
|
|
|
|
dl = DL_Create(c);
|
|
dl->user_ctx = ctx;
|
|
dl->next = ctx->packagelist;
|
|
if (DL_CreateThread(dl, FS_OpenTemp(), UnpackAndExtractPakFiles_Complete))
|
|
ctx->packagelist = dl;
|
|
}
|
|
|
|
ctx->pub.downloading = true;
|
|
while(!ctx->shutdown && ctx->packagelist)
|
|
{
|
|
int total=0, done=0;
|
|
ctx->resetvideo = false;
|
|
Sys_LockMutex(ctx->mutex);
|
|
for (dl = ctx->packagelist; dl; dl = dl->next)
|
|
{
|
|
total += dl->totalsize;
|
|
done += dl->completed;
|
|
}
|
|
dl = ctx->packagelist;
|
|
if (total != ctx->pub.dlsize || done != ctx->pub.dldone)
|
|
{
|
|
ctx->pub.dlsize = total;
|
|
ctx->pub.dldone = done;
|
|
if (ctx->bfuncs.StatusChanged)
|
|
ctx->bfuncs.StatusChanged(ctx->hostinstance);
|
|
}
|
|
if (!dl->file)
|
|
ctx->packagelist = dl->next;
|
|
else
|
|
dl = NULL;
|
|
Sys_UnlockMutex(ctx->mutex);
|
|
|
|
/*file downloads are not canceled while the plugin is locked, to avoid a race condition*/
|
|
if (dl)
|
|
DL_Close(dl);
|
|
Sleep(10);
|
|
}
|
|
ctx->pub.downloading = false;
|
|
}
|
|
#endif
|
|
|
|
Plug_CreatePluginProcess(ctx);
|
|
|
|
if (ctx->bfuncs.StatusChanged)
|
|
ctx->bfuncs.StatusChanged(ctx->hostinstance);
|
|
|
|
while(1)
|
|
{
|
|
DWORD avail;
|
|
//use Peek so we can read exactly how much there is without blocking, so we don't have to read byte-by-byte.
|
|
PeekNamedPipe(ctx->pipefromengine, NULL, 0, NULL, &avail, NULL);
|
|
if (!avail)
|
|
avail = 1; //so we do actually sleep.
|
|
if (avail > sizeof(buffer)-1 - bufoffs)
|
|
avail = sizeof(buffer)-1 - bufoffs;
|
|
if (!ReadFile(ctx->pipefromengine, buffer + bufoffs, avail, &avail, NULL) || !avail)
|
|
{
|
|
//broken pipe, client died.
|
|
break;
|
|
}
|
|
bufoffs += avail;
|
|
while(1)
|
|
{
|
|
buffer[bufoffs] = 0;
|
|
nl = strchr(buffer, '\n');
|
|
if (nl)
|
|
{
|
|
*nl = 0;
|
|
if (!strncmp(buffer, "status ", 7))
|
|
{
|
|
//don't just strcpy it, copy by byte, saves locking.
|
|
int i = strlen(buffer+7)+1;
|
|
if (i > sizeof(ctx->pub.statusmessage))
|
|
i = sizeof(ctx->pub.statusmessage);
|
|
ctx->pub.statusmessage[i] = 0;
|
|
while (i-->0)
|
|
{
|
|
ctx->pub.statusmessage[i] = buffer[7+i];
|
|
}
|
|
|
|
if (ctx->bfuncs.StatusChanged)
|
|
ctx->bfuncs.StatusChanged(ctx->hostinstance);
|
|
}
|
|
else if (!strcmp(buffer, "status"))
|
|
{
|
|
*ctx->pub.statusmessage = 0;
|
|
if (ctx->bfuncs.StatusChanged)
|
|
ctx->bfuncs.StatusChanged(ctx->hostinstance);
|
|
}
|
|
else if (!strcmp(buffer, "curserver"))
|
|
{
|
|
Plug_LockPlugin(ctx, true);
|
|
free(ctx->curserver);
|
|
ctx->curserver = strdup(buffer + 10);
|
|
Plug_LockPlugin(ctx, false);
|
|
}
|
|
else
|
|
{
|
|
//handle anything else we need to handle here
|
|
OutputDebugStringA("Unknown command from engine \"");
|
|
OutputDebugStringA(buffer);
|
|
OutputDebugStringA("\"\n");
|
|
}
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
ctx->pub.running = false;
|
|
*ctx->pub.statusmessage = 0;
|
|
if (ctx->bfuncs.StatusChanged)
|
|
ctx->bfuncs.StatusChanged(ctx->hostinstance);
|
|
|
|
if (ctx == activecontext)
|
|
activecontext = NULL;
|
|
return 0;
|
|
}
|
|
|
|
void Plug_LockPlugin(struct context *ctx, qboolean lockstate)
|
|
{
|
|
if (!ctx || !ctx->mutex)
|
|
return;
|
|
|
|
if (lockstate)
|
|
Sys_LockMutex(ctx->mutex);
|
|
else
|
|
Sys_UnlockMutex(ctx->mutex);
|
|
}
|
|
//#define Plug_LockPlugin(c,s) do{Plug_LockPlugin(c,s);VS_DebugLocation(__FILE__, __LINE__, s?"Lock":"Unlock"); }while(0)
|
|
|
|
//begins the context, fails if one is already active
|
|
qboolean Plug_StartContext(struct context *ctx)
|
|
{
|
|
if (activecontext && !ctx->multiplecontexts)
|
|
return false;
|
|
if (ctx->pub.running)
|
|
return true;
|
|
|
|
if (ctx->thread)
|
|
Plug_StopContext(ctx, true);
|
|
|
|
ctx->pub.running = true;
|
|
if (!ctx->multiplecontexts)
|
|
activecontext = ctx;
|
|
if (!ctx->mutex)
|
|
ctx->mutex = Sys_CreateMutex();
|
|
ctx->thread = Sys_CreateThread("pluginctx", Plug_PluginThread, ctx, THREADP_NORMAL, 0);
|
|
|
|
return true;
|
|
}
|
|
|
|
//asks a context to stop, is not instant.
|
|
void Plug_StopContext(struct context *ctx, qboolean wait)
|
|
{
|
|
void *thread;
|
|
if (ctx == NULL)
|
|
ctx = activecontext;
|
|
if (!ctx)
|
|
return;
|
|
Plug_ExecuteCommand(ctx, "quit force\n");
|
|
|
|
thread = ctx->thread;
|
|
if (ctx->thread)
|
|
{
|
|
if (wait)
|
|
{
|
|
while (ctx->pub.running && ctx->windowhnd)
|
|
{
|
|
MSG msg;
|
|
while (PeekMessage(&msg, ctx->windowhnd, 0, 0, PM_REMOVE))
|
|
{
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
Sleep(10);
|
|
}
|
|
Sys_WaitOnThread(ctx->thread);
|
|
ctx->thread = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
//creates a plugin context
|
|
struct context *Plug_CreateContext(void *sysctx, const struct browserfuncs *funcs)
|
|
{
|
|
struct context *ctx;
|
|
|
|
if (!sysctx || !funcs)
|
|
return NULL;
|
|
|
|
ctx = malloc(sizeof(struct context));
|
|
if (!ctx)
|
|
return NULL;
|
|
memset(ctx, 0, sizeof(struct context));
|
|
memcpy(&ctx->bfuncs, funcs, sizeof(ctx->bfuncs));
|
|
|
|
//link the instance to the context and the context to the instance
|
|
ctx->hostinstance = sysctx;
|
|
|
|
ctx->gamename = strdup("q1");
|
|
|
|
//add it to the linked list
|
|
ctx->next = contextlist;
|
|
contextlist = ctx;
|
|
|
|
ctx->qtvf.connectiontype = QTVCT_NONE;
|
|
|
|
return ctx;
|
|
}
|
|
|
|
//change the plugin's parent window, width, and height, returns true if the window handle actually changed, false otherwise
|
|
qboolean Plug_ChangeWindow(struct context *ctx, void *whnd, int left, int top, int width, int height)
|
|
{
|
|
qboolean result = false;
|
|
|
|
Plug_LockPlugin(ctx, true);
|
|
|
|
//if the window changed
|
|
if (ctx->windowhnd != whnd)
|
|
{
|
|
result = true;
|
|
ctx->windowhnd = whnd;
|
|
ctx->resetvideo = 2;
|
|
}
|
|
|
|
ctx->windowleft = left;
|
|
ctx->windowtop = top;
|
|
ctx->windowwidth = width;
|
|
ctx->windowheight = height;
|
|
|
|
if (ctx->pub.running && !ctx->resetvideo)
|
|
ctx->resetvideo = true;
|
|
|
|
if (ctx->pub.running)
|
|
Plug_ExecuteCommand(ctx, "vid_recenter %i %i %i %i %#llx\n", ctx->windowleft, ctx->windowtop, ctx->windowwidth, ctx->windowheight, (long long)ctx->windowhnd);
|
|
|
|
Plug_LockPlugin(ctx, false);
|
|
|
|
return result;
|
|
}
|
|
|
|
void Plug_DestroyContext(struct context *ctx)
|
|
{
|
|
struct context *prev;
|
|
if (ctx == contextlist)
|
|
contextlist = ctx->next;
|
|
else
|
|
{
|
|
for (prev = contextlist; prev->next; prev = prev->next)
|
|
{
|
|
if (prev->next == ctx)
|
|
{
|
|
prev->next = ctx->next;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ctx->splashdownload)
|
|
{
|
|
DL_Close(ctx->splashdownload);
|
|
ctx->splashdownload = NULL;
|
|
}
|
|
|
|
Plug_StopContext(ctx, true);
|
|
|
|
if (ctx->mutex)
|
|
Sys_DestroyMutex(ctx->mutex);
|
|
|
|
//actually these ifs are not required, just the frees
|
|
if (ctx->gamename)
|
|
free(ctx->gamename);
|
|
if (ctx->password)
|
|
free(ctx->password);
|
|
if (ctx->datadownload)
|
|
free(ctx->datadownload);
|
|
if (ctx->splashdata)
|
|
free(ctx->splashdata);
|
|
|
|
free(ctx);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////
|
|
|
|
#if 0
|
|
#include "fs.h"
|
|
extern searchpathfuncs_t zipfilefuncs;
|
|
|
|
static int ExtractDataFile(const char *fname, int fsize, void *ptr)
|
|
{
|
|
char buffer[8192];
|
|
int read;
|
|
void *zip = ptr;
|
|
flocation_t loc;
|
|
int slashes;
|
|
const char *s;
|
|
vfsfile_t *compressedpak;
|
|
vfsfile_t *decompressedpak;
|
|
|
|
if (zipfilefuncs.FindFile(zip, &loc, fname, NULL))
|
|
{
|
|
compressedpak = zipfilefuncs.OpenVFS(zip, &loc, "rb");
|
|
if (compressedpak)
|
|
{
|
|
//this extra logic is so we can handle things like nexuiz/data/blah.pk3
|
|
//as well as just data/blah.pk3
|
|
slashes = 0;
|
|
for (s = strchr(fname, '/'); s; s = strchr(s+1, '/'))
|
|
slashes++;
|
|
for (; slashes > 1; slashes--)
|
|
fname = strchr(fname, '/')+1;
|
|
|
|
if (!slashes)
|
|
{
|
|
FS_CreatePath(fname, FS_GAMEONLY);
|
|
decompressedpak = FS_OpenVFS(fname, "wb", FS_GAMEONLY);
|
|
}
|
|
else
|
|
{
|
|
FS_CreatePath(fname, FS_ROOT);
|
|
decompressedpak = FS_OpenVFS(fname, "wb", FS_ROOT);
|
|
}
|
|
if (decompressedpak)
|
|
{
|
|
for(;;)
|
|
{
|
|
read = VFS_READ(compressedpak, buffer, sizeof(buffer));
|
|
if (read <= 0)
|
|
break;
|
|
VFS_WRITE(decompressedpak, buffer, read);
|
|
}
|
|
VFS_CLOSE(decompressedpak);
|
|
}
|
|
VFS_CLOSE(compressedpak);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void UnpackAndExtractPakFiles_Complete(struct dl_download *dl)
|
|
{
|
|
extern searchpathfuncs_t zipfilefuncs;
|
|
void *zip;
|
|
|
|
Plug_LockPlugin(dl->user_ctx, true);
|
|
|
|
if (dl->status == DL_FINISHED)
|
|
zip = zipfilefuncs.OpenNew(dl->file, dl->url);
|
|
else
|
|
zip = NULL;
|
|
/*the zip code will have eaten the file handle*/
|
|
dl->file = NULL;
|
|
if (zip)
|
|
{
|
|
/*scan it to extract its contents*/
|
|
zipfilefuncs.EnumerateFiles(zip, "*.pk3", ExtractDataFile, zip);
|
|
zipfilefuncs.EnumerateFiles(zip, "*.pak", ExtractDataFile, zip);
|
|
|
|
/*close it, delete the temp file from disk, etc*/
|
|
zipfilefuncs.ClosePath(zip);
|
|
|
|
/*restart the filesystem so those new files can be found*/
|
|
Plug_ExecuteCommand(dl->user_ctx, "fs_restart\n");
|
|
}
|
|
|
|
Plug_LockPlugin(dl->user_ctx, false);
|
|
}
|
|
#endif
|
|
|
|
void LoadSplashImage(struct dl_download *dl)
|
|
{
|
|
struct context *ctx = dl->user_ctx;
|
|
vfsfile_t *f = dl->file;
|
|
int x, y;
|
|
int width = 0;
|
|
int height = 0;
|
|
int len;
|
|
char *buffer;
|
|
unsigned char *image;
|
|
|
|
Plug_LockPlugin(ctx, true);
|
|
ctx->splashwidth = 0;
|
|
ctx->splashheight = 0;
|
|
image = ctx->splashdata;
|
|
ctx->splashdata = NULL;
|
|
free(image);
|
|
Plug_LockPlugin(ctx, false);
|
|
|
|
if (!f)
|
|
{
|
|
if (ctx->bfuncs.StatusChanged)
|
|
ctx->bfuncs.StatusChanged(ctx->hostinstance);
|
|
return;
|
|
}
|
|
|
|
len = VFS_GETLEN(f);
|
|
buffer = malloc(len);
|
|
VFS_READ(f, buffer, len);
|
|
VFS_CLOSE(f);
|
|
dl->file = NULL;
|
|
|
|
image = NULL;
|
|
if (!image)
|
|
image = ReadJPEGFile(buffer, len, &width, &height);
|
|
if (!image)
|
|
image = ReadPNGFile(buffer, len, &width, &height, dl->url);
|
|
|
|
free(buffer);
|
|
if (image)
|
|
{
|
|
Plug_LockPlugin(ctx, true);
|
|
if (ctx->splashdata)
|
|
free(ctx->splashdata);
|
|
ctx->splashdata = malloc(width*height*4);
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
ctx->splashdata[(y*width + x)*4+0] = image[((height-y-1)*width + x)*4+2];
|
|
ctx->splashdata[(y*width + x)*4+1] = image[((height-y-1)*width + x)*4+1];
|
|
ctx->splashdata[(y*width + x)*4+2] = image[((height-y-1)*width + x)*4+0];
|
|
}
|
|
}
|
|
ctx->splashwidth = width;
|
|
ctx->splashheight = height;
|
|
BZ_Free(image);
|
|
Plug_LockPlugin(ctx, false);
|
|
if (ctx->bfuncs.StatusChanged)
|
|
ctx->bfuncs.StatusChanged(ctx->hostinstance);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
static void ReadQTVFileDescriptor(struct context *ctx, vfsfile_t *f, const char *name)
|
|
{
|
|
CL_ParseQTVFile(f, name, &ctx->qtvf);
|
|
|
|
pscript_property_splash_sets(ctx, ctx->qtvf.splashscreen);
|
|
}
|
|
|
|
void CL_QTVPlay (vfsfile_t *newf, qboolean iseztv);
|
|
static void BeginDemo(struct context *ctx, vfsfile_t *f, const char *name)
|
|
{
|
|
if (!activecontext)
|
|
activecontext = ctx;
|
|
|
|
CL_QTVPlay(f, false);
|
|
}
|
|
static void EndDemo(struct context *ctx, vfsfile_t *f, const char *name)
|
|
{
|
|
Cmd_ExecuteString("disconnect", RESTRICT_LOCAL);
|
|
}
|
|
#endif
|
|
/////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct pscript_property
|
|
{
|
|
char *name;
|
|
|
|
char *cvarname;
|
|
|
|
char *(*getstring)(struct context *ctx);
|
|
void (*setstring)(struct context *ctx, const char *val);
|
|
|
|
int (*getint)(struct context *ctx);
|
|
void (*setint)(struct context *ctx, int val);
|
|
|
|
float (*getfloat)(struct context *ctx);
|
|
void (*setfloat)(struct context *ctx, float val);
|
|
};
|
|
|
|
int pscript_property_running_getb(struct context *ctx)
|
|
{
|
|
if (ctx->pub.running)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void pscript_property_running_setb(struct context *ctx, int i)
|
|
{
|
|
i = !!i;
|
|
if (ctx->pub.running == i)
|
|
return;
|
|
if (i)
|
|
Plug_StartContext(ctx);
|
|
else
|
|
Plug_StopContext(ctx, false);
|
|
}
|
|
|
|
char *pscript_property_startserver_gets(struct context *ctx)
|
|
{
|
|
return strdup(ctx->qtvf.server);
|
|
}
|
|
void pscript_property_startserver_sets(struct context *ctx, const char *val)
|
|
{
|
|
if (strchr(val, '$') || strchr(val, ';') || strchr(val, '\n'))
|
|
return;
|
|
|
|
ctx->qtvf.connectiontype = QTVCT_JOIN;
|
|
Q_strncpyz(ctx->qtvf.server, val, sizeof(ctx->qtvf.server));
|
|
}
|
|
char *pscript_property_curserver_gets(struct context *ctx)
|
|
{
|
|
extern char lastdemoname[];
|
|
if (!pscript_property_running_getb(ctx))
|
|
return pscript_property_startserver_gets(ctx);
|
|
|
|
if (ctx->curserver)
|
|
return strdup(ctx->curserver);
|
|
else
|
|
return strdup("");
|
|
}
|
|
void pscript_property_curserver_sets(struct context *ctx, const char *val)
|
|
{
|
|
if (strchr(val, '$') || strchr(val, ';') || strchr(val, '\n'))
|
|
return;
|
|
|
|
if (!pscript_property_running_getb(ctx))
|
|
{
|
|
pscript_property_startserver_sets(ctx, val);
|
|
return;
|
|
}
|
|
|
|
Plug_ExecuteCommand(ctx, "connect \"%s\"\n", val);
|
|
}
|
|
|
|
void pscript_property_stream_sets(struct context *ctx, const char *val)
|
|
{
|
|
if (strchr(val, '$') || strchr(val, ';') || strchr(val, '\n'))
|
|
return;
|
|
|
|
ctx->qtvf.connectiontype = QTVCT_STREAM;
|
|
Q_strncpyz(ctx->qtvf.server, val, sizeof(ctx->qtvf.server));
|
|
|
|
Plug_ExecuteCommand(ctx, "qtvplay \"%s\"\n", val);
|
|
}
|
|
void pscript_property_map_sets(struct context *ctx, const char *val)
|
|
{
|
|
if (strchr(val, '$') || strchr(val, ';') || strchr(val, '\n'))
|
|
return;
|
|
ctx->qtvf.connectiontype = QTVCT_MAP;
|
|
Q_strncpyz(ctx->qtvf.server, val, sizeof(ctx->qtvf.server));
|
|
|
|
Plug_ExecuteCommand(ctx, "map \"%s\"\n", val);
|
|
}
|
|
|
|
float pscript_property_curver_getf(struct context *ctx)
|
|
{
|
|
int base = FTE_VER_MAJOR * 10000 + FTE_VER_MINOR * 100;
|
|
return base;
|
|
// return version_number();
|
|
}
|
|
|
|
void pscript_property_availver_setf(struct context *ctx, float val)
|
|
{
|
|
ctx->pub.availver = val;
|
|
if (ctx->pub.availver <= pscript_property_curver_getf(ctx))
|
|
ctx->pub.availver = 0;
|
|
}
|
|
|
|
void pscript_property_datadownload_sets(struct context *ctx, const char *val)
|
|
{
|
|
free(ctx->datadownload);
|
|
ctx->datadownload = strdup(val);
|
|
}
|
|
|
|
void pscript_property_game_sets(struct context *ctx, const char *val)
|
|
{
|
|
if (strchr(val, '$') || strchr(val, ';') || strchr(val, '\n'))
|
|
return;
|
|
|
|
if (!strstr(val, "."))
|
|
if (!strstr(val, "/"))
|
|
if (!strstr(val, "\\"))
|
|
if (!strstr(val, ":"))
|
|
{
|
|
free(ctx->gamename);
|
|
ctx->gamename = strdup(val);
|
|
}
|
|
}
|
|
|
|
void pscript_property_splash_sets(struct context *ctx, const char *val)
|
|
{
|
|
if (ctx->splashdownload)
|
|
DL_Close(ctx->splashdownload);
|
|
ctx->splashdownload = NULL;
|
|
|
|
if (val != ctx->qtvf.splashscreen)
|
|
Q_strncpyz(ctx->qtvf.splashscreen, val, sizeof(ctx->qtvf.splashscreen));
|
|
|
|
ctx->splashdownload = DL_Create(ctx->qtvf.splashscreen);
|
|
ctx->splashdownload->user_ctx = ctx;
|
|
if (!DL_CreateThread(ctx->splashdownload, VFSPIPE_Open(), LoadSplashImage))
|
|
{
|
|
DL_Close(ctx->splashdownload);
|
|
ctx->splashdownload = NULL;
|
|
}
|
|
}
|
|
|
|
char *pscript_property_build_gets(struct context *ctx)
|
|
{
|
|
return strdup(DISTRIBUTION " " __DATE__ " " __TIME__
|
|
#if defined(DEBUG) || defined(_DEBUG)
|
|
" (debug)"
|
|
#endif
|
|
);
|
|
}
|
|
|
|
float pscript_property_multi_getf(struct context *ctx)
|
|
{
|
|
return ctx->multiplecontexts;
|
|
}
|
|
void pscript_property_multi_setf(struct context *ctx, float f)
|
|
{
|
|
ctx->multiplecontexts = !!f;
|
|
}
|
|
|
|
static struct pscript_property pscript_properties[] =
|
|
{
|
|
{"", NULL, pscript_property_curserver_gets, pscript_property_curserver_sets},
|
|
{"server", NULL, pscript_property_curserver_gets, pscript_property_curserver_sets},
|
|
{"running", NULL, NULL, NULL, pscript_property_running_getb, pscript_property_running_setb},
|
|
{"startserver", NULL, pscript_property_startserver_gets, pscript_property_startserver_sets},
|
|
{"join", NULL, NULL, pscript_property_curserver_sets},
|
|
{"playername", "name"},
|
|
{NULL, "skin"},
|
|
{NULL, "team"},
|
|
{NULL, "topcolor"},
|
|
{NULL, "bottomcolor"},
|
|
{NULL, "password"}, //cvars are write only, just so you know.
|
|
// {NULL, "spectator"},
|
|
{"mapsrc", "cl_download_mapsrc"},
|
|
{"fullscreen", "vid_fullscreen"},
|
|
|
|
{"datadownload",NULL, NULL, pscript_property_datadownload_sets},
|
|
|
|
{"game", NULL, NULL, pscript_property_game_sets},
|
|
{"availver", NULL, NULL, NULL, NULL, NULL, NULL, pscript_property_availver_setf},
|
|
{"plugver", NULL, NULL, NULL, NULL, NULL, pscript_property_curver_getf},
|
|
{"multiple", NULL, NULL, NULL, NULL, NULL, pscript_property_multi_getf, pscript_property_multi_setf},
|
|
|
|
{"splash", NULL, NULL, pscript_property_splash_sets},
|
|
|
|
{"stream", NULL, NULL, pscript_property_stream_sets},
|
|
{"map", NULL, NULL, pscript_property_map_sets},
|
|
|
|
{"build", NULL, pscript_property_build_gets},
|
|
|
|
{NULL}
|
|
};
|
|
|
|
int Plug_FindProp(struct context *ctx, const char *field)
|
|
{
|
|
struct pscript_property *prop;
|
|
for (prop = pscript_properties; prop->name||prop->cvarname; prop++)
|
|
{
|
|
if (!stricmp(prop->name?prop->name:prop->cvarname, field))
|
|
{
|
|
// if (prop->onlyifactive)
|
|
// {
|
|
// if (!ctx->pub.running)
|
|
// return -1;
|
|
// }
|
|
return prop - pscript_properties;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
qboolean Plug_SetString(struct context *ctx, int fieldidx, const char *value)
|
|
{
|
|
struct pscript_property *field = pscript_properties + fieldidx;
|
|
if (!ctx || fieldidx < 0 || fieldidx >= sizeof(pscript_properties)/sizeof(pscript_properties[0]) || !value)
|
|
return false;
|
|
if (field->setstring)
|
|
{
|
|
Plug_LockPlugin(ctx, true);
|
|
field->setstring(ctx, value);
|
|
Plug_LockPlugin(ctx, false);
|
|
}
|
|
else if (field->setint)
|
|
{
|
|
Plug_LockPlugin(ctx, true);
|
|
field->setint(ctx, atoi(value));
|
|
Plug_LockPlugin(ctx, false);
|
|
}
|
|
else if (field->setfloat)
|
|
{
|
|
Plug_LockPlugin(ctx, true);
|
|
field->setfloat(ctx, atof(value));
|
|
Plug_LockPlugin(ctx, false);
|
|
}
|
|
else if (field->cvarname && ctx->pub.running)
|
|
{
|
|
Plug_LockPlugin(ctx, true);
|
|
Plug_ExecuteCommand(ctx, "%s \"%s\"\n", field->cvarname, value);
|
|
Plug_LockPlugin(ctx, false);
|
|
}
|
|
else
|
|
return false;
|
|
return true;
|
|
}
|
|
qboolean Plug_SetWString(struct context *ctx, int fieldidx, const wchar_t *value)
|
|
{
|
|
char tmp[1024];
|
|
wcstombs(tmp, value, sizeof(tmp));
|
|
return Plug_SetString(ctx, fieldidx, tmp);
|
|
}
|
|
qboolean Plug_SetInteger(struct context *ctx, int fieldidx, int value)
|
|
{
|
|
struct pscript_property *field = pscript_properties + fieldidx;
|
|
if (!ctx || fieldidx < 0 || fieldidx >= sizeof(pscript_properties)/sizeof(pscript_properties[0]))
|
|
return false;
|
|
if (field->setint)
|
|
{
|
|
Plug_LockPlugin(ctx, true);
|
|
field->setint(ctx, value);
|
|
Plug_LockPlugin(ctx, false);
|
|
}
|
|
else if (field->setfloat)
|
|
{
|
|
Plug_LockPlugin(ctx, true);
|
|
field->setfloat(ctx, value);
|
|
Plug_LockPlugin(ctx, false);
|
|
}
|
|
else if (field->cvarname && ctx->pub.running)
|
|
{
|
|
Plug_LockPlugin(ctx, true);
|
|
Plug_ExecuteCommand(ctx, "%s \"%i\"\n", field->cvarname, value);
|
|
Plug_LockPlugin(ctx, false);
|
|
}
|
|
else
|
|
return false;
|
|
return true;
|
|
}
|
|
qboolean Plug_SetFloat(struct context *ctx, int fieldidx, float value)
|
|
{
|
|
struct pscript_property *field = pscript_properties + fieldidx;
|
|
if (!ctx || fieldidx < 0 || fieldidx >= sizeof(pscript_properties)/sizeof(pscript_properties[0]))
|
|
return false;
|
|
if (field->setfloat)
|
|
{
|
|
Plug_LockPlugin(ctx, true);
|
|
field->setfloat(ctx, value);
|
|
Plug_LockPlugin(ctx, false);
|
|
}
|
|
else if (field->setint)
|
|
{
|
|
Plug_LockPlugin(ctx, true);
|
|
field->setint(ctx, value);
|
|
Plug_LockPlugin(ctx, false);
|
|
}
|
|
else if (field->cvarname && ctx->pub.running)
|
|
{
|
|
Plug_LockPlugin(ctx, true);
|
|
Plug_ExecuteCommand(ctx, "%s \"%f\"\n", field->cvarname, value);
|
|
Plug_LockPlugin(ctx, false);
|
|
}
|
|
else
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
qboolean Plug_GetString(struct context *ctx, int fieldidx, const char **value)
|
|
{
|
|
struct pscript_property *field = pscript_properties + fieldidx;
|
|
if (!ctx || fieldidx < 0 || fieldidx >= sizeof(pscript_properties)/sizeof(pscript_properties[0]))
|
|
return false;
|
|
|
|
if (field->getstring)
|
|
{
|
|
*value = field->getstring(ctx);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
void Plug_GotString(const char *value)
|
|
{
|
|
free((char*)value);
|
|
}
|
|
qboolean Plug_GetInteger(struct context *ctx, int fieldidx, int *value)
|
|
{
|
|
struct pscript_property *field = pscript_properties + fieldidx;
|
|
if (!ctx || fieldidx < 0 || fieldidx >= sizeof(pscript_properties)/sizeof(pscript_properties[0]))
|
|
return false;
|
|
|
|
if (field->getint)
|
|
{
|
|
*value = field->getint(ctx);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
qboolean Plug_GetFloat(struct context *ctx, int fieldidx, float *value)
|
|
{
|
|
struct pscript_property *field = pscript_properties + fieldidx;
|
|
if (!ctx || fieldidx < 0 || fieldidx >= sizeof(pscript_properties)/sizeof(pscript_properties[0]))
|
|
return false;
|
|
|
|
if (field->getfloat)
|
|
{
|
|
*value = field->getfloat(ctx);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
void *Plug_GetSplashBack(struct context *ctx, void *hdc, int *width, int *height)
|
|
{
|
|
BITMAPINFOHEADER bmh;
|
|
|
|
if (!ctx->splashdata)
|
|
return NULL;
|
|
|
|
bmh.biSize = sizeof(bmh);
|
|
bmh.biWidth = *width = ctx->splashwidth;
|
|
bmh.biHeight = *height = ctx->splashheight;
|
|
bmh.biPlanes = 1;
|
|
bmh.biBitCount = 32;
|
|
bmh.biCompression = BI_RGB;
|
|
bmh.biSizeImage = 0;
|
|
bmh.biXPelsPerMeter = 0;
|
|
bmh.biYPelsPerMeter = 0;
|
|
bmh.biClrUsed = 0;
|
|
bmh.biClrImportant = 0;
|
|
|
|
return CreateDIBitmap(hdc,
|
|
&bmh,
|
|
CBM_INIT,
|
|
(LPSTR)ctx->splashdata,
|
|
(LPBITMAPINFO)&bmh,
|
|
DIB_RGB_COLORS );
|
|
}
|
|
void Plug_ReleaseSplashBack(struct context *ctx, void *bmp)
|
|
{
|
|
DeleteObject(bmp);
|
|
}
|
|
#endif
|
|
|
|
static const struct plugfuncs exportedplugfuncs_1 =
|
|
{
|
|
Plug_CreateContext,
|
|
Plug_DestroyContext,
|
|
Plug_LockPlugin,
|
|
Plug_StartContext,
|
|
Plug_StopContext,
|
|
Plug_ChangeWindow,
|
|
|
|
Plug_FindProp,
|
|
Plug_SetString,
|
|
Plug_GetString,
|
|
Plug_GotString,
|
|
Plug_SetInteger,
|
|
Plug_GetInteger,
|
|
Plug_SetFloat,
|
|
Plug_GetFloat,
|
|
|
|
Plug_GetSplashBack,
|
|
Plug_ReleaseSplashBack,
|
|
|
|
Plug_SetWString
|
|
};
|
|
|
|
const struct plugfuncs *Plug_GetFuncs(int ver)
|
|
{
|
|
if (ver == 1)
|
|
return &exportedplugfuncs_1;
|
|
else
|
|
return NULL;
|
|
}
|