#include "quakedef.h" #include "winquake.h" #include "sys_plugfte.h" #include "../http/iweb.h" #ifndef _WIN32 #include #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 void BZ_Free(void *ptr) { free(ptr); } void *BZF_Malloc(int size) { return malloc(size); } //FIXME: we can't use this. void *BZ_Malloc(int size) { return BZF_Malloc(size); } void QDECL Q_strncpyz(char *d, const char *s, int n) { int i; n--; if (n < 0) return; //this could be an error for (i=0; *s; i++) { if (i == n) break; *d++ = *s++; } *d='\0'; } char *COM_SkipPath (const char *pathname) { const char *last; last = pathname; while (*pathname) { if (*pathname=='/' || *pathname == '\\') last = pathname+1; pathname++; } return (char *)last; } void VARGS Q_vsnprintfz (char *dest, size_t size, const char *fmt, va_list argptr) { vsnprintf (dest, size, fmt, argptr); dest[size-1] = 0; } void VARGS Q_snprintfz (char *dest, size_t size, const char *fmt, ...) { va_list argptr; va_start (argptr, fmt); Q_vsnprintfz(dest, size, fmt, argptr); va_end (argptr); } char *COM_TrimString(char *str) { int i; static char buffer[256]; while (*str <= ' ' && *str>'\0') str++; for (i = 0; i < 255; i++) { if (*str <= ' ') break; buffer[i] = *str++; } buffer[i] = '\0'; return buffer; } void VARGS Con_Printf (const char *fmt, ...) { va_list argptr; char dest[256]; va_start (argptr, fmt); Q_vsnprintfz(dest, sizeof(dest), fmt, argptr); va_end (argptr); #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 #define pgetaddrinfo getaddrinfo #define pfreeaddrinfo freeaddrinfo #else #include #endif #endif qboolean NET_StringToSockaddr (const char *s, int defaultport, struct sockaddr_qstorage *sadr, int *addrfamily, int *addrsize) { struct addrinfo *addrinfo = NULL; struct addrinfo *pos; struct addrinfo udp6hint; int error; char *port; char dupbase[256]; int len; #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; pgetaddrinfo = (void*)GetProcAddress(lib, "getaddrinfo"); pfreeaddrinfo = (void*)GetProcAddress(lib, "freeaddrinfo"); if (!pgetaddrinfo || !pfreeaddrinfo) return false; #endif if (!(*s)) return false; memset (sadr, 0, sizeof(*sadr)); memset(&udp6hint, 0, sizeof(udp6hint)); udp6hint.ai_family = 0;//Any... we check for AF_INET6 or 4 udp6hint.ai_socktype = SOCK_DGRAM; udp6hint.ai_protocol = IPPROTO_UDP; //handle parsing of ipv6 literal addresses if (*s == '[') { port = strstr(s, "]"); if (!port) error = EAI_NONAME; else { len = port - (s+1); if (len >= sizeof(dupbase)) len = sizeof(dupbase)-1; strncpy(dupbase, s+1, len); dupbase[len] = '\0'; error = 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 false; } ((struct sockaddr*)sadr)->sa_family = 0; for (pos = addrinfo; pos; pos = pos->ai_next) { switch(pos->ai_family) { default: //unrecognised address families are ignored. break; case AF_INET6: if (((struct sockaddr_in *)sadr)->sin_family == AF_INET6) break; //first one should be best... //fallthrough case AF_INET: memcpy(sadr, pos->ai_addr, pos->ai_addrlen); if (pos->ai_family == AF_INET) goto dblbreak; //don't try finding any more, this is quake, they probably prefer ip4... break; } } dblbreak: pfreeaddrinfo (addrinfo); if (!((struct sockaddr*)sadr)->sa_family) //none suitablefound return false; 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 true; } char *COM_ParseOut (const char *data, char *out, int outlen) { int c; int len; len = 0; out[0] = 0; if (!data) return NULL; // skip whitespace skipwhite: while ( (c = *data) <= ' ') { if (c == 0) return NULL; // end of file; data++; } // skip // comments if (c=='/') { if (data[1] == '/') { while (*data && *data != '\n') data++; goto skipwhite; } } //skip / * comments if (c == '/' && data[1] == '*') { data+=2; while(*data) { if (*data == '*' && data[1] == '/') { data+=2; goto skipwhite; } data++; } goto skipwhite; } // handle quoted strings specially if (c == '\"') { data++; while (1) { if (len >= outlen-1) { out[len] = 0; return (char*)data; } c = *data++; if (c=='\"' || !c) { out[len] = 0; return (char*)data; } out[len] = c; len++; } } // parse a regular word do { if (len >= outlen-1) { out[len] = 0; return (char*)data; } out[len] = c; data++; len++; c = *data; } while (c>32); out[len] = 0; return (char*)data; } typedef struct { vfsfile_t funcs; char *data; int maxlen; int writepos; int readpos; } vfspipe_t; void VFSPIPE_Close(vfsfile_t *f) { vfspipe_t *p = (vfspipe_t*)f; free(p->data); free(p); } unsigned long VFSPIPE_GetLen(vfsfile_t *f) { vfspipe_t *p = (vfspipe_t*)f; return p->writepos - p->readpos; } unsigned long VFSPIPE_Tell(vfsfile_t *f) { return 0; } qboolean VFSPIPE_Seek(vfsfile_t *f, unsigned long offset) { 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; } #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++; //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; // Q_strncpyz(exe, "C:/Games/Quake/fte_trunk/fteglqw_dbg.exe", exelen); // Q_strncpyz(exe, "/home/david/quake/fte_trunk/engine/debug/fteqw.gl", exelen); /* 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); } 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 (strchr(argv[i], ' ') || strchr(arg[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); write(2, "execv failed\n", 13); 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; }