mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-13 07:47:45 +00:00
e403cebc65
Multiple download sources can be specified (eg for mod+map). Takes the form "providedfile:urltodownload providedfile:urltodownload". This comes via the browser so relative urls = woot. The old single-url-only path is no longer supported. This is an incompatible change, but we're still young. The unpacking process is still the same, and still only paks/pk3s are extracted. Note that the providedfile is relative to the base dir rather than the game dir. You can specify potential alternate gamedirs. The QTV file is not mandatory, you can use map="blah" join="blah" stream="0@blah", so long as you set the mime type as: type="application/x-fteplugin". src="someqtvfile" still works, of course (and doesn't mandate the type= field, although that should probably be specified to make life easier on configuring servers to use the correct mime types). The splash image can be set with: splash="myurlhere". Note that only jpg and png files are supported. This comes via the browser so relative urls = woot. Game property can take multiple game dirs now, too. First argument is the basic game. If its one of the special ones then FTE is meant to hunt down your prior install for that. If its not recognised then its added as the only game directory. Additional ones are added after (eg: "id1 fortress" adds just id1+fortress while "q1 fortress" adds id1+qw+fte+fortress). Use appropriately. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@3160 fc73d0e0-1445-4013-8a0c-d673dee63da5
1157 lines
27 KiB
C
1157 lines
27 KiB
C
#include "quakedef.h"
|
|
#include "winquake.h"
|
|
#define bool int //we ain't c++ (grr microsoft stdbool.h gief!)
|
|
|
|
#ifdef _WIN32
|
|
#ifndef _WINDOWS
|
|
#define _WINDOWS //stupid GCC
|
|
#endif
|
|
#endif
|
|
|
|
#include "npapi/npupp.h"
|
|
|
|
#define NPQTV_VERSION 0.1
|
|
|
|
#define FIREFOX_BUGS_OVER_25MB
|
|
|
|
//TODO: player name input (before allowing them to join)
|
|
//TODO: fix active gl context (per thread, and we hijacked the browser's thread)
|
|
|
|
|
|
NPNetscapeFuncs *browserfuncs;
|
|
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
#ifndef GetWindowLongPtr
|
|
#define GetWindowLongPtr GetWindowLong
|
|
#endif
|
|
#ifndef SetWindowLongPtr
|
|
#define SetWindowLongPtr SetWindowLong
|
|
#define LONG_PTR LONG
|
|
#endif
|
|
|
|
|
|
extern HWND sys_parentwindow;
|
|
extern unsigned int sys_parentwidth;
|
|
extern unsigned int sys_parentheight;
|
|
HINSTANCE global_hInstance;
|
|
|
|
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
|
|
{
|
|
switch (fdwReason)
|
|
{
|
|
case DLL_PROCESS_ATTACH:
|
|
global_hInstance = hinstDLL;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
char binaryname[MAX_PATH];
|
|
|
|
struct qstream
|
|
{
|
|
vfsfile_t *pipe;
|
|
struct pipetype *type;
|
|
|
|
struct qstream *next;
|
|
|
|
char url[1];
|
|
};
|
|
struct context
|
|
{
|
|
NPWindow window;
|
|
qboolean contextrunning;
|
|
int waitingfordatafiles;
|
|
float availver;
|
|
|
|
#ifdef _WIN32
|
|
WNDPROC oldproc;
|
|
#endif
|
|
|
|
char *datadownload;
|
|
char *gamename;
|
|
char *password;
|
|
char *onstart;
|
|
char *onend;
|
|
char *ondemoend;
|
|
|
|
NPP nppinstance;
|
|
|
|
struct qstream *donestreams;
|
|
|
|
int wait_size;
|
|
int wait_offset;
|
|
struct qstream *wait_stream;
|
|
|
|
qtvfile_t qtvf;
|
|
|
|
unsigned char *splashdata;
|
|
int splashwidth;
|
|
int splashheight;
|
|
|
|
struct context *next;
|
|
};
|
|
|
|
struct context *activecontext;
|
|
struct context *contextlist;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////
|
|
|
|
struct pipetype
|
|
{
|
|
enum {
|
|
WAIT_NO,
|
|
WAIT_YES,
|
|
WAIT_DONE
|
|
} wait;
|
|
qboolean needseeking;
|
|
void (*completionfunc) (struct context *ctx, vfsfile_t *file, const char *streamsource);
|
|
void (*beginfunc) (struct context *ctx, vfsfile_t *file, const char *streamsource);
|
|
};
|
|
|
|
#include "fs.h"
|
|
extern searchpathfuncs_t zipfilefuncs;
|
|
|
|
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;
|
|
}
|
|
|
|
void UnpackAndExtractPakFiles_Complete(struct context *ctx, vfsfile_t *file, const char *streamsource)
|
|
{
|
|
extern searchpathfuncs_t zipfilefuncs;
|
|
void *zip;
|
|
|
|
zip = zipfilefuncs.OpenNew(file, streamsource);
|
|
if (zip)
|
|
{
|
|
zipfilefuncs.EnumerateFiles(zip, "*.pk3", ExtractDataFile, zip);
|
|
zipfilefuncs.EnumerateFiles(zip, "*.pak", ExtractDataFile, zip);
|
|
|
|
zipfilefuncs.ClosePath(zip);
|
|
|
|
Cmd_ExecuteString("fs_restart", RESTRICT_LOCAL);
|
|
}
|
|
}
|
|
struct pipetype UnpackAndExtractPakFiles =
|
|
{
|
|
WAIT_YES,
|
|
true,
|
|
UnpackAndExtractPakFiles_Complete
|
|
};
|
|
|
|
void LoadSplashImage(struct context *ctx, vfsfile_t *f, const char *name)
|
|
{
|
|
int x, y;
|
|
int width = 0;
|
|
int height = 0;
|
|
int len = VFS_GETLEN(f);
|
|
char *buffer = malloc(len);
|
|
unsigned char *image;
|
|
VFS_READ(f, buffer, len);
|
|
VFS_CLOSE(f);
|
|
|
|
image = NULL;
|
|
if (!image)
|
|
image = ReadJPEGFile(buffer, len, &width, &height);
|
|
if (!image)
|
|
image = ReadPNGFile(buffer, len, &width, &height, name);
|
|
|
|
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->window.window)
|
|
InvalidateRgn(ctx->window.window, NULL, FALSE);
|
|
}
|
|
}
|
|
|
|
struct pipetype SplashscreenImageDescriptor =
|
|
{
|
|
WAIT_DONE,
|
|
false,
|
|
LoadSplashImage
|
|
};
|
|
|
|
static void ReadQTVFileDescriptor(struct context *ctx, vfsfile_t *f, const char *name)
|
|
{
|
|
CL_ParseQTVFile(f, name, &ctx->qtvf);
|
|
|
|
if (*ctx->qtvf.splashscreen)
|
|
{
|
|
browserfuncs->geturlnotify(ctx->nppinstance, ctx->qtvf.splashscreen, NULL, &SplashscreenImageDescriptor);
|
|
}
|
|
}
|
|
struct pipetype QTVFileDescriptor =
|
|
{
|
|
WAIT_DONE,
|
|
false,
|
|
ReadQTVFileDescriptor
|
|
};
|
|
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 pipetype DemoFileDescriptor =
|
|
{
|
|
WAIT_NO,
|
|
false,
|
|
EndDemo,
|
|
BeginDemo
|
|
};
|
|
|
|
/////////////////////////////////////
|
|
|
|
|
|
#ifdef _WIN32
|
|
void DrawWndBack(struct context *ctx, HWND hWnd, HDC hdc, PAINTSTRUCT *p)
|
|
{
|
|
if (ctx->splashdata)
|
|
{
|
|
HBITMAP bmp;
|
|
BITMAPINFOHEADER bmh;
|
|
HDC memDC;
|
|
|
|
bmh.biSize = sizeof(bmh);
|
|
bmh.biWidth = ctx->splashwidth;
|
|
bmh.biHeight = 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;
|
|
|
|
memDC = CreateCompatibleDC(hdc);
|
|
bmp = CreateDIBitmap(hdc,
|
|
&bmh,
|
|
CBM_INIT,
|
|
(LPSTR)ctx->splashdata,
|
|
(LPBITMAPINFO)&bmh,
|
|
DIB_RGB_COLORS );
|
|
|
|
SelectObject(memDC, bmp);
|
|
// StretchBlt(hdc, 0, 0, p->rcPaint.right-p->rcPaint.left, p->rcPaint.bottom-p->rcPaint.top, memDC, 0, 0, ctx->splashwidth, ctx->splashheight, SRCCOPY);
|
|
StretchBlt(hdc, 0, 0, ctx->window.width, ctx->window.height, memDC, 0, 0, ctx->splashwidth, ctx->splashheight, SRCCOPY);
|
|
SelectObject(memDC, NULL);
|
|
DeleteDC(memDC);
|
|
DeleteObject(bmp);
|
|
}
|
|
else
|
|
PatBlt(hdc, p->rcPaint.left, p->rcPaint.top, p->rcPaint.right-p->rcPaint.left,p->rcPaint.bottom-p->rcPaint.top,PATCOPY);
|
|
}
|
|
|
|
LRESULT CALLBACK MyPluginWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
struct qstream *str;
|
|
struct context *ctx;
|
|
ctx = (struct context *)GetWindowLongPtr(hWnd, GWL_USERDATA);
|
|
if (!ctx)
|
|
return DefWindowProc(hWnd, msg, wParam, lParam);
|
|
|
|
switch(msg)
|
|
{
|
|
case WM_MOVE:
|
|
if (ctx->contextrunning)
|
|
{
|
|
PostMessage(mainwindow, WM_MOVE, 0, 0);
|
|
}
|
|
break;
|
|
case WM_TIMER:
|
|
if (ctx->contextrunning && !ctx->waitingfordatafiles)
|
|
{
|
|
while (ctx->donestreams)
|
|
{
|
|
str = ctx->donestreams;
|
|
ctx->donestreams = str->next;
|
|
|
|
if (str->pipe)
|
|
{
|
|
if (str->type->completionfunc)
|
|
str->type->completionfunc(ctx, str->pipe, str->url);
|
|
else
|
|
VFS_CLOSE(str->pipe);
|
|
}
|
|
|
|
free(str);
|
|
}
|
|
|
|
if (sys_parentwindow != ctx->window.window)
|
|
{
|
|
if (qrenderer == -1)
|
|
{
|
|
//urgh, its not started up yet
|
|
sys_parentwindow = ctx->window.window;
|
|
|
|
Host_FinishInit();
|
|
}
|
|
else
|
|
{
|
|
sys_parentwindow = ctx->window.window;
|
|
if (sys_parentwindow)
|
|
{
|
|
sys_parentwidth = ctx->window.width;
|
|
sys_parentheight = ctx->window.height;
|
|
Cmd_ExecuteString("vid_restart", RESTRICT_LOCAL);
|
|
}
|
|
}
|
|
|
|
}
|
|
else if (sys_parentwindow)
|
|
{
|
|
NPQTV_Sys_MainLoop();
|
|
if (!host_initialized)
|
|
{
|
|
//quit was issued
|
|
ctx->contextrunning = false;
|
|
activecontext = NULL;
|
|
InvalidateRgn(hWnd, NULL, FALSE);
|
|
|
|
if (ctx->onend)
|
|
browserfuncs->geturl(ctx->nppinstance, va("javascript:%s;", ctx->onend), "_self");
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
|
|
case WM_PAINT:
|
|
if (activecontext == ctx && !ctx->contextrunning && ctx->window.window)
|
|
{
|
|
char *s;
|
|
int argc;
|
|
char *argv[16];
|
|
sys_parentwindow = NULL;
|
|
|
|
GetModuleFileName(global_hInstance, binaryname, sizeof(binaryname));
|
|
argv[0] = binaryname;
|
|
argc = 1;
|
|
|
|
activecontext = ctx;
|
|
|
|
switch(ctx->qtvf.connectiontype)
|
|
{
|
|
default:
|
|
break;
|
|
case QTVCT_STREAM:
|
|
argv[argc++] = "+qtvplay";
|
|
argv[argc++] = ctx->qtvf.server;
|
|
break;
|
|
case QTVCT_CONNECT:
|
|
argv[argc++] = "+connect";
|
|
argv[argc++] = ctx->qtvf.server;
|
|
break;
|
|
case QTVCT_JOIN:
|
|
argv[argc++] = "+join";
|
|
argv[argc++] = ctx->qtvf.server;
|
|
break;
|
|
case QTVCT_OBSERVE:
|
|
argv[argc++] = "+observe";
|
|
argv[argc++] = ctx->qtvf.server;
|
|
break;
|
|
case QTVCT_MAP:
|
|
argv[argc++] = "+map";
|
|
argv[argc++] = ctx->qtvf.server;
|
|
break;
|
|
}
|
|
|
|
if (ctx->password)
|
|
{
|
|
argv[argc++] = "+password";
|
|
argv[argc++] = ctx->password;
|
|
}
|
|
|
|
//figure out the game dirs (first token is the base game)
|
|
s = ctx->gamename;
|
|
s = COM_ParseOut(s, com_token, sizeof(com_token));
|
|
if (!*com_token || !strcmp(com_token, "q1") || !strcmp(com_token, "qw") || !strcmp(com_token, "quake"))
|
|
argv[argc++] = "-quake";
|
|
else if (!strcmp(com_token, "q2") || !strcmp(com_token, "quake2"))
|
|
argv[argc++] = "-q2";
|
|
else if (!strcmp(com_token, "q3") || !strcmp(com_token, "quake3"))
|
|
argv[argc++] = "-q3";
|
|
else if (!strcmp(com_token, "hl") || !strcmp(com_token, "halflife"))
|
|
argv[argc++] = "-halflife";
|
|
else if (!strcmp(com_token, "h2") || !strcmp(com_token, "hexen2"))
|
|
argv[argc++] = "-hexen2";
|
|
else if (!strcmp(com_token, "nex") || !strcmp(com_token, "nexuiz"))
|
|
argv[argc++] = "-nexuiz";
|
|
else
|
|
{
|
|
argv[argc++] = "-basegame";
|
|
argv[argc++] = strdup(com_token); //FIXME: this will leak
|
|
}
|
|
//later options are additions to that
|
|
while ((s = COM_ParseOut(s, com_token, sizeof(com_token))))
|
|
{
|
|
argv[argc++] = "-addbasegame";
|
|
argv[argc++] = strdup(com_token); //FIXME: this will leak
|
|
}
|
|
|
|
sys_parentwidth = ctx->window.width;
|
|
sys_parentheight = ctx->window.height;
|
|
ctx->contextrunning = NPQTV_Sys_Startup(argc, argv);
|
|
|
|
//now that the file system is started up, check to make sure its complete
|
|
if (ctx->datadownload)
|
|
{
|
|
char *s = ctx->datadownload;
|
|
char *c;
|
|
vfsfile_t *f;
|
|
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);
|
|
browserfuncs->geturlnotify(ctx->nppinstance, c, NULL, &UnpackAndExtractPakFiles);
|
|
ctx->waitingfordatafiles++;
|
|
}
|
|
}
|
|
|
|
if (ctx->contextrunning)
|
|
{
|
|
if (ctx->onstart)
|
|
browserfuncs->geturl(ctx->nppinstance, va("javascript:%s;", ctx->onstart), "_self");
|
|
|
|
//windows timers have low precision, ~10ms
|
|
//they're low priority anyway, so we might as well just create lots and spam them
|
|
SetTimer(hWnd, 1, 1, NULL);
|
|
SetTimer(hWnd, 2, 1, NULL);
|
|
SetTimer(hWnd, 3, 1, NULL);
|
|
SetTimer(hWnd, 4, 1, NULL);
|
|
SetTimer(hWnd, 5, 1, NULL);
|
|
}
|
|
}
|
|
|
|
if (ctx->waitingfordatafiles)
|
|
{
|
|
HDC hdc;
|
|
PAINTSTRUCT paint;
|
|
char *s;
|
|
|
|
hdc = BeginPaint(hWnd, &paint);
|
|
DrawWndBack(ctx, hWnd, hdc, &paint);
|
|
SetBkMode(hdc, TRANSPARENT);
|
|
TextOutA(hdc, 0, 0, "Downloading Data, please wait", 16);
|
|
if (!ctx->wait_stream)
|
|
s = "connecting";
|
|
else if (ctx->wait_size > 0)
|
|
s = va("%i bytes (%i%%)", ctx->wait_offset, (int)((100.0f*ctx->wait_offset)/ctx->wait_size));
|
|
else
|
|
s = va("%i bytes", ctx->wait_offset);
|
|
TextOutA(hdc, 0, 32, s, strlen(s));
|
|
EndPaint(hWnd, &paint);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
HDC hdc;
|
|
PAINTSTRUCT paint;
|
|
char *s;
|
|
|
|
hdc = BeginPaint(hWnd, &paint);
|
|
DrawWndBack(ctx, hWnd, hdc, &paint);
|
|
SetBkMode(hdc, TRANSPARENT);
|
|
if (!ctx->contextrunning)
|
|
{
|
|
if (!activecontext)
|
|
{
|
|
s = "Click to activate";
|
|
TextOutA(hdc, 0, 0, s, strlen(s));
|
|
}
|
|
if (ctx->availver)
|
|
{
|
|
s = va("Your plugin is out of date");
|
|
TextOutA(hdc, 0, 16, s, strlen(s));
|
|
s = va("Version %3.1f is available", ctx->availver);
|
|
TextOutA(hdc, 0, 32, s, strlen(s));
|
|
}
|
|
}
|
|
EndPaint(hWnd, &paint);
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case WM_LBUTTONDOWN:
|
|
if (!activecontext)
|
|
{
|
|
activecontext = ctx;
|
|
InvalidateRgn(hWnd, NULL, FALSE);
|
|
}
|
|
else if (activecontext != ctx)
|
|
Cbuf_AddText("quit\n", RESTRICT_LOCAL);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//I would call the previous wndproc... but that crashes firefox
|
|
return DefWindowProc(hWnd, msg, wParam, lParam);
|
|
}
|
|
|
|
#endif
|
|
|
|
NPError NP_LOADDS NPP_New(NPMIMEType pluginType, NPP instance,
|
|
uint16 mode, int16 argc, char* argn[],
|
|
char* argv[], NPSavedData* saved)
|
|
{
|
|
int i;
|
|
struct context *ctx;
|
|
|
|
if (!instance || instance->pdata)
|
|
{
|
|
return NPERR_INVALID_INSTANCE_ERROR;
|
|
}
|
|
if (mode != NP_EMBED && mode != NP_FULL)
|
|
{
|
|
return NPERR_INVALID_PLUGIN_ERROR;
|
|
}
|
|
|
|
ctx = malloc(sizeof(struct context));
|
|
if (!ctx)
|
|
{
|
|
return NPERR_OUT_OF_MEMORY_ERROR;
|
|
}
|
|
|
|
memset(ctx, 0, sizeof(struct context));
|
|
|
|
//link the instance to the context and the context to the instance
|
|
instance->pdata = ctx;
|
|
ctx->nppinstance = instance;
|
|
|
|
ctx->gamename = strdup("q1");
|
|
|
|
//parse out the properties
|
|
for (i = 0; i < argc; i++)
|
|
{
|
|
if (!stricmp(argn[i], "dataDownload"))
|
|
{
|
|
ctx->datadownload = strdup(argv[i]);
|
|
}
|
|
else if (!stricmp(argn[i], "game"))
|
|
{
|
|
if (!strstr(argn[i], "."))
|
|
if (!strstr(argn[i], "/"))
|
|
if (!strstr(argn[i], "\\"))
|
|
if (!strstr(argn[i], ":"))
|
|
{
|
|
free(ctx->gamename);
|
|
ctx->gamename = strdup(argv[i]);
|
|
}
|
|
}
|
|
else if (!stricmp(argn[i], "connType"))
|
|
{
|
|
if (ctx->qtvf.connectiontype)
|
|
continue;
|
|
if (!stricmp(argn[i], "join"))
|
|
ctx->qtvf.connectiontype = QTVCT_JOIN;
|
|
else if (!stricmp(argn[i], "qtv"))
|
|
ctx->qtvf.connectiontype = QTVCT_STREAM;
|
|
else if (!stricmp(argn[i], "connect"))
|
|
ctx->qtvf.connectiontype = QTVCT_CONNECT;
|
|
else if (!stricmp(argn[i], "map"))
|
|
ctx->qtvf.connectiontype = QTVCT_MAP;
|
|
else if (!stricmp(argn[i], "join"))
|
|
ctx->qtvf.connectiontype = QTVCT_JOIN;
|
|
else if (!stricmp(argn[i], "observe"))
|
|
ctx->qtvf.connectiontype = QTVCT_OBSERVE;
|
|
else
|
|
ctx->qtvf.connectiontype = QTVCT_NONE;
|
|
}
|
|
else if (!stricmp(argn[i], "server") || !stricmp(argn[i], "stream"))
|
|
{
|
|
if (*ctx->qtvf.server)
|
|
continue;
|
|
Q_strncpyz(ctx->qtvf.server, argv[i], sizeof(ctx->qtvf.server));
|
|
}
|
|
else if (!stricmp(argn[i], "map"))
|
|
{
|
|
if (ctx->qtvf.connectiontype)
|
|
continue;
|
|
ctx->qtvf.connectiontype = QTVCT_MAP;
|
|
Q_strncpyz(ctx->qtvf.server, argv[i], sizeof(ctx->qtvf.server));
|
|
}
|
|
else if (!stricmp(argn[i], "stream"))
|
|
{
|
|
if (ctx->qtvf.connectiontype)
|
|
continue;
|
|
ctx->qtvf.connectiontype = QTVCT_STREAM;
|
|
Q_strncpyz(ctx->qtvf.server, argv[i], sizeof(ctx->qtvf.server));
|
|
}
|
|
else if (!stricmp(argn[i], "join"))
|
|
{
|
|
if (ctx->qtvf.connectiontype)
|
|
continue;
|
|
ctx->qtvf.connectiontype = QTVCT_JOIN;
|
|
Q_strncpyz(ctx->qtvf.server, argv[i], sizeof(ctx->qtvf.server));
|
|
}
|
|
else if (!stricmp(argn[i], "observe"))
|
|
{
|
|
if (ctx->qtvf.connectiontype)
|
|
continue;
|
|
ctx->qtvf.connectiontype = QTVCT_OBSERVE;
|
|
Q_strncpyz(ctx->qtvf.server, argv[i], sizeof(ctx->qtvf.server));
|
|
}
|
|
else if (!stricmp(argn[i], "password"))
|
|
{
|
|
ctx->password = strdup(argv[i]);
|
|
}
|
|
else if (!stricmp(argn[i], "splash"))
|
|
{
|
|
Q_strncpyz(ctx->qtvf.splashscreen, argv[i], sizeof(ctx->qtvf.splashscreen));
|
|
browserfuncs->geturlnotify(ctx->nppinstance, ctx->qtvf.splashscreen, NULL, &SplashscreenImageDescriptor);
|
|
}
|
|
else if (!stricmp(argn[i], "onStart"))
|
|
{
|
|
ctx->onstart = strdup(argv[i]);
|
|
}
|
|
else if (!stricmp(argn[i], "onEnd"))
|
|
{
|
|
ctx->onend = strdup(argv[i]);
|
|
}
|
|
else if (!stricmp(argn[i], "onDemoEnd"))
|
|
{
|
|
ctx->ondemoend = strdup(argv[i]);
|
|
}
|
|
else if (!stricmp(argn[i], "availVer"))
|
|
{
|
|
ctx->availver = atof(argv[i]);
|
|
if (ctx->availver <= NPQTV_VERSION)
|
|
ctx->availver = 0;
|
|
}
|
|
else if (!stricmp(argn[i], "begin"))
|
|
{
|
|
if (atoi(argv[i]) && !activecontext)
|
|
activecontext = ctx;
|
|
}
|
|
}
|
|
|
|
if (!*ctx->qtvf.server)
|
|
ctx->qtvf.connectiontype = QTVCT_NONE;
|
|
else if (ctx->qtvf.connectiontype == QTVCT_NONE)
|
|
ctx->qtvf.connectiontype = QTVCT_STREAM;
|
|
|
|
//add it to the linked list
|
|
ctx->next = contextlist;
|
|
contextlist = ctx;
|
|
return NPERR_NO_ERROR;
|
|
}
|
|
NPError NP_LOADDS NPP_Destroy(NPP instance, NPSavedData** save)
|
|
{
|
|
struct context *ctx = instance->pdata;
|
|
struct context *prev;
|
|
|
|
if (!ctx)
|
|
return NPERR_INVALID_INSTANCE_ERROR;
|
|
|
|
#ifdef _WIN32
|
|
if (ctx->window.window)
|
|
{
|
|
if (ctx->oldproc)
|
|
SetWindowLongPtr(ctx->window.window, GWL_WNDPROC, (LONG_PTR)ctx->oldproc);
|
|
SetWindowLongPtr(ctx->window.window, GWL_USERDATA, (LONG_PTR)NULL);
|
|
}
|
|
#endif
|
|
|
|
//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);
|
|
|
|
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->contextrunning)
|
|
{
|
|
NPQTV_Sys_Shutdown();
|
|
}
|
|
if (ctx == activecontext)
|
|
{
|
|
activecontext = NULL;
|
|
sys_parentwindow = NULL;
|
|
}
|
|
|
|
free(ctx);
|
|
instance->pdata = NULL;
|
|
|
|
return NPERR_NO_ERROR;
|
|
}
|
|
NPError NP_LOADDS NPP_SetWindow(NPP instance, NPWindow* window)
|
|
{
|
|
extern cvar_t vid_width;
|
|
struct context *ctx = instance->pdata;
|
|
|
|
#ifdef _WIN32
|
|
HWND oldwindow;
|
|
WNDPROC p;
|
|
|
|
if (!ctx)
|
|
return NPERR_INVALID_INSTANCE_ERROR;
|
|
|
|
oldwindow = ctx->window.window;
|
|
|
|
memcpy(&ctx->window, window, sizeof(ctx->window));
|
|
|
|
//if the window changed
|
|
if (ctx->window.window != oldwindow)
|
|
{
|
|
//we switched window?
|
|
if (oldwindow && ctx->oldproc)
|
|
{
|
|
SetWindowLongPtr(oldwindow, GWL_WNDPROC, (LONG_PTR)ctx->oldproc);
|
|
ctx->oldproc = NULL;
|
|
}
|
|
|
|
p = (WNDPROC)GetWindowLongPtr(ctx->window.window, GWL_WNDPROC);
|
|
if (p != MyPluginWndProc)
|
|
ctx->oldproc = p;
|
|
|
|
SetWindowLongPtr(ctx->window.window, GWL_WNDPROC, (LONG_PTR)MyPluginWndProc);
|
|
SetWindowLongPtr(ctx->window.window, GWL_USERDATA, (LONG_PTR)ctx);
|
|
|
|
if (ctx->contextrunning && mainwindow && oldwindow == sys_parentwindow)
|
|
{
|
|
sys_parentwindow = ctx->window.window;
|
|
SetParent(mainwindow, ctx->window.window);
|
|
|
|
oldwindow = sys_parentwindow;
|
|
}
|
|
}
|
|
|
|
if (ctx->contextrunning)
|
|
{
|
|
sys_parentwidth = ctx->window.width;
|
|
sys_parentheight = ctx->window.height;
|
|
Cvar_ForceCallback(&vid_width);
|
|
}
|
|
|
|
InvalidateRgn(ctx->window.window, NULL, FALSE);
|
|
#endif
|
|
return NPERR_NO_ERROR;
|
|
}
|
|
|
|
NPError NP_LOADDS NPP_NewStream(NPP instance, NPMIMEType type,
|
|
NPStream* stream, NPBool seekable,
|
|
uint16* stype)
|
|
{
|
|
// struct context *ctx = instance->pdata;
|
|
struct qstream *qstr;
|
|
|
|
stream->pdata = qstr = malloc(sizeof(*qstr) + strlen(stream->url));
|
|
memset(qstr, 0, sizeof(*qstr));
|
|
strcpy(qstr->url, stream->url);
|
|
|
|
if (!stream->notifyData)
|
|
{
|
|
//choose source type based on mime type
|
|
if (!strncmp(type, "text/x-quaketvident", 5))
|
|
stream->notifyData = &QTVFileDescriptor;
|
|
else if (!strcmp(type, "application/x-multiviewdemo"))
|
|
stream->notifyData = &DemoFileDescriptor;
|
|
|
|
//well that failed, try choosing based on extension
|
|
else if (!strcmp(COM_FileExtension(stream->url), "qtv"))
|
|
stream->notifyData = &QTVFileDescriptor;
|
|
|
|
else
|
|
return NPERR_INVALID_PARAM;
|
|
}
|
|
qstr->type = stream->notifyData;
|
|
|
|
if (qstr->type->needseeking)
|
|
{
|
|
*stype = NP_ASFILEONLY; //everything is a download
|
|
|
|
#ifdef FIREFOX_BUGS_OVER_25MB
|
|
*stype = NP_NORMAL;
|
|
qstr->pipe = FS_OpenTemp();
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
*stype = NP_NORMAL;
|
|
qstr->pipe = VFSPIPE_Open();
|
|
}
|
|
|
|
return NPERR_NO_ERROR;
|
|
}
|
|
NPError NP_LOADDS NPP_DestroyStream(NPP instance, NPStream* stream,
|
|
NPReason reason)
|
|
{
|
|
struct context *ctx = instance->pdata;
|
|
struct qstream *qstr = stream->pdata;
|
|
|
|
if (!qstr) //urm, got canceled before it finished downloading?
|
|
return NPERR_NO_ERROR;
|
|
|
|
if (ctx->wait_stream == qstr)
|
|
ctx->wait_stream = NULL;
|
|
|
|
if (qstr->type->wait == WAIT_YES)
|
|
{
|
|
ctx->waitingfordatafiles--;
|
|
}
|
|
|
|
if (qstr->type->wait == WAIT_DONE)
|
|
qstr->type->completionfunc(ctx, qstr->pipe, qstr->url);
|
|
else
|
|
{
|
|
qstr->next = ctx->donestreams;
|
|
ctx->donestreams = qstr;
|
|
}
|
|
|
|
return NPERR_NO_ERROR;
|
|
}
|
|
int32 NP_LOADDS NPP_WriteReady(NPP instance, NPStream* stream)
|
|
{
|
|
struct qstream *qstr = stream->pdata;
|
|
vfsfile_t *pipe = qstr?qstr->pipe:NULL;
|
|
|
|
if (pipe && pipe->seekingisabadplan)
|
|
return 1024*1024 - VFS_GETLEN(pipe);
|
|
else
|
|
return 8192;
|
|
}
|
|
int32 NP_LOADDS NPP_Write(NPP instance, NPStream* stream, int32 offset,
|
|
int32 len, void* buffer)
|
|
{
|
|
int bytes = NPP_WriteReady(instance, stream);
|
|
struct context *ctx = instance->pdata;
|
|
struct qstream *qstr = stream->pdata;
|
|
|
|
if (qstr && qstr->type && qstr->type->wait)
|
|
{
|
|
if (!ctx->wait_stream)
|
|
ctx->wait_stream = qstr;
|
|
if (ctx->wait_stream == qstr)
|
|
{
|
|
ctx->wait_offset = offset;
|
|
ctx->wait_size = stream->end;
|
|
|
|
InvalidateRgn(ctx->window.window, NULL, FALSE);
|
|
}
|
|
}
|
|
|
|
if (!qstr || !qstr->pipe)
|
|
return bytes;
|
|
|
|
//we're not meant to read more bytes than we said we could read.
|
|
if (len > bytes)
|
|
len = bytes;
|
|
|
|
return VFS_WRITE(qstr->pipe, buffer, len);
|
|
}
|
|
void NP_LOADDS NPP_StreamAsFile(NPP instance, NPStream* stream,
|
|
const char* fname)
|
|
{
|
|
struct qstream *qstr = stream->pdata;
|
|
|
|
if (!qstr)
|
|
return;
|
|
|
|
if (qstr->pipe)
|
|
VFS_CLOSE(qstr->pipe);
|
|
qstr->pipe = VFSOS_Open(fname, "rb");
|
|
}
|
|
void NP_LOADDS NPP_Print(NPP instance, NPPrint* platformPrint)
|
|
{
|
|
//we don't support printing.
|
|
//paper and ink doesn't give a good frame rate.
|
|
return;
|
|
}
|
|
int16 NP_LOADDS NPP_HandleEvent(NPP instance, void* event)
|
|
{
|
|
// MessageBox(NULL, "NPP_HandleEvent", "npapi", 0);
|
|
return NPERR_NO_ERROR;
|
|
}
|
|
void NP_LOADDS NPP_URLNotify(NPP instance, const char* url,
|
|
NPReason reason, void* notifyData)
|
|
{
|
|
}
|
|
|
|
NPError NP_LOADDS NPP_GetValue(NPP instance, NPPVariable variable, void *value)
|
|
{
|
|
switch(variable)
|
|
{
|
|
case NPPVpluginScriptableNPObject:
|
|
*(void**)value = NULL; //no scripting object, sorry.
|
|
return NPERR_NO_ERROR;
|
|
default:
|
|
return NPERR_INVALID_PARAM;
|
|
}
|
|
|
|
return NPERR_NO_ERROR;
|
|
}
|
|
NPError NP_LOADDS NPP_SetValue(NPP instance, NPNVariable variable, void *value)
|
|
{
|
|
switch(variable)
|
|
{
|
|
default:
|
|
return NPERR_INVALID_PARAM;
|
|
}
|
|
return NPERR_NO_ERROR;
|
|
}
|
|
|
|
NPError OSCALL NP_Initialize(NPNetscapeFuncs* pFuncs)
|
|
{
|
|
browserfuncs = pFuncs;
|
|
return NPERR_NO_ERROR;
|
|
}
|
|
|
|
NPError OSCALL NP_Shutdown(void)
|
|
{
|
|
if (contextlist)
|
|
{ //the browser isn't meant to call this when there's still instances left...
|
|
return NPERR_GENERIC_ERROR;
|
|
}
|
|
|
|
return NPERR_NO_ERROR;
|
|
}
|
|
|
|
NPError OSCALL NP_GetValue(void *instance, NPPVariable variable, void *value)
|
|
{
|
|
if (value == NULL)
|
|
return NPERR_INVALID_PARAM;
|
|
|
|
switch(variable)
|
|
{
|
|
case NPPVpluginNameString:
|
|
*(char**)value = "QTV Viewer";
|
|
break;
|
|
case NPPVpluginDescriptionString:
|
|
*(char**)value = "QTV Viewer";
|
|
break;
|
|
default:
|
|
return NPERR_INVALID_PARAM;
|
|
}
|
|
return NPERR_NO_ERROR;
|
|
}
|
|
|
|
NPError OSCALL NP_GetEntryPoints (NPPluginFuncs* pFuncs)
|
|
{
|
|
if (pFuncs->size < sizeof(NPPluginFuncs))
|
|
return NPERR_INVALID_FUNCTABLE_ERROR;
|
|
pFuncs->size = sizeof(NPPluginFuncs);
|
|
|
|
pFuncs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR;
|
|
|
|
pFuncs->newp = NPP_New;
|
|
pFuncs->destroy = NPP_Destroy;
|
|
pFuncs->setwindow = NPP_SetWindow;
|
|
pFuncs->newstream = NPP_NewStream;
|
|
pFuncs->destroystream = NPP_DestroyStream;
|
|
pFuncs->asfile = NPP_StreamAsFile;
|
|
pFuncs->writeready = NPP_WriteReady;
|
|
pFuncs->write = NPP_Write;
|
|
pFuncs->print = NPP_Print;
|
|
pFuncs->event = NPP_HandleEvent;
|
|
pFuncs->urlnotify = NPP_URLNotify;
|
|
pFuncs->javaClass = NULL;
|
|
pFuncs->getvalue = NPP_GetValue;
|
|
pFuncs->setvalue = NPP_SetValue;
|
|
|
|
return NPERR_NO_ERROR;
|
|
}
|
|
|
|
char *NP_GetMIMEDescription(void)
|
|
{
|
|
return "test/x-qtv:qtv:QTV Stream Description";
|
|
}
|