fteqw/engine/client/sys_plugfte.c
Spoike 03919e0dd4 fix vector arrays.
try to fix some misc issues.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4872 fc73d0e0-1445-4013-8a0c-d673dee63da5
2015-05-16 08:02:05 +00:00

2270 lines
53 KiB
C

#include "quakedef.h"
#include "winquake.h"
#include "sys_plugfte.h"
#include "../http/iweb.h"
#ifndef _WIN32
#include <sys/stat.h>
#endif
static void UnpackAndExtractPakFiles_Complete(struct dl_download *dl);
static void pscript_property_splash_sets(struct context *ctx, const char *val);
void *globalmutex;
#ifdef _MSC_VER
# ifdef _WIN64
# pragma comment(lib, MSVCLIBSPATH "zlib64.lib")
# else
# pragma comment(lib, MSVCLIBSPATH "zlib.lib")
# endif
#endif
#ifdef _WIN32
dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs)
{
return NULL;
}
#else
#include <dlfcn.h>
void Sys_CloseLibrary(dllhandle_t *lib)
{
if (lib)
dlclose(lib);
}
dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs)
{
char soname[MAX_OSPATH];
int i;
dllhandle_t lib;
lib = NULL;
if (!lib)
{
Q_snprintfz(soname, sizeof(soname), "%s.so", name);
lib = dlopen (soname, RTLD_LAZY);
}
if (!lib)
lib = dlopen (name, RTLD_LAZY);
if (!lib)
{
Con_Printf("%s\n", dlerror());
return NULL;
}
if (funcs)
{
for (i = 0; funcs[i].name; i++)
{
*funcs[i].funcptr = dlsym(lib, funcs[i].name);
if (!*funcs[i].funcptr)
break;
}
if (funcs[i].name)
{
Con_Printf("Unable to find symbol \"%s\" in \"%s\"\n", funcs[i].name, name);
Sys_CloseLibrary((dllhandle_t*)lib);
lib = NULL;
}
}
return (dllhandle_t*)lib;
}
#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, char *buffer, int buffersize)
{
int i;
while (*str <= ' ' && *str>'\0')
str++;
for (i = 0; i < buffersize-1; 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);
#ifdef _WIN32
OutputDebugString(dest);
#else
FILE *f = fopen("/tmp/ftelog", "a");
if (f)
{
fputs(dest, f);
fclose(f);
}
write(2, dest, strlen(dest));
#endif
}
#ifdef _WIN32
// don't use these functions in MSVC8
#if (_MSC_VER < 1400)
int VARGS linuxlike_snprintf(char *buffer, int size, const char *format, ...)
{
#undef _vsnprintf
int ret;
va_list argptr;
if (size <= 0)
return 0;
size--;
va_start (argptr, format);
ret = _vsnprintf (buffer,size, format,argptr);
va_end (argptr);
buffer[size] = '\0';
return ret;
}
int VARGS linuxlike_vsnprintf(char *buffer, int size, const char *format, va_list argptr)
{
#undef _vsnprintf
int ret;
if (size <= 0)
return 0;
size--;
ret = _vsnprintf (buffer,size, format,argptr);
buffer[size] = '\0';
return ret;
}
#else
int VARGS linuxlike_snprintf_vc8(char *buffer, int size, const char *format, ...)
{
int ret;
va_list argptr;
va_start (argptr, format);
ret = vsnprintf_s (buffer,size, _TRUNCATE, format,argptr);
va_end (argptr);
return ret;
}
#endif
#endif
#include "netinc.h"
#ifndef _WIN32
#define pgetaddrinfo getaddrinfo
#define pfreeaddrinfo freeaddrinfo
#else
#if 0//def _MSC_VER
#include <wspiapi.h>
#define pgetaddrinfo getaddrinfo
#define pfreeaddrinfo freeaddrinfo
#else
#include <ws2tcpip.h>
#endif
#endif
size_t NET_StringToSockaddr2 (const char *s, int defaultport, struct sockaddr_qstorage *sadr, int *addrfamily, int *addrsize, size_t addrcount)
{
struct addrinfo *addrinfo = NULL;
struct addrinfo *pos;
struct addrinfo udp6hint;
int error;
char *port;
char dupbase[256];
int len;
#ifndef pgetaddrinfo
int (WINAPI *pgetaddrinfo)(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res);
void (WINAPI *pfreeaddrinfo)(struct addrinfo *ai);
void *lib = LoadLibrary("ws2_32.dll");
if (!lib)
return false;
if (addrcount != 1)
return false;
pgetaddrinfo = (void*)GetProcAddress(lib, "getaddrinfo");
pfreeaddrinfo = (void*)GetProcAddress(lib, "freeaddrinfo");
if (!pgetaddrinfo || !pfreeaddrinfo)
return 0;
#endif
if (!(*s))
return 0;
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 = pgetaddrinfo(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 = pgetaddrinfo(dupbase, port+1, &udp6hint, &addrinfo);
}
else
error = EAI_NONAME;
if (error) //failed, try string with no port.
error = pgetaddrinfo(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 0;
}
((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:
pfreeaddrinfo (addrinfo);
if (!((struct sockaddr*)sadr)->sa_family) //none suitablefound
return 0;
if (addrfamily)
*addrfamily = ((struct sockaddr*)sadr)->sa_family;
if (((struct sockaddr*)sadr)->sa_family == AF_INET)
{
if (!((struct sockaddr_in *)sadr)->sin_port)
((struct sockaddr_in *)sadr)->sin_port = htons(defaultport);
if (addrsize)
*addrsize = sizeof(struct sockaddr_in);
}
else
{
if (!((struct sockaddr_in6 *)sadr)->sin6_port)
((struct sockaddr_in6 *)sadr)->sin6_port = htons(defaultport);
if (addrsize)
*addrsize = sizeof(struct sockaddr_in6);
}
return 1;
}
char *COM_ParseType (const char *data, char *out, int outlen, com_tokentype_t *toktype)
{
int c;
int len;
// if (out == com_token)
// COM_AssertMainThread("COM_ParseOut: com_token");
len = 0;
out[0] = 0;
if (toktype)
*toktype = TTP_EOF;
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 == '\"')
{
if (toktype)
*toktype = TTP_STRING;
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
if (toktype)
*toktype = TTP_RAWTOKEN;
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;
}
#if 0
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)
{
Con_Printf("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;
}
#endif
#if defined(OFFICIAL_RELEASE)
#define BUILDTYPE "rel"
#else
#define BUILDTYPE "test"
#define UPDATE_URL "http://triptohell.info/moodles/"
#define UPDATE_URL_VERSION UPDATE_URL "version.txt"
//we should perhaps use the uname stuff instead.
#ifdef _WIN32
#ifdef _WIN64
#define UPDATE_URL_BUILD UPDATE_URL "win64/fte" EXETYPE ".exe"
#else
#define UPDATE_URL_BUILD UPDATE_URL "win32/fte" EXETYPE ".exe"
#endif
#else
#if defined(__amd64__)
#define UPDATE_URL_BUILD UPDATE_URL "linux_amd64/fteqw." EXETYPE "64"
#else
#define UPDATE_URL_BUILD UPDATE_URL "linux_x86/fteqw." EXETYPE "32"
#endif
#endif
#endif
#ifdef _WIN32
//#if defined(GLQUAKE) && defined(D3DQUAKE)
// #define EXETYPE "qw"
//#elif defined(GLQUAKE)
// #ifdef MINIMAL
// #define EXETYPE "minglqw"
// #else
#define EXETYPE "glqw"
// #endif
//#elif defined(D3DQUAKE)
// #define EXETYPE "d3dqw"
//#elif defined(SWQUAKE)
// #define EXETYPE "swqw"
//#else //erm...
// #define EXETYPE "qw"
//#endif
#else
#define EXETYPE "gl"
#endif
#ifdef _WIN32
qboolean MyRegGetStringValue(HKEY base, const char *keyname, const char *valuename, void *data, size_t datalen)
{
qboolean result = false;
HKEY subkey;
DWORD type = REG_NONE;
if (RegOpenKeyEx(base, keyname, 0, KEY_READ, &subkey) == ERROR_SUCCESS)
{
result = ERROR_SUCCESS == RegQueryValueEx(subkey, valuename, NULL, &type, data, &datalen);
RegCloseKey (subkey);
}
if (type == REG_SZ || type == REG_EXPAND_SZ)
((char*)data)[datalen] = 0;
else
((char*)data)[0] = 0;
return result;
}
qboolean MyRegSetValue(void *base, const char *keyname, const char *valuename, int type, const void *data, int datalen)
{
HKEY subkey;
if (RegCreateKeyEx(base, keyname, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &subkey, NULL) == ERROR_SUCCESS)
{
RegSetValueEx(subkey, valuename, 0, type, data, datalen);
RegCloseKey (subkey);
}
return true;
}
void MyRegDeleteKeyValue(void *base, const char *keyname, const char *valuename)
{
HKEY subkey;
if (RegOpenKeyEx(base, keyname, 0, KEY_WRITE, &subkey) == ERROR_SUCCESS)
{
RegDeleteValue(subkey, valuename);
RegCloseKey (subkey);
}
}
qboolean Update_GetHomeDirectory(char *homedir, int homedirsize)
{
HMODULE shfolder = LoadLibrary("shfolder.dll");
if (shfolder)
{
HRESULT (WINAPI *dSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath);
dSHGetFolderPath = (void *)GetProcAddress(shfolder, "SHGetFolderPathA");
if (dSHGetFolderPath)
{
char folder[MAX_PATH];
// 0x5 == CSIDL_PERSONAL
if (dSHGetFolderPath(NULL, 0x5, NULL, 0, folder) == S_OK)
{
Q_snprintfz(homedir, homedirsize, "%s/My Games/%s/", folder, FULLENGINENAME);
return true;
}
}
// FreeLibrary(shfolder);
}
return false;
}
void Sys_mkdir (char *path)
{
CreateDirectory (path, NULL);
}
#else
void Sys_mkdir (char *path)
{
mkdir (path, 0777);
}
qboolean Update_GetHomeDirectory(char *homedir, int homedirsize)
{
qboolean result = false;
char *val = getenv("HOME");
if (!val) val = ".";
else result = true;
Q_strncpyz(homedir, val, homedirsize);
Q_strncatz(homedir, "/.fte/", homedirsize);
return result;
}
#endif
static void Update_CreatePath (char *path)
{
char *ofs;
for (ofs = path+1 ; *ofs ; ofs++)
{
if (*ofs == '/' || *ofs == '\\')
{ // create the directory
*ofs = 0;
Sys_mkdir (path);
*ofs = '/';
}
}
}
#include "fs.h"
struct dl_download *enginedownloadactive;
void Update_Version_Updated(struct dl_download *dl)
{
//happens in a thread, avoid va
if (dl->file)
{
if (dl->status == DL_FINISHED)
{
char buf[8192];
unsigned int size = 0, chunk, fullsize;
char pendingname[MAX_OSPATH];
FILE *pending;
Update_GetHomeDirectory(pendingname, sizeof(pendingname));
Q_strncatz(pendingname, "pending" BUILDTYPE EXETYPE ".tmp", sizeof(pendingname));
fullsize = VFS_GETLEN(dl->file);
Update_CreatePath(pendingname);
#ifndef _WIN32
unlink(pendingname);
#endif
pending = fopen(pendingname, "wb");
if (pending)
{
while(1)
{
chunk = VFS_READ(dl->file, buf, sizeof(buf));
if (!chunk)
break;
size += fwrite(buf, 1, chunk, pending);
}
fclose(pending);
if (fullsize == size)
{
#ifdef _WIN32
MyRegSetValue(HKEY_CURRENT_USER, "Software\\"FULLENGINENAME, "pending" BUILDTYPE EXETYPE, REG_SZ, pendingname, strlen(pendingname)+1);
#else
chmod(pendingname, S_IRUSR|S_IXUSR);
#endif
}
else
{
dl->status = DL_FAILED;
}
}
else
dl->status = DL_FAILED;
}
VFS_CLOSE(dl->file);
dl->file = NULL;
}
}
qboolean Plug_GetDownloadedName(char *updatedpath, int updatedpathlen)
{
#ifdef _WIN32
char pendingpath[MAX_OSPATH];
char temppath[MAX_OSPATH];
Sys_LockMutex(globalmutex);
if (!enginedownloadactive || (enginedownloadactive->status == DL_FINISHED || enginedownloadactive->status == DL_FAILED))
{
if (enginedownloadactive)
DL_Close(enginedownloadactive);
enginedownloadactive = NULL;
/*if there's some downloaded binary which isn't running yet, kill the old active one and update it*/
MyRegGetStringValue(HKEY_CURRENT_USER, "Software\\"FULLENGINENAME, "pending" BUILDTYPE EXETYPE, pendingpath, sizeof(pendingpath));
if (*pendingpath)
{
MyRegDeleteKeyValue(HKEY_CURRENT_USER, "Software\\"FULLENGINENAME, "pending" BUILDTYPE EXETYPE);
Update_GetHomeDirectory(temppath, sizeof(temppath));
Update_CreatePath(temppath);
Q_strncatz(temppath, "cur" BUILDTYPE EXETYPE".exe", sizeof(temppath));
DeleteFile(temppath);
if (MoveFile(pendingpath, temppath))
MyRegSetValue(HKEY_CURRENT_USER, "Software\\"FULLENGINENAME, BUILDTYPE EXETYPE, REG_SZ, temppath, strlen(temppath)+1);
}
/*grab the binary to run from the registry*/
MyRegGetStringValue(HKEY_CURRENT_USER, "Software\\"FULLENGINENAME, BUILDTYPE EXETYPE, updatedpath, updatedpathlen);
if (*updatedpath && INVALID_FILE_ATTRIBUTES==GetFileAttributes(updatedpath)) //make sure its actually still there.
*updatedpath = 0;
if (!*updatedpath)
{
/*ooer, its not set, try and download one. updates are handled by the client itself.*/
enginedownloadactive = DL_Create(UPDATE_URL_BUILD);
if (enginedownloadactive)
{
enginedownloadactive->user_ctx = NULL;
DL_CreateThread(enginedownloadactive, VFSPIPE_Open(), Update_Version_Updated);
}
}
}
Sys_UnlockMutex(globalmutex);
#else
char pendingpath[MAX_OSPATH];
char currentpath[MAX_OSPATH];
struct stat st;
Update_GetHomeDirectory(currentpath, sizeof(currentpath));
Q_strncatz(currentpath, "cur" BUILDTYPE EXETYPE, sizeof(currentpath));
Update_GetHomeDirectory(pendingpath, sizeof(pendingpath));
Q_strncatz(pendingpath, "pending" BUILDTYPE EXETYPE ".tmp", sizeof(pendingpath));
//truncated to be the same string? :s
if (!strcmp(currentpath, pendingpath))
return false;
Sys_LockMutex(globalmutex);
if (!enginedownloadactive || (enginedownloadactive->status == DL_FINISHED || enginedownloadactive->status == DL_FAILED))
{
//make sure the download is cleaned up properly.
if (enginedownloadactive)
DL_Close(enginedownloadactive);
enginedownloadactive = NULL;
//if the engine wrote a new version to use, copy it over to the proper path now (two paths means we're less likely to be trying to write a file that is already open).
if (!stat(pendingpath, &st))
{
//rename supposedly overwrites automagically. I'm paranoid.
unlink(currentpath);
rename(pendingpath, currentpath);
}
if (!stat(currentpath, &st))
{
//there's a current file, yay.
Q_strncpyz(updatedpath, currentpath, updatedpathlen);
}
else
{ //no engine available to run. we'll have to download one (we only need to do it in this case because the engine itself autoupdates,
//and we can't because we don't actually know what version we have, only the binary that's running knows its version.
//that's the theory anyway.
Con_Printf("downloading %s\n", UPDATE_URL_BUILD);
enginedownloadactive = DL_Create(UPDATE_URL_BUILD);
if (enginedownloadactive)
{
enginedownloadactive->user_ctx = NULL;
DL_CreateThread(enginedownloadactive, VFSPIPE_Open(), Update_Version_Updated);
}
}
}
Sys_UnlockMutex(globalmutex);
#endif
return !!*updatedpath;
}
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;
#else
int pipetoengine;
int pipefromengine;
pid_t 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 <= ' ')
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 = '?';
}
return strdup(arg);
}
qboolean Plug_GetBinaryName(char *exe, int exelen, char *basedir, int basedirlen)
{
// char buffer[1024];
// char cmd[64];
// char value[1024];
// FILE *f;
*exe = 0;
*basedir = 0;
#ifdef _DEBUG
#ifdef _WIN32
Q_strncpyz(exe, "C:/Games/Quake/fte_trunk/fteglqw_dbg.exe", exelen);
#else
Q_strncpyz(exe, "/home/david/quake/fte_trunk/engine/debug/fteqw.gl", exelen);
#endif
#endif
/*
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(exe, exelen, "%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);
}
*/
if (!*exe)
return Plug_GetDownloadedName(exe, exelen);
return false;
}
int Plug_GenCommandline(struct context *ctx, char **argv, int maxargs)
{
char *s;
int argc;
char tok[256];
char exe[1024];
char basedir[1024];
qboolean autoupdate;
*exe = 0;
*basedir = 0;
autoupdate = Plug_GetBinaryName(exe, sizeof(exe), basedir, sizeof(basedir));
if (!*exe)
{
Con_Printf("Unable to determine what to run\n");
return 0; //error
}
argc = 0;
ADDRARG(exe);
if (autoupdate)
{
ADDRARG("-autoupdate");
}
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);
}
//make sure we always use the home directory. because its better than using some random browser-defined working directory.
ADDRARG("-usehome");
if (ctx->datadownload)
{
ADDRARG("-manifest");
ADDCARG(ctx->datadownload);
}
return argc;
}
qboolean Plug_GenCommandlineString(struct context *ctx, char *cmdline, int cmdlinelen)
{
char *argv[64];
int argc, i;
argc = Plug_GenCommandline(ctx, argv, 64);
if (!argc)
return false;
for (i = 0; i < argc; i++)
{
//add quotes for any arguments with spaces
if (!*argv[i] || strchr(argv[i], ' ') || strchr(argv[i], '\t'))
{
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];
va_start (va, message);
vsnprintf (finalmessage, sizeof(finalmessage)-1, message, va);
va_end (va);
if (ctx->pipetoengine)
{
#ifdef _WIN32
DWORD written = 0;
WriteFile(ctx->pipetoengine, finalmessage, strlen(finalmessage), &written, NULL);
#else
write(ctx->pipetoengine, finalmessage, strlen(finalmessage));
#endif
}
}
qboolean Plug_CreatePluginProcess(struct context *ctx)
{
#ifdef _WIN32
char cmdline[8192];
PROCESS_INFORMATION childinfo;
STARTUPINFO startinfo;
SECURITY_ATTRIBUTES pipesec = {sizeof(pipesec), NULL, TRUE};
if (!Plug_GenCommandlineString(ctx, cmdline, sizeof(cmdline)))
return false;
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 %#p\n", ctx->windowleft, ctx->windowtop, ctx->windowwidth, ctx->windowheight, (void*)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);
return true;
#else
int in[2];
int out[2];
char *argv[64] = {NULL};
int i;
i = Plug_GenCommandline(ctx, argv, sizeof(argv) / sizeof(argv[0]));
if (!i)
return false;
argv[i] = NULL;
/* char buf[512];
snprintf(buf, sizeof(buf), "%i args\n", i);
write(2, buf, strlen(buf));
for (i = 0; argv[i]; i++)
{
snprintf(buf, sizeof(buf), "argv[%i] = %s\n", i, argv[i]);
write(2, buf, strlen(buf));
}
*/
pipe(in);
pipe(out);
ctx->pipefromengine = in[0];
ctx->pipetoengine = out[1];
Plug_ExecuteCommand(ctx, "vid_recenter %i %i %i %i %#p\n", ctx->windowleft, ctx->windowtop, ctx->windowwidth, ctx->windowheight, (void*)ctx->windowhnd);
ctx->engineprocess = fork();
if (ctx->engineprocess == 0)
{
//close the ends we don't care about
close(in[0]);
close(out[1]);
//dupe them to the stdin/stdout, for ease of use, and clean up the extra handles
dup2(out[0], 0);
dup2(in[1], 1);
//dup2(in[1], 2);
close(in[1]);
close(out[0]);
//invoke the child and kill ourselves if that failed.
write(2, "invoking engine\n", 16);
execv(argv[0], argv);
fprintf(stderr, "execv failed: %s\n", argv[0]);
exit(1);
}
close(in[1]);
close(out[0]);
if (ctx->engineprocess == -1)
return false;
return true;
#endif
}
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)
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
if (!Plug_CreatePluginProcess(ctx))
{
if (!enginedownloadactive)
{
strcpy(ctx->pub.statusmessage, "Unable to determine engine to run. Plugin is not correctly installed.");
if (ctx->bfuncs.StatusChanged)
ctx->bfuncs.StatusChanged(ctx->hostinstance);
ctx->pub.running = false;
}
else
{
int os = -1;
while(1)
{
Sys_LockMutex(globalmutex);
if (!enginedownloadactive)
{
Sys_UnlockMutex(globalmutex);
if (!Plug_CreatePluginProcess(ctx))
ctx->pub.running = false;
break;
}
if (os != enginedownloadactive->status)
{
os = enginedownloadactive->status;
switch(os)
{
case DL_PENDING:
strcpy(ctx->pub.statusmessage, "Download pending.");
break;
case DL_FAILED:
strcpy(ctx->pub.statusmessage, "Engine download failed. Try later, or fix internet connection.");
os = -2; //stop trying.
ctx->pub.running = false;
break;
case DL_RESOLVING:
strcpy(ctx->pub.statusmessage, "Download pending. Resolving hostname.");
break;
case DL_QUERY:
strcpy(ctx->pub.statusmessage, "Download pending. Reqeusting file.");
break;
case DL_ACTIVE:
Q_snprintfz(ctx->pub.statusmessage, sizeof(ctx->pub.statusmessage), "Download pending. At %u of %u.", enginedownloadactive->completed, enginedownloadactive->totalsize);
os = -1; //keep refreshing
break;
case DL_FINISHED:
strcpy(ctx->pub.statusmessage, "Download completed!");
DL_Close(enginedownloadactive);
enginedownloadactive = NULL;
break;
}
Sys_UnlockMutex(globalmutex);
if (ctx->bfuncs.StatusChanged)
ctx->bfuncs.StatusChanged(ctx->hostinstance);
}
else
Sys_UnlockMutex(globalmutex);
if (os == -2)
break;
#ifdef _WIN32
Sleep(1000);
#else
sleep(1);
#endif
}
}
}
if (ctx->bfuncs.StatusChanged)
ctx->bfuncs.StatusChanged(ctx->hostinstance);
if (ctx->pub.running)
while(1)
{
#ifdef _WIN32
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.
*ctx->pub.statusmessage = 0;
break;
}
#else
int avail;
// Con_Printf("Attempting to read from pipe...\n");
if (bufoffs == sizeof(buffer))
{
Con_Printf("ERROR: Pipe full!\n");
bufoffs = 0; //not much we can really do
}
avail = read(ctx->pipefromengine, buffer + bufoffs, 1);
if (!avail)
{
Con_Printf("eof on pipe\n");
//broken pipe, client died?
*ctx->pub.statusmessage = 0;
break;
}
if (avail < 0)
{
int err = errno;
if (err == EAGAIN)
continue;
perror("hello moo\n");
//broken pipe, client died?
Q_strncpyz(ctx->pub.statusmessage, "pipe error", sizeof(ctx->pub.statusmessage));
break;
}
#endif
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);
Con_Printf("Status changed to \"%s\"\n", buffer+6);
}
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
Con_Printf("Unknown command from engine \"%s\"\n", buffer);
}
}
else
break;
}
}
if (ctx->bfuncs.StatusChanged)
ctx->bfuncs.StatusChanged(ctx->hostinstance);
if (ctx == activecontext)
activecontext = NULL;
ctx->pub.running = false;
return 0;
}
//begins the context, fails if one is already active
qboolean Plug_StartContext(struct context *ctx)
{
if (activecontext && !ctx->multiplecontexts)
return false;
Sys_LockMutex(globalmutex);
if (!ctx->pub.running)
{
if (ctx->thread)
{
//make sure the thread is killed properly, so we don't get two threads trying to drive the same context
Sys_UnlockMutex(globalmutex);
Plug_StopContext(ctx, true);
Sys_LockMutex(globalmutex);
}
}
if (!ctx->pub.running && !ctx->thread)
{
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);
}
Sys_UnlockMutex(globalmutex);
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 (thread)
{
if (wait)
{
#ifdef _WIN32
while (ctx->pub.running && ctx->windowhnd)
{
MSG msg;
while (PeekMessage(&msg, ctx->windowhnd, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Sleep(10);
}
#endif
ctx->thread = NULL;
Sys_WaitOnThread(thread);
}
}
}
//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 %#p\n", ctx->windowleft, ctx->windowtop, ctx->windowwidth, ctx->windowheight, (void*)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)
{
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,
#ifdef _WIN32
Plug_GetSplashBack,
Plug_ReleaseSplashBack,
#endif
Plug_SetWString
};
const struct plugfuncs *Plug_GetFuncs(int ver)
{
if (!globalmutex)
globalmutex = Sys_CreateMutex();
if (ver == 1)
return &exportedplugfuncs_1;
else
return NULL;
}