d6d0105d3c
Added a higher quality particle config. Quick hack for crepuscular rays. added new command, eg: pr_dumpplatform -FFTE -O csplat used package reporting on servers, auto package downloading on clients, should be fully implemented. Smoothed out players a little. Added option to menus. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@3926 fc73d0e0-1445-4013-8a0c-d673dee63da5
1140 lines
26 KiB
C
1140 lines
26 KiB
C
#include "quakedef.h"
|
|
#include "winquake.h"
|
|
#include "sys_plugfte.h"
|
|
#include "../http/iweb.h"
|
|
|
|
static void UnpackAndExtractPakFiles_Complete(struct dl_download *dl);
|
|
static void pscript_property_splash_sets(struct context *ctx, const char *val);
|
|
|
|
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?");
|
|
return false;
|
|
}
|
|
int VFSPIPE_ReadBytes(vfsfile_t *f, void *buffer, int len)
|
|
{
|
|
vfspipe_t *p = (vfspipe_t*)f;
|
|
if (len > p->writepos - p->readpos)
|
|
len = p->writepos - p->readpos;
|
|
memcpy(buffer, p->data+p->readpos, len);
|
|
p->readpos += len;
|
|
|
|
if (p->readpos > 8192)
|
|
{
|
|
//shift the memory down periodically
|
|
//fixme: use cyclic buffer? max size, etc?
|
|
memmove(p->data, p->data+p->readpos, p->writepos-p->readpos);
|
|
|
|
p->writepos -= p->readpos;
|
|
p->readpos = 0;
|
|
}
|
|
return len;
|
|
}
|
|
int VFSPIPE_WriteBytes(vfsfile_t *f, const void *buffer, int len)
|
|
{
|
|
vfspipe_t *p = (vfspipe_t*)f;
|
|
if (p->writepos + len > p->maxlen)
|
|
{
|
|
p->maxlen = p->writepos + len;
|
|
p->data = realloc(p->data, p->maxlen);
|
|
}
|
|
memcpy(p->data+p->writepos, buffer, len);
|
|
p->writepos += len;
|
|
return len;
|
|
}
|
|
|
|
vfsfile_t *VFSPIPE_Open(void)
|
|
{
|
|
vfspipe_t *newf;
|
|
newf = malloc(sizeof(*newf));
|
|
newf->data = NULL;
|
|
newf->maxlen = 0;
|
|
newf->readpos = 0;
|
|
newf->writepos = 0;
|
|
newf->funcs.Close = VFSPIPE_Close;
|
|
newf->funcs.Flush = NULL;
|
|
newf->funcs.GetLen = VFSPIPE_GetLen;
|
|
newf->funcs.ReadBytes = VFSPIPE_ReadBytes;
|
|
newf->funcs.Seek = VFSPIPE_Seek;
|
|
newf->funcs.Tell = VFSPIPE_Tell;
|
|
newf->funcs.WriteBytes = VFSPIPE_WriteBytes;
|
|
newf->funcs.seekingisabadplan = true;
|
|
|
|
return &newf->funcs;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct context
|
|
{
|
|
struct contextpublic pub;
|
|
|
|
void *windowhnd;
|
|
int windowleft;
|
|
int windowtop;
|
|
int windowwidth;
|
|
int windowheight;
|
|
|
|
int waitingfordatafiles;
|
|
|
|
char *datadownload;
|
|
char *gamename;
|
|
char *password;
|
|
char *onstart;
|
|
char *onend;
|
|
char *ondemoend;
|
|
|
|
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;
|
|
|
|
struct context *next;
|
|
|
|
struct browserfuncs bfuncs;
|
|
};
|
|
|
|
#ifdef _WIN32
|
|
|
|
extern HWND sys_parentwindow;
|
|
extern unsigned int sys_parentwidth;
|
|
extern unsigned int sys_parentheight;
|
|
HINSTANCE global_hInstance;
|
|
static char binaryname[MAX_PATH];
|
|
|
|
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
|
|
{
|
|
switch (fdwReason)
|
|
{
|
|
case DLL_PROCESS_ATTACH:
|
|
global_hInstance = hinstDLL;
|
|
GetModuleFileName(global_hInstance, binaryname, sizeof(binaryname));
|
|
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)
|
|
#define ADDCARG(x) do {if (argc < maxargs) argv[argc++] = strdup(cleanarg(x));} while(0)
|
|
char *cleanarg(char *arg)
|
|
{
|
|
//no hacking us, please.
|
|
while (*arg == '-' || *arg == '+')
|
|
arg++;
|
|
while (*arg && *(unsigned char*)arg <= ' ')
|
|
arg++;
|
|
if (*arg)
|
|
return arg;
|
|
return "badarg";
|
|
}
|
|
|
|
int Plug_GenCommandline(struct context *ctx, char **argv, int maxargs)
|
|
{
|
|
char *s;
|
|
int argc;
|
|
char tok[256];
|
|
|
|
argv[0] = strdup(binaryname);
|
|
argc = 1;
|
|
|
|
switch(ctx->qtvf.connectiontype)
|
|
{
|
|
default:
|
|
break;
|
|
case QTVCT_STREAM:
|
|
ADDRARG("+qtvplay");
|
|
ADDCARG(ctx->qtvf.server);
|
|
break;
|
|
case QTVCT_CONNECT:
|
|
ADDRARG("+connect");
|
|
ADDCARG(ctx->qtvf.server);
|
|
break;
|
|
case QTVCT_JOIN:
|
|
ADDRARG("+join");
|
|
ADDCARG(ctx->qtvf.server);
|
|
break;
|
|
case QTVCT_OBSERVE:
|
|
ADDRARG("+observe");
|
|
ADDCARG(ctx->qtvf.server);
|
|
break;
|
|
case QTVCT_MAP:
|
|
ADDRARG("+map");
|
|
ADDCARG(ctx->qtvf.server);
|
|
break;
|
|
}
|
|
|
|
if (ctx->password)
|
|
{
|
|
ADDRARG("+password");
|
|
ADDCARG(ctx->password);
|
|
}
|
|
|
|
//figure out the game dirs (first token is the base game)
|
|
s = ctx->gamename;
|
|
s = COM_ParseOut(s, tok, sizeof(tok));
|
|
if (!*tok || !strcmp(tok, "q1") || !strcmp(tok, "qw") || !strcmp(tok, "quake"))
|
|
ADDRARG("-quake");
|
|
else if (!strcmp(tok, "q2") || !strcmp(tok, "quake2"))
|
|
ADDRARG("-q2");
|
|
else if (!strcmp(tok, "q3") || !strcmp(tok, "quake3"))
|
|
ADDRARG("-q3");
|
|
else if (!strcmp(tok, "hl") || !strcmp(tok, "halflife"))
|
|
ADDRARG("-halflife");
|
|
else if (!strcmp(tok, "h2") || !strcmp(tok, "hexen2"))
|
|
ADDRARG("-hexen2");
|
|
else if (!strcmp(tok, "nex") || !strcmp(tok, "nexuiz"))
|
|
ADDRARG("-nexuiz");
|
|
else
|
|
{
|
|
ADDRARG("-basegame");
|
|
ADDCARG(tok);
|
|
}
|
|
//later options are additions to that
|
|
while ((s = COM_ParseOut(s, tok, sizeof(tok))))
|
|
{
|
|
if (argc == sizeof(argv)/sizeof(argv[0]))
|
|
break;
|
|
ADDRARG("-addbasegame");
|
|
ADDCARG(tok);
|
|
}
|
|
return argc;
|
|
}
|
|
|
|
#if _MSC_VER >= 1300
|
|
#define CATCHCRASH
|
|
#endif
|
|
|
|
#ifdef CATCHCRASH
|
|
#ifdef _DEBUG
|
|
#include "dbghelp.h"
|
|
DWORD CrashExceptionHandler (DWORD exceptionCode, LPEXCEPTION_POINTERS exceptionInfo);
|
|
#endif
|
|
#endif
|
|
|
|
int Plug_PluginThread(void *ctxptr)
|
|
{
|
|
char *argv[16];
|
|
int argc = 0;
|
|
struct context *ctx = ctxptr;
|
|
struct dl_download *dl;
|
|
|
|
#ifdef CATCHCRASH
|
|
__try
|
|
#endif
|
|
{
|
|
argc = Plug_GenCommandline(ctx, argv, sizeof(argv)/sizeof(argv[0]));
|
|
NPQTV_Sys_Startup(argc, argv);
|
|
|
|
if (ctx->datadownload)
|
|
{
|
|
char *s = ctx->datadownload;
|
|
char *c;
|
|
vfsfile_t *f;
|
|
Sys_LockMutex(ctx->mutex);
|
|
while ((s = COM_ParseOut(s, com_token, sizeof(com_token))))
|
|
{
|
|
//FIXME: do we want to add some sort of file size indicator?
|
|
c = strchr(com_token, ':');
|
|
if (!c)
|
|
continue;
|
|
*c++ = 0;
|
|
f = FS_OpenVFS(com_token, "rb", FS_ROOT);
|
|
if (f)
|
|
{
|
|
Con_Printf("Already have %s\n", com_token);
|
|
VFS_CLOSE(f);
|
|
continue;
|
|
}
|
|
|
|
Con_Printf("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;
|
|
}
|
|
Sys_UnlockMutex(ctx->mutex);
|
|
}
|
|
|
|
ctx->pub.downloading = true;
|
|
while(host_initialized && !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;
|
|
|
|
if (host_initialized && !ctx->shutdown)
|
|
{
|
|
Sys_LockMutex(ctx->mutex);
|
|
ctx->resetvideo = false;
|
|
sys_parentwindow = ctx->windowhnd;
|
|
sys_parentleft = ctx->windowleft;
|
|
sys_parenttop = ctx->windowtop;
|
|
sys_parentwidth = ctx->windowwidth;
|
|
sys_parentheight = ctx->windowheight;
|
|
Host_FinishInit();
|
|
Sys_UnlockMutex(ctx->mutex);
|
|
}
|
|
if (ctx->bfuncs.StatusChanged)
|
|
ctx->bfuncs.StatusChanged(ctx->hostinstance);
|
|
|
|
while(host_initialized)
|
|
{
|
|
Sys_LockMutex(ctx->mutex);
|
|
if (ctx->shutdown)
|
|
{
|
|
ctx->shutdown = false;
|
|
Host_Shutdown(); /*will shut down the host*/
|
|
}
|
|
else if (ctx->resetvideo)
|
|
{
|
|
sys_parentwindow = ctx->windowhnd;
|
|
sys_parentleft = ctx->windowleft;
|
|
sys_parenttop = ctx->windowtop;
|
|
sys_parentwidth = ctx->windowwidth;
|
|
sys_parentheight = ctx->windowheight;
|
|
if (ctx->resetvideo == 2)
|
|
SetParent(mainwindow, sys_parentwindow);
|
|
ctx->resetvideo = false;
|
|
Cbuf_AddText("vid_recenter\n", RESTRICT_LOCAL);
|
|
}
|
|
else
|
|
NPQTV_Sys_MainLoop();
|
|
Sys_UnlockMutex(ctx->mutex);
|
|
}
|
|
}
|
|
#ifdef CATCHCRASH
|
|
#ifdef _DEBUG
|
|
__except (CrashExceptionHandler(GetExceptionCode(), GetExceptionInformation()))
|
|
{
|
|
|
|
}
|
|
#else
|
|
__except (EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
Host_Shutdown();
|
|
MessageBox(sys_parentwindow, "Sorry, FTE plugin crashed.\nYou probably should restart your browser", "FTE crashed", 0);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
Sys_LockMutex(ctx->mutex);
|
|
while (ctx->packagelist)
|
|
{
|
|
dl = ctx->packagelist;
|
|
ctx->packagelist = dl->next;
|
|
|
|
/*don't close while locked*/
|
|
Sys_UnlockMutex(ctx->mutex);
|
|
DL_Close(dl);
|
|
Sys_LockMutex(ctx->mutex);
|
|
}
|
|
ctx->pub.running = false;
|
|
Sys_UnlockMutex(ctx->mutex);
|
|
if (ctx->bfuncs.StatusChanged)
|
|
ctx->bfuncs.StatusChanged(ctx->hostinstance);
|
|
|
|
while(argc-- > 0)
|
|
free(argv[argc]);
|
|
|
|
activecontext = NULL;
|
|
return 0;
|
|
}
|
|
|
|
void Plug_LockPlugin(struct context *ctx, qboolean lockstate)
|
|
{
|
|
if (!ctx || !ctx->mutex)
|
|
return;
|
|
|
|
if (lockstate)
|
|
Sys_LockMutex(ctx->mutex);
|
|
else
|
|
Sys_UnlockMutex(ctx->mutex);
|
|
}
|
|
//#define Plug_LockPlugin(c,s) do{Plug_LockPlugin(c,s);VS_DebugLocation(__FILE__, __LINE__, s?"Lock":"Unlock"); }while(0)
|
|
|
|
//begins the context, fails if one is already active
|
|
qboolean Plug_StartContext(struct context *ctx)
|
|
{
|
|
if (activecontext)
|
|
return false;
|
|
ctx->pub.running = true;
|
|
activecontext = ctx;
|
|
ctx->mutex = Sys_CreateMutex();
|
|
ctx->thread = Sys_CreateThread(Plug_PluginThread, ctx, THREADP_NORMAL, 0);
|
|
|
|
return true;
|
|
}
|
|
|
|
//asks a context to stop, is not instant.
|
|
void Plug_StopContext(struct context *ctx)
|
|
{
|
|
if (ctx == NULL)
|
|
ctx = activecontext;
|
|
if (!ctx)
|
|
return;
|
|
if (ctx->pub.running == true)
|
|
ctx->shutdown = true;
|
|
}
|
|
|
|
//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;
|
|
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;
|
|
}
|
|
|
|
ctx->shutdown = true;
|
|
if (ctx->thread)
|
|
Sys_WaitOnThread(ctx->thread);
|
|
ctx->thread = NULL;
|
|
|
|
//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);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////
|
|
|
|
#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*/
|
|
Cmd_ExecuteString("fs_restart", RESTRICT_LOCAL);
|
|
}
|
|
|
|
Plug_LockPlugin(dl->user_ctx, false);
|
|
}
|
|
|
|
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;
|
|
|
|
ctx->splashwidth = 0;
|
|
ctx->splashheight = 0;
|
|
image = ctx->splashdata;
|
|
ctx->splashdata = NULL;
|
|
free(image);
|
|
|
|
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)
|
|
{
|
|
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);
|
|
|
|
if (ctx->bfuncs.StatusChanged)
|
|
ctx->bfuncs.StatusChanged(ctx->hostinstance);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct pscript_property
|
|
{
|
|
char *name;
|
|
qboolean onlyifactive;
|
|
|
|
cvar_t *cvar;
|
|
|
|
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);
|
|
}
|
|
|
|
char *pscript_property_startserver_gets(struct context *ctx)
|
|
{
|
|
return strdup(ctx->qtvf.server);
|
|
}
|
|
void pscript_property_startserver_sets(struct context *ctx, const char *val)
|
|
{
|
|
if (strchr(val, '$') || strchr(val, ';') || strchr(val, '\n'))
|
|
return;
|
|
|
|
ctx->qtvf.connectiontype = QTVCT_JOIN;
|
|
Q_strncpyz(ctx->qtvf.server, val, sizeof(ctx->qtvf.server));
|
|
}
|
|
char *pscript_property_curserver_gets(struct context *ctx)
|
|
{
|
|
extern char lastdemoname[];
|
|
if (!pscript_property_running_getb(ctx))
|
|
return pscript_property_startserver_gets(ctx);
|
|
|
|
if (cls.demoplayback)
|
|
return strdup(va("demo:%s",lastdemoname));
|
|
else if (cls.state != ca_disconnected)
|
|
return strdup(cls.servername);
|
|
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;
|
|
}
|
|
|
|
Q_strncpyz(cls.servername, val, sizeof(cls.servername));
|
|
CL_BeginServerConnect(0);
|
|
}
|
|
|
|
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));
|
|
|
|
if (pscript_property_running_getb(ctx))
|
|
Cmd_ExecuteString(va("qtvplay \"%s\"\n", val), RESTRICT_INSECURE);
|
|
}
|
|
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));
|
|
|
|
if (pscript_property_running_getb(ctx))
|
|
Cmd_ExecuteString(va("map \"%s\"\n", val), RESTRICT_INSECURE);
|
|
}
|
|
|
|
float pscript_property_curver_getf(struct context *ctx)
|
|
{
|
|
return version_number();
|
|
}
|
|
|
|
void pscript_property_availver_setf(struct context *ctx, float val)
|
|
{
|
|
ctx->pub.availver = val;
|
|
if (ctx->pub.availver <= version_number())
|
|
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
|
|
);
|
|
}
|
|
|
|
extern cvar_t skin, team, topcolor, bottomcolor, vid_fullscreen, cl_download_mapsrc;
|
|
static struct pscript_property pscript_properties[] =
|
|
{
|
|
{"", false, NULL, pscript_property_curserver_gets, pscript_property_curserver_sets},
|
|
{"server", false, NULL, pscript_property_curserver_gets, pscript_property_curserver_sets},
|
|
{"running", false, NULL, NULL, NULL, pscript_property_running_getb, pscript_property_running_setb},
|
|
{"startserver", false, NULL, pscript_property_startserver_gets, pscript_property_startserver_sets},
|
|
{"join", false, NULL, NULL, pscript_property_curserver_sets},
|
|
{"playername", true, &name},
|
|
{NULL, true, &skin},
|
|
{NULL, true, &team},
|
|
{NULL, true, &topcolor},
|
|
{NULL, true, &bottomcolor},
|
|
{NULL, true, &password},
|
|
// {NULL, true, &spectator},
|
|
{"mapsrc", true, &cl_download_mapsrc},
|
|
{"fullscreen", true, &vid_fullscreen},
|
|
|
|
{"datadownload",false, NULL, NULL, pscript_property_datadownload_sets},
|
|
|
|
{"game", false, NULL, NULL, pscript_property_game_sets},
|
|
{"availver", false, NULL, NULL, NULL, NULL, NULL, NULL, pscript_property_availver_setf},
|
|
{"plugver", false, NULL, NULL, NULL, NULL, NULL, pscript_property_curver_getf},
|
|
|
|
{"splash", false, NULL, NULL, pscript_property_splash_sets},
|
|
|
|
{"stream", false, NULL, NULL, pscript_property_stream_sets},
|
|
{"map", false, NULL, NULL, pscript_property_map_sets},
|
|
|
|
{"build", false, 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->cvar; prop++)
|
|
{
|
|
if (!stricmp(prop->name?prop->name:prop->cvar->name, 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->cvar && ctx->pub.running)
|
|
{
|
|
Plug_LockPlugin(ctx, true);
|
|
Cvar_Set(field->cvar, 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->cvar && ctx->pub.running)
|
|
{
|
|
Plug_LockPlugin(ctx, true);
|
|
Cvar_SetValue(field->cvar, 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->cvar && ctx->pub.running)
|
|
{
|
|
Plug_LockPlugin(ctx, true);
|
|
Cvar_SetValue(field->cvar, value);
|
|
Plug_LockPlugin(ctx, false);
|
|
}
|
|
else
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
qboolean Plug_GetString(struct context *ctx, int fieldidx, const char **value)
|
|
{
|
|
struct pscript_property *field = pscript_properties + fieldidx;
|
|
if (!ctx || fieldidx < 0 || fieldidx >= sizeof(pscript_properties)/sizeof(pscript_properties[0]))
|
|
return false;
|
|
|
|
if (field->getstring)
|
|
{
|
|
*value = field->getstring(ctx);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
void Plug_GotString(const char *value)
|
|
{
|
|
free((char*)value);
|
|
}
|
|
qboolean Plug_GetInteger(struct context *ctx, int fieldidx, int *value)
|
|
{
|
|
struct pscript_property *field = pscript_properties + fieldidx;
|
|
if (!ctx || fieldidx < 0 || fieldidx >= sizeof(pscript_properties)/sizeof(pscript_properties[0]))
|
|
return false;
|
|
|
|
if (field->getint)
|
|
{
|
|
*value = field->getint(ctx);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
qboolean Plug_GetFloat(struct context *ctx, int fieldidx, float *value)
|
|
{
|
|
struct pscript_property *field = pscript_properties + fieldidx;
|
|
if (!ctx || fieldidx < 0 || fieldidx >= sizeof(pscript_properties)/sizeof(pscript_properties[0]))
|
|
return false;
|
|
|
|
if (field->getfloat)
|
|
{
|
|
*value = field->getfloat(ctx);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
void *Plug_GetSplashBack(struct context *ctx, void *hdc, int *width, int *height)
|
|
{
|
|
BITMAPINFOHEADER bmh;
|
|
|
|
if (!ctx->splashdata)
|
|
return NULL;
|
|
|
|
bmh.biSize = sizeof(bmh);
|
|
bmh.biWidth = *width = ctx->splashwidth;
|
|
bmh.biHeight = *height = ctx->splashheight;
|
|
bmh.biPlanes = 1;
|
|
bmh.biBitCount = 32;
|
|
bmh.biCompression = BI_RGB;
|
|
bmh.biSizeImage = 0;
|
|
bmh.biXPelsPerMeter = 0;
|
|
bmh.biYPelsPerMeter = 0;
|
|
bmh.biClrUsed = 0;
|
|
bmh.biClrImportant = 0;
|
|
|
|
return CreateDIBitmap(hdc,
|
|
&bmh,
|
|
CBM_INIT,
|
|
(LPSTR)ctx->splashdata,
|
|
(LPBITMAPINFO)&bmh,
|
|
DIB_RGB_COLORS );
|
|
}
|
|
void Plug_ReleaseSplashBack(struct context *ctx, void *bmp)
|
|
{
|
|
DeleteObject(bmp);
|
|
}
|
|
#endif
|
|
|
|
static const struct plugfuncs exportedplugfuncs_1 =
|
|
{
|
|
Plug_CreateContext,
|
|
Plug_DestroyContext,
|
|
Plug_LockPlugin,
|
|
Plug_StartContext,
|
|
Plug_StopContext,
|
|
Plug_ChangeWindow,
|
|
|
|
Plug_FindProp,
|
|
Plug_SetString,
|
|
Plug_GetString,
|
|
Plug_GotString,
|
|
Plug_SetInteger,
|
|
Plug_GetInteger,
|
|
Plug_SetFloat,
|
|
Plug_GetFloat,
|
|
|
|
Plug_GetSplashBack,
|
|
Plug_ReleaseSplashBack,
|
|
|
|
Plug_SetWString
|
|
};
|
|
|
|
const struct plugfuncs *Plug_GetFuncs(int ver)
|
|
{
|
|
if (ver == 1)
|
|
return &exportedplugfuncs_1;
|
|
else
|
|
return NULL;
|
|
}
|