mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-27 06:02:16 +00:00
bed989f529
automatic lightmap texture scaling to retain more performance on large maps. r_clutter preliminary implementation should probably fix up the shader still. CSQC_Parse_Damage implemented. finally implement q2 inventory. fix mixer overflow crash. glsl can now use s_diffuse etc to force inclusion of a diffuse sampler/texture, meaning shaders don't need to include them. fix issue with writeip git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4841 fc73d0e0-1445-4013-8a0c-d673dee63da5
2269 lines
53 KiB
C
2269 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, char *keyname, char *valuename, void *data, DWORD 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;
|
|
}
|
|
|
|
void MyRegSetValue(HKEY base, char *keyname, char *valuename, int type, void *data, DWORD 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);
|
|
}
|
|
}
|
|
void MyRegDeleteKeyValue(HKEY base, char *keyname, 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;
|
|
}
|