From 484e8bbfc2723bab488ec5b30a235b09b40f896f Mon Sep 17 00:00:00 2001 From: Spoike Date: Wed, 10 May 2017 02:08:58 +0000 Subject: [PATCH] playdemo accepts https urls now. will start playing before the file has finished downloading, to avoid unnecessary delays. reworked network addresses to separate address family and connection type. this should make banning people more reliable, as well as simplifying a whole load of logic (no need to check for ipv4 AND ipv6). tcpconnect will keep trying to connect even if the connection wasn't instant, instead of giving up instantly. rewrote tcp connections quite a bit. sv_port_tcp now handles qtv+qizmo+http+ws+rtcbroker+tls equivalents. qtv_streamport is now a legacy cvar and now acts equivalently to sv_port_tcp (but still separate). rewrote screenshot and video capture code to use strides. this solves image-is-upside down issues with vulkan. ignore alt key in browser port. oh no! no more red text! oh no! no more alt-being-wrongly-down-and-being-unable-to-type-anything-without-forcing-alt-released! reworked audio decoder interface. now has clearly defined success/unavailable/end-of-file results. this should solve a whole load of issues with audio streaming. fixed various openal audio streaming issues too. openal also got some workarounds for emscripten's poor emulation. fixed ogg decoder to retain sync properly if seeked. updated menu_media a bit. now reads vorbis comments/id3v1 tags to get proper track names. also saves the playlist so you don't have to manually repopulate the list so it might actually be usable now (after how many years?) r_stains now defaults to 0, and is no longer enabled by presets. use decals if you want that sort of thing. added fs_noreexec cvar, so configs will not be reexeced on gamedir change. this also means defaults won't be reapplied, etc. added 'nvvk' renderer on windows, using nvidia's vulkan-inside-opengl gl extension. mostly just to see how much slower it is. fixed up the ftp server quite a lot. more complete, more compliant, and should do ipv6 properly to-boot. file transfers also threaded. fixed potential crash inside runclientphys. experimental sv_antilag=3 setting. totally untested. the aim is to avoid missing due to lagged knockbacks. may be expensive for the server. browser port's websockets support fixed. experimental support for webrtc ('works for me', requires a broker server). updated avplug(renamed to ffmpeg so people know what it is) to use ffmpeg 3.2.4 properly, with its new encoder api. should be much more robust... also added experimental audio decoder for game music etc (currently doesn't resample, so playback rates are screwed, disabled by cvar). git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5097 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- engine/client/cl_demo.c | 55 +- engine/client/cl_input.c | 19 +- engine/client/cl_main.c | 346 ++-- engine/client/cl_parse.c | 169 +- engine/client/cl_screen.c | 209 +- engine/client/client.h | 7 +- engine/client/image.c | 163 +- engine/client/keys.c | 2 + engine/client/m_download.c | 8 +- engine/client/m_mp3.c | 395 ++-- engine/client/m_options.c | 84 +- engine/client/m_script.c | 28 +- engine/client/m_single.c | 6 +- engine/client/merged.h | 4 +- engine/client/net_master.c | 53 +- engine/client/pr_clcmd.c | 2 +- engine/client/pr_csqc.c | 2 +- engine/client/renderer.c | 33 +- engine/client/roq.h | 2 +- engine/client/screen.h | 2 +- engine/client/snd_al.c | 203 +- engine/client/snd_dma.c | 92 +- engine/client/snd_mem.c | 31 +- engine/client/snd_mix.c | 115 +- engine/client/snd_ov.c | 59 +- engine/client/sound.h | 44 +- engine/client/sys_droid.c | 16 +- engine/client/sys_linux.c | 15 +- engine/client/sys_morphos.c | 11 +- engine/client/sys_npfte.c | 4 + engine/client/sys_plugfte.c | 6 +- engine/client/sys_sdl.c | 22 +- engine/client/sys_win.c | 96 +- engine/client/sys_xdk.c | 9 +- engine/client/vid.h | 2 +- engine/client/vid_headless.c | 29 +- engine/client/view.c | 6 +- engine/client/wad.c | 8 +- engine/client/winquake.h | 1 + engine/common/bothdefs.h | 3 +- engine/common/cmd.c | 59 +- engine/common/cmd.h | 2 +- engine/common/com_phys_ode.c | 4 - engine/common/common.c | 32 +- engine/common/common.h | 41 +- engine/common/cvar.c | 15 +- engine/common/cvar.h | 22 +- engine/common/fs.c | 60 +- engine/common/fs.h | 5 + engine/common/fs_stdio.c | 17 +- engine/common/fs_win32.c | 29 +- engine/common/fs_xz.c | 2 +- engine/common/fs_zip.c | 12 +- engine/common/net.h | 45 +- engine/common/net_chan.c | 24 +- engine/common/net_ice.c | 148 +- engine/common/net_ssl_gnutls.c | 2 +- engine/common/net_ssl_winsspi.c | 81 +- engine/common/net_wins.c | 2614 ++++++++++++++++++-------- engine/common/netinc.h | 13 +- engine/common/plugin.c | 17 +- engine/common/pmove.c | 4 +- engine/common/sys.h | 7 +- engine/common/sys_win_threads.c | 9 + engine/common/zone.c | 12 + engine/common/zone.h | 2 + engine/dotnet2005/botlib.vcproj | 127 ++ engine/dotnet2005/droid.vcproj | 2 +- engine/dotnet2005/ftequake.sln | 8 +- engine/dotnet2005/ftequake.vcproj | 2 +- engine/dotnet2005/npfte.vcproj | 2 +- engine/gl/gl_screen.c | 8 +- engine/gl/gl_vidnt.c | 207 +- engine/gl/r_bishaders.h | 2 +- engine/http/ftpserver.c | 765 ++++++-- engine/http/httpclient.c | 121 +- engine/http/httpserver.c | 74 +- engine/http/iweb.h | 43 +- engine/http/iwebiface.c | 303 ++- engine/http/webgen.c | 10 +- engine/nacl/fs_ppapi.c | 10 +- engine/server/pr_cmds.c | 7 +- engine/server/pr_q1qvm.c | 4 +- engine/server/savegame.c | 5 +- engine/server/server.h | 39 +- engine/server/sv_ccmds.c | 9 +- engine/server/sv_cluster.c | 2 +- engine/server/sv_main.c | 14 +- engine/server/sv_mvd.c | 909 ++++----- engine/server/sv_rankin.c | 76 +- engine/server/sv_send.c | 8 +- engine/server/sv_sys_unix.c | 16 +- engine/server/sv_sys_win.c | 12 +- engine/server/sv_user.c | 10 +- engine/server/world.c | 69 + engine/shaders/glsl/rtlight.glsl | 2 +- engine/vk/vk_init.c | 469 +++-- engine/vk/vkrenderer.h | 8 +- engine/web/ftejslib.h | 14 +- engine/web/ftejslib.js | 296 ++- engine/web/gl_vidweb.c | 4 +- engine/web/prejs.js | 17 +- engine/web/sys_web.c | 10 +- plugins/Makefile | 47 +- plugins/avplug/avaudio.c | 398 ++++ plugins/avplug/avdecode.c | 39 +- plugins/avplug/avencode.c | 281 +-- plugins/ezhud/ezquakeisms.c | 5 - plugins/odeplug/odeplug.vcproj | 7 +- plugins/plugin.c | 8 + plugins/plugin.h | 3 + quakec/csaddon/src/csaddon.qc | 56 +- quakec/csaddon/src/csfixups.qc | 2 +- quakec/csaddon/src/csplat.qc | 51 +- quakec/csaddon/src/editor_brushes.qc | 648 ++++--- quakec/csaddon/src/editor_ents.qc | 102 +- specs/browser.txt | 86 + 117 files changed, 7612 insertions(+), 3444 deletions(-) create mode 100644 plugins/avplug/avaudio.c diff --git a/engine/client/cl_demo.c b/engine/client/cl_demo.c index 65f60592..90d80ecc 100644 --- a/engine/client/cl_demo.c +++ b/engine/client/cl_demo.c @@ -30,6 +30,7 @@ int cls_lastto; int cls_lasttype; void CL_PlayDemo(char *demoname, qboolean usesystempath); +void CL_PlayDemoFile(vfsfile_t *f, char *demoname, qboolean issyspath); char lastdemoname[256]; static qboolean lastdemowassystempath; @@ -72,16 +73,13 @@ void CL_StopPlayback (void) Media_CaptureDemoEnd(); - VFS_CLOSE (cls.demoinfile); + if (cls.demoinfile) + VFS_CLOSE (cls.demoinfile); cls.demoinfile = NULL; cls.state = ca_disconnected; cls.demoplayback = DPB_NONE; cls.demoseeking = false; //just in case - if (cls.demoindownload) - cls.demoindownload->status = DL_FAILED; - cls.demoindownload = NULL; - if (cls.timedemo) CL_FinishTimeDemo (); @@ -320,7 +318,7 @@ int readdemobytes(int *readpos, void *data, int len) if (*readpos+len > demobuffersize) { - if (i < 0 || (i == 0 && len && !cls.demoinfile->seekingisabadplan)) + if (i < 0 || (i == 0 && len && cls.demoinfile->seekstyle < SS_SLOW)) { //0 means no data available yet, don't error on that (unless we can seek, in which case its not a stream and we won't get any more data later on). endofdemo = true; return 0; @@ -435,8 +433,16 @@ void CL_DemoJump_f(void) cls.demoseektime = newtime; else { + vfsfile_t *df = cls.demoinfile; Con_Printf("Rewinding demo\n"); - CL_PlayDemo(lastdemoname, lastdemowassystempath); + if (df->seekstyle != SS_UNSEEKABLE) + { + VFS_SEEK(df, 0); + cls.demoinfile = NULL; + CL_PlayDemoFile(df, lastdemoname, lastdemowassystempath); + } + else + CL_PlayDemo(lastdemoname, lastdemowassystempath); //now fastparse it. cls.demoseektime = newtime; @@ -2072,10 +2078,11 @@ void CL_PlayDemo_f (void) #ifdef WEBCLIENT #if 1 - if (!strncmp(Cmd_Argv(1), "ftp://", 6) || !strncmp(Cmd_Argv(1), "http://", 7)) + if (!strncmp(Cmd_Argv(1), "ftp://", 6) || !strncmp(Cmd_Argv(1), "http://", 7) || !strncmp(Cmd_Argv(1), "https://", 7)) { if (Cmd_ExecLevel == RESTRICT_LOCAL) - HTTP_CL_Get(Cmd_Argv(1), COM_SkipPath(Cmd_Argv(1)), CL_PlayDownloadedDemo); + Host_RunFile(Cmd_Argv(1), strlen(Cmd_Argv(1)), NULL); +// HTTP_CL_Get(Cmd_Argv(1), COM_SkipPath(Cmd_Argv(1)), CL_PlayDownloadedDemo); return; } #endif @@ -2092,16 +2099,8 @@ void CL_PlayDemo_f (void) CL_PlayDemo(demoname, false); } -void CL_DemoStreamFullyDownloaded(struct dl_download *dl) -{ - //let the file get closed by the demo playback code. - dl->file = NULL; - //kill the reference now that its done. - if (cls.demoindownload == dl) - cls.demoindownload = NULL; -} //dl is provided so that we can receive files via chunked/gziped http downloads and on systems that don't provide sockets etc. its tracked so we can cancel the download if the client aborts playback early. -void CL_PlayDemoStream(vfsfile_t *file, struct dl_download *dl, char *filename, qboolean issyspath, int demotype, float bufferdelay) +void CL_PlayDemoStream(vfsfile_t *file, char *filename, qboolean issyspath, int demotype, float bufferdelay) { int protocol = CP_UNKNOWN; @@ -2137,9 +2136,6 @@ void CL_PlayDemoStream(vfsfile_t *file, struct dl_download *dl, char *filename, return; } - if (dl) - dl->notifycomplete = CL_DemoStreamFullyDownloaded; - // // disconnect from server // @@ -2151,7 +2147,6 @@ void CL_PlayDemoStream(vfsfile_t *file, struct dl_download *dl, char *filename, // // open the demo file // - cls.demoindownload = dl; cls.demoinfile = file; if (!cls.demoinfile) { @@ -2212,20 +2207,20 @@ void CL_PlayDemoFile(vfsfile_t *f, char *demoname, qboolean issyspath) if (!Q_strcasecmp(demoname + strlen(demoname) - 3, "dm2") || !Q_strcasecmp(demoname + strlen(demoname) - 6, "dm2.gz")) { - CL_PlayDemoStream(f, NULL, demoname, issyspath, DPB_QUAKE2, 0); + CL_PlayDemoStream(f, demoname, issyspath, DPB_QUAKE2, 0); return; } #endif if (!Q_strcasecmp(demoname + strlen(demoname) - 3, "mvd") || !Q_strcasecmp(demoname + strlen(demoname) - 6, "mvd.gz")) { - CL_PlayDemoStream(f, NULL, demoname, issyspath, DPB_MVD, 0); + CL_PlayDemoStream(f, demoname, issyspath, DPB_MVD, 0); return; } if (!Q_strcasecmp(demoname + strlen(demoname) - 3, "qwd") || !Q_strcasecmp(demoname + strlen(demoname) - 6, "qwd.gz")) { - CL_PlayDemoStream(f, NULL, demoname, issyspath, DPB_QUAKEWORLD, 0); + CL_PlayDemoStream(f, demoname, issyspath, DPB_QUAKEWORLD, 0); return; } @@ -2252,7 +2247,7 @@ void CL_PlayDemoFile(vfsfile_t *f, char *demoname, qboolean issyspath) ft *= -1; if (chr == '\n') { - CL_PlayDemoStream(f, NULL, demoname, issyspath, DPB_NETQUAKE, 0); + CL_PlayDemoStream(f, demoname, issyspath, DPB_NETQUAKE, 0); return; } VFS_SEEK(f, start); @@ -2293,7 +2288,7 @@ void CL_PlayDemoFile(vfsfile_t *f, char *demoname, qboolean issyspath) if (protocol >= PROTOCOL_VERSION_Q2_DEMO_MIN && protocol <= PROTOCOL_VERSION_Q2_DEMO_MAX) { VFS_SEEK(f, start); - CL_PlayDemoStream(f, NULL, demoname, issyspath, DPB_QUAKE2, 0); + CL_PlayDemoStream(f, demoname, issyspath, DPB_QUAKE2, 0); return; } break; @@ -2309,7 +2304,7 @@ void CL_PlayDemoFile(vfsfile_t *f, char *demoname, qboolean issyspath) //could also be .qwz or .dmz or whatever that nq extension is. we don't support either. //mvd and qwd have no identifying markers, other than the extension. - CL_PlayDemoStream(f, NULL, demoname, issyspath, DPB_QUAKEWORLD, 0); + CL_PlayDemoStream(f, demoname, issyspath, DPB_QUAKEWORLD, 0); } #ifdef WEBCLIENT void CL_PlayDownloadedDemo(struct dl_download *dl) @@ -2609,7 +2604,7 @@ void CL_QTVPoll (void) if (streamavailable) { - CL_PlayDemoStream(qtvrequest, NULL, NULL, false, iseztv?DPB_EZTV:DPB_MVD, BUFFERTIME); + CL_PlayDemoStream(qtvrequest, NULL, false, iseztv?DPB_EZTV:DPB_MVD, BUFFERTIME); qtvrequest = NULL; demo_resetcache(qtvrequestsize - (tail-qtvrequestbuffer), tail); return; @@ -2894,7 +2889,7 @@ void CL_QTVPlay_f (void) if (raw) { VFS_WRITE(newf, msg, msglen); - CL_PlayDemoStream(qtvrequest, NULL, qtvhostname, false, DPB_MVD, BUFFERTIME); + CL_PlayDemoStream(qtvrequest, qtvhostname, false, DPB_MVD, BUFFERTIME); } else { diff --git a/engine/client/cl_input.c b/engine/client/cl_input.c index 5f9f281a..95bf93dc 100644 --- a/engine/client/cl_input.c +++ b/engine/client/cl_input.c @@ -568,18 +568,19 @@ cvar_t cl_pitchspeed = CVAR("cl_pitchspeed","150"); cvar_t cl_anglespeedkey = CVAR("cl_anglespeedkey","1.5"); +#define GATHERBIT(bname,bit) if (bname.state[pnum] & 3) {bits |= bit;} bname.state[pnum] &= ~2; void CL_GatherButtons (usercmd_t *cmd, int pnum) { unsigned int bits = 0; - if (in_attack .state[pnum] & 3) bits |= 1; in_attack.state[pnum] &= ~2; - if (in_jump .state[pnum] & 3) bits |= 2; in_jump.state[pnum] &= ~2; - if (in_use .state[pnum] & 3) bits |= 4; in_use.state[pnum] &= ~2; - if (in_button3.state[pnum] & 3) bits |= 4; in_button3.state[pnum] &= ~2; //yup, flag 4 twice. - if (in_button4.state[pnum] & 3) bits |= 8; in_button4.state[pnum] &= ~2; - if (in_button5.state[pnum] & 3) bits |= 16; in_button5.state[pnum] &= ~2; - if (in_button6.state[pnum] & 3) bits |= 32; in_button6.state[pnum] &= ~2; - if (in_button7.state[pnum] & 3) bits |= 64; in_button7.state[pnum] &= ~2; - if (in_button8.state[pnum] & 3) bits |= 128; in_button8.state[pnum] &= ~2; + GATHERBIT(in_attack, 1); + GATHERBIT(in_jump, 2); + GATHERBIT(in_use, 4); + GATHERBIT(in_button3, 4); //yup, flag 4 twice. + GATHERBIT(in_button4, 8); + GATHERBIT(in_button5, 16); + GATHERBIT(in_button6, 32); + GATHERBIT(in_button7, 64); + GATHERBIT(in_button8, 128); cmd->buttons = bits; } diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 538a1ba6..727286e0 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -1061,7 +1061,15 @@ void CL_CheckForResend (void) if (contype & 1) { Q_snprintfz (data, sizeof(data), "%c%c%c%cgetchallenge\n", 255, 255, 255, 255); - keeptrying &= NET_SendPacket (NS_CLIENT, strlen(data), data, &connectinfo.adr)==NETERR_SENT; + switch(NET_SendPacket (NS_CLIENT, strlen(data), data, &connectinfo.adr)) + { + case NETERR_CLOGGED: //temporary failure + case NETERR_SENT: //yay, works! + break; + default: + keeptrying = false; + break; + } } /*NQ*/ #ifdef NQPROT @@ -1093,7 +1101,15 @@ void CL_CheckForResend (void) MSG_WriteString(&sb, "getchallenge"); *(int*)sb.data = LongSwap(NETFLAG_CTL | sb.cursize); - keeptrying &= NET_SendPacket (NS_CLIENT, sb.cursize, sb.data, &connectinfo.adr)==NETERR_SENT; + switch(NET_SendPacket (NS_CLIENT, sb.cursize, sb.data, &connectinfo.adr)) + { + case NETERR_CLOGGED: //temporary failure + case NETERR_SENT: //yay, works! + break; + default: + keeptrying = false; + break; + } } #endif @@ -1318,7 +1334,7 @@ void CL_IRCConnect_f (void) { CL_Disconnect_f (); - if (FTENET_AddToCollection(cls.sockets, "TCP", Cmd_Argv(2), NA_IRC, false)) + if (FTENET_AddToCollection(cls.sockets, "TCP", Cmd_Argv(2), NA_IP, NP_IRC, false)) { char *server; server = Cmd_Argv (1); @@ -1747,7 +1763,7 @@ void CL_Disconnect (void) #ifdef TCPCONNECT //disconnects it, without disconnecting the others. - FTENET_AddToCollection(cls.sockets, "conn", NULL, NA_INVALID, false); + FTENET_AddToCollection(cls.sockets, "conn", NULL, NA_INVALID, NP_DGRAM, false); #endif Cvar_ForceSet(&cl_servername, "none"); @@ -3418,17 +3434,11 @@ void CL_ReadPackets (void) { case CP_NETQUAKE: #ifdef NQPROT - switch(NQNetChan_Process(&cls.netchan)) + if(NQNetChan_Process(&cls.netchan)) { - case NQP_ERROR: - break; - case NQP_DATAGRAM://datagram -// cls.netchan.incoming_sequence = cls.netchan.outgoing_sequence - 3; - case NQP_RELIABLE://reliable MSG_ChangePrimitives(cls.netchan.netprim); CL_WriteDemoMessage (&net_message, msg_readcount); CLNQ_ParseServerMessage (); - break; } #endif break; @@ -4314,7 +4324,7 @@ void Host_RunFileNotify(struct dl_download *dl) #define HRF_OPENED (1<<4) #define HRF_DOWNLOADED (1<<5) //file was actually downloaded, and not from the local system -#define HRF_WAITING (1<<6) //file looks important enough that we should wait for it to start to download or something to see what file type it is. +#define HRF_WAITING (1<<6) //file looks important enough that we should wait for it to start to download or something before we try doing other stuff. // (1<<7) #define HRF_DEMO_MVD (1<<8) @@ -4328,12 +4338,14 @@ void Host_RunFileNotify(struct dl_download *dl) #define HRF_PACKAGE (1<<15) //pak or pk3 that should be installed. #define HRF_ARCHIVE (1<<16) //zip - treated as a multiple-file 'installer' #define HRF_MODEL (1<<17) +#define HRF_CONFIG (1<<18) //exec it on the console... #define HRF_ACTION (HRF_OVERWRITE|HRF_NOOVERWRITE|HRF_ABORT) #define HRF_DEMO (HRF_DEMO_MVD|HRF_DEMO_QWD|HRF_DEMO_DM2|HRF_DEMO_DEM) -#define HRF_FILETYPES (HRF_DEMO|HRF_QTVINFO|HRF_MANIFEST|HRF_BSP|HRF_PACKAGE|HRF_MODEL|HRF_ARCHIVE) +#define HRF_FILETYPES (HRF_DEMO|HRF_QTVINFO|HRF_MANIFEST|HRF_BSP|HRF_PACKAGE|HRF_ARCHIVE|HRF_MODEL|HRF_CONFIG) typedef struct { unsigned int flags; + struct dl_download *dl; vfsfile_t *srcfile; vfsfile_t *dstfile; char fname[1]; //system path or url. @@ -4341,13 +4353,108 @@ typedef struct { extern int waitingformanifest; void Host_DoRunFile(hrf_t *f); -void CL_PlayDemoStream(vfsfile_t *file, struct dl_download *, char *filename, qboolean issyspath, int demotype, float bufferdelay); +void CL_PlayDemoStream(vfsfile_t *file, char *filename, qboolean issyspath, int demotype, float bufferdelay); void CL_ParseQTVDescriptor(vfsfile_t *f, const char *name); qboolean FS_PathURLCache(char *url, char *path, size_t pathsize); +//guesses the file type based upon its file extension. mdl/md3/iqm distinctions are not important, so we can usually get away with this in the context of quake. +unsigned int Host_GuessFileType(const char *mimetype, const char *filename) +{ + if (mimetype) + { + if (!strcmp(mimetype, "application/x-qtv")) //what uses this? + return HRF_QTVINFO; + else if (!strcmp(mimetype, "text/x-quaketvident")) + return HRF_QTVINFO; + else if (!strcmp(mimetype, "application/x-fteplugin")) + return HRF_MANIFEST; + else if (!strcmp(mimetype, "application/x-ftemanifest")) + return HRF_MANIFEST; + else if (!strcmp(mimetype, "application/x-multiviewdemo")) + return HRF_DEMO_MVD; + else if (!strcmp(mimetype, "application/zip")) + return HRF_ARCHIVE; +// else if (!strcmp(mimetype, "application/x-ftebsp")) +// return HRF_BSP; +// else if (!strcmp(mimetype, "application/x-ftepackage")) +// return HRF_PACKAGE; + } + + if (filename) + { //find the query or location part of the url, so we can ignore extra stuff. + struct + { + unsigned int type; + const char *ext; + } exts[] = + { + //demo formats + {HRF_DEMO_QWD, "qwd"}, + {HRF_DEMO_QWD, "qwd.gz"}, + {HRF_DEMO_MVD, "mvd"}, + {HRF_DEMO_MVD, "mvd.gz"}, + {HRF_DEMO_DM2, "dm2"}, + {HRF_DEMO_DM2, "dm2.gz"}, + {HRF_DEMO_DEM, "dem"}, + {HRF_DEMO_DEM, "dem.gz"}, + {HRF_QTVINFO, "qtv"}, + //other stuff + {HRF_MANIFEST, "fmf"}, + {HRF_BSP, "bsp"}, + {HRF_BSP, "map"}, + {HRF_CONFIG, "cfg"}, + {HRF_CONFIG, "rc"}, + {HRF_PACKAGE, "pak"}, + {HRF_PACKAGE, "pk3"}, + {HRF_PACKAGE, "pk4"}, + {HRF_PACKAGE, "wad"}, + {HRF_ARCHIVE, "zip"}, + //model formats + {HRF_MODEL, "mdl"}, + {HRF_MODEL, "md2"}, + {HRF_MODEL, "md3"}, + {HRF_MODEL, "iqm"}, + {HRF_MODEL, "psk"}, + {HRF_MODEL, "zym"}, + {HRF_MODEL, "dpm"}, + //sprites + {HRF_MODEL, "spr"}, + {HRF_MODEL, "spr2"}, + //static stuff + {HRF_MODEL, "obj"}, + {HRF_MODEL, "lwo"}, + {HRF_MODEL, "ase"}, + }; + size_t i; + const char *ext; + const char *stop = filename+strlen(filename); + const char *tag = strchr(filename, '?'); + if (tag && tag < stop) + stop = tag; + tag = strchr(filename, '#'); + if (tag && tag < stop) + stop = tag; + + ext = COM_GetFileExtension(filename, stop); + if (!Q_strstopcasecmp(ext, stop, ".php")) //deal with extra extensions the easy way + ext = COM_GetFileExtension(filename, stop=ext); + if (!Q_strstopcasecmp(ext, stop, ".gz")) //deal with extra extensions the easy way + ext = COM_GetFileExtension(filename, ext); + if (*ext == '.') + ext++; + + for (i = 0; i < countof(exts); i++) + if (!Q_strstopcasecmp(ext, stop, exts[i].ext)) + return exts[i].type; + } + return 0; +} + void Host_RunFileDownloaded(struct dl_download *dl) { hrf_t *f = dl->user_ctx; + if(!f) //download was previously cancelled. + return; if (dl->status == DL_FAILED) { f->flags |= HRF_ABORT; @@ -4355,6 +4462,9 @@ void Host_RunFileDownloaded(struct dl_download *dl) } else { + if (f->srcfile) //erk? + VFS_CLOSE(f->srcfile); + f->flags |= HRF_OPENED; f->srcfile = dl->file; dl->file = NULL; } @@ -4373,50 +4483,10 @@ qboolean Host_BeginFileDownload(struct dl_download *dl, char *mimetype) waitingformanifest--; } - if (mimetype && !(f->flags & HRF_FILETYPES)) - { - if (!strcmp(mimetype, "application/x-qtv")) //what uses this? - f->flags |= HRF_QTVINFO; - else if (!strcmp(mimetype, "text/x-quaketvident")) - f->flags |= HRF_QTVINFO; - else if (!strcmp(mimetype, "application/x-fteplugin")) - f->flags |= HRF_MANIFEST; - else if (!strcmp(mimetype, "application/x-ftemanifest")) - f->flags |= HRF_MANIFEST; - else if (!strcmp(mimetype, "application/x-multiviewdemo")) - f->flags |= HRF_DEMO_MVD; - else if (!strcmp(mimetype, "application/zip")) - f->flags |= HRF_ARCHIVE; -// else if (!strcmp(mimetype, "application/x-ftebsp")) -// f->flags |= HRF_BSP; -// else if (!strcmp(mimetype, "application/x-ftepackage")) -// f->flags |= HRF_PACKAGE; - - if (f->flags & HRF_MANIFEST) - waitingformanifest++; - } - if (!(f->flags & HRF_FILETYPES)) { - char ext[8]; - COM_FileExtension(f->fname, ext, sizeof(ext)); - if (!strcmp(ext, "qwd")) - f->flags |= HRF_DEMO_QWD; - else if (!strcmp(ext, "mvd")) - f->flags |= HRF_DEMO_MVD; - else if (!strcmp(ext, "dm2")) - f->flags |= HRF_DEMO_DM2; - else if (!strcmp(ext, "dem")) - f->flags |= HRF_DEMO_DEM; - else if (!strcmp(ext, "qtv")) - f->flags |= HRF_QTVINFO; - else if (!strcmp(ext, "fmf")) - f->flags |= HRF_MANIFEST; - else if (!strcmp(ext, "bsp")) - f->flags |= HRF_BSP; - else if (!strcmp(ext, "pak") || !strcmp(ext, "pk3")) - f->flags |= HRF_PACKAGE; - else + f->flags |= Host_GuessFileType(mimetype, f->fname); + if (!(f->flags & HRF_FILETYPES)) { if (mimetype) Con_Printf("mime type \"%s\" and file extension of \"%s\" not recognised\n", mimetype, f->fname); @@ -4428,27 +4498,33 @@ qboolean Host_BeginFileDownload(struct dl_download *dl, char *mimetype) return false; } - if (f->flags & HRF_MANIFEST) + if ((f->flags & HRF_MANIFEST) && !(f->flags & HRF_WAITING)) + { + f->flags |= HRF_WAITING; waitingformanifest++; + } } + //seeking means we can rewind if (f->flags & HRF_DEMO_QWD) - CL_PlayDemoStream((dl->file = VFSPIPE_Open()), dl, f->fname, true, DPB_QUAKEWORLD, 0); + CL_PlayDemoStream((dl->file = VFSPIPE_Open(2, true)), f->fname, true, DPB_QUAKEWORLD, 0); else if (f->flags & HRF_DEMO_MVD) - CL_PlayDemoStream((dl->file = VFSPIPE_Open()), dl, f->fname, true, DPB_MVD, 0); + CL_PlayDemoStream((dl->file = VFSPIPE_Open(2, true)), f->fname, true, DPB_MVD, 0); #ifdef Q2CLIENT else if (f->flags & HRF_DEMO_DM2) - CL_PlayDemoStream((dl->file = VFSPIPE_Open()), dl, f->fname, true, DPB_QUAKE2, 0); + CL_PlayDemoStream((dl->file = VFSPIPE_Open(2, true)), f->fname, true, DPB_QUAKE2, 0); #endif #ifdef NQPROT -//fixme: the demo code can't handle the cd track like this. -// else if (f->flags & HRF_DEMO_DEM) -// CL_PlayDemoStream((dl->file = VFSPIPE_Open()), dl, f->fname, DPB_NETQUAKE, 0); + else if (f->flags & HRF_DEMO_DEM) + { //fixme: the demo code can't handle the cd track with streamed/missing-so-far writes. + dl->file = VFSPIPE_Open(1, true); //make sure the reader will be seekable, so we can rewind. +// CL_PlayDemoStream((dl->file = VFSPIPE_Open(2, true)), f->fname, DPB_NETQUAKE, 0); + } #endif else if (f->flags & (HRF_MANIFEST | HRF_QTVINFO)) { //just use a pipe instead of a temp file, working around an issue with temp files on android - dl->file = VFSPIPE_Open(); + dl->file = VFSPIPE_Open(1, false); return true; } else if (f->flags & HRF_ARCHIVE) @@ -4476,9 +4552,11 @@ qboolean Host_BeginFileDownload(struct dl_download *dl, char *mimetype) //demos stream, so we want to continue the http download, but we don't want to do anything with the result. if (f->flags & HRF_DEMO) result = true; - - f->flags |= HRF_ABORT; - Host_DoRunFile(f); + else + { + f->flags |= HRF_ABORT; + Host_DoRunFile(f); + } return result; } void Host_RunFilePrompted(void *ctx, int button) @@ -4527,7 +4605,8 @@ void Host_DoRunFile(hrf_t *f) if (f->flags & HRF_ABORT) { - if (f->flags & HRF_MANIFEST) +done: + if (f->flags & HRF_WAITING) waitingformanifest--; if (f->srcfile) @@ -4540,8 +4619,6 @@ void Host_DoRunFile(hrf_t *f) if (!(f->flags & HRF_FILETYPES)) { - char ext[8]; - #ifdef WEBCLIENT if (isurl(f->fname) && !f->srcfile) { @@ -4552,51 +4629,38 @@ void Host_DoRunFile(hrf_t *f) dl = HTTP_CL_Get(f->fname, NULL, Host_RunFileDownloaded); if (dl) { - f->flags |= HRF_WAITING|HRF_DOWNLOADED; + f->flags |= HRF_DOWNLOADED; dl->notifystarted = Host_BeginFileDownload; dl->user_ctx = f; - waitingformanifest++; + if (!(f->flags & HRF_WAITING)) + { + f->flags |= HRF_WAITING; + waitingformanifest++; + } return; } } } #endif - //if we get here, we have no mime type to give us any clues. - COM_FileExtension(f->fname, ext, sizeof(ext)); - if (!Q_strcasecmp(ext, "qwd")) - f->flags |= HRF_DEMO_QWD; - else if (!Q_strcasecmp(ext, "mvd")) - f->flags |= HRF_DEMO_MVD; - else if (!Q_strcasecmp(ext, "dm2")) - f->flags |= HRF_DEMO_DM2; - else if (!Q_strcasecmp(ext, "dem")) - f->flags |= HRF_DEMO_DEM; - else if (!Q_strcasecmp(ext, "qtv")) - f->flags |= HRF_QTVINFO; - else if (!Q_strcasecmp(ext, "fmf")) - f->flags |= HRF_MANIFEST; - else if (!Q_strcasecmp(ext, "bsp")) - f->flags |= HRF_BSP; - else if (!Q_strcasecmp(ext, "pak") || !Q_strcasecmp(ext, "pk3") || !Q_strcasecmp(ext, "pk4") || !Q_strcasecmp(ext, "wad")) - f->flags |= HRF_PACKAGE; - else if (!Q_strcasecmp(ext, "mdl") || !Q_strcasecmp(ext, "md2") || !Q_strcasecmp(ext, "md3") || !Q_strcasecmp(ext, "iqm") - || !Q_strcasecmp(ext, "psk") || !Q_strcasecmp(ext, "zym") || !Q_strcasecmp(ext, "dpm") || !Q_strcasecmp(ext, "spr") || !Q_strcasecmp(ext, "spr2") - || !Q_strcasecmp(ext, "obj") || !Q_strcasecmp(ext, "lwo") || !Q_strcasecmp(ext, "ase")) - f->flags |= HRF_MODEL; - + f->flags |= Host_GuessFileType(NULL, f->fname); + //if we still don't know what it is, give up. if (!(f->flags & HRF_FILETYPES)) { Con_Printf("Host_DoRunFile: unknown filetype\n"); - f->flags |= HRF_ABORT; - Host_DoRunFile(f); - return; + goto done; } if (f->flags & HRF_MANIFEST) - waitingformanifest++; + { + if (!(f->flags & HRF_WAITING)) + { + f->flags |= HRF_WAITING; + waitingformanifest++; + } + } } if (f->flags & HRF_DEMO) @@ -4605,9 +4669,7 @@ void Host_DoRunFile(hrf_t *f) FS_FixupGamedirForExternalFile(f->fname, loadcommand, sizeof(loadcommand)); Cbuf_AddText(va("playdemo \"%s\"\n", loadcommand), RESTRICT_LOCAL); - f->flags |= HRF_ABORT; - Host_DoRunFile(f); - return; + goto done; } else if (f->flags & HRF_BSP) { @@ -4617,9 +4679,7 @@ void Host_DoRunFile(hrf_t *f) { COM_StripExtension(qname+5, loadcommand, sizeof(loadcommand)); Cbuf_AddText(va("map \"%s\"\n", loadcommand), RESTRICT_LOCAL); - f->flags |= HRF_ABORT; - Host_DoRunFile(f); - return; + goto done; } snprintf(loadcommand, sizeof(loadcommand), "map \"%s\"\n", shortname); @@ -4672,9 +4732,7 @@ void Host_DoRunFile(hrf_t *f) } } - f->flags |= HRF_ABORT; - Host_DoRunFile(f); - return; + goto done; } } } @@ -4684,9 +4742,7 @@ void Host_DoRunFile(hrf_t *f) Con_Printf("%s is not within the current gamedir\n", f->fname); else Cbuf_AddText(va("modelviewer \"%s\"\n", loadcommand), RESTRICT_LOCAL); - f->flags |= HRF_ABORT; - Host_DoRunFile(f); - return; + goto done; } else if (f->flags & HRF_ARCHIVE) { @@ -4703,16 +4759,35 @@ void Host_DoRunFile(hrf_t *f) { COM_Gamedir("", packagespaths); } - f->flags |= HRF_ABORT; - Host_DoRunFile(f); - return; + goto done; + } + else if (f->flags & HRF_CONFIG) + { + if (!(f->flags & HRF_ACTION)) + { + Key_Dest_Remove(kdm_console); + M_Menu_Prompt(Host_RunFilePrompted, f, va("Exec %s?\n", COM_SkipPath(f->fname)), "Yes", NULL, "Cancel"); + return; + } + if (f->flags & HRF_OPENED) + { + size_t len = VFS_GETLEN(f->srcfile); + char *fdata = BZ_Malloc(len+2); + if (fdata) + { + VFS_READ(f->srcfile, fdata, len); + fdata[len++] = '\n'; + fdata[len] = 0; + Cbuf_AddText(fdata, RESTRICT_INSECURE); + BZ_Free(fdata); + } + goto done; + } } else if (!(f->flags & HRF_QTVINFO)) { Con_Printf("Host_DoRunFile: filetype not handled\n"); - f->flags |= HRF_ABORT; - Host_DoRunFile(f); - return; + goto done; } //at this point we need the file to have been opened. @@ -4740,9 +4815,7 @@ void Host_DoRunFile(hrf_t *f) if (!f->srcfile) { Con_Printf("Unable to open %s\n", f->fname); - f->flags |= HRF_ABORT; - Host_DoRunFile(f); - return; + goto done; } if (f->flags & HRF_MANIFEST) @@ -4757,9 +4830,7 @@ void Host_DoRunFile(hrf_t *f) CL_ParseQTVDescriptor(f->srcfile, f->fname); f->srcfile = NULL; - f->flags |= HRF_ABORT; - Host_DoRunFile(f); - return; + goto done; } VFS_SEEK(f->srcfile, 0); @@ -4768,7 +4839,7 @@ void Host_DoRunFile(hrf_t *f) if (f->dstfile) { //do a real diff. - if (f->srcfile->seekingisabadplan || VFS_GETLEN(f->srcfile) != VFS_GETLEN(f->dstfile)) + if (f->srcfile->seekstyle == SS_UNSEEKABLE || VFS_GETLEN(f->srcfile) != VFS_GETLEN(f->dstfile)) { //if we can't seek, or the sizes differ, just assume that the file is modified. haschanged = true; @@ -4794,6 +4865,7 @@ void Host_DoRunFile(hrf_t *f) { if (!(f->flags & HRF_ACTION)) { + Key_Dest_Remove(kdm_console); M_Menu_Prompt(Host_RunFilePrompted, f, va("File already exists.\nWhat would you like to do?\n%s\n", displayname), "Overwrite", "Run old", "Cancel"); return; } @@ -4802,6 +4874,7 @@ void Host_DoRunFile(hrf_t *f) { if (!(f->flags & HRF_ACTION)) { + Key_Dest_Remove(kdm_console); M_Menu_Prompt(Host_RunFilePrompted, f, va("File appears new.\nWould you like to install\n%s\n", displayname), "Install!", "", "Cancel"); return; } @@ -4814,6 +4887,11 @@ void Host_DoRunFile(hrf_t *f) f->dstfile = FS_OpenVFS(qname, "wb", FS_GAMEONLY); if (f->dstfile) { +#ifdef FTE_TARGET_WEB + VFS_SEEK(f->dstfile, VFS_GETLEN(f->srcfile)); + VFS_WRITE(f->dstfile, "zomg", 0); //hack to ensure the file is there, avoiding excessive copies. + VFS_SEEK(f->dstfile, 0); +#endif while(1) { len = VFS_READ(f->srcfile, buffer, sizeof(buffer)); @@ -4897,6 +4975,8 @@ qboolean Host_RunFile(const char *fname, int nlen, vfsfile_t *file) else Con_Printf("Unknown url command: %s\n", cmd); + if(file) + VFS_CLOSE(file); Z_Free(t); return true; } @@ -4904,6 +4984,9 @@ qboolean Host_RunFile(const char *fname, int nlen, vfsfile_t *file) f = Z_Malloc(sizeof(*f) + nlen); memcpy(f->fname, fname, nlen); f->fname[nlen] = 0; + f->srcfile = file; + if (file) + f->flags |= HRF_OPENED; Con_Printf("Opening external file: %s\n", f->fname); @@ -5601,6 +5684,7 @@ void CL_ExecInitialConfigs(char *resetcommand) Cbuf_AddText ("exec frontend.cfg\n", RESTRICT_LOCAL); #endif Cbuf_AddText ("cl_warncmd 1\n", RESTRICT_LOCAL); //and then it's allowed to start moaning. + COM_ParsePlusSets(true); com_parseutf8.ival = com_parseutf8.value; @@ -5841,10 +5925,6 @@ void Host_Shutdown(void) M_Shutdown(true); -#ifdef PLUGINS - Plug_Shutdown(false); -#endif - #ifdef CSQC_DAT CSQC_Shutdown(); #endif @@ -5853,12 +5933,16 @@ void Host_Shutdown(void) UI_Stop(); #endif -// Host_WriteConfiguration (); - + S_Shutdown(true); CDAudio_Shutdown (); IN_Shutdown (); R_ShutdownRenderer(true); - S_Shutdown(true); + +#ifdef PLUGINS + Plug_Shutdown(false); +#endif + +// Host_WriteConfiguration (); #ifdef CL_MASTER MasterInfo_Shutdown(); #endif diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index 706f91e2..57f05343 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -31,11 +31,13 @@ static qboolean CL_CheckModelResources (char *name); #ifdef NQPROT static char *CLNQ_ParseProQuakeMessage (char *s); #endif +static void DLC_Poll(qdownload_t *dl); +static void CL_ProcessUserInfo (int slot, player_info_t *player); -char cl_dp_csqc_progsname[128]; -int cl_dp_csqc_progssize; -int cl_dp_csqc_progscrc; -int cl_dp_serverextension_download; +static char cl_dp_csqc_progsname[128]; +static int cl_dp_csqc_progssize; +static int cl_dp_csqc_progscrc; +static int cl_dp_serverextension_download; #ifdef AVAIL_ZLIB #ifndef ZEXPORT @@ -45,7 +47,7 @@ int cl_dp_serverextension_download; #endif -char *svc_qwstrings[] = +static char *svc_qwstrings[] = { "svc_bad", "svc_nop", @@ -175,7 +177,7 @@ char *svc_qwstrings[] = "???", }; -char *svc_nqstrings[] = +static char *svc_nqstrings[] = { "nqsvc_bad", "nqsvc_nop", @@ -655,7 +657,7 @@ void CL_GetDownloadSizes(unsigned int *filecount, qofs_t *totalsize, qboolean *s } } -void CL_DisenqueDownload(char *filename) +static void CL_DisenqueDownload(char *filename) { downloadlist_t *dl, *nxt; if(cl.downloadlist) //remove from enqued download list @@ -683,7 +685,7 @@ void CL_DisenqueDownload(char *filename) } #ifdef WEBCLIENT -void CL_WebDownloadFinished(struct dl_download *dl) +static void CL_WebDownloadFinished(struct dl_download *dl) { if (dl->status == DL_FAILED) { @@ -701,7 +703,7 @@ void CL_WebDownloadFinished(struct dl_download *dl) } #endif -void CL_SendDownloadStartRequest(char *filename, char *localname, unsigned int flags) +static void CL_SendDownloadStartRequest(char *filename, char *localname, unsigned int flags) { static int dlsequence; qdownload_t *dl; @@ -839,7 +841,7 @@ void CL_DownloadFinished(qdownload_t *dl) } } -qboolean CL_CheckFile(const char *filename) +static qboolean CL_CheckFile(const char *filename) { if (strstr (filename, "..")) { @@ -985,7 +987,7 @@ static qboolean CL_CheckMD2Skins (qbyte *precache_model) return ret; } -qboolean CL_CheckHLBspWads(char *file) +static qboolean CL_CheckHLBspWads(char *file) { lump_t lump; dheader_t *dh; @@ -1034,7 +1036,7 @@ qboolean CL_CheckHLBspWads(char *file) return false; } -qboolean CL_CheckQ2BspWals(char *file) +static qboolean CL_CheckQ2BspWals(char *file) { qboolean gotone = false; #ifdef Q2BSPS @@ -1110,7 +1112,7 @@ static qboolean CL_CheckModelResources (char *name) Model_NextDownload ================= */ -void Model_CheckDownloads (void) +static void Model_CheckDownloads (void) { char *s; int i; @@ -1167,7 +1169,7 @@ void Model_CheckDownloads (void) } } -int CL_LoadModels(int stage, qboolean dontactuallyload) +static int CL_LoadModels(int stage, qboolean dontactuallyload) { int i; @@ -1419,7 +1421,7 @@ int CL_LoadModels(int stage, qboolean dontactuallyload) return stage; } -int CL_LoadSounds(int stage, qboolean dontactuallyload) +static int CL_LoadSounds(int stage, qboolean dontactuallyload) { int i; float giveuptime = Sys_DoubleTime()+0.1; //small things get padded into a single frame @@ -1490,7 +1492,7 @@ void Sound_CheckDownload(const char *s) Sound_NextDownload ================= */ -void Sound_CheckDownloads (void) +static void Sound_CheckDownloads (void) { int i; @@ -1720,7 +1722,7 @@ void CL_SendDownloadReq(sizebuf_t *msg) #include #endif -char *ZLibDownloadDecode(int *messagesize, char *input, int finalsize) +static char *ZLibDownloadDecode(int *messagesize, char *input, int finalsize) { char *outbuf = Hunk_TempAlloc(finalsize); z_stream zs; @@ -1911,7 +1913,7 @@ qboolean DL_Begun(qdownload_t *dl) return true; } -void DL_Completed(qdownload_t *dl, qofs_t start, qofs_t end) +static void DL_Completed(qdownload_t *dl, qofs_t start, qofs_t end) { struct dlblock_s *prev = NULL, *b, *n, *e; if (end <= start) @@ -2028,7 +2030,7 @@ void DL_Completed(qdownload_t *dl, qofs_t start, qofs_t end) static float chunkrate; -void CL_ParseChunkedDownload(qdownload_t *dl) +static void CL_ParseChunkedDownload(qdownload_t *dl) { qbyte *svname; int flag; @@ -2318,7 +2320,7 @@ static void DLC_RequestDownloadChunks(qdownload_t *dl, float frametime) } } -void DLC_Poll(qdownload_t *dl) +static void DLC_Poll(qdownload_t *dl) { static float lasttime; DLC_RequestDownloadChunks(dl, realtime - lasttime); @@ -2457,7 +2459,7 @@ CL_ParseDownload A download message has been received from the server ===================== */ -void CL_ParseDownload (qboolean zlib) +static void CL_ParseDownload (qboolean zlib) { extern cvar_t cl_dlemptyterminate; int size, percent; @@ -2641,7 +2643,7 @@ qboolean CL_ParseOOBDownload(void) return true; } -void CLDP_ParseDownloadData(void) +static void CLDP_ParseDownloadData(void) { qdownload_t *dl = cls.download; unsigned char buffer[1<<16]; @@ -2669,7 +2671,7 @@ void CLDP_ParseDownloadData(void) MSG_WriteShort(&cls.netchan.message, size); } -void CLDP_ParseDownloadBegin(char *s) +static void CLDP_ParseDownloadBegin(char *s) { qdownload_t *dl = cls.download; char buffer[8192]; @@ -2714,7 +2716,7 @@ void CLDP_ParseDownloadBegin(char *s) } } -void CLDP_ParseDownloadFinished(char *s) +static void CLDP_ParseDownloadFinished(char *s) { qdownload_t *dl = cls.download; unsigned short runningcrc = 0; @@ -2861,7 +2863,7 @@ void CL_StopUpload(void) upload_pos = upload_size = 0; } -qboolean CL_StartUploadFile(char *filename) +static qboolean CL_StartUploadFile(char *filename) { if (!COM_CheckParm("-fileul")) { @@ -2870,7 +2872,10 @@ qboolean CL_StartUploadFile(char *filename) } if (cls.state < ca_onserver) + { + Con_Printf("not connected\n"); return false; // gotta be connected + } CL_StopUpload(); @@ -2894,7 +2899,7 @@ qboolean CL_StartUploadFile(char *filename) ===================================================================== */ #ifdef CLIENTONLY -float nextdemotime; +static float nextdemotime; #endif void CL_ClearParseState(void) @@ -2931,7 +2936,7 @@ void CL_ClearParseState(void) CL_ParseServerData ================== */ -void CLQW_ParseServerData (void) +static void CLQW_ParseServerData (void) { int pnum; int clnum; @@ -3223,7 +3228,7 @@ void CLQW_ParseServerData (void) } #ifdef Q2CLIENT -void CLQ2_ParseServerData (void) +static void CLQ2_ParseServerData (void) { char *str; int i; @@ -3393,7 +3398,7 @@ void CL_ParseEstablished(void) } #ifdef NQPROT -void CLNQ_ParseProtoVersion(void) +static void CLNQ_ParseProtoVersion(void) { int protover; struct netprim_s netprim; @@ -3534,7 +3539,7 @@ static int CL_Darkplaces_Particle_Precache(const char *pname) //FIXME: move to header void CL_KeepaliveMessage(void){} -void CLNQ_ParseServerData(void) //Doesn't change gamedir - use with caution. +static void CLNQ_ParseServerData(void) //Doesn't change gamedir - use with caution. { int nummodels, numsounds; char *str; @@ -3729,7 +3734,7 @@ Con_DPrintf ("CL_SignonReply: %i\n", cls.signon); } #define DEFAULT_VIEWHEIGHT 22 -void CLNQ_ParseClientdata (void) +static void CLNQ_ParseClientdata (void) { int i; const int seat = 0; @@ -3885,7 +3890,7 @@ void CLNQ_ParseClientdata (void) CL_ParseSoundlist ================== */ -void CL_ParseSoundlist (qboolean lots) +static void CL_ParseSoundlist (qboolean lots) { int numsounds; char *str; @@ -3961,7 +3966,7 @@ void CL_ParseSoundlist (qboolean lots) CL_ParseModellist ================== */ -void CL_ParseModellist (qboolean lots) +static void CL_ParseModellist (qboolean lots) { int nummodels; char *str; @@ -4037,10 +4042,8 @@ void CL_ParseModellist (qboolean lots) SCR_SetLoadingFile("loading data"); } -void CL_ProcessUserInfo (int slot, player_info_t *player); - #ifdef Q2CLIENT -void CLQ2_ParseClientinfo(int i, char *s) +static void CLQ2_ParseClientinfo(int i, char *s) { char *model, *name; player_info_t *player; @@ -4089,7 +4092,7 @@ void CLQ2_ParseClientinfo(int i, char *s) CL_ProcessUserInfo (i, player); } -void CLQ2_ParseConfigString (void) +static void CLQ2_ParseConfigString (void) { unsigned int i; char *s; @@ -4259,7 +4262,7 @@ qboolean CL_CheckBaselines (int size) CL_ParseBaseline ================== */ -void CL_ParseBaseline (entity_state_t *es, int baselinetype2) +static void CL_ParseBaseline (entity_state_t *es, int baselinetype2) { int i; unsigned int bits; @@ -4289,7 +4292,7 @@ void CL_ParseBaseline (entity_state_t *es, int baselinetype2) es->trans = (bits & FITZ_B_ALPHA) ? MSG_ReadByte() : 255; es->scale = (bits & RMQFITZ_B_SCALE) ? MSG_ReadByte() : 16; } -void CL_ParseBaselineDelta (void) +static void CL_ParseBaselineDelta (void) { entity_state_t es; @@ -4302,33 +4305,7 @@ void CL_ParseBaselineDelta (void) memcpy(cl_baselines + es.number, &es, sizeof(es)); } -void CLNQ_ParseBaseline2 (entity_state_t *es, qboolean dp) -{ - int i; - int bits; - - memcpy(es, &nullentitystate, sizeof(entity_state_t)); - - if (dp) - bits = FITZ_B_LARGEMODEL|FITZ_B_LARGEFRAME; - else - bits = MSG_ReadByte(); - es->modelindex = (bits & FITZ_B_LARGEMODEL) ? MSG_ReadShort() : MSG_ReadByte(); - es->frame = (bits & FITZ_B_LARGEFRAME) ? MSG_ReadShort() : MSG_ReadByte(); - es->colormap = MSG_ReadByte(); - es->skinnum = MSG_ReadByte(); - - for (i=0 ; i<3 ; i++) - { - es->origin[i] = MSG_ReadCoord (); - es->angles[i] = MSG_ReadAngle (); - } - - es->trans = (bits & FITZ_B_ALPHA) ? MSG_ReadByte() : 255; - es->scale = (bits & RMQFITZ_B_SCALE) ? MSG_ReadByte() : 16; -} - -void CLQ2_Precache_f (void) +static void CLQ2_Precache_f (void) { Model_CheckDownloads(); Sound_CheckDownloads(); @@ -4353,7 +4330,7 @@ like torches ===================== */ void R_StaticEntityToRTLight(int i); -void CL_ParseStaticProt (int baselinetype) +static void CL_ParseStaticProt (int baselinetype) { entity_t *ent; int i; @@ -4486,7 +4463,7 @@ void CL_ParseStaticProt (int baselinetype) CL_ParseStaticSound =================== */ -void CL_ParseStaticSound (qboolean large) +static void CL_ParseStaticSound (qboolean large) { extern cvar_t cl_staticsounds; vec3_t org; @@ -4525,7 +4502,7 @@ ACTION MESSAGES CL_ParseStartSoundPacket ================== */ -void CLQW_ParseStartSoundPacket(void) +static void CLQW_ParseStartSoundPacket(void) { vec3_t pos; int channel, ent; @@ -4579,7 +4556,7 @@ void CLQW_ParseStartSoundPacket(void) } #ifdef Q2CLIENT -void CLQ2_ParseStartSoundPacket(void) +static void CLQ2_ParseStartSoundPacket(void) { vec3_t pos_v; float *pos; @@ -4674,7 +4651,7 @@ void CLQ2_ParseStartSoundPacket(void) #endif #if defined(NQPROT) || defined(PEXT_SOUNDDBL) -void CLNQ_ParseStartSoundPacket(void) +static void CLNQ_ParseStartSoundPacket(void) { vec3_t pos, vel; int channel, ent; @@ -4954,7 +4931,7 @@ void CL_NewTranslation (int slot) CL_UpdateUserinfo ============== */ -void CL_ProcessUserInfo (int slot, player_info_t *player) +static void CL_ProcessUserInfo (int slot, player_info_t *player) { int i; char *col; @@ -5030,7 +5007,7 @@ void CL_ProcessUserInfo (int slot, player_info_t *player) CL_UpdateUserinfo ============== */ -void CL_UpdateUserinfo (void) +static void CL_UpdateUserinfo (void) { int slot; player_info_t *player; @@ -5062,7 +5039,7 @@ void CL_UpdateUserinfo (void) CL_SetInfo ============== */ -void CL_ParseSetInfo (void) +static void CL_ParseSetInfo (void) { int slot; player_info_t *player; @@ -5094,7 +5071,7 @@ void CL_ParseSetInfo (void) CL_ServerInfo ============== */ -void CL_ServerInfo (void) +static void CL_ServerInfo (void) { // int slot; // player_info_t *player; @@ -5153,7 +5130,7 @@ static void CL_SetStat_Internal (int pnum, int stat, int ivalue, float fvalue) } #ifdef NQPROT -void CL_SetStatMovevar(int pnum, int stat, float value) +static void CL_SetStatMovevar(int pnum, int stat, float value) { switch(stat) { @@ -5247,7 +5224,7 @@ static void CL_SetStatNumeric (int pnum, int stat, int ivalue, float fvalue) #endif } -void CL_SetStatString (int pnum, int stat, char *value) +static void CL_SetStatString (int pnum, int stat, char *value) { if (stat < 0 || stat >= MAX_CL_STATS) return; @@ -5275,7 +5252,7 @@ void CL_SetStatString (int pnum, int stat, char *value) CL_MuzzleFlash ============== */ -void CL_MuzzleFlash (int entnum) +static void CL_MuzzleFlash (int entnum) { dlight_t *dl; player_state_t *pl; @@ -5363,7 +5340,7 @@ void CL_MuzzleFlash (int entnum) #ifdef Q2CLIENT void Q2S_StartSound(vec3_t origin, int entnum, int entchannel, sfx_t *sfx, float fvol, float attenuation, float timeofs); -void CLQ2_ParseMuzzleFlash (void) +static void CLQ2_ParseMuzzleFlash (void) { vec3_t fv, rv, dummy; dlight_t *dl; @@ -5556,7 +5533,7 @@ void CLQ2_ParseMuzzleFlash (void) } } -void CLQ2_ParseMuzzleFlash2 (void) +static void CLQ2_ParseMuzzleFlash2 (void) { int ent; int flash_number; @@ -5570,7 +5547,7 @@ void CLQ2_ParseMuzzleFlash2 (void) CLQ2_RunMuzzleFlash2(ent, flash_number); } -void CLQ2_ParseInventory (int seat) +static void CLQ2_ParseInventory (int seat) { unsigned int i; for (i=0 ; i= sizeof(printtext)) @@ -6193,8 +6170,8 @@ static void CL_ParseTeamInfo(void) #endif -char stufftext[4096]; -void CL_ParseStuffCmd(char *msg, int destsplit) //this protects stuffcmds from network segregation. +static char stufftext[4096]; +static void CL_ParseStuffCmd(char *msg, int destsplit) //this protects stuffcmds from network segregation. { #ifdef NQPROT if (!*stufftext && *msg == 1 && !cls.allow_csqc) @@ -6409,7 +6386,7 @@ void CL_ParseStuffCmd(char *msg, int destsplit) //this protects stuffcmds from n } } -void CL_ParsePrecache(void) +static void CL_ParsePrecache(void) { int i, code = (unsigned short)MSG_ReadShort(); char *s = MSG_ReadString(); @@ -6464,7 +6441,7 @@ void CL_ParsePrecache(void) } } -void Con_HexDump(qbyte *packet, size_t len) +static void Con_HexDump(qbyte *packet, size_t len) { int i; int pos; @@ -6501,7 +6478,7 @@ void CL_DumpPacket(void) Con_HexDump(net_message.data, net_message.cursize); } -void CL_ParsePortalState(void) +static void CL_ParsePortalState(void) { int mode = MSG_ReadByte(); int a1, a2; @@ -7126,7 +7103,7 @@ void CLQW_ParseServerMessage (void) } #ifdef Q2CLIENT -void CLQ2_ParseZPacket(void) +static void CLQ2_ParseZPacket(void) { #ifndef AVAIL_ZLIB Host_EndGame ("CLQ2_ParseZPacket: zlib not supported in this build"); @@ -7171,7 +7148,7 @@ void CLQ2_ParseZPacket(void) msg_badread = false; #endif } -void CLR1Q2_ParseSetting(void) +static void CLR1Q2_ParseSetting(void) { int setting = MSG_ReadLong(); int value = MSG_ReadLong(); @@ -7467,7 +7444,7 @@ static char *CLNQ_ParseProQuakeMessage (char *s) return s; } -qboolean CLNQ_ParseNQPrints(char *s) +static qboolean CLNQ_ParseNQPrints(char *s) { int i; char *start = s; @@ -7569,7 +7546,7 @@ qboolean CLNQ_ParseNQPrints(char *s) return false; } -void CLNQ_CheckPlayerIsSpectator(int i) +static void CLNQ_CheckPlayerIsSpectator(int i) { cl.players[i].spectator = (cl.players[i].frags==-999) || //DP mods tend to use -999 diff --git a/engine/client/cl_screen.c b/engine/client/cl_screen.c index 7aea00e1..56ac0cf2 100644 --- a/engine/client/cl_screen.c +++ b/engine/client/cl_screen.c @@ -2119,89 +2119,105 @@ typedef struct _TargaHeader { #if defined(AVAIL_JPEGLIB) && !defined(NO_JPEG) -qboolean screenshotJPEG(char *filename, enum fs_relative fsroot, int compression, qbyte *screendata, int screenwidth, int screenheight, enum uploadfmt fmt); +qboolean screenshotJPEG(char *filename, enum fs_relative fsroot, int compression, qbyte *screendata, int bytestride, int screenwidth, int screenheight, enum uploadfmt fmt); #endif #ifdef AVAIL_PNGLIB -int Image_WritePNG (char *filename, enum fs_relative fsroot, int compression, void **buffers, int numbuffers, int width, int height, enum uploadfmt fmt); +int Image_WritePNG (char *filename, enum fs_relative fsroot, int compression, void **buffers, int numbuffers, int bytestride, int width, int height, enum uploadfmt fmt); #endif -void WriteBMPFile(char *filename, enum fs_relative fsroot, qbyte *in, int width, int height); +void WriteBMPFile(char *filename, enum fs_relative fsroot, qbyte *in, int bytestride, int width, int height); -qboolean WriteTGA(char *filename, enum fs_relative fsroot, qbyte *rgb_buffer, int width, int height, enum uploadfmt fmt) +qboolean WriteTGA(char *filename, enum fs_relative fsroot, qbyte *fte_restrict rgb_buffer, int bytestride, int width, int height, enum uploadfmt fmt) { size_t c, i; vfsfile_t *vfs; - if (fmt != TF_BGRA32 && fmt != TF_RGB24 && fmt != TF_RGBA32 && fmt != TF_BGR24) + if (fmt != TF_BGRA32 && fmt != TF_RGB24 && fmt != TF_RGBA32 && fmt != TF_BGR24 && fmt != TF_RGBX32 && fmt != TF_BGRX32) return false; FS_CreatePath(filename, fsroot); vfs = FS_OpenVFS(filename, "wb", fsroot); if (vfs) { + int ipx,opx; + qboolean rgb; unsigned char header[18]; memset (header, 0, 18); - header[2] = 2; // uncompressed type + + if (fmt == TF_BGRA32 || fmt == TF_RGBA32) + { + rgb = fmt==TF_RGBA32; + ipx = 4; + opx = 4; + } + else if (fmt == TF_RGBX32 || fmt == TF_BGRX32) + { + rgb = fmt==TF_RGBX32; + ipx = 4; + opx = 3; + } + else + { + rgb = fmt==TF_RGB24; + ipx = 3; + opx = 3; + } + + header[2] = 2; // uncompressed type header[12] = width&255; header[13] = width>>8; header[14] = height&255; header[15] = height>>8; - header[16] = 24; // pixel size + header[16] = opx*8; // pixel size + header[17] = 0x00; // flags - if (fmt == TF_BGRA32) - { -#if 0 - header[16] = 32; -#else - qbyte tmp[3]; - // compact+swap - c = width*height; - for (i=0 ; ierr)->setjmp_buffer, 1); + longjmp(((jpeg_error_mgr_wrapper *) cinfo->err)->setjmp_buffer, 1); } -qboolean screenshotJPEG(char *filename, enum fs_relative fsroot, int compression, qbyte *screendata, int screenwidth, int screenheight, enum uploadfmt fmt) +qboolean screenshotJPEG(char *filename, enum fs_relative fsroot, int compression, qbyte *screendata, int stride, int screenwidth, int screenheight, enum uploadfmt fmt) { qbyte *buffer; vfsfile_t *outfile; @@ -1657,58 +1664,61 @@ qboolean screenshotJPEG(char *filename, enum fs_relative fsroot, int compression struct jpeg_compress_struct cinfo; JSAMPROW row_pointer[1]; + qbyte *rgbdata = NULL; + //convert in-place if needed. //bgr->rgb may require copying out entirely for the first pixel to work properly. - if (fmt == TF_BGRA32) - { - qbyte *in=screendata, *out=screendata; + if (fmt == TF_BGRA32 || fmt == TF_BGRX32 || fmt == TF_BGR24) + { //byteswap and strip alpha + size_t ps = (fmt == TF_BGR24)?3:4; + qbyte *in=screendata, *out=rgbdata=Hunk_TempAlloc(screenwidth*screenheight*3); + size_t y, x; size_t sz = screenwidth*screenheight; - while(sz --> 0) + for (y = 0; y < screenheight; y++) { - int r = in[2]; - int g = in[1]; - int b = in[0]; - out[0] = r; - out[1] = g; - out[2] = b; - in+=4; - out+=3; + for (x = 0; x < screenwidth; x++) + { + int r = in[2]; + int g = in[1]; + int b = in[0]; + out[0] = r; + out[1] = g; + out[2] = b; + in+=ps; + out+=3; + } + in-=screenwidth*ps; + in+=stride; } fmt = TF_RGB24; + stride = screenwidth*3; + screendata = rgbdata; } - else if (fmt == TF_RGBA32) - { - qbyte *in=screendata, *out=screendata; + else if (fmt == TF_RGBA32 || fmt == TF_RGBX32 || (fmt == TF_RGB24 && stride < 0)) + { //strip alpha, no need to byteswap + size_t ps = (fmt == TF_RGB24)?3:4; + qbyte *in=screendata, *out=rgbdata=Hunk_TempAlloc(screenwidth*screenheight*3); + size_t y, x; size_t sz = screenwidth*screenheight; - while(sz --> 0) + for (y = 0; y < screenheight; y++) { - int r = in[0]; - int g = in[1]; - int b = in[2]; - out[0] = r; - out[1] = g; - out[2] = b; - in+=4; - out+=3; - } - fmt = TF_RGB24; - } - else if (fmt == TF_BGR24) - { - qbyte *in=screendata, *out=screendata; - size_t sz = screenwidth*screenheight; - while(sz --> 0) - { - int r = in[0]; - int g = in[1]; - int b = in[2]; - out[0] = r; - out[1] = g; - out[2] = b; - in+=3; - out+=3; + for (x = 0; x < screenwidth; x++) + { + int r = in[0]; + int g = in[1]; + int b = in[2]; + out[0] = r; + out[1] = g; + out[2] = b; + in+=ps; + out+=3; + } + in-=screenwidth*ps; + in+=stride; } fmt = TF_RGB24; + stride = screenwidth*3; + screendata = rgbdata; } else if (fmt != TF_RGB24) { @@ -1754,7 +1764,7 @@ qboolean screenshotJPEG(char *filename, enum fs_relative fsroot, int compression while (cinfo.next_scanline < cinfo.image_height) { - *row_pointer = &buffer[(cinfo.image_height - cinfo.next_scanline - 1) * cinfo.image_width * 3]; + *row_pointer = &buffer[cinfo.next_scanline * stride]; qjpeg_write_scanlines(&cinfo, row_pointer, 1); } qjpeg_finish_compress(&cinfo); @@ -1803,9 +1813,6 @@ void WritePCXfile (const char *filename, enum fs_relative fsroot, qbyte *data, i // pack the image pack = (qbyte *)(pcx+1); - - data += rowbytes * (height - 1); - for (i=0 ; i= 3 && h >= 4 && w*h+sizeof(int)*2+768+2 == len) + { + qboolean foundalpha = false; + qbyte *in = (qbyte*)((int*)buf+2); + qbyte *palette = in + w*h+2, *p; + data = BZ_Malloc(w * h * sizeof(int)); + for (i = 0; i < w * h; i++) + { + if (in[i] == 255) + foundalpha = true; + p = palette + 3*in[i]; + data[(i<<2)+0] = p[2]; + data[(i<<2)+1] = p[1]; + data[(i<<2)+2] = p[0]; + data[(i<<2)+3] = 255; + } + *width = w; + *height = h; + *hasalpha = foundalpha; + return data; + } } TRACE(("dbg: Read32BitImageFile: life sucks\n")); @@ -4984,7 +5011,7 @@ image_t *Image_GetTexture(const char *identifier, const char *subpath, unsigned if (sizelimit) dl->sizelimit = sizelimit; dl->user_ctx = tex; - dl->file = VFSPIPE_Open(); + dl->file = VFSPIPE_Open(1, false); dl->isquery = true; } #ifdef MULTITHREAD diff --git a/engine/client/keys.c b/engine/client/keys.c index 0257ee48..21a2d118 100644 --- a/engine/client/keys.c +++ b/engine/client/keys.c @@ -1299,6 +1299,7 @@ qboolean Key_EntryLine(unsigned char **line, int lineoffset, int *linepos, int k return false; } } +#ifndef FTE_TARGET_WEB //browser port gets keys stuck down when task switching, especially alt+tab. don't confuse users. else if (com_parseutf8.ival >= 0) //don't do this for iso8859-1. the major user of that is hexen2 which doesn't have these chars. { if (ctrl && !keydown[K_RALT]) @@ -1331,6 +1332,7 @@ qboolean Key_EntryLine(unsigned char **line, int lineoffset, int *linepos, int k if (keydown[K_LALT] && unicode > 32 && unicode < 128) unicode |= 0xe080; // red char } +#endif unicode = utf8_encode(utf8, unicode, sizeof(utf8)-1); if (unicode) diff --git a/engine/client/m_download.c b/engine/client/m_download.c index 7676711d..c492cd4e 100644 --- a/engine/client/m_download.c +++ b/engine/client/m_download.c @@ -1072,7 +1072,6 @@ static void PM_PreparePackageList(void) //figure out what we've previously installed. if (!loadedinstalled) { - char nat[MAX_OSPATH]; vfsfile_t *f = FS_OpenVFS(INSTALLEDFILES, "rb", FS_ROOT); loadedinstalled = true; if (f) @@ -1084,6 +1083,7 @@ static void PM_PreparePackageList(void) #ifdef PLUGINS { int foundone = false; + char nat[MAX_OSPATH]; FS_NativePath("", FS_BINARYPATH, nat, sizeof(nat)); Con_DPrintf("Loading plugins from \"%s\"\n", nat); Sys_EnumerateFiles(nat, "fteplug_*" ARCH_CPU_POSTFIX ARCH_DL_POSTFIX, PM_EnumeratedPlugin, &foundone, NULL); @@ -1631,7 +1631,7 @@ static void PM_UpdatePackageList(qboolean autoupdate, int retry) doautoupdate |= autoupdate; - //kick off the initial tier of downloads. + //kick off the initial tier of list-downloads. for (i = 0; i < numdownloadablelists; i++) { if (downloadablelist[i].received) @@ -1645,13 +1645,13 @@ static void PM_UpdatePackageList(qboolean autoupdate, int retry) { downloadablelist[i].curdl->user_num = i; - downloadablelist[i].curdl->file = VFSPIPE_Open(); + downloadablelist[i].curdl->file = VFSPIPE_Open(1, false); downloadablelist[i].curdl->isquery = true; DL_CreateThread(downloadablelist[i].curdl, NULL, NULL); } else { - Con_Printf("Could not contact server - %s\n", downloadablelist[i].url); + Con_Printf("Could not contact updates server - %s\n", downloadablelist[i].url); downloadablelist[i].received = -1; } } diff --git a/engine/client/m_mp3.c b/engine/client/m_mp3.c index 5f56d589..1e921db8 100644 --- a/engine/client/m_mp3.c +++ b/engine/client/m_mp3.c @@ -4,7 +4,10 @@ #include "quakedef.h" #ifdef GLQUAKE -#include "glquake.h"//fixme +#include "glquake.h" +#endif +#ifdef VKQUAKE +#include "../vk/vkrenderer.h" #endif #include "shader.h" @@ -134,6 +137,7 @@ cvar_t media_hijackwinamp = CVAR("media_hijackwinamp", "0"); #endif int selectedoption=-1; +static int mouseselectedoption=-1; int numtracks; int nexttrack=-1; mediatrack_t *tracks; @@ -142,6 +146,7 @@ char media_iofilename[MAX_OSPATH]=""; #if !defined(NOMEDIAMENU) && !defined(NOBUILTINMENUS) void Media_LoadTrackNames (char *listname); +void Media_SaveTrackNames (char *listname); int loadedtracknames; #endif qboolean Media_EvaluateNextTrack(void); @@ -177,8 +182,9 @@ float media_fadeouttime; //return value is the new sample to start playing. //*starttime says the time into the track that we should resume playing at //this is on the main thread with the mixer locked, its safe to do stuff, but try not to block -char *Media_NextTrack(int musicchannelnum, float *starttime) +sfx_t *Media_NextTrack(int musicchannelnum, float *starttime) { + sfx_t *s = NULL; if (bgmvolume.value <= 0) return NULL; @@ -208,7 +214,10 @@ char *Media_NextTrack(int musicchannelnum, float *starttime) if (Media_EvaluateNextTrack()) { media_playlistcurrent = MEDIA_PLAYLIST; - return media_currenttrack; + if (*media_currenttrack == '#') + return S_PrecacheSound2(media_currenttrack+1, true); + else + return S_PrecacheSound(media_currenttrack); } } if (!media_playlistcurrent && (media_playlisttypes & MEDIA_CVARLIST)) @@ -231,6 +240,8 @@ char *Media_NextTrack(int musicchannelnum, float *starttime) } else *starttime = 0; + + s = S_PrecacheSound(media_currenttrack); } } } @@ -249,7 +260,7 @@ char *Media_NextTrack(int musicchannelnum, float *starttime) #ifdef HAVE_JUKEBOX media_playlistcurrent = MEDIA_GAMEMUSIC; #endif - return ""; + return NULL; } #endif if (*media_playtrack) @@ -259,9 +270,10 @@ char *Media_NextTrack(int musicchannelnum, float *starttime) Q_strncpyz(media_friendlyname, "", sizeof(media_friendlyname)); media_playlistcurrent = MEDIA_GAMEMUSIC; #endif + s = S_PrecacheSound(media_currenttrack); } } - return media_currenttrack; + return s; } //begin cross-fading @@ -887,10 +899,15 @@ void M_Media_Add_f (void) { char *fname = Cmd_Argv(1); + if (!loadedtracknames) + Media_LoadTrackNames("sound/media.m3u"); + if (Cmd_Argc() == 1) Con_Printf("%s \n", Cmd_Argv(0)); else Media_AddTrack(fname); + + Media_SaveTrackNames("sound/media.m3u"); } void M_Media_Remove_f (void) { @@ -900,6 +917,8 @@ void M_Media_Remove_f (void) Con_Printf("%s \n", Cmd_Argv(0)); else Media_RemoveTrack(fname); + + Media_SaveTrackNames("sound/media.m3u"); } @@ -920,7 +939,12 @@ void M_Media_Draw (menu_t *menu) { mediatrack_t *track; int y; - int op, i; + int op, i, mop; + qboolean playing; + + float time, duration; + char title[256]; + playing = S_GetMusicInfo(0, &time, &duration, title, sizeof(title)); #define MP_Hightlight(x,y,text,hl) (hl?M_PrintWhite(x, y, text):M_Print(x, y, text)) @@ -943,17 +967,36 @@ void M_Media_Draw (menu_t *menu) else { M_Print (12, 32, "Currently playing:"); - M_Print (12, 40, *media_friendlyname?media_friendlyname:media_currenttrack); + if (*title) + { + M_Print (12, 40, title); + M_Print (12, 48, media_currenttrack); + } + else + M_Print (12, 40, *media_friendlyname?media_friendlyname:media_currenttrack); } - y=52; + y=60; op = selectedoption - (vid.height-y)/16; if (op + (vid.height-y)/8>numtracks) op = numtracks - (vid.height-y)/8; if (op < MEDIA_MIN) op = MEDIA_MIN; + mop = (mousecursor_y - y)/8; + mop += MEDIA_MIN; + mouseselectedoption = MEDIA_MIN-1; + if (mousecursor_x < 12 + ((vid.width - 320)>>1) || mousecursor_x > 320-24 + ((vid.width - 320)>>1)) + mop = mouseselectedoption; while(op < 0) { + if (op == mop) + { + float alphamax = 0.5, alphamin = 0.2; + mouseselectedoption = op; + R2D_ImageColours(.5,.4,0,(sin(realtime*2)+1)*0.5*(alphamax-alphamin)+alphamin); + R2D_FillBlock(12 + ((vid.width - 320)>>1), y, 320-24, 8); + R2D_ImageColours(1.0, 1.0, 1.0, 1.0); + } switch(op) { case MEDIA_VOLUME: @@ -966,8 +1009,7 @@ void M_Media_Draw (menu_t *menu) break; case MEDIA_FASTFORWARD: { - float time, duration; - if (S_GetMusicInfo(0, &time, &duration)) + if (playing) { int itime = time; int iduration = duration; @@ -1004,7 +1046,7 @@ void M_Media_Draw (menu_t *menu) y+=8; break; case MEDIA_REPEAT: - if (media_shuffle.value) + if (!media_shuffle.value) { if (media_repeat.value) MP_Hightlight (12, y, "Repeat on", op == selectedoption); @@ -1027,6 +1069,21 @@ void M_Media_Draw (menu_t *menu) for (track = tracks, i=0; track && inext, i++); for (; track; track=track->next, y+=8, op++) { + if (track->length != (int)duration && *title && !strcmp(track->filename, media_currenttrack)) + { + Q_strncpyz(track->nicename, title, sizeof(track->nicename)); + track->length = duration; + Media_SaveTrackNames("sound/media.m3u"); + } + + if (op == mop) + { + float alphamax = 0.5, alphamin = 0.2; + mouseselectedoption = op; + R2D_ImageColours(.5,.4,0,(sin(realtime*2)+1)*0.5*(alphamax-alphamin)+alphamin); + R2D_FillBlock(12 + ((vid.width - 320)>>1), y, 320-24, 8); + R2D_ImageColours(1.0, 1.0, 1.0, 1.0); + } if (op == selectedoption) M_PrintWhite (12, y, track->nicename); else @@ -1146,8 +1203,14 @@ qboolean M_Media_Key (int key, menu_t *menu) } } } - else if (key == K_ENTER || key == K_KP_ENTER) + else if (key == K_ENTER || key == K_KP_ENTER || key == K_MOUSE1) { + if (key == K_MOUSE1) + { + if (mouseselectedoption < MEDIA_MIN) + return false; + selectedoption = mouseselectedoption; + } switch(selectedoption) { case MEDIA_FASTFORWARD: @@ -1260,6 +1323,9 @@ qboolean M_Media_Key (int key, menu_t *menu) void M_Menu_Media_f (void) { menu_t *menu; + if (!loadedtracknames) + Media_LoadTrackNames("sound/media.m3u"); + Key_Dest_Add(kdm_emenu); menu = M_CreateMenu(0); @@ -1272,6 +1338,20 @@ void M_Menu_Media_f (void) +void Media_SaveTrackNames (char *listname) +{ + mediatrack_t *tr; + vfsfile_t *f = FS_OpenVFS(listname, "wb", FS_GAMEONLY); + if (!f) + return; + VFS_PRINTF(f, "#EXTM3U\n"); + for (tr = tracks; tr; tr = tr->next) + { + VFS_PRINTF(f, "#EXTINF:%i,%s\n%s\n", tr->length, tr->nicename, tr->filename); + } + VFS_CLOSE(f); +} + //safeprints only. void Media_LoadTrackNames (char *listname) { @@ -1307,7 +1387,10 @@ void Media_LoadTrackNames (char *listname) if (!trackname) return; - lineend[-1]='\0'; + if (lineend > data && lineend[-1] == '\r') + lineend[-1]='\0'; + else + lineend[0]='\0'; filename = data = lineend+1; @@ -1315,22 +1398,15 @@ void Media_LoadTrackNames (char *listname) if (lineend) { - lineend[-1]='\0'; + if (lineend > data && lineend[-1] == '\r') + lineend[-1]='\0'; + else + lineend[0]='\0'; data = lineend+1; } newtrack = Z_Malloc(sizeof(mediatrack_t)); -#ifndef _WIN32 //crossplatform - lcean up any dos names - if (filename[1] == ':') - { - snprintf(newtrack->filename, sizeof(newtrack->filename)-1, "/mnt/%c/%s", filename[0]-'A'+'a', filename+3); - while((filename = strchr(newtrack->filename, '\\'))) - *filename = '/'; - - } - else -#endif - Q_strncpyz(newtrack->filename, filename, sizeof(newtrack->filename)); + Q_strncpyz(newtrack->filename, filename, sizeof(newtrack->filename)); Q_strncpyz(newtrack->nicename, trackname, sizeof(newtrack->nicename)); newtrack->length = atoi(len); newtrack->next = tracks; @@ -1374,17 +1450,7 @@ void Media_LoadTrackNames (char *listname) - -#undef dwFlags -#undef lpFormat -#undef lpData -#undef cbData -#undef lTime - - ///temporary residence for media handling -#include "roq.h" - #ifdef HAVE_API_VFW #if 0 @@ -1563,7 +1629,7 @@ struct cin_s } image; struct { - roq_info *roqfilm; + struct roq_info_s *roqfilm; // float lastmediatime; float nextframetime; } roq; @@ -2044,6 +2110,7 @@ cin_t *Media_Plugin_TryLoad(char *name) //Quake3 RoQ Support #ifdef Q3CLIENT +#include "roq.h" static void Media_Roq_Shutdown(struct cin_s *cin) { roq_close(cin->roq.roqfilm); @@ -2810,6 +2877,7 @@ struct int pbo_handle; #endif enum uploadfmt format; + int stride; int width; int height; } offscreen_queue[4]; //ringbuffer of offscreen_captureframe...captureframe @@ -2899,13 +2967,13 @@ static void *QDECL capture_raw_begin (char *streamname, int videorate, int width } return ctx; } -static void QDECL capture_raw_video (void *vctx, void *data, int frame, int width, int height, enum uploadfmt fmt) +static void QDECL capture_raw_video (void *vctx, int frame, void *data, int stride, int width, int height, enum uploadfmt fmt) { struct capture_raw_ctx *ctx = vctx; char filename[MAX_OSPATH]; ctx->frames = frame+1; Q_snprintfz(filename, sizeof(filename), "%s%8.8i.%s", ctx->videonameprefix, frame, ctx->videonameextension); - SCR_ScreenShot(filename, ctx->fsroot, &data, 1, width, height, fmt); + SCR_ScreenShot(filename, ctx->fsroot, &data, 1, stride, width, height, fmt); if (capturethrottlesize.ival) { @@ -3128,40 +3196,60 @@ static void *QDECL capture_avi_begin (char *streamname, int videorate, int width return ctx; } -static void QDECL capture_avi_video(void *vctx, void *vdata, int frame, int width, int height, enum uploadfmt fmt) +static void QDECL capture_avi_video(void *vctx, int frame, void *vdata, int stride, int width, int height, enum uploadfmt fmt) { + //vfw api is bottom up. struct capture_avi_ctx *ctx = vctx; - qbyte *data = vdata; - int c, i; - qbyte temp; + qbyte *data, *in, *out; + int x, y; - if (fmt == TF_BGRA32) + //we need to output a packed bottom-up bgr image. + + //switch the input from logically top-down to bottom-up (regardless of the physical ordering of its rows) + in = (qbyte*)vdata + stride*(height-1); + stride = -stride; + + if (fmt == TF_BGR24 && stride == width*3) + { //no work needed! + data = in; + } + else { - // truncate bgra to bgr - c = width*height; - for (i=0 ; i 0; out += width*3, in += stride) + { + for (x = 0; x < width; x++) + { + out[x*3+0] = in[x*ipx+2]; + out[x*3+1] = in[x*ipx+1]; + out[x*3+2] = in[x*ipx+0]; + } + } + } + else if (fmt == TF_BGR24 || fmt == TF_BGRX32 || fmt == TF_BGRA32) + { //just strip alpha (or just flip) + for (y = height; y --> 0; out += width*3, in += stride) + { + for (x = 0; x < width; x++) + { + out[x*3+0] = in[x*ipx+0]; + out[x*3+1] = in[x*ipx+1]; + out[x*3+2] = in[x*ipx+2]; + } + } + } + else + { //probably spammy, but oh well + Con_Printf("capture_avi_video: Unsupported image format\n"); + return; } } - else if (fmt == TF_RGB24) - { - // swap rgb to bgr - c = width*height*3; - for (i=0 ; istarttime; + Con_Printf("%d video frames ignored, %g secs, %gfps\n", ctx->frames, duration, ctx->frames/duration); + Z_Free(ctx); } static void *QDECL capture_null_begin (char *streamname, int videorate, int width, int height, int *sndkhz, int *sndchannels, int *sndbits) { - return (void*)~0; + struct capture_null_context *ctx = Z_Malloc(sizeof(*ctx)); + *sndkhz = 11025; + *sndchannels = 2; + *sndbits = 32; //floats! + ctx->starttime = Sys_DoubleTime(); + return ctx; } -static void QDECL capture_null_video(void *vctx, void *vdata, int frame, int width, int height, enum uploadfmt fmt) +static void QDECL capture_null_video(void *vctx, int frame, void *vdata, int stride, int width, int height, enum uploadfmt fmt) { + struct capture_null_context *ctx = vctx; + ctx->frames = frame+1; } static void QDECL capture_null_audio(void *vctx, void *data, int bytes) { @@ -3301,10 +3405,19 @@ double Media_TweekCaptureFrameTime(double oldtime, double time) return oldtime + time; } +#ifdef VKQUAKE +static void Media_CapturedFrame (void *data, int bytestride, size_t width, size_t height, enum uploadfmt fmt) +{ + if (currentcapture_funcs) + currentcapture_funcs->capture_video(currentcapture_ctx, offscreen_captureframe, data, bytestride, width, height, fmt); + offscreen_captureframe++; +} +#endif + void Media_RecordFrame (void) { char *buffer; - int truewidth, trueheight; + int bytestride, truewidth, trueheight; enum uploadfmt fmt; if (!currentcapture_funcs) @@ -3413,8 +3526,9 @@ void Media_RecordFrame (void) buffer = qglMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB); if (buffer) { + qbyte *firstrow = (offscreen_queue[frame].stride<0)?buffer - offscreen_queue[frame].stride*(offscreen_queue[frame].height-1):buffer; //FIXME: thread these (with audio too, to avoid races) - currentcapture_funcs->capture_video(currentcapture_ctx, buffer, offscreen_captureframe, offscreen_queue[frame].width, offscreen_queue[frame].height, offscreen_queue[frame].format); + currentcapture_funcs->capture_video(currentcapture_ctx, offscreen_captureframe, firstrow, offscreen_queue[frame].stride, offscreen_queue[frame].width, offscreen_queue[frame].height, offscreen_queue[frame].format); qglUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB); } offscreen_captureframe++; @@ -3442,6 +3556,8 @@ void Media_RecordFrame (void) break; } + offscreen_queue[frame].stride = vid.pixelwidth*-imagesize;//gl is upside down + imagesize *= offscreen_queue[frame].width * offscreen_queue[frame].height; qglGenBuffersARB(1, &offscreen_queue[frame].pbo_handle); @@ -3472,49 +3588,25 @@ void Media_RecordFrame (void) } else #endif -#if 0//def VKQUAKE - if (offscreen_format != TF_INVALID && qrenderer == QR_VULKAN) - { - //try and collect any finished frames - while (offscreen_captureframe + countof(offscreen_queue) <= captureframe) - { - frame = offscreen_captureframe%countof(offscreen_queue); - vkFenceWait(); - buffer = NULL;//qglMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB); - if (buffer) - { - //FIXME: thread these (with audio too, to avoid races) - currentcapture_funcs->capture_video(currentcapture_ctx, buffer, offscreen_captureframe, offscreen_queue[frame].width, offscreen_queue[frame].height, offscreen_queue[frame].format); - //qglUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB); - } - offscreen_captureframe++; - } - - frame = captureframe%countof(offscreen_queue); - if (no frame yet) - { - create a buffer - map the buffer persistently - } - - vkCopyImageToBuffer - vkSubmitFence - } +#ifdef VKQUAKE + if (qrenderer == QR_VULKAN) + VKVID_QueueGetRGBData(Media_CapturedFrame); else #endif { offscreen_captureframe = captureframe+1; //submit the current video frame. audio will be mixed to match. - buffer = VID_GetRGBInfo(&truewidth, &trueheight, &fmt); + buffer = VID_GetRGBInfo(&bytestride, &truewidth, &trueheight, &fmt); if (buffer) { - currentcapture_funcs->capture_video(currentcapture_ctx, buffer, captureframe, truewidth, trueheight, fmt); + qbyte *firstrow = (bytestride<0)?buffer - bytestride*(trueheight-1):buffer; + currentcapture_funcs->capture_video(currentcapture_ctx, captureframe, firstrow, bytestride, truewidth, trueheight, fmt); BZ_Free (buffer); } else { Con_DPrintf("Unable to grab video image\n"); - currentcapture_funcs->capture_video(currentcapture_ctx, NULL, captureframe, 0, 0, TF_INVALID); + currentcapture_funcs->capture_video(currentcapture_ctx, captureframe, NULL, 0, 0, 0, TF_INVALID); } } captureframe++; @@ -3709,7 +3801,8 @@ void Media_StopRecordFilm_f (void) buffer = qglMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB); if (buffer) { - currentcapture_funcs->capture_video(currentcapture_ctx, buffer, offscreen_captureframe, offscreen_queue[frame].width, offscreen_queue[frame].height, offscreen_queue[frame].format); + qbyte *firstrow = (offscreen_queue[frame].stride<0)?buffer - offscreen_queue[frame].stride*(offscreen_queue[frame].height-1):buffer; + currentcapture_funcs->capture_video(currentcapture_ctx, offscreen_captureframe, firstrow, offscreen_queue[frame].stride, offscreen_queue[frame].width, offscreen_queue[frame].height, offscreen_queue[frame].format); qglUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB); } offscreen_captureframe++; @@ -3756,7 +3849,7 @@ void Media_StopRecordFilm_f (void) capture_fakesounddevice = NULL; if (recordingdemo) //start up their regular audio devices again. - Cmd_ExecuteString("snd_restart", RESTRICT_LOCAL); + S_DoRestart(false); recordingdemo=false; @@ -3828,21 +3921,21 @@ static void Media_RecordFilm (char *recordingname, qboolean demo) sndbits = 0; } + vid.fbpwidth = vid.pixelwidth; + vid.fbpheight = vid.pixelheight; + if (demo && capturewidth.ival && captureheight.ival) + { #ifdef GLQUAKE - if (demo && capturewidth.ival && captureheight.ival && qrenderer == QR_OPENGL && gl_config.ext_framebuffer_objects) - { - capturingfbo = true; - capturetexture = R2D_RT_Configure("$democapture", capturewidth.ival, captureheight.ival, TF_BGRA32, RT_IMAGEFLAGS); - captureoldfbo = GLBE_FBO_Update(&capturefbo, FBO_RB_DEPTH|(Sh_StencilShadowsActive()?FBO_RB_STENCIL:0), &capturetexture, 1, r_nulltex, capturewidth.ival, captureheight.ival, 0); - vid.fbpwidth = capturewidth.ival; - vid.fbpheight = captureheight.ival; - vid.framebuffer = capturetexture; - } - else + if (qrenderer == QR_OPENGL && gl_config.ext_framebuffer_objects) + { + capturingfbo = true; + capturetexture = R2D_RT_Configure("$democapture", capturewidth.ival, captureheight.ival, TF_BGRA32, RT_IMAGEFLAGS); + captureoldfbo = GLBE_FBO_Update(&capturefbo, FBO_RB_DEPTH|(Sh_StencilShadowsActive()?FBO_RB_STENCIL:0), &capturetexture, 1, r_nulltex, capturewidth.ival, captureheight.ival, 0); + vid.fbpwidth = capturewidth.ival; + vid.fbpheight = captureheight.ival; + vid.framebuffer = capturetexture; + } #endif - { - vid.fbpwidth = vid.pixelwidth; - vid.fbpheight = vid.pixelheight; } offscreen_format = TF_INVALID; @@ -3897,11 +3990,25 @@ static void Media_RecordFilm_f (void) if (Cmd_Argc() != 2) { int i; - Con_Printf("capture \nRecords video output in an avi file.\nUse capturerate and capturecodec to configure.\n"); + Con_Printf("capture \nRecords video output in an avi file.\nUse capturerate and capturecodec to configure.\n\n"); for (i = 0; i < countof(pluginencodersfunc); i++) { if (pluginencodersfunc[i]) - Con_Printf("%s: %s\n", pluginencodersfunc[i]->drivername, pluginencodersfunc[i]->description); + Con_Printf("%s%s^7: %s\n", !strcmp(pluginencodersfunc[i]->drivername, capturedriver.string)?"^2":"^3", pluginencodersfunc[i]->drivername, pluginencodersfunc[i]->description); + } + Con_Printf("\n"); + + Con_Printf("Current capture settings:\n"); + Con_Printf(" ^[/capturedriver %s^]\n", capturedriver.string); + Con_Printf(" ^[/capturecodec %s^]\n", capturecodec.string); + Con_Printf(" ^[/capturedemowidth %s^]\n", capturewidth.string); + Con_Printf(" ^[/capturedemoheight %s^]\n", captureheight.string); + Con_Printf(" ^[/capturerate %s^]\n", capturerate.string); + Con_Printf(" ^[/capturesound %s^]\n", capturesound.string); + if (capturesound.value) + { + Con_Printf(" ^[/capturesoundchannels %s^]\n", capturesoundchannels.string); + Con_Printf(" ^[/capturesoundbits %s^]\n", capturesoundbits.string); } return; } @@ -4685,9 +4792,11 @@ typedef struct unsigned int srcoffset; /*in bytes*/ unsigned int srclen; /*in bytes*/ qbyte srcdata[1]; + + char title[256]; } mp3decoder_t; -static void S_MP3_Purge(sfx_t *sfx) +static void QDECL S_MP3_Purge(sfx_t *sfx) { mp3decoder_t *dec = sfx->decoder.buf; @@ -4705,17 +4814,50 @@ static void S_MP3_Purge(sfx_t *sfx) sfx->loadstate = SLS_NOTLOADED; } -float S_MP3_Query(sfx_t *sfx, sfxcache_t *buf) +float QDECL S_MP3_Query(sfx_t *sfx, sfxcache_t *buf, char *title, size_t titlesize) { + mp3decoder_t *dec = sfx->decoder.buf; //we don't know unless we decode it all if (buf) { } + if (titlesize && dec->srclen >= 128) + { //id3v1 is a 128 byte blob at the end of the file. + char trimartist[31]; + char trimtitle[31]; + char *p; + struct + { + char tag[3]; //TAG + char title[30]; + char artist[30]; + char album[30]; + char year[4]; + char comment[30];//[28]+null+track + qbyte genre; + } *id3v1 = (void*)(dec->srcdata + dec->srclen-128); + if (id3v1->tag[0] == 'T' && id3v1->tag[1] == 'A' && id3v1->tag[2] == 'G') + { //yup, there's an id3v1 tag there + memcpy(trimartist, id3v1->artist, 30); + for(p = trimartist+30; p>trimartist && p[-1] == ' '; ) + p--; + *p = 0; + memcpy(trimtitle, id3v1->title, 30); + for(p = trimtitle+30; p>trimtitle && p[-1] == ' '; ) + p--; + *p = 0; + if (*trimartist && *trimtitle) + Q_snprintfz(title, titlesize, "%.30s - %.30s", trimartist, trimtitle); + else if (*id3v1->title) + Q_snprintfz(title, titlesize, "%.30s", trimtitle); + } + return 1;//no real idea. + } return 0; } /*must be thread safe*/ -sfxcache_t *S_MP3_Locate(sfx_t *sfx, sfxcache_t *buf, ssamplepos_t start, int length) +sfxcache_t *QDECL S_MP3_Locate(sfx_t *sfx, sfxcache_t *buf, ssamplepos_t start, int length) { int newlen; if (buf) @@ -4758,6 +4900,8 @@ sfxcache_t *S_MP3_Locate(sfx_t *sfx, sfxcache_t *buf, ssamplepos_t start, int le strhdr.cbStruct = sizeof(strhdr); strhdr.pbSrc = dec->srcdata + dec->srcoffset; strhdr.cbSrcLength = dec->srclen - dec->srcoffset; + if (!strhdr.cbSrcLength) + break; strhdr.pbDst = buffer; strhdr.cbDstLength = sizeof(buffer); @@ -4794,11 +4938,13 @@ sfxcache_t *S_MP3_Locate(sfx_t *sfx, sfxcache_t *buf, ssamplepos_t start, int le buf->data = dec->dstdata; buf->length = dec->dstcount; - buf->loopstart = -1; buf->numchannels = dec->srcchannels; buf->soundoffset = dec->dststart; buf->speed = snd_speed; buf->width = dec->srcwidth/8; + + if (dec->srclen == dec->srcoffset && start >= dec->dststart+dec->dstcount) + return NULL; //once we reach the EOF, start reporting errors. } return buf; } @@ -4821,7 +4967,7 @@ typedef struct #define MPEGLAYER3_ID_MPEG 1 #endif -qboolean S_LoadMP3Sound (sfx_t *s, qbyte *data, int datalen, int sndspeed) +qboolean QDECL S_LoadMP3Sound (sfx_t *s, qbyte *data, size_t datalen, int sndspeed) { WAVEFORMATEX pcm_format; MPEGLAYER3WAVEFORMAT mp3format; @@ -4843,6 +4989,7 @@ qboolean S_LoadMP3Sound (sfx_t *s, qbyte *data, int datalen, int sndspeed) s->decoder.purge = S_MP3_Purge; s->decoder.decodedata = S_MP3_Locate; s->decoder.querydata = S_MP3_Query; + s->loopstart = -1; dec->dstdata = NULL; dec->dstcount = 0; @@ -4883,6 +5030,8 @@ qboolean S_LoadMP3Sound (sfx_t *s, qbyte *data, int datalen, int sndspeed) } S_MP3_Locate(s, NULL, 0, 100); + + return true; } #endif diff --git a/engine/client/m_options.c b/engine/client/m_options.c index 7e513100..38acc2a5 100644 --- a/engine/client/m_options.c +++ b/engine/client/m_options.c @@ -850,7 +850,7 @@ const char *presetexec[] = "r_nolerp 0;" , // nice options - "r_stains 0.75;" +// "r_stains 0.75;" "gl_texturemode ll;" #ifndef MINIMAL // "r_particlesystem script;" @@ -2193,48 +2193,48 @@ void M_Menu_Singleplayer_Cheats_Hexen2 (void) else currentmap = 0; - MC_AddRedText(menu, 16, 170, y, "Hexen2 Singleplayer Cheats", false); y+=8; - MC_AddWhiteText(menu, 16, 170, y, "^Ue080^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue082 ", false); y+=8; - y+=8; - #ifndef CLIENTONLY - info->skillcombo = MC_AddCombo(menu,16,170, y, "Difficulty", skilloptions, currentskill); y+=8; - #endif - info->mapcombo = MC_AddCombo(menu,16,170, y, "Map", mapoptions, currentmap); y+=8; - #ifndef CLIENTONLY - MC_AddCheckBox(menu, 16, 170, y, "Cheats", &sv_cheats,0); y+=8; - #endif - MC_AddConsoleCommand(menu, 16, 170, y, "Toggle Godmode", "god\n"); y+=8; - MC_AddConsoleCommand(menu, 16, 170, y, "Toggle Flymode", "fly\n"); y+=8; - MC_AddConsoleCommand(menu, 16, 170, y, "Toggle Noclip", "noclip\n"); y+=8; - #ifndef CLIENTONLY - MC_AddSlider(menu, 16, 170, y, "Gravity", &sv_gravity,0,800,25); y+=8; - #endif - MC_AddSlider(menu, 16, 170, y, "Forward Speed", &cl_forwardspeed,0,1000,50); y+=8; - MC_AddSlider(menu, 16, 170, y, "Side Speed", &cl_sidespeed,0,1000,50); y+=8; - MC_AddSlider(menu, 16, 170, y, "Back Speed", &cl_backspeed,0,1000,50); y+=8; - #ifndef CLIENTONLY - MC_AddSlider(menu, 16, 170, y, "Max Movement Speed", &sv_maxspeed,0,1000,50); y+=8; - #endif - MC_AddConsoleCommand(menu, 16, 170, y, "Sheep Transformation", "impulse 14\n"); y+=8; - MC_AddConsoleCommand(menu, 16, 170, y, "Change To Paladin (lvl3+)", "impulse 171\n"); y+=8; - MC_AddConsoleCommand(menu, 16, 170, y, "Change To Crusader (lvl3+)", "impulse 172\n"); y+=8; - MC_AddConsoleCommand(menu, 16, 170, y, "Change to Necromancer (lvl3+)", "impulse 173\n"); y+=8; - MC_AddConsoleCommand(menu, 16, 170, y, "Change to Assassin (lvl3+)", "impulse 174\n"); y+=8; - MC_AddConsoleCommand(menu, 16, 170, y, "Remove Monsters", "impulse 35\n"); y+=8; - MC_AddConsoleCommand(menu, 16, 170, y, "Freeze Monsters", "impulse 36\n"); y+=8; - MC_AddConsoleCommand(menu, 16, 170, y, "Unfreeze Monsters", "impulse 37\n"); y+=8; - MC_AddConsoleCommand(menu, 16, 170, y, "Increase Level By 1", "impulse 40\n"); y+=8; - MC_AddConsoleCommand(menu, 16, 170, y, "Increase Experience", "impulse 41\n"); y+=8; - MC_AddConsoleCommand(menu, 16, 170, y, "Display Co-ordinates", "impulse 42\n"); y+=8; - MC_AddConsoleCommand(menu, 16, 170, y, "All Weapons & Mana", "impulse 9\n"); y+=8; - MC_AddConsoleCommand(menu, 16, 170, y, "All Weapons & Mana & Items", "impulse 43\n"); y+=8; - MC_AddConsoleCommand(menu, 16, 170, y, "No Enemy Targetting", "notarget\n"); y+=8; - MC_AddConsoleCommand(menu, 16, 170, y, "Enable Crosshair", "crosshair 1\n"); y+=8; - MC_AddConsoleCommand(menu, 16, 170, y, "20 Of Each Artifact", "impulse 299\n"); y+=8; - MC_AddConsoleCommand(menu, 16, 170, y, "Restart Map", "impulse 99\n"); y+=8; + MC_AddRedText(menu, 16, 170, y, "Hexen2 Singleplayer Cheats", false); y+=8; + MC_AddWhiteText(menu, 16, 170, y, "^Ue080^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue081^Ue082 ", false); y+=8; + y+=8; + #ifndef CLIENTONLY + info->skillcombo = MC_AddCombo(menu,16,170, y, "Difficulty", skilloptions, currentskill); y+=8; + #endif + info->mapcombo = MC_AddCombo(menu,16,170, y, "Map", mapoptions, currentmap); y+=8; + #ifndef CLIENTONLY + MC_AddCheckBox(menu, 16, 170, y, "Cheats", &sv_cheats,0); y+=8; + #endif + MC_AddConsoleCommand(menu, 16, 170, y, "Toggle Godmode", "god\n"); y+=8; + MC_AddConsoleCommand(menu, 16, 170, y, "Toggle Flymode", "fly\n"); y+=8; + MC_AddConsoleCommand(menu, 16, 170, y, "Toggle Noclip", "noclip\n"); y+=8; + #ifndef CLIENTONLY + MC_AddSlider(menu, 16, 170, y, "Gravity", &sv_gravity,0,800,25); y+=8; + #endif + MC_AddSlider(menu, 16, 170, y, "Forward Speed", &cl_forwardspeed,0,1000,50); y+=8; + MC_AddSlider(menu, 16, 170, y, "Side Speed", &cl_sidespeed,0,1000,50); y+=8; + MC_AddSlider(menu, 16, 170, y, "Back Speed", &cl_backspeed,0,1000,50); y+=8; + #ifndef CLIENTONLY + MC_AddSlider(menu, 16, 170, y, "Max Movement Speed", &sv_maxspeed,0,1000,50); y+=8; + #endif + MC_AddConsoleCommand(menu, 16, 170, y, "Sheep Transformation", "impulse 14\n"); y+=8; + MC_AddConsoleCommand(menu, 16, 170, y, "Change To Paladin (lvl3+)", "impulse 171\n"); y+=8; + MC_AddConsoleCommand(menu, 16, 170, y, "Change To Crusader (lvl3+)", "impulse 172\n"); y+=8; + MC_AddConsoleCommand(menu, 16, 170, y, "Change to Necromancer (lvl3+)", "impulse 173\n"); y+=8; + MC_AddConsoleCommand(menu, 16, 170, y, "Change to Assassin (lvl3+)", "impulse 174\n"); y+=8; + MC_AddConsoleCommand(menu, 16, 170, y, "Remove Monsters", "impulse 35\n"); y+=8; + MC_AddConsoleCommand(menu, 16, 170, y, "Freeze Monsters", "impulse 36\n"); y+=8; + MC_AddConsoleCommand(menu, 16, 170, y, "Unfreeze Monsters", "impulse 37\n"); y+=8; + MC_AddConsoleCommand(menu, 16, 170, y, "Increase Level By 1", "impulse 40\n"); y+=8; + MC_AddConsoleCommand(menu, 16, 170, y, "Increase Experience", "impulse 41\n"); y+=8; + MC_AddConsoleCommand(menu, 16, 170, y, "Display Co-ordinates", "impulse 42\n"); y+=8; + MC_AddConsoleCommand(menu, 16, 170, y, "All Weapons & Mana", "impulse 9\n"); y+=8; + MC_AddConsoleCommand(menu, 16, 170, y, "All Weapons & Mana & Items", "impulse 43\n"); y+=8; + MC_AddConsoleCommand(menu, 16, 170, y, "No Enemy Targetting", "notarget\n"); y+=8; + MC_AddConsoleCommand(menu, 16, 170, y, "Enable Crosshair", "crosshair 1\n"); y+=8; + MC_AddConsoleCommand(menu, 16, 170, y, "20 Of Each Artifact", "impulse 299\n"); y+=8; + MC_AddConsoleCommand(menu, 16, 170, y, "Restart Map", "impulse 99\n"); y+=8; - y+=8; - MC_AddCommand(menu, 16, 170, y, "Apply Changes", M_Apply_SP_Cheats_H2); y+=8; + y+=8; + MC_AddCommand(menu, 16, 170, y, "Apply Changes", M_Apply_SP_Cheats_H2); y+=8; menu->selecteditem = (union menuoption_s *)info->skillcombo; menu->cursoritem = (menuoption_t*)MC_AddWhiteText(menu, 250, 0, cursorpositionY, NULL, false); diff --git a/engine/client/m_script.c b/engine/client/m_script.c index 4fbc6809..a4308a49 100644 --- a/engine/client/m_script.c +++ b/engine/client/m_script.c @@ -436,19 +436,19 @@ menutext 0 24 "Cancel" */ void M_Script_Init(void) { - Cmd_AddCommandD("menuclear", M_MenuS_Clear_f, "Pop the currently scripted menu."); - Cmd_AddCommandD("menucallback", M_MenuS_Callback_f, "Explicitly invoke the active script menu's callback function with the given option set."); - Cmd_AddCommand("conmenu", M_MenuS_Script_f); - Cmd_AddCommand("menubox", M_MenuS_Box_f); - Cmd_AddCommand("menuedit", M_MenuS_Edit_f); - Cmd_AddCommand("menueditpriv", M_MenuS_EditPriv_f); - Cmd_AddCommand("menutext", M_MenuS_Text_f); - Cmd_AddCommand("menutextbig", M_MenuS_TextBig_f); - Cmd_AddCommand("menupic", M_MenuS_Picture_f); - Cmd_AddCommand("menucheck", M_MenuS_CheckBox_f); - Cmd_AddCommand("menuslider", M_MenuS_Slider_f); - Cmd_AddCommand("menubind", M_MenuS_Bind_f); - Cmd_AddCommand("menucomboi", M_MenuS_Comboi_f); - Cmd_AddCommand("menucombos", M_MenuS_Combos_f); + Cmd_AddCommandD("menuclear", M_MenuS_Clear_f, "Pop the currently scripted menu."); + Cmd_AddCommandD("menucallback", M_MenuS_Callback_f, "Explicitly invoke the active script menu's callback function with the given option set."); + Cmd_AddCommandD("conmenu", M_MenuS_Script_f, "conmenu \nCreates a new (built-in) scripted menu. any following commands that define scipted menu items will add their items to this new menu. The callback will be called with argument 'cancel' when the menu is closed."); + Cmd_AddCommandD("menubox", M_MenuS_Box_f, "x y width height"); + Cmd_AddCommandD("menuedit", M_MenuS_Edit_f, "x y caption cvarname"); + Cmd_AddCommandD("menueditpriv", M_MenuS_EditPriv_f, "x y caption def"); + Cmd_AddCommandD("menutext", M_MenuS_Text_f, "x y caption cbcommand"); + Cmd_AddCommandD("menutextbig", M_MenuS_TextBig_f, "x y caption cbcommand"); + Cmd_AddCommandD("menupic", M_MenuS_Picture_f, "x y picname"); + Cmd_AddCommandD("menucheck", M_MenuS_CheckBox_f, "x y caption cvarname bitmask"); + Cmd_AddCommandD("menuslider", M_MenuS_Slider_f, "x y caption cvarname min max"); + Cmd_AddCommandD("menubind", M_MenuS_Bind_f, "x y caption bindcommand"); + Cmd_AddCommandD("menucomboi", M_MenuS_Comboi_f, "x y caption cvarname [caption0] [caption1] ..."); + Cmd_AddCommandD("menucombos", M_MenuS_Combos_f, "x y caption cvarname [caption0] [value0] [caption1] [value1] ...\nif 'caption0' is { then the options will be parsed from trailing lines\n"); } #endif diff --git a/engine/client/m_single.c b/engine/client/m_single.c index e63c2ef5..7211e995 100644 --- a/engine/client/m_single.c +++ b/engine/client/m_single.c @@ -1051,9 +1051,9 @@ void M_Menu_MediaFiles_f (void) info->numext = 0; #ifdef HAVE_JUKEBOX - info->ext[info->numext] = ".m3u"; - info->command[info->numext] = "mediaplaylist"; - info->numext++; +// info->ext[info->numext] = ".m3u"; +// info->command[info->numext] = "mediaplaylist"; +// info->numext++; #if defined(AVAIL_MP3_ACM) || defined(FTE_TARGET_WEB) info->ext[info->numext] = ".mp3"; info->command[info->numext] = "media_add"; diff --git a/engine/client/merged.h b/engine/client/merged.h index ddd2c4a3..5d0fa6a6 100644 --- a/engine/client/merged.h +++ b/engine/client/merged.h @@ -119,7 +119,7 @@ extern void (*R_RenderView) (void); // must set r_refdef first extern qboolean (*VID_Init) (rendererstate_t *info, unsigned char *palette); extern void (*VID_DeInit) (void); -extern char *(*VID_GetRGBInfo) (int *truevidwidth, int *truevidheight, enum uploadfmt *fmt); +extern char *(*VID_GetRGBInfo) (int *stride, int *truevidwidth, int *truevidheight, enum uploadfmt *fmt); //if stride is negative, then the return value points to the last line intead of the first. this allows it to be freed normally. extern void (*VID_SetWindowCaption) (const char *msg); extern void SCR_Init (void); @@ -425,7 +425,7 @@ typedef struct rendererinfo_s { void (*VID_DestroyCursor) (void *cursor); //may be null void (*VID_SetWindowCaption) (const char *msg); - char *(*VID_GetRGBInfo) (int *truevidwidth, int *truevidheight, enum uploadfmt *fmt); + char *(*VID_GetRGBInfo) (int *bytestride, int *truevidwidth, int *truevidheight, enum uploadfmt *fmt); qboolean (*SCR_UpdateScreen) (void); diff --git a/engine/client/net_master.c b/engine/client/net_master.c index 748a59a3..c54cf0d2 100644 --- a/engine/client/net_master.c +++ b/engine/client/net_master.c @@ -306,7 +306,7 @@ void SV_Master_Worker_Resolved(void *ctx, void *data, size_t a, size_t b) if (sv.state) { //tcp masters require a route - if (na->type == NA_TCP || na->type == NA_TCPV6 || na->type == NA_TLSV4 || na->type == NA_TLSV6) + if (NET_AddrIsReliable(na)) NET_EnsureRoute(svs.sockets, master->cv.name, master->cv.string, false); //q2+qw masters are given a ping to verify that they're still up @@ -636,6 +636,7 @@ int lastpollsockIPX; #define FIRSTUDP6SOCKET (FIRSTUDP4SOCKET+POLLUDP4SOCKETS) #define POLLTOTALSOCKETS (FIRSTUDP6SOCKET+POLLUDP6SOCKETS) SOCKET pollsocketsList[POLLTOTALSOCKETS]; +char pollsocketsBCast[POLLTOTALSOCKETS]; void Master_SetupSockets(void) { @@ -1366,11 +1367,19 @@ void CLMaster_AddMaster_Worker_Resolved(void *ctx, void *data, size_t a, size_t if (mast->mastertype == MT_BCAST) //broadcasts { if (mast->adr.type == NA_IP) - mast->adr.type = NA_BROADCAST_IP; + memset(mast->adr.address.ip+4, 0xff, sizeof(mast->adr.address.ip)); if (mast->adr.type == NA_IPX) - mast->adr.type = NA_BROADCAST_IPX; + { + memset(mast->adr.address.ipx+0, 0, 4); + memset(mast->adr.address.ipx+4, 0xff, 6); + } if (mast->adr.type == NA_IPV6) - mast->adr.type = NA_BROADCAST_IP6; + { + memset(mast->adr.address.ip6, 0, sizeof(mast->adr.address.ip6)); + mast->adr.address.ip6[0] = 0xff; + mast->adr.address.ip6[1] = 0x02; + mast->adr.address.ip6[15] = 0x01; + } } //fix up default ports if not specified @@ -1694,6 +1703,7 @@ qboolean Master_LoadMasterList (char *filename, qboolean withcomment, int defaul void NET_SendPollPacket(int len, void *data, netadr_t to) { + unsigned long bcast; int ret; struct sockaddr_qstorage addr; @@ -1705,39 +1715,66 @@ void NET_SendPollPacket(int len, void *data, netadr_t to) if (lastpollsockIPX>=POLLIPXSOCKETS) lastpollsockIPX=0; if (pollsocketsList[FIRSTIPXSOCKET+lastpollsockIPX]==INVALID_SOCKET) - pollsocketsList[FIRSTIPXSOCKET+lastpollsockIPX] = IPX_OpenSocket(PORT_ANY, true); + { + pollsocketsList[FIRSTIPXSOCKET+lastpollsockIPX] = IPX_OpenSocket(PORT_ANY); + pollsocketsBCast[FIRSTIPXSOCKET+lastpollsockIPX] = false; + } if (pollsocketsList[FIRSTIPXSOCKET+lastpollsockIPX]==INVALID_SOCKET) return; //bother + + bcast = !memcmp(to.address.ipx, "\0\0\0\0\xff\xff\xff\xff\xff\xff", sizeof(to.address.ipx)); + if (pollsocketsBCast[FIRSTIPXSOCKET+lastpollsockIPX] != bcast) + { + if (setsockopt(pollsocketsList[FIRSTIPXSOCKET+lastpollsockIPX], SOL_SOCKET, SO_BROADCAST, (char *)&bcast, sizeof(bcast)) == -1) + return; + pollsocketsBCast[FIRSTIPXSOCKET+lastpollsockIPX] = bcast; + } ret = sendto (pollsocketsList[FIRSTIPXSOCKET+lastpollsockIPX], data, len, 0, (struct sockaddr *)&addr, sizeof(addr) ); } else #endif -#ifdef IPPROTO_IPV6 +#ifdef HAVE_IPV6 if (((struct sockaddr*)&addr)->sa_family == AF_INET6) { lastpollsockUDP6++; if (lastpollsockUDP6>=POLLUDP6SOCKETS) lastpollsockUDP6=0; if (pollsocketsList[FIRSTUDP6SOCKET+lastpollsockUDP6]==INVALID_SOCKET) - pollsocketsList[FIRSTUDP6SOCKET+lastpollsockUDP6] = UDP6_OpenSocket(PORT_ANY, true); + { + pollsocketsList[FIRSTUDP6SOCKET+lastpollsockUDP6] = UDP6_OpenSocket(PORT_ANY); + pollsocketsBCast[FIRSTUDP6SOCKET+lastpollsockUDP6] = false; + } if (pollsocketsList[FIRSTUDP6SOCKET+lastpollsockUDP6]==INVALID_SOCKET) return; //bother ret = sendto (pollsocketsList[FIRSTUDP6SOCKET+lastpollsockUDP6], data, len, 0, (struct sockaddr *)&addr, sizeof(addr) ); } else #endif +#ifdef HAVE_IPV4 if (((struct sockaddr*)&addr)->sa_family == AF_INET) { lastpollsockUDP4++; if (lastpollsockUDP4>=POLLUDP4SOCKETS) lastpollsockUDP4=0; if (pollsocketsList[FIRSTUDP4SOCKET+lastpollsockUDP4]==INVALID_SOCKET) - pollsocketsList[FIRSTUDP4SOCKET+lastpollsockUDP4] = UDP_OpenSocket(PORT_ANY, true); + { + pollsocketsList[FIRSTUDP4SOCKET+lastpollsockUDP4] = UDP_OpenSocket(PORT_ANY); + pollsocketsBCast[FIRSTUDP4SOCKET+lastpollsockUDP4] = false; + } if (pollsocketsList[FIRSTUDP4SOCKET+lastpollsockUDP4]==INVALID_SOCKET) return; //bother + + bcast = !memcmp(to.address.ip, "\xff\xff\xff\xff", sizeof(to.address.ip)); + if (pollsocketsBCast[FIRSTUDP4SOCKET+lastpollsockUDP4] != bcast) + { + if (setsockopt(pollsocketsList[FIRSTUDP4SOCKET+lastpollsockUDP4], SOL_SOCKET, SO_BROADCAST, (char *)&bcast, sizeof(bcast)) == -1) + return; + pollsocketsBCast[FIRSTUDP4SOCKET+lastpollsockUDP4] = bcast; + } ret = sendto (pollsocketsList[FIRSTUDP4SOCKET+lastpollsockUDP4], data, len, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in) ); } else +#endif return; if (ret == -1) diff --git a/engine/client/pr_clcmd.c b/engine/client/pr_clcmd.c index cf057bf9..0d3e4b96 100644 --- a/engine/client/pr_clcmd.c +++ b/engine/client/pr_clcmd.c @@ -603,7 +603,7 @@ void QCBUILTIN PF_soundlength (pubprogfuncs_t *prinst, struct globalvars_s *pr_g sfxcache_t cachebuf, *cache; if (sfx->decoder.querydata) { - G_FLOAT(OFS_RETURN) = sfx->decoder.querydata(sfx, NULL); + G_FLOAT(OFS_RETURN) = sfx->decoder.querydata(sfx, NULL, NULL, 0); return; } else if (sfx->decoder.decodedata) diff --git a/engine/client/pr_csqc.c b/engine/client/pr_csqc.c index a55acbc3..854078a6 100644 --- a/engine/client/pr_csqc.c +++ b/engine/client/pr_csqc.c @@ -5624,7 +5624,7 @@ static void QCBUILTIN PF_resourcestatus(pubprogfuncs_t *prinst, struct globalvar } } if (!sfx) - sfx = S_FindName(resname, doload); + sfx = S_FindName(resname, doload, false); if (!sfx) G_FLOAT(OFS_RETURN) = RESSTATE_NOTKNOWN; else diff --git a/engine/client/renderer.c b/engine/client/renderer.c index 5800e1c6..a54190f2 100644 --- a/engine/client/renderer.c +++ b/engine/client/renderer.c @@ -169,7 +169,7 @@ qboolean r_softwarebanding; cvar_t r_speeds = CVAR ("r_speeds", "0"); cvar_t r_stainfadeammount = CVAR ("r_stainfadeammount", "1"); cvar_t r_stainfadetime = CVAR ("r_stainfadetime", "1"); -cvar_t r_stains = CVARFC("r_stains", IFMINIMAL("0","0.75"), +cvar_t r_stains = CVARFC("r_stains", IFMINIMAL("0","0"), CVAR_ARCHIVE, Cvar_Limiter_ZeroToOne_Callback); cvar_t r_renderscale = CVARD("r_renderscale", "1", "Provides a way to enable subsampling or super-sampling"); @@ -208,9 +208,8 @@ cvar_t scr_conalpha = CVARC ("scr_conalpha", "0.7", cvar_t scr_consize = CVAR ("scr_consize", "0.5"); cvar_t scr_conspeed = CVAR ("scr_conspeed", "2000"); // 10 - 170 -cvar_t scr_fov = CVARFDC("fov", "90", - CVAR_ARCHIVE, "field of vision, 1-170 degrees, standard fov is 90, nquake defaults to 108.", - SCR_Fov_Callback); +cvar_t scr_fov = CVARFCD("fov", "90", CVAR_ARCHIVE, SCR_Fov_Callback, + "field of vision, 1-170 degrees, standard fov is 90, nquake defaults to 108."); cvar_t scr_printspeed = CVAR ("scr_printspeed", "16"); cvar_t scr_showpause = CVAR ("showpause", "1"); cvar_t scr_showturtle = CVAR ("showturtle", "0"); @@ -218,9 +217,7 @@ cvar_t scr_turtlefps = CVAR ("scr_turtlefps", "10"); cvar_t scr_sshot_compression = CVAR ("scr_sshot_compression", "75"); cvar_t scr_sshot_type = CVAR ("scr_sshot_type", "png"); cvar_t scr_sshot_prefix = CVAR ("scr_sshot_prefix", "screenshots/fte-"); -cvar_t scr_viewsize = CVARFC("viewsize", "100", - CVAR_ARCHIVE, - SCR_Viewsize_Callback); +cvar_t scr_viewsize = CVARFC("viewsize", "100", CVAR_ARCHIVE, SCR_Viewsize_Callback); #ifdef ANDROID cvar_t vid_conautoscale = CVARF ("vid_conautoscale", "2", @@ -319,8 +316,8 @@ cvar_t r_deluxmapping_cvar = CVARAFD ("r_deluxmapping", "0", "r_deluxemappin qboolean r_deluxmapping; cvar_t r_shaderblobs = CVARD ("r_shaderblobs", "0", "If enabled, can massively accelerate vid restarts / loading (especially with the d3d renderer). Can cause issues when upgrading engine versions, so this is disabled by default."); cvar_t gl_compress = CVARFD ("gl_compress", "0", CVAR_ARCHIVE, "Enable automatic texture compression even for textures which are not pre-compressed."); -cvar_t gl_conback = CVARFDC ("gl_conback", "", - CVAR_RENDERERCALLBACK, "Specifies which conback shader/image to use. The Quake fallback is gfx/conback.lmp", R2D_Conback_Callback); +cvar_t gl_conback = CVARFCD ("gl_conback", "", + CVAR_RENDERERCALLBACK, R2D_Conback_Callback, "Specifies which conback shader/image to use. The Quake fallback is gfx/conback.lmp"); //cvar_t gl_detail = CVARF ("gl_detail", "0", // CVAR_ARCHIVE); //cvar_t gl_detailscale = CVAR ("gl_detailscale", "5"); @@ -372,15 +369,15 @@ cvar_t gl_specular_fallbackexp = CVARF ("gl_specular_fallbackexp", "1", CVAR cvar_t gl_texture_anisotropic_filtering = CVARFC("gl_texture_anisotropic_filtering", "0", CVAR_ARCHIVE | CVAR_RENDERERCALLBACK, Image_TextureMode_Callback); -cvar_t gl_texturemode = CVARFDC("gl_texturemode", "GL_LINEAR_MIPMAP_LINEAR", - CVAR_ARCHIVE | CVAR_RENDERERCALLBACK | CVAR_SAVE, - "Specifies how world/model textures appear. Typically 3 letters eg lln.\nFirst letter can be l(inear) or n(earest) and says how to sample from the mip (when downsampling).\nThe middle letter can . to disable mipmaps, or l or n to describe whether to blend between mipmaps.\nThe third letter says what to do when the texture is too low resolution and is thus the most noticable with low resolution textures, a n will make it look like lego, while an l will keep it smooth.", Image_TextureMode_Callback); +cvar_t gl_texturemode = CVARFCD("gl_texturemode", "GL_LINEAR_MIPMAP_LINEAR", + CVAR_ARCHIVE | CVAR_RENDERERCALLBACK | CVAR_SAVE, Image_TextureMode_Callback, + "Specifies how world/model textures appear. Typically 3 letters eg lln.\nFirst letter can be l(inear) or n(earest) and says how to sample from the mip (when downsampling).\nThe middle letter can . to disable mipmaps, or l or n to describe whether to blend between mipmaps.\nThe third letter says what to do when the texture is too low resolution and is thus the most noticable with low resolution textures, a n will make it look like lego, while an l will keep it smooth."); cvar_t gl_mipcap = CVARAFC("d_mipcap", "0 1000", "gl_miptexLevel", CVAR_ARCHIVE | CVAR_RENDERERCALLBACK, Image_TextureMode_Callback); -cvar_t gl_texturemode2d = CVARFDC("gl_texturemode2d", "GL_LINEAR", - CVAR_ARCHIVE | CVAR_RENDERERCALLBACK, - "Specifies how 2d images are sampled. format is a 3-tupple ", Image_TextureMode_Callback); +cvar_t gl_texturemode2d = CVARFCD("gl_texturemode2d", "GL_LINEAR", + CVAR_ARCHIVE | CVAR_RENDERERCALLBACK, Image_TextureMode_Callback, + "Specifies how 2d images are sampled. format is a 3-tupple "); cvar_t vid_triplebuffer = CVARAFD ("vid_triplebuffer", "1", "gl_triplebuffer", CVAR_ARCHIVE, "Specifies whether the hardware is forcing tripplebuffering on us, this is the number of extra page swaps required before old data has been completely overwritten."); @@ -973,7 +970,7 @@ void (*R_RenderView) (void); // must set r_refdef first qboolean (*VID_Init) (rendererstate_t *info, unsigned char *palette); void (*VID_DeInit) (void); -char *(*VID_GetRGBInfo) (int *truevidwidth, int *truevidheight, enum uploadfmt *fmt); +char *(*VID_GetRGBInfo) (int *stride, int *truevidwidth, int *truevidheight, enum uploadfmt *fmt); void (*VID_SetWindowCaption) (const char *msg); qboolean (*SCR_UpdateScreen) (void); @@ -1066,6 +1063,7 @@ extern rendererinfo_t swrendererinfo; #ifdef VKQUAKE extern rendererinfo_t vkrendererinfo; //rendererinfo_t headlessvkrendererinfo; +extern rendererinfo_t nvvkrendererinfo; #endif #ifdef HEADLESSQUAKE extern rendererinfo_t headlessrenderer; @@ -1095,6 +1093,9 @@ rendererinfo_t *rendererinfo[] = #endif #ifdef VKQUAKE &vkrendererinfo, + #ifdef _WIN32 + &nvvkrendererinfo, + #endif #endif #ifdef D3D8QUAKE &d3d8rendererinfo, diff --git a/engine/client/roq.h b/engine/client/roq.h index e57d9ed4..2cd315e7 100644 --- a/engine/client/roq.h +++ b/engine/client/roq.h @@ -20,7 +20,7 @@ typedef struct { int idx[4]; } roq_qcell; -typedef struct { +typedef struct roq_info_s { vfsfile_t *fp; qofs_t maxpos; //addition for pack files. all seeks add this, all tells subtract this. int buf_size; diff --git a/engine/client/screen.h b/engine/client/screen.h index c6e61faf..c046e783 100644 --- a/engine/client/screen.h +++ b/engine/client/screen.h @@ -122,7 +122,7 @@ typedef enum uploadfmt TF_SYSTEMDECODE } uploadfmt_t; -qboolean SCR_ScreenShot (char *filename, enum fs_relative fsroot, void **buffer, int numbuffers, int width, int height, enum uploadfmt fmt); +qboolean SCR_ScreenShot (char *filename, enum fs_relative fsroot, void **buffer, int numbuffers, int bytestride, int width, int height, enum uploadfmt fmt); void SCR_DrawTwoDimensional(int uimenu, qboolean nohud); diff --git a/engine/client/snd_al.c b/engine/client/snd_al.c index 5d7e696c..02c1e209 100644 --- a/engine/client/snd_al.c +++ b/engine/client/snd_al.c @@ -526,7 +526,9 @@ static void OpenAL_ListenerUpdate(soundcardinfo_t *sc, int entnum, vec3_t origin { palListenerf(AL_GAIN, 1); palListenerfv(AL_POSITION, oali->ListenPos); +#ifndef FTE_TARGET_WEB //webaudio sucks. palListenerfv(AL_VELOCITY, oali->ListenVel); +#endif palListenerfv(AL_ORIENTATION, oali->ListenOri); } } @@ -581,6 +583,8 @@ static void OpenAL_ChannelUpdate(soundcardinfo_t *sc, channel_t *chan, unsigned float pitch, cvolume; int chnum = chan - sc->channel; ALuint buf; + qboolean stream; + qboolean srcrel; if (chnum >= oali->max_sources) { @@ -638,17 +642,19 @@ static void OpenAL_ChannelUpdate(soundcardinfo_t *sc, channel_t *chan, unsigned palDeleteBuffers(1, &buf); } } - } - if (!schanged && sfx) //if we don't figure out when they've finished, they'll not get replaced properly. - { - palGetSourcei(src, AL_SOURCE_STATE, &buf); - if (buf != AL_PLAYING) + else if (!schanged && sfx) //if we don't figure out when they've finished, they'll not get replaced properly. { - schanged = true; - if (chan->flags & CF_FORCELOOP) - chan->pos = 0; - else - sfx = chan->sfx = NULL; + palGetSourcei(src, AL_SOURCE_STATE, &buf); + if (buf != AL_PLAYING) + { + schanged = true; + if(sfx->loopstart != -1) + chan->pos = sfx->loopstart<flags & CF_FORCELOOP) + chan->pos = 0; + else + sfx = chan->sfx = NULL; + } } } @@ -673,7 +679,10 @@ static void OpenAL_ChannelUpdate(soundcardinfo_t *sc, channel_t *chan, unsigned if (!(chan->flags & CF_ABSVOLUME)) cvolume *= volume.value*voicevolumemod; - if (schanged || sfx->decoder.decodedata) + //openal doesn't support loopstart (entire sample loops or not at all), so if we're meant to skip the first half then we need to stream it. + stream = sfx->decoder.decodedata || sfx->loopstart > 0; + + if (schanged || stream) { if (!sfx->openal_buffer) { @@ -688,39 +697,63 @@ static void OpenAL_ChannelUpdate(soundcardinfo_t *sc, channel_t *chan, unsigned } return; //not available yet } - if (sfx->decoder.decodedata) + if (stream) { + ALuint queuedbufs; int offset; sfxcache_t sbuf, *sc; - palGetSourcei(src, AL_BUFFERS_QUEUED, &buf); - if (buf <= 3) + palGetSourcei(src, AL_BUFFERS_QUEUED, &queuedbufs); + while (queuedbufs < 3) { //decode periodically instead of all at the start. - sc = sfx->decoder.decodedata(sfx, &sbuf, chan->pos>>PITCHSHIFT, 65536); + int tryduration = snd_speed*0.5; + ssamplepos_t pos = chan->pos>>PITCHSHIFT; + + if (sfx->decoder.decodedata) + sc = sfx->decoder.decodedata(sfx, &sbuf, pos, tryduration); + else + { + sc = sfx->decoder.buf; + if (pos >= sc->length) + sc = NULL; + } if (sc) { memcpy(&sbuf, sc, sizeof(sbuf)); //hack up the sound to offset it correctly - offset = (chan->pos>>PITCHSHIFT) - sbuf.soundoffset; - sbuf.data += offset * sc->width*sc->numchannels; - sbuf.length -= offset; - - + if (pos < sbuf.soundoffset || pos > sbuf.soundoffset+sbuf.length) + sbuf.length = 0; //didn't contain the requested samples... the decoder is struggling. + else + { + offset = pos - sbuf.soundoffset; + sbuf.data += offset * sc->width*sc->numchannels; + sbuf.length -= offset; + } sbuf.soundoffset = 0; - if (!buf) - { //queue 124 samples if we're starting/resetting a new stream this is to try to cover up discintinuities caused by low samle rates - sfxcache_t silence = sbuf; + if (sbuf.length > tryduration) + sbuf.length = tryduration; //don't bother queuing more than 3*0.5 secs + + if (sbuf.length) + { + //build a buffer with it and queue it up. + //buffer will be purged later on when its unqueued + OpenAL_LoadCache(&buf, &sbuf, max(1,cvolume)); + palSourceQueueBuffers(src, 1, &buf); + } + else + { //decoder isn't ready yet, but didn't signal an error/eof. queue a little silence, because that's better than constant micro stutters + sfxcache_t silence; + silence.speed = snd_speed; + silence.width = 2; + silence.numchannels = 1; silence.data = NULL; - silence.length = 0.1 * sbuf.speed; + silence.length = 0.1 * silence.speed; + silence.soundoffset = 0; OpenAL_LoadCache(&buf, &silence, 1); palSourceQueueBuffers(src, 1, &buf); } - - //build a buffer with it and queue it up. - //buffer will be purged later on when its unqueued - OpenAL_LoadCache(&buf, &sbuf, max(1,cvolume)); - palSourceQueueBuffers(src, 1, &buf); + queuedbufs++; //yay chan->pos += sbuf.length<flags & CF_FORCELOOP) - chan->pos = 0; -// else if(sbuf.loopstart != -1) -// chan->pos = sbuf.loopstart<sfx = NULL; - if (sfx->decoder.ended) - { - if (!S_IsPlayingSomewhere(sfx)) - sfx->decoder.ended(sfx); - } - } - return; + if(sfx->loopstart != -1) + chan->pos = sfx->loopstart<flags & CF_FORCELOOP) + chan->pos = 0; + else //we don't want to play anything more. + break; + if (!queuedbufs) + { //queue 0.1 secs if we're starting/resetting a new stream this is to try to cover up discintinuities caused by packetloss or whatever + sfxcache_t silence; + silence.speed = snd_speed; + silence.width = 2; + silence.numchannels = 1; + silence.data = NULL; + silence.length = 0.1 * silence.speed; + silence.soundoffset = 0; + OpenAL_LoadCache(&buf, &silence, 1); + palSourceQueueBuffers(src, 1, &buf); + queuedbufs++; } } } + if (!queuedbufs) + { + palGetSourcei(src, AL_SOURCE_STATE, &buf); + if (buf != AL_PLAYING) + { + chan->sfx = NULL; + if (sfx->decoder.ended) + { + if (!S_IsPlayingSomewhere(sfx)) + sfx->decoder.ended(sfx); + } + return; + } + } } else { @@ -771,15 +819,20 @@ static void OpenAL_ChannelUpdate(soundcardinfo_t *sc, channel_t *chan, unsigned } palSourcef(src, AL_GAIN, min(cvolume, 1)); //openal only supports a max volume of 1. anything above is an error and will be clamped. - if ((chan->flags & CF_NOSPACIALISE) || (chan->entnum && chan->entnum == oali->ListenEnt) || !chan->dist_mult) + srcrel = (chan->flags & CF_NOSPACIALISE) || (chan->entnum && chan->entnum == oali->ListenEnt) || !chan->dist_mult; + if (srcrel) { palSourcefv(src, AL_POSITION, vec3_origin); +#ifndef FTE_TARGET_WEB //webaudio sucks. palSourcefv(src, AL_VELOCITY, vec3_origin); +#endif } else { palSourcefv(src, AL_POSITION, chan->origin); +#ifndef FTE_TARGET_WEB //webaudio sucks. palSourcefv(src, AL_VELOCITY, chan->velocity); +#endif } if (schanged) @@ -804,8 +857,8 @@ static void OpenAL_ChannelUpdate(soundcardinfo_t *sc, channel_t *chan, unsigned } #endif - palSourcei(src, AL_LOOPING, (chan->flags & CF_FORCELOOP)?AL_TRUE:AL_FALSE); - if ((chan->flags & CF_NOSPACIALISE) || chan->entnum == oali->ListenEnt || !chan->dist_mult) + palSourcei(src, AL_LOOPING, (!stream && (chan->flags & CF_FORCELOOP))?AL_TRUE:AL_FALSE); + if (srcrel) { palSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE); // palSourcef(src, AL_ROLLOFF_FACTOR, 0.0f); @@ -819,22 +872,46 @@ static void OpenAL_ChannelUpdate(soundcardinfo_t *sc, channel_t *chan, unsigned //this is disgustingly shit. //logically we want to set the distance divisor to 1 and the rolloff factor to dist_mult. //but openal clamps in an annoying order (probably to keep things signed in hardware) and webaudio refuses infinity, so we need to special case no attenuation to get around the issue - if (!chan->dist_mult) + if (srcrel) { - palSourcef(src, AL_ROLLOFF_FACTOR, 0); - palSourcef(src, AL_REFERENCE_DISTANCE, 0); - palSourcef(src, AL_MAX_DISTANCE, 1); //doesn't matter, so long as its not a nan +#ifdef FTE_TARGET_WEB + switch(0) //emscripten omits it, and this is webaudio's default too. +#else + switch(s_al_distancemodel.ival) +#endif + { + default: + case 0: + case 1: + palSourcef(src, AL_ROLLOFF_FACTOR, 0); + palSourcef(src, AL_REFERENCE_DISTANCE, 1); //0 would be silent, or a division by 0 + palSourcef(src, AL_MAX_DISTANCE, 1); //only used for clamped mode + break; + case 2: + case 3: + palSourcef(src, AL_ROLLOFF_FACTOR, 0); + palSourcef(src, AL_REFERENCE_DISTANCE, 0); //doesn't matter when rolloff is 0 + palSourcef(src, AL_MAX_DISTANCE, 1); //doesn't matter, so long as its not a nan + break; + } } else { +#ifdef FTE_TARGET_WEB + switch(2) //emscripten hardcodes it. +#else switch(s_al_distancemodel.ival) +#endif { default: + case 0: + case 1: palSourcef(src, AL_ROLLOFF_FACTOR, s_al_reference_distance.value); palSourcef(src, AL_REFERENCE_DISTANCE, 1); palSourcef(src, AL_MAX_DISTANCE, 1/chan->dist_mult); //clamp to the maximum distance you'd normally be allowed to hear... this is probably going to be annoying. break; case 2: //linear, mimic quake. + case 3: //linear clamped to further than ref distance palSourcef(src, AL_ROLLOFF_FACTOR, 1); palSourcef(src, AL_REFERENCE_DISTANCE, 0); palSourcef(src, AL_MAX_DISTANCE, 1/chan->dist_mult); @@ -974,7 +1051,9 @@ static qboolean OpenAL_Init(soundcardinfo_t *sc, const char *devname) PrintALError("alGensources for normal sources"); palListenerfv(AL_POSITION, oali->ListenPos); +#ifndef FTE_TARGET_WEB //webaudio sucks. palListenerfv(AL_VELOCITY, oali->ListenVel); +#endif palListenerfv(AL_ORIENTATION, oali->ListenOri); return true; @@ -1007,24 +1086,38 @@ static void QDECL OnChangeALSettings (cvar_t *var, char *value) switch (s_al_distancemodel.ival) { case 0: + //gain = AL_REFERENCE_DISTANCE / (AL_REFERENCE_DISTANCE + AL_ROLLOFF_FACTOR * (distance – AL_REFERENCE_DISTANCE) ) palDistanceModel(AL_INVERSE_DISTANCE); break; - case 1: + case 1: //openal's default mode + //istance = max(distance,AL_REFERENCE_DISTANCE); + //distance = min(distance,AL_MAX_DISTANCE); + //gain = AL_REFERENCE_DISTANCE / (AL_REFERENCE_DISTANCE + AL_ROLLOFF_FACTOR * (distance – AL_REFERENCE_DISTANCE) ) palDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); break; - case 2: + case 2: //most quake-like + //distance = min(distance, AL_MAX_DISTANCE) // avoid negative gain + //gain = ( 1 – AL_ROLLOFF_FACTOR * (distance – AL_REFERENCE_DISTANCE) / (AL_MAX_DISTANCE – AL_REFERENCE_DISTANCE) ) palDistanceModel(AL_LINEAR_DISTANCE); break; case 3: + //distance = max(distance, AL_REFERENCE_DISTANCE) + //distance = min(distance, AL_MAX_DISTANCE) + //gain = ( 1 – AL_ROLLOFF_FACTOR * (distance – AL_REFERENCE_DISTANCE) / (AL_MAX_DISTANCE – AL_REFERENCE_DISTANCE) ) palDistanceModel(AL_LINEAR_DISTANCE_CLAMPED); break; case 4: + //gain = (distance / AL_REFERENCE_DISTANCE) ^ (- AL_ROLLOFF_FACTOR) palDistanceModel(AL_EXPONENT_DISTANCE); break; case 5: + //distance = max(distance, AL_REFERENCE_DISTANCE) + //distance = min(distance, AL_MAX_DISTANCE) + //gain = (distance / AL_REFERENCE_DISTANCE) ^ (- AL_ROLLOFF_FACTOR) palDistanceModel(AL_EXPONENT_DISTANCE_CLAMPED); break; case 6: + //gain = 1 palDistanceModel(AL_NONE); break; default: diff --git a/engine/client/snd_dma.c b/engine/client/snd_dma.c index 63b14811..d2b2f346 100644 --- a/engine/client/snd_dma.c +++ b/engine/client/snd_dma.c @@ -36,7 +36,6 @@ static void S_StopAllSounds_f (void); static void S_UpdateCard(soundcardinfo_t *sc); static void S_ClearBuffer (soundcardinfo_t *sc); -sfx_t *S_FindName (const char *name, qboolean create); // ======================================================================= // Internal sound data & structures @@ -147,13 +146,13 @@ cvar_t snd_voip_vad_delay = CVARD("cl_voip_vad_delay", "0.3", "Keeps sending vo cvar_t snd_voip_capturingvol = CVARAFD("cl_voip_capturingvol", "0.5", NULL, CVAR_ARCHIVE, "Volume multiplier applied while capturing, to avoid your audio from being heard by others. Does not affect game volume when other speak (minimum of cl_voip_capturingvol and cl_voip_ducking is used)."); cvar_t snd_voip_showmeter = CVARAFD("cl_voip_showmeter", "1", NULL, CVAR_ARCHIVE, "Shows your speech volume above the standard hud. 0=hide, 1=show when transmitting, 2=ignore voice-activation disable"); -cvar_t snd_voip_play = CVARAFDC("cl_voip_play", "1", NULL, CVAR_ARCHIVE, "Enables voip playback. Value is a volume scaler.", S_Voip_Play_Callback); +cvar_t snd_voip_play = CVARAFCD("cl_voip_play", "1", NULL, CVAR_ARCHIVE, S_Voip_Play_Callback, "Enables voip playback. Value is a volume scaler."); cvar_t snd_voip_ducking = CVARAFD("cl_voip_ducking", "0.5", NULL, CVAR_ARCHIVE, "Scales game audio by this much when someone is talking to you. Does not affect your speaker volume when you speak (minimum of cl_voip_capturingvol and cl_voip_ducking is used)."); -cvar_t snd_voip_micamp = CVARAFDC("cl_voip_micamp", "2", NULL, CVAR_ARCHIVE, "Amplifies your microphone when using voip.", 0); -cvar_t snd_voip_codec = CVARAFDC("cl_voip_codec", "", NULL, CVAR_ARCHIVE, "0: speex(@11khz). 1: raw. 2: opus. 3: speex(@8khz). 4: speex(@16). 5:speex(@32).", 0); -cvar_t snd_voip_noisefilter = CVARAFDC("cl_voip_noisefilter", "1", NULL, CVAR_ARCHIVE, "Enable the use of the noise cancelation filter.", 0); -cvar_t snd_voip_autogain = CVARAFDC("cl_voip_autogain", "0", NULL, CVAR_ARCHIVE, "Attempts to normalize your voice levels to a standard level. Useful for lazy people, but interferes with voice activation levels.", 0); -cvar_t snd_voip_opus_bitrate = CVARAFDC("cl_voip_opus_bitrate", "3000", NULL, CVAR_ARCHIVE, "For codecs with non-specific bitrates, this specifies the target bitrate to use.", 0); +cvar_t snd_voip_micamp = CVARAFD("cl_voip_micamp", "2", NULL, CVAR_ARCHIVE, "Amplifies your microphone when using voip."); +cvar_t snd_voip_codec = CVARAFD("cl_voip_codec", "", NULL, CVAR_ARCHIVE, "0: speex(@11khz). 1: raw. 2: opus. 3: speex(@8khz). 4: speex(@16). 5:speex(@32)."); +cvar_t snd_voip_noisefilter = CVARAFD("cl_voip_noisefilter", "1", NULL, CVAR_ARCHIVE, "Enable the use of the noise cancelation filter."); +cvar_t snd_voip_autogain = CVARAFD("cl_voip_autogain", "0", NULL, CVAR_ARCHIVE, "Attempts to normalize your voice levels to a standard level. Useful for lazy people, but interferes with voice activation levels."); +cvar_t snd_voip_opus_bitrate = CVARAFD("cl_voip_opus_bitrate", "3000", NULL, CVAR_ARCHIVE, "For codecs with non-specific bitrates, this specifies the target bitrate to use."); #endif extern vfsfile_t *rawwritefile; @@ -1853,7 +1852,7 @@ void S_DoRestart (qboolean onlyifneeded) { if (!cl.sound_name[i][0]) break; - cl.sound_precache[i] = S_FindName (cl.sound_name[i], true); + cl.sound_precache[i] = S_FindName (cl.sound_name[i], true, false); } } @@ -2121,7 +2120,7 @@ S_FindName also touches it ================== */ -sfx_t *S_FindName (const char *name, qboolean create) +sfx_t *S_FindName (const char *name, qboolean create, qboolean syspath) { int i; sfx_t *sfx; @@ -2134,7 +2133,7 @@ sfx_t *S_FindName (const char *name, qboolean create) // see if already loaded for (i=0 ; i < num_sfx ; i++) - if (!Q_strcmp(known_sfx[i].name, name)) + if (!Q_strcmp(known_sfx[i].name, name) && known_sfx[i].syspath == syspath) { known_sfx[i].touched = true; return &known_sfx[i]; @@ -2147,7 +2146,8 @@ sfx_t *S_FindName (const char *name, qboolean create) { sfx = &known_sfx[i]; strcpy (sfx->name, name); - known_sfx[i].touched = true; + sfx->syspath = syspath; + sfx->touched = true; num_sfx++; } @@ -2240,7 +2240,7 @@ void S_TouchSound (char *name) if (!sound_started) return; - S_FindName (name, true); + S_FindName (name, true, false); } /* @@ -2249,14 +2249,14 @@ S_PrecacheSound ================== */ -sfx_t *S_PrecacheSound (const char *name) +sfx_t *S_PrecacheSound2 (const char *name, qboolean syspath) { sfx_t *sfx; if (nosound.ival || !known_sfx || !*name) return NULL; - sfx = S_FindName (name, true); + sfx = S_FindName (name, true, syspath); // cache it in if (precache.ival && sndcardinfo) @@ -2708,7 +2708,7 @@ void S_StartSound(int entnum, int entchannel, sfx_t *sfx, vec3_t origin, vec3_t S_UnlockMixer(); } -qboolean S_GetMusicInfo(int musicchannel, float *time, float *duration) +qboolean S_GetMusicInfo(int musicchannel, float *time, float *duration, char *title, size_t titlesize) { qboolean result = false; soundcardinfo_t *sc; @@ -2716,6 +2716,9 @@ qboolean S_GetMusicInfo(int musicchannel, float *time, float *duration) *time = 0; *duration = 0; + if (titlesize) + *title = 0; + musicchannel += MUSIC_FIRST; S_LockMixer(); @@ -2724,18 +2727,23 @@ qboolean S_GetMusicInfo(int musicchannel, float *time, float *duration) sfx = sc->channel[musicchannel].sfx; if (sfx) { - if (sfx->loadstate == SLS_LOADED && sfx->decoder.querydata) - *duration = sfx->decoder.querydata(sfx, NULL); - else if (sfx->decoder.buf) + Q_strncpyz(title, COM_SkipPath(sfx->name), titlesize); + if (sfx->loadstate == SLS_LOADED) { - sfxcache_t *c = sfx->decoder.buf; - *duration = (float)c->length / c->speed; - } - else - *duration = 0; + if (sfx->decoder.querydata) + *duration = sfx->decoder.querydata(sfx, NULL, title, titlesize); + else if (sfx->decoder.buf) + { + sfxcache_t *c = sfx->decoder.buf; + *duration = (float)c->length / c->speed; + } + else + *duration = 0; - *time = (sc->channel[musicchannel].pos>>PITCHSHIFT) / (float)snd_speed; //the time into the sound, ignoring play rate. - result = true; + //FIXME: openal doesn't report the actual time. + *time = (sc->channel[musicchannel].pos>>PITCHSHIFT) / (float)snd_speed; //the time into the sound, ignoring play rate. + result = true; + } } } S_UnlockMixer(); @@ -3024,25 +3032,18 @@ void S_UpdateAmbientSounds (soundcardinfo_t *sc) if (!chan->sfx) { float time = 0; - char *nexttrack = Media_NextTrack(i-MUSIC_FIRST, &time); - sfx_t *newmusic; - - if (nexttrack && *nexttrack) + sfx_t *newmusic = Media_NextTrack(i-MUSIC_FIRST, &time); + if (newmusic && newmusic->loadstate != SLS_FAILED) { - newmusic = S_PrecacheSound(nexttrack); + chan->sfx = newmusic; + chan->rate = 1<pos = (int)(time * sc->sn.speed) * chan->rate; + changed = true; - if (newmusic && newmusic->loadstate != SLS_FAILED) - { - chan->sfx = newmusic; - chan->rate = 1<pos = (int)(time * sc->sn.speed) * chan->rate; - changed = true; - - chan->master_vol = bound(0, 1, 255); - chan->vol[0] = chan->vol[1] = chan->vol[2] = chan->vol[3] = chan->vol[4] = chan->vol[5] = chan->master_vol; - if (sc->ChannelUpdate) - sc->ChannelUpdate(sc, chan, changed); - } + chan->master_vol = bound(0, 1, 255); + chan->vol[0] = chan->vol[1] = chan->vol[2] = chan->vol[3] = chan->vol[4] = chan->vol[5] = chan->master_vol; + if (sc->ChannelUpdate) + sc->ChannelUpdate(sc, chan, changed); } } if (chan->sfx) @@ -3664,7 +3665,7 @@ void S_SoundList_f(void) else if (sfx->decoder.decodedata) { if (sfx->decoder.querydata) - sc = (sfx->decoder.querydata(sfx, &scachebuf) < 0)?NULL:&scachebuf; + sc = (sfx->decoder.querydata(sfx, &scachebuf, NULL, 0) < 0)?NULL:&scachebuf; else sc = NULL; //don't bother trying to actually decode anything here. if (!sc) @@ -3683,7 +3684,7 @@ void S_SoundList_f(void) size = (sc->soundoffset+sc->length)*sc->width*(sc->numchannels); duration = (sc->soundoffset+sc->length) / sc->speed; total += size; - if (sc->loopstart >= 0) + if (sfx->loopstart >= 0) Con_Printf ("L"); else Con_Printf (" "); @@ -3748,14 +3749,13 @@ void S_ClearRaw(void) } //returns an sfxcache_t stating where the data is -sfxcache_t *S_Raw_Locate(sfx_t *sfx, sfxcache_t *buf, ssamplepos_t start, int length) +sfxcache_t *QDECL S_Raw_Locate(sfx_t *sfx, sfxcache_t *buf, ssamplepos_t start, int length) { streaming_t *s = sfx->decoder.buf; if (buf) { buf->data = s->data; buf->length = s->length; - buf->loopstart = -1; buf->numchannels = s->numchannels; buf->soundoffset = 0; buf->speed = snd_speed; diff --git a/engine/client/snd_mem.c b/engine/client/snd_mem.c index a0626cc2..d3d114f1 100644 --- a/engine/client/snd_mem.c +++ b/engine/client/snd_mem.c @@ -587,9 +587,9 @@ static qboolean ResampleSfx (sfx_t *sfx, int inrate, int inchannels, int inwidth sc->soundoffset = 0; sc->data = (qbyte*)(sc+1); if (inloopstart == -1) - sc->loopstart = inloopstart; + sfx->loopstart = inloopstart; else - sc->loopstart = inloopstart * scale; + sfx->loopstart = inloopstart * scale; SND_ResampleStream (data, inrate, @@ -612,7 +612,7 @@ static qboolean ResampleSfx (sfx_t *sfx, int inrate, int inchannels, int inwidth #define DSPK_EXP 0.0433 /* -sfxcache_t *S_LoadDoomSpeakerSound (sfx_t *s, qbyte *data, int datalen, int sndspeed) +qboolean QDECL S_LoadDoomSpeakerSound (sfx_t *s, qbyte *data, size_t datalen, int sndspeed) { sfxcache_t *sc; @@ -688,7 +688,7 @@ sfxcache_t *S_LoadDoomSpeakerSound (sfx_t *s, qbyte *data, int datalen, int snds return sc; } */ -static qboolean S_LoadDoomSound (sfx_t *s, qbyte *data, int datalen, int sndspeed) +static qboolean QDECL S_LoadDoomSound (sfx_t *s, qbyte *data, size_t datalen, int sndspeed) { // format data from Unofficial Doom Specs v1.6 unsigned short *dataus; @@ -717,7 +717,7 @@ static qboolean S_LoadDoomSound (sfx_t *s, qbyte *data, int datalen, int sndspee } #endif -static qboolean S_LoadWavSound (sfx_t *s, qbyte *data, int datalen, int sndspeed) +static qboolean QDECL S_LoadWavSound (sfx_t *s, qbyte *data, size_t datalen, int sndspeed) { wavinfo_t info; @@ -740,11 +740,11 @@ static qboolean S_LoadWavSound (sfx_t *s, qbyte *data, int datalen, int sndspeed return ResampleSfx (s, info.rate, info.numchannels, info.width, info.samples, info.loopstart, data + info.dataofs); } -qboolean S_LoadOVSound (sfx_t *s, qbyte *data, int datalen, int sndspeed); +qboolean QDECL S_LoadOVSound (sfx_t *s, qbyte *data, size_t datalen, int sndspeed); #ifdef FTE_TARGET_WEB //web browsers contain their own decoding libraries that our openal stuff can use. -static qboolean S_LoadBrowserFile (sfx_t *s, qbyte *data, int datalen, int sndspeed) +static qboolean QDECL S_LoadBrowserFile (sfx_t *s, qbyte *data, size_t datalen, int sndspeed) { sfxcache_t *sc; s->decoder.buf = sc = BZ_Malloc(sizeof(sfxcache_t) + datalen); @@ -812,23 +812,12 @@ static void S_LoadSoundWorker (void *ctx, void *ctxdata, size_t a, size_t b) char *name = s->name; size_t filesize; - if (name[1] == ':' && name[2] == '\\') + s->loopstart = -1; + + if (s->syspath) { vfsfile_t *f; -#ifndef _WIN32 //convert from windows to a suitable alternative. - char unixname[128]; - Q_snprintfz(unixname, sizeof(unixname), "/mnt/%c/%s", name[0]-'A'+'a', name+3); - name = unixname; - while (*name) - { - if (*name == '\\') - *name = '/'; - name++; - } - name = unixname; -#endif - if ((f = VFSOS_Open(name, "rb"))) { filesize = VFS_GETLEN(f); diff --git a/engine/client/snd_mix.c b/engine/client/snd_mix.c index dd5cfcfb..bedcfbc4 100644 --- a/engine/client/snd_mix.c +++ b/engine/client/snd_mix.c @@ -166,11 +166,11 @@ void S_PaintChannels(soundcardinfo_t *sc, int endtime) { scache = s->decoder.buf; ch->pos += (end-sc->paintedtime)*ch->rate; - if (ch->pos > scache->length) + if ((ch->pos>>PITCHSHIFT) > scache->length) { ch->pos = 0; - if (scache->loopstart != -1) - ch->pos = scache->loopstart<loopstart != -1) + ch->pos = s->loopstart<flags & CF_FORCELOOP)) { ch->sfx = NULL; @@ -188,31 +188,71 @@ void S_PaintChannels(soundcardinfo_t *sc, int endtime) ltime = sc->paintedtime; while (ltime < end) { + ssamplepos_t spos = ch->pos>>PITCHSHIFT; if (s->decoder.decodedata) - scache = s->decoder.decodedata(s, &scachebuf, ch->pos>>PITCHSHIFT, 1 + (((end - ltime) * ch->rate)>>PITCHSHIFT)); /*1 for luck - balances audio termination below*/ - else - scache = s->decoder.buf; - if (!scache) + scache = s->decoder.decodedata(s, &scachebuf, spos, 1 + (((end - ltime) * ch->rate)>>PITCHSHIFT)); /*1 for luck - balances audio termination below*/ + else if (s->decoder.buf) { - ch->sfx = NULL; - break; + scache = s->decoder.buf; + if (spos >= scache->length) + scache = NULL; //EOF + } + else + scache = NULL; + + if (!scache) + { //hit eof, loop it or stop it + if (s->loopstart != -1) /*some wavs contain a loop offset directly in the sound file, such samples loop even if a non-looping builtin was used*/ + { + ch->pos &= ~((-1)<pos += s->loopstart<flags & CF_FORCELOOP) /*(static)channels which are explicitly looping always loop from the start*/ + { + /*restart it*/ + ch->pos = 0; + } + else + { // channel just stopped + ch->sfx = NULL; + if (s->decoder.ended) + { + if (!S_IsPlayingSomewhere(s)) + s->decoder.ended(s); + } + break; + } + + //retry at that new offset (continue here could give an infinite loop) + spos = ch->pos>>PITCHSHIFT; + if (s->decoder.decodedata) + scache = s->decoder.decodedata(s, &scachebuf, spos, 1 + (((end - ltime) * ch->rate)>>PITCHSHIFT)); /*1 for luck - balances audio termination below*/ + else if (s->decoder.buf) + { + scache = s->decoder.buf; + if (spos >= scache->length) + scache = NULL; //EOF + } + else + scache = NULL; + + if (!scache) + break; } - // find how many samples till the sample ends (clamp max length) - avail = scache->length; - if (avail > maxlen) - avail = snd_speed*10; - avail = (((int)(scache->soundoffset + avail)<pos) / ch->rate; + if (spos < scache->soundoffset || spos > scache->soundoffset+scache->length) + avail = 0; //urm, we would be trying to read outside of the buffer. let mixing slip when there's no data available yet. + else + { + // find how many samples till the sample ends (clamp max length) + avail = scache->length; + if (avail > maxlen) + avail = snd_speed*10; + avail = (((int)(scache->soundoffset + avail)<pos) / ch->rate; + } // mix the smaller of how much is available or the time left count = min(avail, end - ltime); - if (avail < 0) - { - Sys_Printf("sound already past end of buffer\n"); - avail = 0; - count = 0; - } - if (count > 0) { if (ch->pos < 0) //sounds with a pos of 0 are delay-start sounds @@ -254,37 +294,8 @@ void S_PaintChannels(soundcardinfo_t *sc, int endtime) ltime += count; ch->pos += ch->rate * count; } - - if (count == avail) - { - if (scache->loopstart != -1) /*some wavs contain a loop offset directly in the sound file, such samples loop even if a non-looping builtin was used*/ - { - if (scache->length <= scache->loopstart) - break; - ch->pos &= ~((-1)<pos += scache->loopstart<length) - { - scache->loopstart=-1; - break; - } - } - else if ((ch->flags & CF_FORCELOOP) && scache->length) /*(static)channels which are explicitly looping always loop from the start*/ - { - /*restart it*/ - ch->pos = 0; - } - else - { // channel just stopped - ch->sfx = NULL; - if (s->decoder.ended) - { - if (!S_IsPlayingSomewhere(s)) - s->decoder.ended(s); - } - break; - } - } + else + break; } } diff --git a/engine/client/snd_ov.c b/engine/client/snd_ov.c index ec61a6c4..c15c9601 100644 --- a/engine/client/snd_ov.c +++ b/engine/client/snd_ov.c @@ -77,12 +77,12 @@ typedef struct { sfx_t *s; } ovdecoderbuffer_t; -float OV_Query(struct sfx_s *sfx, struct sfxcache_s *buf); -static sfxcache_t *OV_DecodeSome(struct sfx_s *sfx, struct sfxcache_s *buf, ssamplepos_t start, int length); -static void OV_CancelDecoder(sfx_t *s); +float QDECL OV_Query(struct sfx_s *sfx, struct sfxcache_s *buf, char *name, size_t namesize); +static sfxcache_t *QDECL OV_DecodeSome(struct sfx_s *sfx, struct sfxcache_s *buf, ssamplepos_t start, int length); +static void QDECL OV_CancelDecoder(sfx_t *s); static qboolean OV_StartDecode(unsigned char *start, unsigned long length, ovdecoderbuffer_t *buffer); -qboolean S_LoadOVSound (sfx_t *s, qbyte *data, int datalen, int sndspeed) +qboolean QDECL S_LoadOVSound (sfx_t *s, qbyte *data, size_t datalen, int sndspeed) { ovdecoderbuffer_t *buffer; @@ -95,6 +95,7 @@ qboolean S_LoadOVSound (sfx_t *s, qbyte *data, int datalen, int sndspeed) buffer->decodedbytecount = 0; buffer->s = s; s->decoder.buf = buffer; + s->loopstart = -1; if (!OV_StartDecode(data, datalen, buffer)) { @@ -121,7 +122,7 @@ qboolean S_LoadOVSound (sfx_t *s, qbyte *data, int datalen, int sndspeed) return true; } -float OV_Query(struct sfx_s *sfx, struct sfxcache_s *buf) +float QDECL OV_Query(struct sfx_s *sfx, struct sfxcache_s *buf, char *name, size_t namesize) { ovdecoderbuffer_t *dec = sfx->decoder.buf; if (!dec) @@ -131,15 +132,33 @@ float OV_Query(struct sfx_s *sfx, struct sfxcache_s *buf) buf->data = NULL; //you're not meant to actually be using the data here buf->soundoffset = 0; buf->length = p_ov_pcm_total(&dec->vf, -1); - buf->loopstart = -1; buf->numchannels = dec->srcchannels; buf->speed = dec->srcspeed; buf->width = 2; } + if (name) + { + vorbis_comment *c = p_ov_comment(&dec->vf, -1); + int i; + const char *artist = NULL; + const char *title = NULL; + for (i = 0; i < c->comments; i++) + { + if (!strncmp(c->user_comments[i], "ARTIST=", 7)) + artist = c->user_comments[i]+7; + else if (!strncmp(c->user_comments[i], "TITLE=", 6)) + title = c->user_comments[i]+6; + } + + if (artist && title) + Q_snprintfz(name, namesize, "%s - %s", artist, title); + else if (title) + Q_snprintfz(name, namesize, "%s", title); + } return p_ov_time_total(&dec->vf, -1); } -static sfxcache_t *OV_DecodeSome(struct sfx_s *sfx, struct sfxcache_s *buf, ssamplepos_t start, int length) +static sfxcache_t *QDECL OV_DecodeSome(struct sfx_s *sfx, struct sfxcache_s *buf, ssamplepos_t start, int length) { extern int snd_speed; extern cvar_t snd_linearresample_stream; @@ -161,13 +180,13 @@ static sfxcache_t *OV_DecodeSome(struct sfx_s *sfx, struct sfxcache_s *buf, ssam // Con_Printf("Rewound to %i\n", start); dec->failed = false; - /*something rewound, purge clear the buffer*/ - dec->decodedbytecount = 0; - dec->decodedbytestart = start; - //check pos - //fixme: seeking might not be supported - p_ov_pcm_seek(&dec->vf, (dec->decodedbytestart * dec->srcspeed) / outspeed); + if (p_ov_pcm_seek(&dec->vf, start * (dec->srcspeed/(2.0*dec->srcchannels*outspeed))) == 0) + { + /*something rewound, purge clear the buffer*/ + dec->decodedbytecount = 0; + dec->decodedbytestart = start; + } } /* if (start > dec->decodedbytestart + dec->decodedbytecount) @@ -191,8 +210,11 @@ static sfxcache_t *OV_DecodeSome(struct sfx_s *sfx, struct sfxcache_s *buf, ssam } else if (trim > dec->decodedbytecount) { - dec->decodedbytecount = 0; - dec->decodedbytestart = start; + if (0==p_ov_pcm_seek(&dec->vf, start * (dec->srcspeed/(2.0*dec->srcchannels*outspeed)))) + { + dec->decodedbytecount = 0; + dec->decodedbytestart = start; + } // Con_Printf("trim > count\n"); } else @@ -228,6 +250,8 @@ static sfxcache_t *OV_DecodeSome(struct sfx_s *sfx, struct sfxcache_s *buf, ssam Con_Printf("ogg decoding failed\n"); break; } + if (start >= dec->decodedbytestart+dec->decodedbytecount) + return NULL; //let the mixer know that we hit the end break; } } @@ -253,6 +277,8 @@ static sfxcache_t *OV_DecodeSome(struct sfx_s *sfx, struct sfxcache_s *buf, ssam Con_Printf("ogg decoding failed\n"); return NULL; } + if (start >= dec->decodedbytestart+dec->decodedbytecount) + return NULL; //let the mixer know that we hit the end break; } @@ -278,7 +304,6 @@ static sfxcache_t *OV_DecodeSome(struct sfx_s *sfx, struct sfxcache_s *buf, ssam buf->data = dec->decodedbuffer; buf->soundoffset = dec->decodedbytestart / (2 * dec->srcchannels); buf->length = dec->decodedbytecount / (2 * dec->srcchannels); - buf->loopstart = -1; buf->numchannels = dec->srcchannels; buf->speed = snd_speed; buf->width = 2; @@ -291,7 +316,7 @@ static sfxcache_t *OV_DecodeSome(struct sfx_s *sfx, struct sfxcache_s *buf, ssam if (s->loadstate != SLS_LOADING) s->loadstate = SLS_NOTLOADED; }*/ -static void OV_CancelDecoder(sfx_t *s) +static void QDECL OV_CancelDecoder(sfx_t *s) { ovdecoderbuffer_t *dec; s->loadstate = SLS_FAILED; diff --git a/engine/client/sound.h b/engine/client/sound.h index 1c8a59a6..6cd2fc46 100644 --- a/engine/client/sound.h +++ b/engine/client/sound.h @@ -37,10 +37,10 @@ typedef struct } portable_samplegroup_t; typedef struct { - struct sfxcache_s *(*decodedata) (struct sfx_s *sfx, struct sfxcache_s *buf, ssamplepos_t start, int length); //return true when done. - float (*querydata) (struct sfx_s *sfx, struct sfxcache_s *buf); //reports length + original format info without actually decoding anything. - void (*ended) (struct sfx_s *sfx); //sound stopped playing and is now silent (allow rewinding or something). - void (*purge) (struct sfx_s *sfx); //sound is being purged from memory. destroy everything. + struct sfxcache_s *(QDECL *decodedata) (struct sfx_s *sfx, struct sfxcache_s *buf, ssamplepos_t start, int length); //return true when done. + float (QDECL *querydata) (struct sfx_s *sfx, struct sfxcache_s *buf, char *title, size_t titlesize); //reports length + original format info without actually decoding anything. + void (QDECL *ended) (struct sfx_s *sfx); //sound stopped playing and is now silent (allow rewinding or something). + void (QDECL *purge) (struct sfx_s *sfx); //sound is being purged from memory. destroy everything. void *buf; } sfxdecode_t; @@ -58,6 +58,9 @@ typedef struct sfx_s int loadstate; //no more super-spammy qboolean touched:1; //if the sound is still relevent + qboolean syspath:1; //if the sound is still relevent + + int loopstart; //-1 or sample index to begin looping at once the sample ends #ifdef AVAIL_OPENAL unsigned int openal_buffer; @@ -68,7 +71,6 @@ typedef struct sfx_s typedef struct sfxcache_s { usamplepos_t length; //sample count - int loopstart; //-1 or sample index to begin looping at once the sample ends unsigned int speed; unsigned int width; unsigned int numchannels; @@ -90,17 +92,32 @@ typedef struct unsigned char *buffer; // pointer to mixed pcm buffer (not directly used by mixer) } dma_t; -#define CF_RELIABLE 1 // serverside only. yeah, evil. screw you. +//client and server +//CF_RELIABLE 1 #define CF_FORCELOOP 2 // forces looping. set on static sounds. #define CF_NOSPACIALISE 4 // these sounds are played at a fixed volume in both speakers, but still gets quieter with distance. //#define CF_PAUSED 8 // rate = 0. or something. -#define CF_ABSVOLUME 16 // ignores volume cvar. +//CF_ABSVOLUME #define CF_NOREVERB 32 // disables reverb on this channel, if possible. #define CF_FOLLOW 64 // follows the owning entity (stops moving if we lose track) +//#define CF_RESERVEDN 128 // reserved for things that should be networked. +//client only +#define CF_ABSVOLUME 16 // ignores volume cvar. +//client-internal +#define CF_AUTOSOUND 1024 // generated from q2 entities, which avoids breaking regular sounds, using it outside the sound system will probably break things. + +//server only +#define CF_RELIABLE 1 // serverside only. yeah, evil. screw you. #define CF_UNICAST 256 // serverside only. the sound is sent to msg_entity only. #define CF_SENDVELOCITY 512 // serverside hint that velocity is important -#define CF_AUTOSOUND 1024 // generated from q2 entities, which avoids breaking regular sounds, using it outside the sound system will probably break things. +//#define CF_UNUSED 2048 +//#define CF_UNUSED 4096 +//#define CF_UNUSED 8192 +//#define CF_UNUSED 16384 +//#define CF_UNUSED 32768 + +#define CF_NETWORKED (CF_NOSPACIALISE|CF_NOREVERB|CF_FORCELOOP|CF_FOLLOW/*|CF_RESERVEDN*/) typedef struct { @@ -186,13 +203,14 @@ qboolean S_HaveOutput(void); void S_Music_Clear(sfx_t *onlyifsample); void S_Music_Seek(float time); -qboolean S_GetMusicInfo(int musicchannel, float *time, float *duration); +qboolean S_GetMusicInfo(int musicchannel, float *time, float *duration, char *title, size_t titlesize); qboolean S_Music_Playing(int musicchannel); float Media_CrossFade(int musicchanel, float vol, float time); //queries the volume we're meant to be playing (checks for fade out). -1 for no more, otherwise returns vol. -char *Media_NextTrack(int musicchanel, float *time); //queries the track we're meant to be playing now. +sfx_t *Media_NextTrack(int musicchanel, float *time); //queries the track we're meant to be playing now. -sfx_t *S_FindName (const char *name, qboolean create); -sfx_t *S_PrecacheSound (const char *sample); +sfx_t *S_FindName (const char *name, qboolean create, qboolean syspath); +sfx_t *S_PrecacheSound2 (const char *sample, qboolean syspath); +#define S_PrecacheSound(s) S_PrecacheSound2(s,false) void S_TouchSound (char *sample); void S_UntouchAll(void); void S_ClearPrecache (void); @@ -293,7 +311,7 @@ void S_LocalSound (const char *s); void S_LocalSound2 (const char *sound, int channel, float volume); qboolean S_LoadSound (sfx_t *s); -typedef qboolean (*S_LoadSound_t) (sfx_t *s, qbyte *data, int datalen, int sndspeed); +typedef qboolean (QDECL *S_LoadSound_t) (sfx_t *s, qbyte *data, size_t datalen, int sndspeed); qboolean S_RegisterSoundInputPlugin(S_LoadSound_t loadfnc); //called to register additional sound input plugins void S_AmbientOff (void); diff --git a/engine/client/sys_droid.c b/engine/client/sys_droid.c index bb8338ef..fc0b7ba5 100644 --- a/engine/client/sys_droid.c +++ b/engine/client/sys_droid.c @@ -432,15 +432,23 @@ char *Sys_ConsoleInput (void) { return NULL; } -void Sys_mkdir (char *path) //not all pre-unix systems have directories (including dos 1) +void Sys_mkdir (const char *path) //not all pre-unix systems have directories (including dos 1) { - mkdir(path, 0777); + mkdir(path, 0755); } -qboolean Sys_remove (char *path) +qboolean Sys_rmdir (const char *path) +{ + if (rmdir (path) == 0) + return true; + if (errno == ENOENT) + return true; + return false; +} +qboolean Sys_remove (const char *path) { return !unlink(path); } -qboolean Sys_Rename (char *oldfname, char *newfname) +qboolean Sys_Rename (const char *oldfname, const char *newfname) { return !rename(oldfname, newfname); } diff --git a/engine/client/sys_linux.c b/engine/client/sys_linux.c index d97a35cf..5b762f4c 100644 --- a/engine/client/sys_linux.c +++ b/engine/client/sys_linux.c @@ -446,16 +446,23 @@ int Sys_FileTime (char *path) } -void Sys_mkdir (char *path) +void Sys_mkdir (const char *path) { mkdir (path, 0777); } - -qboolean Sys_remove (char *path) +qboolean Sys_rmdir (const char *path) +{ + if (rmdir (path) == 0) + return true; + if (errno == ENOENT) + return true; + return false; +} +qboolean Sys_remove (const char *path) { return system(va("rm \"%s\"", path)); } -qboolean Sys_Rename (char *oldfname, char *newfname) +qboolean Sys_Rename (const char *oldfname, const char *newfname) { return !rename(oldfname, newfname); } diff --git a/engine/client/sys_morphos.c b/engine/client/sys_morphos.c index 997119c5..f694e9da 100755 --- a/engine/client/sys_morphos.c +++ b/engine/client/sys_morphos.c @@ -269,7 +269,7 @@ int Sys_EnumerateFiles(const char *gpath, const char *match, int (*func)(const c return ret; } -void Sys_mkdir(char *path) +void Sys_mkdir(const char *path) { BPTR lock; @@ -283,14 +283,19 @@ void Sys_mkdir(char *path) } } -qboolean Sys_remove(char *path) +qboolean Sys_rmdir (const char *path) +{ + return false; +} + +qboolean Sys_remove(const char *path) { if (path[0] == '.' && path[1] == '/') path+= 2; return DeleteFile(path); } -qboolean Sys_Rename (char *oldfname, char *newfname) +qboolean Sys_Rename (const char *oldfname, const char *newfname) { return !rename(oldfname, newfname); } diff --git a/engine/client/sys_npfte.c b/engine/client/sys_npfte.c index 2b1a5d27..43ac2086 100644 --- a/engine/client/sys_npfte.c +++ b/engine/client/sys_npfte.c @@ -1,3 +1,7 @@ +//as of firefox 52(march 2017), firefox no longer supports npapi (except for flash, the buggiest of them all, stupid stupid) +//chrome 42 disabled support by default, and completely removed by 45 (sept 2015). +//no browser with > 1% market share can actually use this. + #include "quakedef.h" #include "winquake.h" #define bool int //we ain't c++ (grr microsoft stdbool.h gief!) diff --git a/engine/client/sys_plugfte.c b/engine/client/sys_plugfte.c index 351bfb4b..a554e374 100644 --- a/engine/client/sys_plugfte.c +++ b/engine/client/sys_plugfte.c @@ -666,12 +666,12 @@ qboolean Update_GetHomeDirectory(char *homedir, int homedirsize) return false; } -void Sys_mkdir (char *path) +void Sys_mkdir (const char *path) { CreateDirectory (path, NULL); } #else -void Sys_mkdir (char *path) +void Sys_mkdir (const char *path) { mkdir (path, 0777); } @@ -792,7 +792,7 @@ qboolean Plug_GetDownloadedName(char *updatedpath, int updatedpathlen) if (enginedownloadactive) { enginedownloadactive->user_ctx = NULL; - DL_CreateThread(enginedownloadactive, VFSPIPE_Open(), Update_Version_Updated); + DL_CreateThread(enginedownloadactive, VFSPIPE_Open(1, false), Update_Version_Updated); } } } diff --git a/engine/client/sys_sdl.c b/engine/client/sys_sdl.c index 7b02d232..19c04d0d 100644 --- a/engine/client/sys_sdl.c +++ b/engine/client/sys_sdl.c @@ -119,7 +119,7 @@ double Sys_DoubleTime (void) } //create a directory -void Sys_mkdir (char *path) +void Sys_mkdir (const char *path) { #if WIN32 _mkdir (path); @@ -129,15 +129,31 @@ void Sys_mkdir (char *path) #endif } +qboolean Sys_rmdir (const char *path) +{ + int ret; +#if WIN32 + ret = _rmdir (path); +#else + //user, group, others + ret = rmdir (path, 0755); //WARNING: DO NOT RUN AS ROOT! +#endif + if (ret == 0) + return true; + if (errno == ENOENT) + return true; + return false; +} + //unlink a file -qboolean Sys_remove (char *path) +qboolean Sys_remove (const char *path) { remove(path); return true; } -qboolean Sys_Rename (char *oldfname, char *newfname) +qboolean Sys_Rename (const char *oldfname, const char *newfname) { return !rename(oldfname, newfname); } diff --git a/engine/client/sys_win.c b/engine/client/sys_win.c index 81564027..379e44f9 100644 --- a/engine/client/sys_win.c +++ b/engine/client/sys_win.c @@ -126,14 +126,24 @@ void Sys_Quit (void) exit(0); } -void Sys_mkdir (char *path) +void Sys_mkdir (const char *path) { wchar_t wide[MAX_OSPATH]; widen(wide, sizeof(wide), path); CreateDirectoryW(wide, NULL); } +qboolean Sys_rmdir (const char *path) +{ + RemoveDirectoryW(wide) -qboolean Sys_remove (char *path) + if (rmdir (path) == 0) + return true; + if (errno == ENOENT) + return true; + return false; +} + +qboolean Sys_remove (const char *path) { wchar_t wide[MAX_OSPATH]; widen(wide, sizeof(wide), path); @@ -144,7 +154,7 @@ qboolean Sys_remove (char *path) return false; //other errors? panic } -qboolean Sys_Rename (char *oldfname, char *newfname) +qboolean Sys_Rename (const char *oldfname, const char *newfname) { wchar_t oldwide[MAX_OSPATH]; wchar_t newwide[MAX_OSPATH]; @@ -389,10 +399,27 @@ dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs) { int i; HMODULE lib; + DWORD err; lib = LoadLibraryU(name); if (!lib) { + err = GetLastError(); + switch(err) + { + case ERROR_MOD_NOT_FOUND: + break; + case ERROR_BAD_EXE_FORMAT: + Con_Printf("Error ERROR_BAD_EXE_FORMAT loading %s\n", name); + break; + case ERROR_PROC_NOT_FOUND: + Con_Printf("Error ERROR_PROC_NOT_FOUND loading %s\n", name); + break; + default: + Con_Printf("Error %u loading %s\n", err, name); + break; + } + if (!strstr(COM_SkipPath(name), ".dll")) { //.dll implies that it is a system dll, or something that is otherwise windows-specific already. char libname[MAX_OSPATH]; @@ -1014,7 +1041,7 @@ FILE IO =============================================================================== */ -void Sys_mkdir (char *path) +void Sys_mkdir (const char *path) { if (WinNT) { @@ -1026,7 +1053,20 @@ void Sys_mkdir (char *path) _mkdir (path); } -qboolean Sys_remove (char *path) +qboolean Sys_rmdir (const char *path) +{ + if (WinNT) + { + wchar_t wide[MAX_OSPATH]; + widen(wide, sizeof(wide), path); + return RemoveDirectoryW(wide); + } + else + return 0==_mkdir (path); +} + + +qboolean Sys_remove (const char *path) { if (WinNT) { @@ -1055,7 +1095,7 @@ qboolean Sys_remove (char *path) } } -qboolean Sys_Rename (char *oldfname, char *newfname) +qboolean Sys_Rename (const char *oldfname, const char *newfname) { if (WinNT) { @@ -4234,4 +4274,48 @@ void WIN_DestroyCursor(void *cursor) { DestroyIcon(cursor); } + + + +/* +static HRESULT STDMETHODCALLTYPE DD_QueryInterface(IDropTarget *This, REFIID riid, void **ppvObject) {return E_NOINTERFACE;} +static ULONG STDMETHODCALLTYPE DD_AddRef(IDropTarget *This) {return 1;} +static ULONG STDMETHODCALLTYPE DD_Release(IDropTarget *This) {return 1;} +static HRESULT STDMETHODCALLTYPE DD_DragEnter(IDropTarget *This, IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) +{ + *pdwEffect &= DROPEFFECT_COPY; + return S_OK; +} +static HRESULT STDMETHODCALLTYPE DD_DragOver(IDropTarget *This, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) +{ + *pdwEffect &= DROPEFFECT_COPY; + return S_OK; +} +static HRESULT STDMETHODCALLTYPE DD_DragLeave(IDropTarget *This) +{ + return S_OK; +} +static HRESULT STDMETHODCALLTYPE DD_Drop(IDropTarget *This, IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) +{ + *pdwEffect &= DROPEFFECT_COPY; + return S_OK; +} +static struct IDropTargetVtbl MyDropTargetVtbl = +{ + DD_QueryInterface, + DD_AddRef, + DD_Release, + DD_DragEnter, + DD_DragOver, + DD_DragLeave, + DD_Drop +}; +static IDropTarget MyDropTarget = {&MyDropTargetVtbl};*/ +void WIN_WindowCreated(HWND window) +{ +// OleInitialize(NULL); +// if (FAILED(RegisterDragDrop(window, &MyDropTarget))) +// Con_Printf("RegisterDragDrop failed\n"); + DragAcceptFiles(window, TRUE); +} #endif diff --git a/engine/client/sys_xdk.c b/engine/client/sys_xdk.c index 9323af0e..2ba3a3c4 100644 --- a/engine/client/sys_xdk.c +++ b/engine/client/sys_xdk.c @@ -125,14 +125,17 @@ qboolean Sys_RandomBytes(qbyte *string, int len) } /*filesystem stuff*/ -void Sys_mkdir (char *path) +void Sys_mkdir (const char *path) { } -qboolean Sys_remove (char *path) +void Sys_rmdir (const char *path) +{ +} +qboolean Sys_remove (const char *path) { return false; //false for failure } -qboolean Sys_Rename (char *oldfname, char *newfname) +qboolean Sys_Rename (const char *oldfname, const char *newfname) { return false; //false for failure } diff --git a/engine/client/vid.h b/engine/client/vid.h index cc03f969..f81ab149 100644 --- a/engine/client/vid.h +++ b/engine/client/vid.h @@ -122,6 +122,6 @@ void GLVID_Update (vrect_t *rects); void GLVID_SwapBuffers(void); enum uploadfmt; -char *GLVID_GetRGBInfo(int *truewidth, int *trueheight, enum uploadfmt *fmt); +char *GLVID_GetRGBInfo(int *bytestride, int *truewidth, int *trueheight, enum uploadfmt *fmt); void GLVID_SetCaption(const char *caption); #endif diff --git a/engine/client/vid_headless.c b/engine/client/vid_headless.c index a85f77e2..cbdfedaa 100644 --- a/engine/client/vid_headless.c +++ b/engine/client/vid_headless.c @@ -158,10 +158,10 @@ static qboolean Headless_VID_ApplyGammaRamps (unsigned int gammarampsize, unsig static void Headless_VID_SetWindowCaption (const char *msg) { } -static char *Headless_VID_GetRGBInfo (int *truevidwidth, int *truevidheight, enum uploadfmt *fmt) +static char *Headless_VID_GetRGBInfo (int *bytestride, int *truevidwidth, int *truevidheight, enum uploadfmt *fmt) { *fmt = TF_INVALID; - *truevidwidth = *truevidheight = 0; + *bytestride = *truevidwidth = *truevidheight = 0; return NULL; } static qboolean Headless_SCR_UpdateScreen (void) @@ -291,12 +291,31 @@ rendererinfo_t headlessrenderer = static qboolean HeadlessVK_CreateSurface(void) { vk.surface = VK_NULL_HANDLE; //nothing to create, we're using offscreen rendering. + vk.allowsubmissionthread = false; //waste of threading return true; } -//static void HeadlessVK_Present(struct vkframe *theframe) -//{ +static void HeadlessVK_Present(struct vkframe *theframe) +{ //VK_DoPresent(theframe); -//} + + if (!theframe) + return; + vk + + qglWaitVkSemaphoreNV(theframe->backbuf->presentsemaphore); + //tell the gl driver to copy it over now + qglDrawVkImageNV(theframe->backbuf->colour.image, theframe->backbuf->colour.sampler, + 0, 0, vid.pixelwidth, vid.pixelheight, //xywh (window coords) + 0, //z + 0, 1, 1, 0); //stst (remember that gl textures are meant to be upside down) + + //at this point the gl driver can signal the fence so our vk code can wake up and start drawing the next frame (if the gpu is slow) + qglSignalVkFenceNV(vk.acquirefences[vk.aquirelast%ACQUIRELIMIT]); + //and tell our code to expect it. + vk.acquirebufferidx[vk.aquirelast%ACQUIRELIMIT] = vk.aquirelast%vk.backbuf_count; + barrier + vk.aquirelast++; +} static qboolean HeadlessVK_Init (rendererstate_t *info, unsigned char *palette) { extern cvar_t vid_conautoscale; diff --git a/engine/client/view.c b/engine/client/view.c index 708097e7..cc254451 100644 --- a/engine/client/view.c +++ b/engine/client/view.c @@ -317,9 +317,9 @@ cshift_t cshift_lava = { {255,80,0}, 150 }; cshift_t cshift_server = { {130,80,50}, 0 }; -cvar_t v_gamma = CVARFDC("gamma", "1.0", CVAR_ARCHIVE|CVAR_RENDERERCALLBACK, "Controls how bright the screen is. Setting this to anything but 1 without hardware gamma requires glsl support and can noticably harm your framerate.", V_Gamma_Callback); -cvar_t v_contrast = CVARFDC("contrast", "1.0", CVAR_ARCHIVE, "Scales colour values linearly to make your screen easier to see. Setting this to anything but 1 without hardware gamma will reduce your framerates a little.", V_Gamma_Callback); -cvar_t v_brightness = CVARFDC("brightness", "0.0", CVAR_ARCHIVE, "Brightness is how much 'white' to add to each and every pixel on the screen.", V_Gamma_Callback); +cvar_t v_gamma = CVARFCD("gamma", "1.0", CVAR_ARCHIVE|CVAR_RENDERERCALLBACK, V_Gamma_Callback, "Controls how bright the screen is. Setting this to anything but 1 without hardware gamma requires glsl support and can noticably harm your framerate."); +cvar_t v_contrast = CVARFCD("contrast", "1.0", CVAR_ARCHIVE, V_Gamma_Callback, "Scales colour values linearly to make your screen easier to see. Setting this to anything but 1 without hardware gamma will reduce your framerates a little."); +cvar_t v_brightness = CVARFCD("brightness", "0.0", CVAR_ARCHIVE, V_Gamma_Callback, "Brightness is how much 'white' to add to each and every pixel on the screen."); qbyte gammatable[256]; // palette is sent through this diff --git a/engine/client/wad.c b/engine/client/wad.c index 8c608b4d..aeed65f1 100644 --- a/engine/client/wad.c +++ b/engine/client/wad.c @@ -842,10 +842,6 @@ void Mod_ParseInfoFromEntityLump(model_t *wmodel) //actually, this should be in Q_strncatz(wads, token, sizeof(wads)); //cache it for later (so that we don't play with any temp memory yet) #endif } - else if (!strcmp("skyname", key)) // for HalfLife maps - { - Q_strncpyz(cl.skyname, token, sizeof(cl.skyname)); - } else if (!strcmp("fog", key)) //q1 extension. FIXME: should be made temporary. { key[0] = 'f'; @@ -889,6 +885,10 @@ void Mod_ParseInfoFromEntityLump(model_t *wmodel) //actually, this should be in Cvar_LockFromServer(&r_telealpha, com_token); Cvar_LockFromServer(&r_telestyle, "1"); } + else if (!strcmp("skyname", key)) // for HalfLife maps + { + Q_strncpyz(cl.skyname, token, sizeof(cl.skyname)); + } else if (!strcmp("sky", key)) // for Quake2 maps { Q_strncpyz(cl.skyname, token, sizeof(cl.skyname)); diff --git a/engine/client/winquake.h b/engine/client/winquake.h index 34058569..4c40f38a 100644 --- a/engine/client/winquake.h +++ b/engine/client/winquake.h @@ -122,6 +122,7 @@ extern HCURSOR hArrowCursor, hCustomCursor; void *WIN_CreateCursor(const char *filename, float hotx, float hoty, float scale); qboolean WIN_SetCursor(void *cursor); void WIN_DestroyCursor(void *cursor); +void WIN_WindowCreated(HWND window); void INS_UpdateClipCursor (void); void CenterWindow(HWND hWndCenter, int width, int height, BOOL lefttopjustify); diff --git a/engine/common/bothdefs.h b/engine/common/bothdefs.h index f1445560..f0e33286 100644 --- a/engine/common/bothdefs.h +++ b/engine/common/bothdefs.h @@ -297,7 +297,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. //#define VM_LUA //q1 lua gamecode interface #define TCPCONNECT //a tcpconnect command, that allows the player to connect to tcp-encapsulated qw protocols. - #define IRCCONNECT //an ircconnect command, that allows the player to connect to irc-encapsulated qw protocols... yeah, really. +// #define IRCCONNECT //an ircconnect command, that allows the player to connect to irc-encapsulated qw protocols... yeah, really. #define PLUGINS //qvm/dll plugins. #define SUPPORT_ICE //Interactive Connectivity Establishment protocol, for peer-to-peer connections @@ -383,6 +383,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #ifdef NO_ZLIB //compile-time option. #undef AVAIL_ZLIB + #undef AVAIL_PNGLIB #undef AVAIL_XZDEC #undef AVAIL_GZDEC #endif diff --git a/engine/common/cmd.c b/engine/common/cmd.c index 16ac9c5e..1c1d06af 100644 --- a/engine/common/cmd.c +++ b/engine/common/cmd.c @@ -714,8 +714,13 @@ void Cmd_Exec_f (void) if (cvar_watched) Cbuf_InsertText (va("echo END %s", buf), level, true); // don't execute anything if it was from server (either the stuffcmd/localcmd, or the file) - if (!strcmp(name, "default.cfg") && !(Cmd_FromGamecode() || untrusted)) - Cbuf_InsertText ("\ncvar_lockdefaults 1\n", level, false); + if (!strcmp(name, "default.cfg")) + { + if (!(Cmd_FromGamecode() || untrusted)) + Cbuf_InsertText ("\ncvar_lockdefaults 1\n", level, false); + if (fs_manifest->defaultoverrides) + Cbuf_InsertText (fs_manifest->defaultoverrides, level, false); + } Cbuf_InsertText (s, level, true); if (cvar_watched) Cbuf_InsertText (va("echo BEGIN %s", buf), level, true); @@ -1246,15 +1251,16 @@ char *VARGS Cmd_Args (void) return cmd_args; } -void Cmd_Args_Set(const char *newargs) +void Cmd_Args_Set(const char *newargs, size_t len) { if (cmd_args_buf) Z_Free(cmd_args_buf); if (newargs) { - cmd_args_buf = (char*)Z_Malloc (Q_strlen(newargs)+1); - Q_strcpy (cmd_args_buf, newargs); + cmd_args_buf = (char*)Z_Malloc (len+1); + memcpy(cmd_args_buf, newargs, len); + cmd_args_buf[len] = 0; cmd_args = cmd_args_buf; } else @@ -1547,13 +1553,13 @@ Parses the given string into command line tokens, stopping at the \n const char *Cmd_TokenizeString (const char *text, qboolean expandmacros, qboolean qctokenize) { int i; + const char *args = NULL; // clear the args from the last string for (i=0 ; i args && (argsend[-1] == '\n' || argsend[-1] == '\r')) + argsend--; + Cmd_Args_Set(args, argsend-args); + } + else + Cmd_Args_Set(NULL, 0); return text; } void Cmd_TokenizePunctation (char *text, char *punctuation) { int i; + char *args = NULL; // clear the args from the last string for (i=0 ; i args && (argsend[-1] == '\n' || argsend[-1] == '\r')) + argsend--; + Cmd_Args_Set(args, argsend-args); + } } @@ -2095,7 +2116,7 @@ void Cmd_Apropos_f (void) ; else continue; - Con_Printf("command ^2%s^7: %s\n", cmd->name, cmd->description?cmd->description:"no description"); + Con_Printf("command ^2%s^7: %s\n", cmd->name, cmd->description?cmd->description:"no description"); } //FIXME: add aliases. } diff --git a/engine/common/cmd.h b/engine/common/cmd.h index a7550006..510254bc 100644 --- a/engine/common/cmd.h +++ b/engine/common/cmd.h @@ -131,7 +131,7 @@ const char *Cmd_TokenizeString (const char *text, qboolean expandmacros, qboolea void Cmd_ExecuteString (char *text, int restrictionlevel); -void Cmd_Args_Set(const char *newargs); +void Cmd_Args_Set(const char *newargs, size_t len); #define RESTRICT_MAX_TOTAL 31 #define RESTRICT_MAX_USER 29 diff --git a/engine/common/com_phys_ode.c b/engine/common/com_phys_ode.c index 84ba3adb..f5adff9f 100644 --- a/engine/common/com_phys_ode.c +++ b/engine/common/com_phys_ode.c @@ -77,9 +77,6 @@ static BUILTIN(void, Sys_CloseLibrary, (dllhandle_t *hdl)); #define ARGNAMES ,version static BUILTINR(rbeplugfuncs_t*, RBE_GetPluginFuncs, (int version)); #undef ARGNAMES -#define ARGNAMES ,name,defaultval,flags,description,groupname -static BUILTINR(cvar_t*, Cvar_GetNVFDG, (const char *name, const char *defaultval, unsigned int flags, const char *description, const char *groupname)); -#undef ARGNAMES static rbeplugfuncs_t *rbefuncs; cvar_t r_meshpitch; @@ -2831,7 +2828,6 @@ static qintptr_t QDECL Plug_ODE_Shutdown(qintptr_t *args) qintptr_t Plug_Init(qintptr_t *args) { CHECKBUILTIN(RBE_GetPluginFuncs); - CHECKBUILTIN(Cvar_GetNVFDG); #ifndef ODE_STATIC CHECKBUILTIN(Sys_LoadLibrary); CHECKBUILTIN(Sys_CloseLibrary); diff --git a/engine/common/common.c b/engine/common/common.c index f2fd45c9..292d4737 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -392,11 +392,18 @@ int Q_strncasecmp (const char *s1, const char *s2, int n) int Q_strcasecmp (const char *s1, const char *s2) { - return Q_strncasecmp (s1, s2, 99999); + return Q_strncasecmp (s1, s2, INT_MAX); } int QDECL Q_stricmp (const char *s1, const char *s2) { - return Q_strncasecmp (s1, s2, 99999); + return Q_strncasecmp (s1, s2, INT_MAX); +} +int Q_strstopcasecmp(const char *s1start, const char *s1end, const char *s2) +{ //safer version of strncasecmp, where s1 is the one with the length, and must exactly match s2 (which is null terminated and probably an immediate. + //return value isn't suitable for sorting. + if (s1end - s1start != strlen(s2)) + return -1; + return Q_strncasecmp (s1start, s2, s1end - s1start); } char *Q_strcasestr(const char *haystack, const char *needle) @@ -2042,7 +2049,7 @@ char *COM_FileExtension (const char *in, char *result, size_t sizeofresult) int i; const char *dot; - for (dot = in + strlen(in); dot >= in && *dot != '.'; dot--) + for (dot = in + strlen(in); dot >= in && *dot != '.' && *dot != '/' && *dot != '\\'; dot--) ; if (dot < in) { @@ -2058,6 +2065,23 @@ char *COM_FileExtension (const char *in, char *result, size_t sizeofresult) return result; } +//returns a pointer to the extension text, including the dot +//term is the end of the string (or null, to make things easy). if its a previous (non-empty) return value, then you can scan backwards to skip .gz or whatever extra postfixes. +const char *COM_GetFileExtension (const char *in, const char *term) +{ + const char *dot; + + if (!term) + term = in + strlen(in); + + for (dot = term; dot >= in && *dot != '.' && *dot != '/' && *dot != '\\'; dot--) + ; + if (dot < in) + return ""; + in = dot; + return in; +} + //Quake 2's tank model has a borked skin (or two). void COM_CleanUpPath(char *str) { @@ -4962,7 +4986,7 @@ void COM_ErrorMe_f(void) #ifdef LOADERTHREAD static void QDECL COM_WorkerCount_Change(cvar_t *var, char *oldvalue); cvar_t worker_flush = CVARD("worker_flush", "1", "If set, process the entire load queue, loading stuff faster but at the risk of stalling the main thread."); -cvar_t worker_count = CVARFDC("worker_count", "", CVAR_NOTFROMSERVER, "Specifies the number of worker threads to utilise.", COM_WorkerCount_Change); +cvar_t worker_count = CVARFCD("worker_count", "", CVAR_NOTFROMSERVER, COM_WorkerCount_Change, "Specifies the number of worker threads to utilise."); cvar_t worker_sleeptime = CVARFD("worker_sleeptime", "0", CVAR_NOTFROMSERVER, "Causes workers to sleep for a period of time after each job."); #define WORKERTHREADS 16 //max diff --git a/engine/common/common.h b/engine/common/common.h index e109c0ea..6d0e99de 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -339,6 +339,7 @@ void QDECL Q_strncpyz(char*d, const char*s, int n); char *Q_strcasestr(const char *haystack, const char *needle); int Q_strncasecmp (const char *s1, const char *s2, int n); int Q_strcasecmp (const char *s1, const char *s2); +int Q_strstopcasecmp(const char *s1start, const char *s1end, const char *s2); int Q_atoi (const char *str); float Q_atof (const char *str); void deleetstring(char *result, const char *leet); @@ -416,7 +417,8 @@ void COM_FileBase (const char *in, char *out, int outlen); int QDECL COM_FileSize(const char *path); void COM_DefaultExtension (char *path, const char *extension, int maxlen); qboolean COM_RequireExtension(char *path, const char *extension, int maxlen); -char *COM_FileExtension (const char *in, char *result, size_t sizeofresult); +char *COM_FileExtension (const char *in, char *result, size_t sizeofresult); //copies the extension, without the dot +const char *COM_GetFileExtension (const char *in, const char *term); //returns the extension WITH the dot, allowing for scanning backwards. void COM_CleanUpPath(char *str); char *VARGS va(const char *format, ...) LIKEPRINTF(1); @@ -432,7 +434,8 @@ extern char com_configdir[MAX_OSPATH]; //dir to put cfg_save configs in //extern char *com_basedir; //qofs_Make is used to 'construct' a variable of qofs_t type. this is so the code can merge two 32bit ints on old systems and use a long long type internally without generating warnings about bit shifts when qofs_t is only 32bit instead. -#if 1//defined(__amd64__) || defined(_AMD64_) || __WORDSIZE == 64 +//#if defined(__amd64__) || defined(_AMD64_) || __WORDSIZE == 64 +#if !defined(FTE_TARGET_WEB) && !defined(NACL) #if !defined(_MSC_VER) || _MSC_VER > 1200 #define FS_64BIT #endif @@ -440,16 +443,23 @@ extern char com_configdir[MAX_OSPATH]; //dir to put cfg_save configs in #ifdef FS_64BIT typedef quint64_t qofs_t; //type to use for a file offset #define qofs_Make(low,high) (low | (((qofs_t)(high))<<32)) - #define qofs_Low(o) ((o)&0xffffffffu) - #define qofs_High(o) ((o)>>32) - #define qofs_Error(o) ((o) == ~(quint64_t)0u) + #define qofs_Low(ofs) ((ofs)&0xffffffffu) + #define qofs_High(ofs) ((ofs)>>32) + #define qofs_Error(ofs) ((ofs) == ~(quint64_t)0u) + + #define PRIxQOFS PRIx64 + #define PRIuQOFS PRIu64 #else typedef quint32_t qofs_t; //type to use for a file offset #define qofs_Make(low,high) (low) - #define qofs_Low(o) (o) - #define qofs_High(o) (0) - #define qofs_Error(o) ((o) == ~0ul) + #define qofs_Low(ofs) (ofs) + #define qofs_High(ofs) (0) + #define qofs_Error(ofs) ((ofs) == ~0ul) + + #define PRIxQOFS "x" + #define PRIuQOFS "u" #endif +#define qofs_ErrorValue() (~(qofs_t)0u) typedef struct searchpathfuncs_s searchpathfuncs_t; typedef struct searchpath_s @@ -491,6 +501,7 @@ struct vfsfile_s; int FS_FLocateFile(const char *filename, unsigned int flags, flocation_t *loc); struct vfsfile_s *FS_OpenReadLocation(flocation_t *location); char *FS_WhichPackForLocation(flocation_t *loc, qboolean makereferenced); +qboolean FS_GetLocMTime(flocation_t *location, time_t *modtime); char *FS_GetPackageDownloadFilename(flocation_t *loc); qboolean FS_GetPackageDownloadable(const char *package); @@ -511,7 +522,13 @@ typedef struct vfsfile_s qofs_t (QDECL *GetLen) (struct vfsfile_s *file); //could give some lag qboolean (QDECL *Close) (struct vfsfile_s *file); //returns false if there was some error. void (QDECL *Flush) (struct vfsfile_s *file); - qboolean seekingisabadplan; + enum + { + SS_SEEKABLE, + SS_SLOW, //probably readonly, its fine for an occasional seek, its just really. really. slow. + SS_PIPE, //read can be seeked, write appends only. + SS_UNSEEKABLE + } seekstyle; #ifdef _DEBUG char dbgname[MAX_QPATH]; @@ -623,8 +640,10 @@ typedef struct char *downloadsurl; //optional installable files (menu) char *installupd; //which download/updated package to install. char *protocolname; //the name used for purposes of dpmaster - char *defaultexec; //execed after cvars are reset, to give game-specific defaults. + char *defaultexec; //execed after cvars are reset, to give game-specific engine-defaults. + char *defaultoverrides; //execed after default.cfg, to give usable defaults even when the mod the user is running is shit. char *eula; //when running as an installer, the user will be presented with this as a prompt + char *rtcbroker; //the broker to use for webrtc connections. struct { qboolean base; @@ -642,6 +661,7 @@ typedef struct int mirrornum; //the index we last tried to download from, so we still work even if mirrors are down. } package[64]; } ftemanifest_t; +extern ftemanifest_t *fs_manifest; //currently active manifest. void FS_Manifest_Free(ftemanifest_t *man); ftemanifest_t *FS_Manifest_Parse(const char *fname, const char *data); void PM_Shutdown(void); @@ -659,7 +679,6 @@ struct gamepacks }; void COM_Gamedir (const char *dir, const struct gamepacks *packagespaths); char *FS_GetGamedir(qboolean publicpathonly); -char *FS_GetBasedir(void); char *FS_GetManifestArgs(void); int FS_GetManifestArgv(char **argv, int maxargs); diff --git a/engine/common/cvar.c b/engine/common/cvar.c index 89213c9f..7e920c2b 100644 --- a/engine/common/cvar.c +++ b/engine/common/cvar.c @@ -422,7 +422,7 @@ showhelp: //default values are meant to be constants. //sometimes that just doesn't make sense. -//so provide a safe way to change it (MUST be initialised to NULL) +//so provide a safe way to change it (MUST be initialised to NULL so that it doesn't get freed) void Cvar_SetEngineDefault(cvar_t *var, char *val) { qboolean wasdefault = (var->defaultstr == var->enginevalue); @@ -1021,11 +1021,22 @@ Cvar_SetValue void Cvar_SetValue (cvar_t *var, float value) { char val[32]; + char *e, *p; // if (value == (int)value) // sprintf (val, "%i",(int)value); //make it look nicer. // else - sprintf (val, "%g",value); + sprintf (val, "%f",value); + p = strchr(val, '.'); + if (p) + { + e = p + strlen(p); + while (e > p && e[-1] == '0') + e--; + if (e[-1] == '.') + e--; + *e = 0; + } Cvar_Set (var, val); } void Cvar_ForceSetValue (cvar_t *var, float value) diff --git a/engine/common/cvar.h b/engine/common/cvar.h index 6bb653b1..ca9ae759 100644 --- a/engine/common/cvar.h +++ b/engine/common/cvar.h @@ -86,21 +86,21 @@ typedef struct cvar_s } cvar_t; #ifdef MINIMAL -#define CVARAFDC(ConsoleName,Value,ConsoleName2,Flags,Description,Callback) {ConsoleName, NULL, NULL, Flags, 0, 0, 0, ConsoleName2, Callback, NULL, Value} +#define CVARAFCD(ConsoleName,Value,ConsoleName2,Flags,Callback,Description) {ConsoleName, NULL, NULL, Flags, 0, 0, 0, ConsoleName2, Callback, NULL, Value} #else -#define CVARAFDC(ConsoleName,Value,ConsoleName2,Flags,Description,Callback) {ConsoleName, NULL, NULL, Flags, 0, 0, 0, ConsoleName2, Callback, Description, Value} +#define CVARAFCD(ConsoleName,Value,ConsoleName2,Flags,Callback,Description) {ConsoleName, NULL, NULL, Flags, 0, 0, 0, ConsoleName2, Callback, Description, Value} #endif -#define CVARAFD(ConsoleName,Value,ConsoleName2,Flags,Description)CVARAFDC(ConsoleName, Value, ConsoleName2, Flags, Description, NULL) -#define CVARAFC(ConsoleName,Value,ConsoleName2,Flags,Callback) CVARAFDC(ConsoleName, Value, ConsoleName2, Flags, NULL, Callback) -#define CVARAF(ConsoleName,Value,ConsoleName2,Flags) CVARAFDC(ConsoleName, Value, ConsoleName2, Flags, NULL, NULL) -#define CVARFDC(ConsoleName,Value,Flags,Description,Callback) CVARAFDC(ConsoleName, Value, NULL, Flags, Description, Callback) -#define CVARFC(ConsoleName,Value,Flags,Callback) CVARAFDC(ConsoleName, Value, NULL, Flags, NULL, Callback) -#define CVARAD(ConsoleName,Value,ConsoleName2,Description) CVARAFDC(ConsoleName, Value, ConsoleName2, 0, Description, NULL) -#define CVARFD(ConsoleName,Value,Flags,Description) CVARAFDC(ConsoleName, Value, NULL, Flags, Description, NULL) +#define CVARAFD(ConsoleName,Value,ConsoleName2,Flags,Description)CVARAFCD(ConsoleName, Value, ConsoleName2, Flags, NULL, Description) +#define CVARAFC(ConsoleName,Value,ConsoleName2,Flags,Callback) CVARAFCD(ConsoleName, Value, ConsoleName2, Flags, Callback, NULL) +#define CVARAF(ConsoleName,Value,ConsoleName2,Flags) CVARAFCD(ConsoleName, Value, ConsoleName2, Flags, NULL, NULL) +#define CVARFCD(ConsoleName,Value,Flags,Callback,Description) CVARAFCD(ConsoleName, Value, NULL, Flags, Callback, Description) +#define CVARFC(ConsoleName,Value,Flags,Callback) CVARAFCD(ConsoleName, Value, NULL, Flags, Callback, NULL) +#define CVARAD(ConsoleName,Value,ConsoleName2,Description) CVARAFCD(ConsoleName, Value, ConsoleName2, 0, NULL, Description) +#define CVARFD(ConsoleName,Value,Flags,Description) CVARAFCD(ConsoleName, Value, NULL, Flags, NULL, Description) #define CVARF(ConsoleName,Value,Flags) CVARFC(ConsoleName, Value, Flags, NULL) #define CVARC(ConsoleName,Value,Callback) CVARFC(ConsoleName, Value, 0, Callback) -#define CVARCD(ConsoleName,Value,Callback,Description) CVARAFDC(ConsoleName, Value, NULL, 0, Description, Callback) -#define CVARD(ConsoleName,Value,Description) CVARAFDC(ConsoleName, Value, NULL, 0, Description, NULL) +#define CVARCD(ConsoleName,Value,Callback,Description) CVARAFCD(ConsoleName, Value, NULL, 0, Callback, Description) +#define CVARD(ConsoleName,Value,Description) CVARAFCD(ConsoleName, Value, NULL, 0, NULL, Description) #define CVAR(ConsoleName,Value) CVARD(ConsoleName, Value, NULL) #define CVARDP4(Flags,ConsoleName,Value,Description) CVARFD(ConsoleName, Value, Flags,Description) diff --git a/engine/common/fs.c b/engine/common/fs.c index 33719926..c1348e53 100644 --- a/engine/common/fs.c +++ b/engine/common/fs.c @@ -23,8 +23,9 @@ static unsigned int fs_restarts; void *fs_thread_mutex; cvar_t com_fs_cache = CVARF("fs_cache", IFMINIMAL("2","1"), CVAR_ARCHIVE); +cvar_t fs_noreexec = CVARD("fs_noreexec", "0", "Disables automatic re-execing configs on gamedir switches.\nThis means your cvar defaults etc may be from the wrong mod, and cfg_save will leave that stuff corrupted!"); cvar_t cfg_reload_on_gamedir = CVAR("cfg_reload_on_gamedir", "1"); -cvar_t fs_game = CVARFDC("game", "", CVAR_NOSAVE|CVAR_NORESET, "Provided for Q2 compat.", fs_game_callback); +cvar_t fs_game = CVARFCD("game", "", CVAR_NOSAVE|CVAR_NORESET, fs_game_callback, "Provided for Q2 compat."); cvar_t fs_gamedir = CVARFD("fs_gamedir", "", CVAR_NOUNSAFEEXPAND|CVAR_NOSET|CVAR_NOSAVE, "Provided for Q2 compat."); cvar_t fs_basedir = CVARFD("fs_basedir", "", CVAR_NOUNSAFEEXPAND|CVAR_NOSET|CVAR_NOSAVE, "Provided for Q2 compat."); int active_fs_cachetype; @@ -210,6 +211,8 @@ void FS_Manifest_Free(ftemanifest_t *man) Z_Free(man->protocolname); Z_Free(man->eula); Z_Free(man->defaultexec); + Z_Free(man->defaultoverrides); + Z_Free(man->rtcbroker); for (i = 0; i < sizeof(man->gamepath) / sizeof(man->gamepath[0]); i++) { Z_Free(man->gamepath[i].path); @@ -247,6 +250,10 @@ static ftemanifest_t *FS_Manifest_Clone(ftemanifest_t *oldm) newm->eula = Z_StrDup(oldm->eula); if (oldm->defaultexec) newm->defaultexec = Z_StrDup(oldm->defaultexec); + if (oldm->defaultoverrides) + newm->defaultoverrides = Z_StrDup(oldm->defaultoverrides); + if (oldm->rtcbroker) + newm->rtcbroker = Z_StrDup(oldm->rtcbroker); newm->disablehomedir = oldm->disablehomedir; for (i = 0; i < sizeof(newm->gamepath) / sizeof(newm->gamepath[0]); i++) @@ -291,6 +298,10 @@ void FS_Manifest_Print(ftemanifest_t *man) Con_Printf("protocolname %s\n", COM_QuotedString(man->protocolname, buffer, sizeof(buffer), false)); if (man->defaultexec) Con_Printf("defaultexec %s\n", COM_QuotedString(man->defaultexec, buffer, sizeof(buffer), false)); + if (man->defaultoverrides) + Con_Printf("%s", man->defaultoverrides); + if (man->rtcbroker) + Con_Printf("rtcbroker %s\n", COM_QuotedString(man->rtcbroker, buffer, sizeof(buffer), false)); for (i = 0; i < sizeof(man->gamepath) / sizeof(man->gamepath[0]); i++) { @@ -531,6 +542,15 @@ static qboolean FS_Manifest_ParseTokens(ftemanifest_t *man) Z_Free(man->defaultexec); man->defaultexec = Z_StrDup(Cmd_Argv(1)); } + else if (!Q_strcasecmp(cmd, "bind") || !Q_strcasecmp(cmd, "set") || !Q_strcasecmp(cmd, "seta")) + { + Z_StrCat(&man->defaultoverrides, va("%s %s\n", Cmd_Argv(0), Cmd_Args())); + } + else if (!Q_strcasecmp(cmd, "rtcbroker")) + { + Z_Free(man->rtcbroker); + man->rtcbroker = Z_StrDup(Cmd_Argv(1)); + } else if (!Q_strcasecmp(cmd, "updateurl")) { Z_Free(man->updateurl); @@ -1618,7 +1638,7 @@ vfsfile_t *VFS_Filter(const char *filename, vfsfile_t *handle) { // char *ext; - if (!handle || handle->WriteBytes || handle->seekingisabadplan) //only on readonly files + if (!handle || handle->WriteBytes || handle->seekstyle == SS_SLOW || handle->seekstyle == SS_UNSEEKABLE) //only on readonly files for which we can undo any header read damage return handle; // ext = COM_FileExtension (filename); #ifdef AVAIL_GZDEC @@ -1878,7 +1898,7 @@ vfsfile_t *FS_OpenVFS(const char *filename, const char *mode, enum fs_relative r COM_CreatePath(fullname); vfs = VFSOS_Open(fullname, mode); } - if (vfs && (*mode == 'w' || *mode == 'a')) + if (vfs || !(*mode == 'w' || *mode == 'a')) return vfs; //fall through case FS_PUBGAMEONLY: @@ -1945,6 +1965,14 @@ vfsfile_t *FS_OpenVFS(const char *filename, const char *mode, enum fs_relative r return NULL; } +qboolean FS_GetLocMTime(flocation_t *location, time_t *modtime) +{ + *modtime = 0; + if (!location->search->handle->FileStat || !location->search->handle->FileStat(location->search->handle, location, modtime)) + return false; + return true; +} + /*opens a vfsfile from an already discovered location*/ vfsfile_t *FS_OpenReadLocation(flocation_t *location) { @@ -5021,6 +5049,7 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean qboolean reloadconfigs = false; qboolean builtingame = false; flocation_t loc; + qboolean allowvidrestart = true; char *vidfile[] = {"gfx.wad", "gfx/conback.lmp", //misc stuff "gfx/palette.lmp", "pics/colormap.pcx"}; //palettes @@ -5033,13 +5062,26 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean for (i = 0; i < countof(vidfile); i++) { - FS_FLocateFile(vidfile[i], FSLF_IFFOUND, &loc); //q1 - vidpath[i] = loc.search?loc.search->handle:NULL; + if (allowvidrestart) + { + FS_FLocateFile(vidfile[i], FSLF_IFFOUND, &loc); //q1 + vidpath[i] = loc.search?loc.search->handle:NULL; + } + else + vidpath[i] = NULL; } + + if (allowreloadconfigs && fs_noreexec.ival) + allowreloadconfigs = false; for (i = 0; i < countof(conffile); i++) { - FS_FLocateFile(conffile[i], FSLF_IFFOUND, &loc); //q1 - confpath[i] = loc.search?loc.search->handle:NULL; + if (allowreloadconfigs) + { + FS_FLocateFile(conffile[i], FSLF_IFFOUND, &loc); //q1 + confpath[i] = loc.search?loc.search->handle:NULL; + } + else + confpath[i] = NULL; } #if defined(NACL) || defined(FTE_TARGET_WEB) || defined(ANDROID) || defined(WINRT) @@ -5153,7 +5195,7 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean } } - if (!man->downloadsurl) + if (!man->downloadsurl && gamemode_info[i].downloadsurl) { Cmd_TokenizeString(va("downloadsurl \"%s\"", gamemode_info[i].downloadsurl), false, false); FS_Manifest_ParseTokens(man); @@ -5262,7 +5304,7 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean COM_CheckRegistered(); - if (qrenderer != QR_NONE) + if (qrenderer != QR_NONE && allowvidrestart) { for (i = 0; i < countof(vidfile); i++) { diff --git a/engine/common/fs.h b/engine/common/fs.h index e5bece0f..c2dc08d1 100644 --- a/engine/common/fs.h +++ b/engine/common/fs.h @@ -47,6 +47,7 @@ struct searchpathfuncs_s qboolean (QDECL *PollChanges)(searchpathfuncs_t *handle); //returns true if there were changes + qboolean (QDECL *FileStat)(searchpathfuncs_t *handle, flocation_t *loc, time_t *mtime); qboolean (QDECL *RenameFile)(searchpathfuncs_t *handle, const char *oldname, const char *newname); //returns true on success, false if source doesn't exist, or if dest does. qboolean (QDECL *RemoveFile)(searchpathfuncs_t *handle, const char *filename); //returns true on success, false if it wasn't found or is readonly. qboolean (QDECL *MkDir)(searchpathfuncs_t *handle, const char *filename); //is this really needed? @@ -86,5 +87,9 @@ int FS_EnumerateKnownGames(qboolean (*callback)(void *usr, ftemanifest_t *man), #define SPF_BASEPATH 128 //part of the basegames, and not the mod gamedir(s). qboolean FS_LoadPackageFromFile(vfsfile_t *vfs, char *pname, char *localname, int *crc, unsigned int flags); +#ifdef AVAIL_XZDEC vfsfile_t *FS_XZ_DecompressWriteFilter(vfsfile_t *infile); +#endif +#ifdef AVAIL_GZDEC vfsfile_t *FS_GZ_WriteFilter(vfsfile_t *outfile, qboolean autoclosefile, qboolean compress); +#endif diff --git a/engine/common/fs_stdio.c b/engine/common/fs_stdio.c index ec9b19c9..99dd5786 100644 --- a/engine/common/fs_stdio.c +++ b/engine/common/fs_stdio.c @@ -93,7 +93,9 @@ vfsfile_t *FSSTDIO_OpenTemp(void) #ifdef _WIN32 /*warning: annother app might manage to open the file before we can. if the file is not opened exclusively then we can end up with issues - on windows, fopen is typically exclusive anyway, but not on unix. but on unix, tmpfile is actually usable, so special-case the windows code*/ + on windows, fopen is typically exclusive anyway, but not on unix. but on unix, tmpfile is actually usable, so special-case the windows code + we also have a special close function to ensure the file is deleted too + */ char *fname = _tempnam(NULL, "ftemp"); f = fopen(fname, "w+b"); if (!f) @@ -321,6 +323,18 @@ static int QDECL FSSTDIO_EnumerateFiles (searchpathfuncs_t *handle, const char * return Sys_EnumerateFiles(sp->rootpath, match, func, parm, handle); } +#include +static qboolean QDECL FSSTDIO_FileStat (searchpathfuncs_t *handle, flocation_t *loc, time_t *mtime) +{ + struct stat s; + if (stat(loc->rawname, &s) != -1) + { + *mtime = s.st_mtime; + return true; + } + return false; +} + searchpathfuncs_t *QDECL FSSTDIO_OpenPath(vfsfile_t *mustbenull, const char *desc, const char *prefix) { @@ -345,6 +359,7 @@ searchpathfuncs_t *QDECL FSSTDIO_OpenPath(vfsfile_t *mustbenull, const char *des np->pub.EnumerateFiles = FSSTDIO_EnumerateFiles; np->pub.OpenVFS = FSSTDIO_OpenVFS; np->pub.PollChanges = FSSTDIO_PollChanges; + np->pub.FileStat = FSSTDIO_FileStat; return &np->pub; } diff --git a/engine/common/fs_win32.c b/engine/common/fs_win32.c index fe6fe418..9623c5c1 100644 --- a/engine/common/fs_win32.c +++ b/engine/common/fs_win32.c @@ -316,6 +316,7 @@ static vfsfile_t *QDECL VFSW32_OpenInternal(vfsw32path_t *handle, const char *qu unsigned int fsize; void *mmap; qboolean didexist = true; + qboolean create; vfsw32file_t *file; qboolean read = !!strchr(mode, 'r'); @@ -323,6 +324,7 @@ static vfsfile_t *QDECL VFSW32_OpenInternal(vfsw32path_t *handle, const char *qu qboolean append = !!strchr(mode, 'a'); qboolean text = !!strchr(mode, 't'); write |= append; + create = write; if (strchr(mode, '+')) read = write = true; @@ -332,7 +334,9 @@ static vfsfile_t *QDECL VFSW32_OpenInternal(vfsw32path_t *handle, const char *qu if (!WinNT) { //FILE_SHARE_DELETE is not supported in 9x, sorry. - if ((write && read) || append) + if (!create && write) + h = CreateFileA(osname, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + else if ((write && read) || append) h = CreateFileA(osname, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); else if (write) h = CreateFileA(osname, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); @@ -356,6 +360,8 @@ static vfsfile_t *QDECL VFSW32_OpenInternal(vfsw32path_t *handle, const char *qu if (h != INVALID_HANDLE_VALUE) ; + else if (!create && write) + h = CreateFileW(wide, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); else if ((write && read) || append) h = CreateFileW(wide, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_DELETE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); else if (write) @@ -430,6 +436,25 @@ vfsfile_t *QDECL VFSW32_Open(const char *osname, const char *mode) return VFSW32_OpenInternal(NULL, NULL, osname, mode); } +#include +static qboolean QDECL VFSW32_FileStat(searchpathfuncs_t *handle, flocation_t *loc, time_t *mtime) +{ + int r; + struct _stat s; + if (WinNT) + { + wchar_t wide[MAX_OSPATH]; + widen(wide, sizeof(wide), loc->rawname); + r = _wstat(wide, &s); + } + else + r = _stat(loc->rawname, &s); + if (r) + return false; + *mtime = s.st_mtime; + return true; +} + static vfsfile_t *QDECL VFSW32_OpenVFS(searchpathfuncs_t *handle, flocation_t *loc, const char *mode) { //path is already cleaned, as anything that gets a valid loc needs cleaning up first. @@ -641,6 +666,8 @@ searchpathfuncs_t *QDECL VFSW32_OpenPath(vfsfile_t *mustbenull, const char *desc np->pub.OpenVFS = VFSW32_OpenVFS; np->pub.PollChanges = VFSW32_PollChanges; + np->pub.FileStat = VFSW32_FileStat; + np->pub.RenameFile = VFSW32_RenameFile; np->pub.RemoveFile = VFSW32_RemoveFile; np->pub.MkDir = VFSW32_MkDir; diff --git a/engine/common/fs_xz.c b/engine/common/fs_xz.c index 84551074..ca828762 100644 --- a/engine/common/fs_xz.c +++ b/engine/common/fs_xz.c @@ -3112,7 +3112,7 @@ vfsfile_t *FS_XZ_DecompressWriteFilter(vfsfile_t *outfile) n->vf.Tell = NULL; n->vf.Close = FS_XZ_Dec_Close; n->vf.WriteBytes = FS_XZ_Dec_Write; - n->vf.seekingisabadplan = true; + n->vf.seekstyle = SS_UNSEEKABLE; return &n->vf; } diff --git a/engine/common/fs_zip.c b/engine/common/fs_zip.c index e6d7a2b1..17322a08 100644 --- a/engine/common/fs_zip.c +++ b/engine/common/fs_zip.c @@ -484,7 +484,7 @@ vfsfile_t *FS_GZ_WriteFilter(vfsfile_t *outfile, qboolean autoclosefile, qboolea n->vf.Tell = NULL; n->vf.Close = FS_GZ_Dec_Close; n->vf.WriteBytes = FS_GZ_Dec_Write; - n->vf.seekingisabadplan = true; + n->vf.seekstyle = SS_UNSEEKABLE; if (n->compress) { @@ -1049,6 +1049,13 @@ static qboolean QDECL VFSZIP_Close (struct vfsfile_s *file) static qboolean FSZIP_ValidateLocalHeader(zipfile_t *zip, zpackfile_t *zfile, qofs_t *datastart, qofs_t *datasize); +static qboolean QDECL FSZIP_FileStat (searchpathfuncs_t *handle, flocation_t *loc, time_t *mtime) +{ + zpackfile_t *pf = loc->fhandle; + *mtime = pf->mtime; + return true; +} + static vfsfile_t *QDECL FSZIP_OpenVFS(searchpathfuncs_t *handle, flocation_t *loc, const char *mode) { zipfile_t *zip = (void*)handle; @@ -1082,7 +1089,7 @@ static vfsfile_t *QDECL FSZIP_OpenVFS(searchpathfuncs_t *handle, flocation_t *lo vfsz->funcs.Seek = VFSZIP_Seek; vfsz->funcs.Tell = VFSZIP_Tell; vfsz->funcs.WriteBytes = NULL; - vfsz->funcs.seekingisabadplan = true; + vfsz->funcs.seekstyle = SS_SLOW; if (!FSZIP_ValidateLocalHeader(zip, pf, &vfsz->startpos, &datasize)) { @@ -1806,6 +1813,7 @@ searchpathfuncs_t *QDECL FSZIP_LoadArchive (vfsfile_t *packhandle, const char *d zip->pub.FindFile = FSZIP_FLocate; zip->pub.ReadFile = FSZIP_ReadFile; zip->pub.EnumerateFiles = FSZIP_EnumerateFiles; + zip->pub.FileStat = FSZIP_FileStat; zip->pub.GeneratePureCRC = FSZIP_GeneratePureCRC; zip->pub.OpenVFS = FSZIP_OpenVFS; return &zip->pub; diff --git a/engine/common/net.h b/engine/common/net.h index a8028ec7..9baf13db 100644 --- a/engine/common/net.h +++ b/engine/common/net.h @@ -25,17 +25,39 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define HAVE_WEBSOCKCL #endif -//FIXME: should split this into loopback/dgram/stream/irc +//FIXME: should split this into loopback/dgram/stream/dtls/tls/irc //with the ipv4/v6/x as a separate parameter -typedef enum {NA_INVALID, NA_LOOPBACK, NA_IP, NA_IPV6, NA_IPX, NA_BROADCAST_IP, NA_BROADCAST_IP6, NA_BROADCAST_IPX, NA_TCP, NA_TCPV6, NA_TLSV4, NA_TLSV6, NA_IRC, NA_WEBSOCKET, NA_NATPMP} netadrtype_t; +typedef enum { + NA_INVALID, + NA_LOOPBACK, + /*NA_HYBRID,*/ //ipv6 hybrid socket that might accept ipv4 packets too. + NA_IP, + NA_IPV6, + NA_IPX, +#ifdef IRCCONNECT + NA_IRC/*remove!*/, +#endif +#ifdef HAVE_WEBSOCKCL + NA_WEBSOCKET, +#endif +} netadrtype_t; +typedef enum { + NP_DGRAM, + NP_DTLS, //connected via ICE/WebRTC + NP_STREAM, + NP_TLS, + NP_WS, + NP_WSS, + NP_IRC, + NP_NATPMP +} netproto_t; typedef enum {NS_CLIENT, NS_SERVER} netsrc_t; -typedef enum {NQP_ERROR, NQP_DATAGRAM, NQP_RELIABLE} nqprot_t; - typedef struct { netadrtype_t type; + netproto_t prot; union { qbyte ip[4]; @@ -105,6 +127,7 @@ int NET_LocalAddressForRemote(struct ftenet_connections_s *collection, netadr_ void NET_PrintAddresses(struct ftenet_connections_s *collection); qboolean NET_AddressSmellsFunny(netadr_t *a); qboolean NET_EnsureRoute(struct ftenet_connections_s *collection, char *routename, char *host, qboolean islisten); +void NET_PrintConnectionsStatus(struct ftenet_connections_s *collection); enum addressscope_e { @@ -115,16 +138,18 @@ enum addressscope_e }; enum addressscope_e NET_ClassifyAddress(netadr_t *adr, char **outdesc); +qboolean NET_AddrIsReliable(netadr_t *adr); //hints that the protocol is reliable. if so, we don't need to wait for acks qboolean NET_CompareAdr (netadr_t *a, netadr_t *b); qboolean NET_CompareBaseAdr (netadr_t *a, netadr_t *b); void NET_AdrToStringResolve (netadr_t *adr, void (*resolved)(void *ctx, void *data, size_t a, size_t b), void *ctx, size_t a, size_t b); char *NET_AdrToString (char *s, int len, netadr_t *a); +char *NET_SockadrToString (char *s, int len, struct sockaddr_qstorage *a); char *NET_BaseAdrToString (char *s, int len, netadr_t *a); size_t NET_StringToSockaddr2 (const char *s, int defaultport, struct sockaddr_qstorage *sadr, int *addrfamily, int *addrsize, size_t addrcount); #define NET_StringToSockaddr(s,p,a,f,z) (NET_StringToSockaddr2(s,p,a,f,z,1)>0) size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t addrcount); #define NET_StringToAdr(s,p,a) NET_StringToAdr2(s,p,a,1) -qboolean NET_PortToAdr (int adrfamily, const char *s, netadr_t *a); +qboolean NET_PortToAdr (netadrtype_t adrfamily, netproto_t adrprot, const char *s, netadr_t *a); qboolean NET_IsClientLegal(netadr_t *adr); qboolean NET_IsLoopBackAddress (netadr_t *adr); @@ -134,7 +159,7 @@ char *NET_AdrToStringMasked (char *s, int len, netadr_t *a, netadr_t *amask); void NET_IntegerToMask (netadr_t *a, netadr_t *amask, int bits); qboolean NET_CompareAdrMasked(netadr_t *a, netadr_t *b, netadr_t *mask); -qboolean FTENET_AddToCollection(struct ftenet_connections_s *col, const char *name, const char *address, netadrtype_t addrtype, qboolean islisten); +qboolean FTENET_AddToCollection(struct ftenet_connections_s *col, const char *name, const char *address, netadrtype_t addrtype, netproto_t addrprot, qboolean islisten); //============================================================================ @@ -230,7 +255,7 @@ qboolean Netchan_CanPacket (netchan_t *chan, int rate); void Netchan_Block (netchan_t *chan, int bytes, int rate); qboolean Netchan_CanReliable (netchan_t *chan, int rate); #ifdef NQPROT -nqprot_t NQNetChan_Process(netchan_t *chan); +qboolean NQNetChan_Process(netchan_t *chan); #endif #ifdef HUFFNETWORK @@ -299,9 +324,9 @@ void Huff_EmitByte(int ch, qbyte *buffer, int *count); #endif -int UDP_OpenSocket (int port, qboolean bcast); -int UDP6_OpenSocket (int port, qboolean bcast); -int IPX_OpenSocket (int port, qboolean bcast); +int UDP_OpenSocket (int port); +int UDP6_OpenSocket (int port); +int IPX_OpenSocket (int port); int NetadrToSockadr (netadr_t *a, struct sockaddr_qstorage *s); void SockadrToNetadr (struct sockaddr_qstorage *s, netadr_t *a); qboolean NET_Sleep(float seconds, qboolean stdinissocket); diff --git a/engine/common/net_chan.c b/engine/common/net_chan.c index 2b4e75b9..1b6d96fa 100644 --- a/engine/common/net_chan.c +++ b/engine/common/net_chan.c @@ -401,7 +401,7 @@ qboolean ServerPaused(void); #endif #ifdef NQPROT -nqprot_t NQNetChan_Process(netchan_t *chan) +qboolean NQNetChan_Process(netchan_t *chan) { int header; int sequence; @@ -412,10 +412,10 @@ nqprot_t NQNetChan_Process(netchan_t *chan) header = LongSwap(MSG_ReadLong()); if (net_message.cursize != (header & NETFLAG_LENGTH_MASK)) - return NQP_ERROR; //size was wrong, couldn't have been ours. + return false; //size was wrong, couldn't have been ours. if (header & NETFLAG_CTL) - return NQP_ERROR; //huh? + return false; //huh? sequence = LongSwap(MSG_ReadLong()); @@ -452,7 +452,7 @@ nqprot_t NQNetChan_Process(netchan_t *chan) , sequence , 0); - return NQP_ERROR; //don't try execing the 'payload'. I hate ack packets. + return false; //don't try execing the 'payload'. I hate ack packets. } if (header & NETFLAG_UNRELIABLE) @@ -461,7 +461,7 @@ nqprot_t NQNetChan_Process(netchan_t *chan) { if (showdrop.ival) Con_Printf("Stale datagram recieved (%i<=%i)\n", sequence, chan->incoming_unreliable); - return NQP_ERROR; + return false; } drop = sequence - chan->incoming_unreliable - 1; if (drop > 0) @@ -489,7 +489,7 @@ nqprot_t NQNetChan_Process(netchan_t *chan) , chan->sock != NS_SERVER?"s2c":"c2s" , chan->incoming_unreliable , net_message.cursize); - return NQP_DATAGRAM; + return true; } if (header & NETFLAG_DATA) { @@ -512,7 +512,7 @@ nqprot_t NQNetChan_Process(netchan_t *chan) if (chan->in_fragment_length + net_message.cursize-8 >= sizeof(chan->in_fragment_buf)) { chan->fatal_error = true; - return NQP_ERROR; + return false; } memcpy(chan->in_fragment_buf + chan->in_fragment_length, net_message.data+8, net_message.cursize-8); @@ -530,7 +530,7 @@ nqprot_t NQNetChan_Process(netchan_t *chan) , chan->sock != NS_SERVER?"s2c":"c2s" , sequence , net_message.cursize); - return NQP_RELIABLE; //we can read it now + return true; //we can read it now } } else @@ -539,10 +539,10 @@ nqprot_t NQNetChan_Process(netchan_t *chan) Con_Printf("Stale reliable (%i)\n", sequence); } - return NQP_ERROR; + return false; } - return NQP_ERROR; //not supported. + return false; //not supported. } #endif @@ -575,7 +575,7 @@ int Netchan_Transmit (netchan_t *chan, int length, qbyte *data, int rate) send.maxsize = MAX_NQMSGLEN + PACKET_HEADER; send.cursize = 0; - if ((chan->remote_address.type == NA_TCP || chan->remote_address.type == NA_TCPV6 || chan->remote_address.type == NA_TLSV4 || chan->remote_address.type == NA_TLSV6) && chan->reliable_length) + if (NET_AddrIsReliable(&chan->remote_address) && chan->reliable_length) { //if over tcp, everything is assumed to be reliable. pretend it got acked. chan->reliable_length = 0; //they got the entire message @@ -606,7 +606,7 @@ int Netchan_Transmit (netchan_t *chan, int length, qbyte *data, int rate) { MSG_WriteLong(&send, 0); MSG_WriteLong(&send, LongSwap(chan->reliable_sequence)); - if (i > MAX_NQDATAGRAM && chan->remote_address.type != NA_TCP) + if (i > MAX_NQDATAGRAM && !NET_AddrIsReliable(&chan->remote_address)) i = MAX_NQDATAGRAM; SZ_Write (&send, chan->reliable_buf+chan->reliable_start, i); diff --git a/engine/common/net_ice.c b/engine/common/net_ice.c index 85d36491..219b24a3 100644 --- a/engine/common/net_ice.c +++ b/engine/common/net_ice.c @@ -76,6 +76,10 @@ struct icestate_s char *conname; //internal id. char *friendlyname; //who you're talking to. + unsigned int originid; //should be randomish + unsigned int originversion;//bumped each time something in the sdp changes. + char originaddress[16]; + struct icecandidate_s *lc; char *lpwd; char *lufrag; @@ -277,21 +281,24 @@ struct icestate_s *QDECL ICE_Create(void *module, const char *conname, const cha Sys_RandomBytes((void*)rnd, sizeof(rnd)); conname = va("fte%08x%08x", rnd[0], rnd[1]); } - + con = Z_Malloc(sizeof(*con)); con->conname = Z_StrDup(conname); con->friendlyname = Z_StrDup(peername); con->proto = proto; con->rpwd = Z_StrDup(""); con->rufrag = Z_StrDup(""); + Sys_RandomBytes((void*)&con->originid, sizeof(con->originid)); + con->originversion = 1; + Q_strncpyz(con->originaddress, "127.0.0.1", sizeof(con->originaddress)); con->mode = mode; if (!collection) { con->connections = collection = FTENET_CreateCollection(true); - FTENET_AddToCollection(collection, "UDP", "0", NA_IP, true); - FTENET_AddToCollection(collection, "natpmp", "natpmp://5351", NA_IP, true); + FTENET_AddToCollection(collection, "UDP", "0", NA_IP, NP_DGRAM, true); + FTENET_AddToCollection(collection, "natpmp", "natpmp://5351", NA_IP, NP_NATPMP, true); } con->next = icelist; @@ -317,7 +324,7 @@ struct icestate_s *QDECL ICE_Create(void *module, const char *conname, const cha netadr_t addr[64]; struct ftenet_generic_connection_s *gcon[sizeof(addr)/sizeof(addr[0])]; - int flags[sizeof(addr)/sizeof(addr[0])]; + unsigned int flags[sizeof(addr)/sizeof(addr[0])]; m = NET_EnumerateAddresses(collection, gcon, flags, addr, sizeof(addr)/sizeof(addr[0])); @@ -325,29 +332,10 @@ struct icestate_s *QDECL ICE_Create(void *module, const char *conname, const cha { if (addr[i].type == NA_IP || addr[i].type == NA_IPV6) { - ICE_AddLCandidateInfo(con, &addr[i], i, ICE_HOST); - - /* - cand = Z_Malloc(sizeof(*cand)); - cand->info.network = i; - cand->info.port = ntohs(adr.port); - adr.port = 0; //to make sure its not part of the string... - Q_strncpyz(cand->info.addr, NET_AdrToString(adrbuf, sizeof(adrbuf), &adr), sizeof(cand->info.addr)); - cand->info.generation = 0; - cand->info.component = 1; - cand->info.foundation = 1; - cand->info.priority = - (1<<24)*(126) + - (1<<8)*((adr.type == NA_IP?32768:0)+net*256+(255-adrno)) + - (1<<0)*(256 - cand->info.component); - - Sys_RandomBytes((void*)rnd, sizeof(rnd)); - Q_strncpyz(cand->info.candidateid, va("x%08x%08x", rnd[0], rnd[1]), sizeof(cand->info.candidateid)); - cand->dirty = true; - - cand->next = con->lc; - con->lc = cand; - */ +// if (flags[i] & ADDR_REFLEX) +// ICE_AddLCandidateInfo(con, &addr[i], i, ICE_SRFLX); //FIXME: needs reladdr relport info +// else + ICE_AddLCandidateInfo(con, &addr[i], i, ICE_HOST); } } } @@ -685,7 +673,6 @@ qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const char *val if (!NET_StringToAdr(con->stunserver, con->stunport, &con->pubstunserver)) return false; } -/* else if (!strcmp(prop, "sdp")) { const char *eol; @@ -694,6 +681,8 @@ qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const char *val eol = strchr(value, '\n'); if (!eol) eol = value+strlen(value); + else + eol++; if (!strncmp(value, "a=ice-pwd:", 10)) ICE_Set(con, "rpwd", value+10); @@ -705,7 +694,7 @@ qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const char *val int codec; char *sl; value += 9; - codec = strtoul(value, &value, 0); + codec = strtoul(value, (char**)&value, 0); if (*value == ' ') value++; COM_ParseOut(value, name, sizeof(name)); @@ -720,10 +709,10 @@ qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const char *val memset(&n, 0, sizeof(n)); value += 12; - n.foundation = strtoul(value, &value, 0); + n.foundation = strtoul(value, (char**)&value, 0); if(*value == ' ')value++; - n.component = strtoul(value, &value, 0); + n.component = strtoul(value, (char**)&value, 0); if(*value == ' ')value++; if (!strncmp(value, "UDP ", 4)) @@ -735,14 +724,14 @@ qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const char *val break; if(*value == ' ')value++; - n.priority = strtoul(value, &value, 0); + n.priority = strtoul(value, (char**)&value, 0); if(*value == ' ')value++; value = COM_ParseOut(value, n.addr, sizeof(n.addr)); if (!value) break; if(*value == ' ')value++; - n.port = strtoul(value, &value, 0); + n.port = strtoul(value, (char**)&value, 0); if(*value == ' ')value++; if (strncmp(value, "typ ", 4)) break; @@ -773,7 +762,7 @@ qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const char *val else if (!strncmp(value, "rport ", 6)) { value += 6; - n.relport = strtoul(value, &value, 0); + n.relport = strtoul(value, (char**)&value, 0); } else { @@ -789,12 +778,40 @@ qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const char *val } } } -*/ else return false; return true; } -qboolean QDECL ICE_Get(struct icestate_s *con, const char *prop, char *value, int valuelen) +static char *ICE_CandidateToSDP(struct icecandidate_s *can, char *value, size_t valuelen) +{ + char *ctype = "?"; + switch(can->info.type) + { + default: + case ICE_HOST: ctype = "host"; break; + case ICE_SRFLX: ctype = "srflx"; break; + case ICE_PRFLX: ctype = "prflx"; break; + case ICE_RELAY: ctype = "relay"; break; + } + Q_snprintfz(value, valuelen, "candidate:%i %i %s %i %s %i typ %s", + can->info.foundation, + can->info.component, + can->info.transport==0?"udp":"ERROR", + can->info.priority, + can->info.addr, + can->info.port, + ctype + ); + Q_strncatz(value, va(" generation %i", can->info.generation), valuelen); + if (can->info.type != ICE_HOST) + { + Q_strncatz(value, va(" raddr %s", can->info.reladdr), valuelen); + Q_strncatz(value, va(" rport %i", can->info.relport), valuelen); + } + + return value; +} +qboolean QDECL ICE_Get(struct icestate_s *con, const char *prop, char *value, size_t valuelen) { if (!strcmp(prop, "sid")) Q_strncpyz(value, con->conname, valuelen); @@ -828,7 +845,6 @@ qboolean QDECL ICE_Get(struct icestate_s *con, const char *prop, char *value, in } } } -/* else if (!strcmp(prop, "sdp")) { struct icecandidate_s *can; @@ -848,13 +864,18 @@ qboolean QDECL ICE_Get(struct icestate_s *con, const char *prop, char *value, in } Q_strncpyz(value, "v=0\n", valuelen); - Q_strncatz(value, va("o=$NAME $? $? IN IP4 $ADR\n"), valuelen); - Q_strncatz(value, "s=\n", valuelen); + Q_strncatz(value, va("o=%s %u %u IN IP4 %s\n", "-", con->originid, con->originversion, con->originaddress), valuelen); //originator + Q_strncatz(value, va("s=%s\n", con->conname), valuelen); Q_strncatz(value, va("c=IN %s %s\n", sender.type==NA_IPV6?"IP6":"IP4", NET_BaseAdrToString(tmpstr, sizeof(tmpstr), &sender)), valuelen); Q_strncatz(value, "t=0 0\n", valuelen); Q_strncatz(value, va("a=ice-pwd:%s\n", con->lpwd), valuelen); Q_strncatz(value, va("a=ice-ufrag:%s\n", con->lufrag), valuelen); - + + if (con->proto == ICEP_QWSERVER || con->proto == ICEP_QWCLIENT) + { + Q_strncatz(value, "m=application 9 DTLS/SCTP 5000\n", valuelen); + } + for (i = 0; i < countof(con->codec); i++) { int codec = atoi(prop+5); @@ -876,39 +897,35 @@ qboolean QDECL ICE_Get(struct icestate_s *con, const char *prop, char *value, in for (can = con->lc; can; can = can->next) { - char *ctype = NULL; + char canline[256]; can->dirty = false; //doesn't matter now. - switch(can->info.type) - { - default: - case ICE_HOST: ctype = "host"; break; - case ICE_SRFLX: ctype = "srflx"; break; - case ICE_PRFLX: ctype = "prflx"; break; - case ICE_RELAY: ctype = "relay"; break; - } - Q_strncatz(value, va("a=candidate:%i %i %s %i %s %i typ %s", - can->info.foundation, - can->info.component, - can->info.transport==0?"UDP":"ERROR", - can->info.priority, - can->info.addr, - can->info.port, - ctype - ), valuelen); - if (can->info.type != ICE_HOST) - { - Q_strncatz(value, va(" raddr %s", can->info.reladdr), valuelen); - Q_strncatz(value, va(" rport %i", can->info.relport), valuelen); - } + Q_strncatz(value, "a=\n", valuelen); + ICE_CandidateToSDP(can, canline, sizeof(canline)); + Q_strncatz(value, canline, valuelen); Q_strncatz(value, "\n", valuelen); } } } -*/ else return false; return true; } +qboolean QDECL ICE_GetLCandidateSDP(struct icestate_s *con, char *out, size_t outsize) +{ + struct icecandidate_s *can; + for (can = con->lc; can; can = can->next) + { + if (can->dirty) + { + struct icecandinfo_s *c = &can->info; + can->dirty = false; + + ICE_CandidateToSDP(can, out, outsize); + return true; + } + } + return false; +} struct icecandinfo_s *QDECL ICE_GetLCandidateInfo(struct icestate_s *con) { struct icecandidate_s *can; @@ -1091,7 +1108,8 @@ icefuncs_t iceapi = ICE_GetLCandidateInfo, ICE_AddRCandidateInfo, ICE_Close, - ICE_CloseModule + ICE_CloseModule, + ICE_GetLCandidateSDP }; qboolean ICE_WasStun(netsrc_t netsrc) diff --git a/engine/common/net_ssl_gnutls.c b/engine/common/net_ssl_gnutls.c index ba24ac2a..d32827dc 100644 --- a/engine/common/net_ssl_gnutls.c +++ b/engine/common/net_ssl_gnutls.c @@ -680,7 +680,7 @@ vfsfile_t *FS_OpenSSL(const char *hostname, vfsfile_t *source, qboolean server, newf->funcs.WriteBytes = SSL_Write; newf->funcs.Seek = SSL_Seek; newf->funcs.Tell = SSL_Tell; - newf->funcs.seekingisabadplan = true; + newf->funcs.seekingisabadplan = SS_UNSEEKABLE; Q_strncpyz(newf->certname, hostname, sizeof(newf->certname)); diff --git a/engine/common/net_ssl_winsspi.c b/engine/common/net_ssl_winsspi.c index 629bd718..0a0ac70b 100644 --- a/engine/common/net_ssl_winsspi.c +++ b/engine/common/net_ssl_winsspi.c @@ -25,6 +25,13 @@ cvar_t *tls_ignorecertificateerrors; #define USE_PROT_DGRAM_SERVER (SP_PROT_DTLS_SERVER) #define USE_PROT_DGRAM_CLIENT (SP_PROT_DTLS_CLIENT) +#ifndef szOID_RSA_SHA512RSA +#define szOID_RSA_SHA512RSA "1.2.840.113549.1.1.13" +#endif +#ifndef SCH_CRED_SNI_CREDENTIAL +#define SCH_CRED_SNI_CREDENTIAL 0x00080000 +#endif + //hungarian ensures we hit no macros. static struct { @@ -501,9 +508,8 @@ static DWORD VerifyServerCertificate(PCCERT_CONTEXT pServerCert, PWSTR pwszServe crypt.pCertFreeCertificateChain(pChainContext); } - return Status; + return Status; } - static PCCERT_CONTEXT SSPI_GetServerCertificate(void) { static PCCERT_CONTEXT ret; @@ -516,8 +522,8 @@ static PCCERT_CONTEXT SSPI_GetServerCertificate(void) if (ret) return ret; - memset(&sigalg, 0, sizeof(sigalg)); - sigalg.pszObjId = szOID_RSA_SHA1RSA; + memset(&sigalg, 0, sizeof(sigalg)); + sigalg.pszObjId = szOID_RSA_SHA512RSA; GetSystemTime(&expiredate); expiredate.wYear += 2; //2 years hence. woo @@ -538,6 +544,20 @@ static PCCERT_CONTEXT SSPI_GetServerCertificate(void) &expiredate, NULL ); + if (!ret) + { //try and downgrade the signature algo if it failed. + sigalg.pszObjId = szOID_RSA_SHA1RSA; + ret = crypt.pCertCreateSelfSignCertificate( + 0, + &issuerblob, + 0, + NULL, + &sigalg, + NULL, + &expiredate, + NULL + ); + } Z_Free(issuerblob.pbData); return ret; @@ -578,12 +598,15 @@ static void SSPI_Handshake (sslfile_t *f) SECURITY_STATUS ss; TimeStamp Lifetime; SecBufferDesc OutBuffDesc; - SecBuffer OutSecBuff; + SecBuffer OutSecBuff[2]; SecBufferDesc InBuffDesc; - SecBuffer InSecBuff[2]; + SecBuffer InSecBuff[3]; ULONG ContextAttributes; SCHANNEL_CRED SchannelCred; + char buf1[128]; + char buf2[128]; + if (f->outcrypt.avail) { //don't let things build up too much @@ -595,12 +618,16 @@ static void SSPI_Handshake (sslfile_t *f) //FIXME: skip this if we've had no new data since last time OutBuffDesc.ulVersion = SECBUFFER_VERSION; - OutBuffDesc.cBuffers = 1; - OutBuffDesc.pBuffers = &OutSecBuff; + OutBuffDesc.cBuffers = 2; + OutBuffDesc.pBuffers = OutSecBuff; - OutSecBuff.cbBuffer = f->outcrypt.datasize - f->outcrypt.avail; - OutSecBuff.BufferType = SECBUFFER_TOKEN; - OutSecBuff.pvBuffer = f->outcrypt.data + f->outcrypt.avail; + OutSecBuff[0].cbBuffer = f->outcrypt.datasize - f->outcrypt.avail; + OutSecBuff[0].BufferType = SECBUFFER_TOKEN; + OutSecBuff[0].pvBuffer = f->outcrypt.data + f->outcrypt.avail; + + OutSecBuff[1].BufferType = 16;//SECBUFFER_TARGET_HOST; + OutSecBuff[1].pvBuffer = buf1; + OutSecBuff[1].cbBuffer = sizeof(buf1); if (f->handshaking == HS_ERROR) return; //gave up. @@ -612,7 +639,7 @@ static void SSPI_Handshake (sslfile_t *f) memset(&SchannelCred, 0, sizeof(SchannelCred)); SchannelCred.dwVersion = SCHANNEL_CRED_VERSION; SchannelCred.grbitEnabledProtocols = f->datagram?USE_PROT_DGRAM_CLIENT:USE_PROT_CLIENT; - SchannelCred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS; /*don't use windows login info or anything*/ + SchannelCred.dwFlags |= SCH_CRED_SNI_CREDENTIAL | SCH_CRED_NO_DEFAULT_CREDS; /*don't use windows login info or anything*/ ss = secur.pAcquireCredentialsHandleA (NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, NULL, &SchannelCred, NULL, NULL, &f->cred, &Lifetime); if (ss < 0) @@ -665,7 +692,7 @@ static void SSPI_Handshake (sslfile_t *f) return; InBuffDesc.ulVersion = SECBUFFER_VERSION; - InBuffDesc.cBuffers = 2; + InBuffDesc.cBuffers = 3; InBuffDesc.pBuffers = InSecBuff; InSecBuff[0].BufferType = SECBUFFER_TOKEN; @@ -676,6 +703,10 @@ static void SSPI_Handshake (sslfile_t *f) InSecBuff[1].pvBuffer = NULL; InSecBuff[1].cbBuffer = 0; + InSecBuff[2].BufferType = 16;//SECBUFFER_TARGET_HOST; + InSecBuff[2].pvBuffer = buf2; + InSecBuff[2].cbBuffer = sizeof(buf2); + ss = secur.pAcceptSecurityContext(&f->cred, (f->handshaking==HS_SERVER)?&f->sechnd:NULL, &InBuffDesc, ASC_REQ_ALLOCATE_MEMORY|ASC_REQ_STREAM|ASC_REQ_CONFIDENTIALITY, SECURITY_NATIVE_DREP, &f->sechnd, &OutBuffDesc, &ContextAttributes, &Lifetime); if (ss == SEC_E_INCOMPLETE_MESSAGE) @@ -727,7 +758,7 @@ static void SSPI_Handshake (sslfile_t *f) } } - if (SSPI_CopyIntoBuffer(&f->outcrypt, OutSecBuff.pvBuffer, OutSecBuff.cbBuffer, true) < OutSecBuff.cbBuffer) + if (SSPI_CopyIntoBuffer(&f->outcrypt, OutSecBuff[0].pvBuffer, OutSecBuff[0].cbBuffer, true) < OutSecBuff[0].cbBuffer) { SSPI_Error(f, "crypt overflow\n"); return; @@ -855,12 +886,13 @@ static qboolean QDECL SSPI_Close (struct vfsfile_s *file) } #include -vfsfile_t *FS_OpenSSL(const char *hostname, vfsfile_t *source, qboolean server, qboolean datagram) +vfsfile_t *FS_OpenSSL(const char *servername, vfsfile_t *source, qboolean server, qboolean datagram) { sslfile_t *newf; int i = 0; int err; unsigned int c; + const char *localname, *peername; if (!source || !SSL_Inited()) { @@ -868,8 +900,16 @@ vfsfile_t *FS_OpenSSL(const char *hostname, vfsfile_t *source, qboolean server, VFS_CLOSE(source); return NULL; } - if (!hostname) - hostname = ""; + if (server) + { + localname = servername; + peername = ""; + } + else + { + localname = ""; + peername = servername; + } /* if (server) //unsupported @@ -880,9 +920,9 @@ vfsfile_t *FS_OpenSSL(const char *hostname, vfsfile_t *source, qboolean server, */ newf = Z_Malloc(sizeof(*newf)); - while(*hostname) + while(*peername) { - c = utf8_decode(&err, hostname, (void*)&hostname); + c = utf8_decode(&err, peername, (void*)&peername); if (c > WCHAR_MAX) err = true; //no 16bit surrogates. they're evil. else if (i == sizeof(newf->wpeername)/sizeof(newf->wpeername[0]) - 1) @@ -897,6 +937,7 @@ vfsfile_t *FS_OpenSSL(const char *hostname, vfsfile_t *source, qboolean server, } } newf->wpeername[i] = 0; + newf->datagram = datagram; newf->handshaking = server?HS_STARTSERVER:HS_STARTCLIENT; newf->stream = source; @@ -907,7 +948,7 @@ vfsfile_t *FS_OpenSSL(const char *hostname, vfsfile_t *source, qboolean server, newf->funcs.Seek = SSPI_Seek; newf->funcs.Tell = SSPI_Tell; newf->funcs.WriteBytes = SSPI_WriteBytes; - newf->funcs.seekingisabadplan = true; + newf->funcs.seekstyle = SS_UNSEEKABLE; SSPI_ExpandBuffer(&newf->outraw, 8192); SSPI_ExpandBuffer(&newf->outcrypt, 8192); diff --git a/engine/common/net_wins.c b/engine/common/net_wins.c index 5f73ea65..d0481701 100644 --- a/engine/common/net_wins.c +++ b/engine/common/net_wins.c @@ -23,6 +23,8 @@ struct sockaddr; #include "quakedef.h" #include "netinc.h" +extern ftemanifest_t *fs_manifest; + // Eww, eww. This is hacky but so is netinc.h, so bite me #ifdef _XBOX struct sockaddr @@ -84,14 +86,22 @@ void (*pfreeaddrinfo) (struct addrinfo*); void NET_GetLocalAddress (int socket, netadr_t *out); //int TCP_OpenListenSocket (const char *localip, int port); #ifdef IPPROTO_IPV6 -int UDP6_OpenSocket (int port, qboolean bcast); +int UDP6_OpenSocket (int port); #endif #ifdef USEIPX void IPX_CloseSocket (int socket); #endif -cvar_t net_hybriddualstack = CVARD("net_hybriddualstack", "1", "Uses hybrid ipv4+ipv6 sockets where possible. Not supported on xp or below."); -cvar_t net_fakeloss = CVARFD("net_fakeloss", "0", CVAR_CHEAT, "Simulates packetloss in both receiving and sending, on a scale from 0 to 1."); -cvar_t net_enabled = CVARD("net_enabled", "1", "If 0, disables all network access, including name resolution and socket creation. Does not affect loopback/internal connections."); +cvar_t net_hybriddualstack = CVARD("net_hybriddualstack", "1", "Uses hybrid ipv4+ipv6 sockets where possible. Not supported on xp or below."); +cvar_t net_fakeloss = CVARFD("net_fakeloss", "0", CVAR_CHEAT, "Simulates packetloss in both receiving and sending, on a scale from 0 to 1."); +cvar_t net_enabled = CVARD("net_enabled", "1", "If 0, disables all network access, including name resolution and socket creation. Does not affect loopback/internal connections."); +#if defined(TCPCONNECT) && !defined(CLIENTONLY) +cvar_t net_enable_qizmo = CVARD("net_enable_qizmo", "1", "Enables compatibility with qizmo's tcp connections serverside. Frankly, using sv_port_tcp without this is a bit pointless."); +cvar_t net_enable_qtv = CVARD("net_enable_qtv", "1", "Listens for qtv proxies, or clients using the qtvplay command."); +cvar_t net_enable_tls = CVARD("net_enable_tls", "1", "If enabled, binary data sent to a tcp connection will be interpretted as a tls handshake (enabling https or wss over the same tcp port."); +cvar_t net_enable_http = CVARD("net_enable_http", "1", "If enabled, tcp ports will accept http clients, potentially serving large files which could distrupt gameplay."); +cvar_t net_enable_websockets = CVARD("net_enable_websockets", "1", "If enabled, tcp ports will accept websocket game clients."); +cvar_t net_enable_webrtcbroker = CVARD("net_enable_webrtcbroker", "1", "If 1, tcp ports will accept websocket connections from clients trying to broker direct webrtc connections. This should be low traffic, but might involve a lot of mostly-idle connections."); +#endif extern cvar_t sv_public, sv_listen_qw, sv_listen_nq, sv_listen_dp, sv_listen_q3; @@ -125,16 +135,6 @@ int NetadrToSockadr (netadr_t *a, struct sockaddr_qstorage *s) return sizeof(struct sockaddr_websocket); #endif #ifdef HAVE_IPV4 - case NA_BROADCAST_IP: - memset (s, 0, sizeof(struct sockaddr_in)); - ((struct sockaddr_in*)s)->sin_family = AF_INET; - - *(int *)&((struct sockaddr_in*)s)->sin_addr = 0xffffffff;//INADDR_BROADCAST; - ((struct sockaddr_in*)s)->sin_port = a->port; - return sizeof(struct sockaddr_in); - - case NA_TLSV4: - case NA_TCP: case NA_IP: memset (s, 0, sizeof(struct sockaddr_in)); ((struct sockaddr_in*)s)->sin_family = AF_INET; @@ -144,19 +144,6 @@ int NetadrToSockadr (netadr_t *a, struct sockaddr_qstorage *s) return sizeof(struct sockaddr_in); #endif #ifdef IPPROTO_IPV6 - case NA_BROADCAST_IP6: - memset (s, 0, sizeof(struct sockaddr_in)); - ((struct sockaddr_in6*)s)->sin6_family = AF_INET6; - - memset((int *)&((struct sockaddr_in6*)s)->sin6_addr, 0, sizeof(*(int *)&((struct sockaddr_in6*)s)->sin6_addr)); - ((struct sockaddr_in6*)s)->sin6_addr.s6_addr[0] = 0xff; - ((struct sockaddr_in6*)s)->sin6_addr.s6_addr[1] = 0x02; - ((struct sockaddr_in6*)s)->sin6_addr.s6_addr[15] = 0x01; - ((struct sockaddr_in6*)s)->sin6_port = a->port; - return sizeof(struct sockaddr_in6); - - case NA_TLSV6: - case NA_TCPV6: case NA_IPV6: memset (s, 0, sizeof(struct sockaddr_in6)); ((struct sockaddr_in6*)s)->sin6_family = AF_INET6; @@ -173,13 +160,6 @@ int NetadrToSockadr (netadr_t *a, struct sockaddr_qstorage *s) memcpy(((struct sockaddr_ipx *)s)->sa_nodenum, &a->address.ipx[4], 6); ((struct sockaddr_ipx *)s)->sa_socket = a->port; return sizeof(struct sockaddr_ipx); - case NA_BROADCAST_IPX: - memset (s, 0, sizeof(struct sockaddr_ipx)); - ((struct sockaddr_ipx*)s)->sa_family = AF_IPX; - memset(&((struct sockaddr_ipx*)s)->sa_netnum, 0, 4); - memset(&((struct sockaddr_ipx*)s)->sa_nodenum, 0xff, 6); - ((struct sockaddr_ipx*)s)->sa_socket = a->port; - return sizeof(struct sockaddr_ipx); #endif default: Sys_Error("NetadrToSockadr: Bad type %i", a->type); @@ -189,7 +169,9 @@ int NetadrToSockadr (netadr_t *a, struct sockaddr_qstorage *s) void SockadrToNetadr (struct sockaddr_qstorage *s, netadr_t *a) { + a->scopeid = 0; a->connum = 0; + a->prot = NP_DGRAM; switch (((struct sockaddr*)s)->sa_family) { @@ -232,9 +214,36 @@ void SockadrToNetadr (struct sockaddr_qstorage *s, netadr_t *a) break; } } +char *NET_SockadrToString (char *s, int len, struct sockaddr_qstorage *a) +{ + netadr_t na; + SockadrToNetadr(a, &na); + return NET_AdrToString(s, len, &na); +} + +qboolean NET_AddrIsReliable(netadr_t *adr) //hints that the protocol is reliable. if so, we don't need to wait for acks +{ + switch(adr->prot) + { + case NP_DGRAM: + case NP_DTLS: + case NP_NATPMP: + default: + return false; + case NP_STREAM: + case NP_TLS: + case NP_WS: + case NP_WSS: + case NP_IRC: + return true; + } +} qboolean NET_CompareAdr (netadr_t *a, netadr_t *b) { + if (a->prot != b->prot) + return false; + if (a->type != b->type) { int i; @@ -286,7 +295,7 @@ qboolean NET_CompareAdr (netadr_t *a, netadr_t *b) #endif #ifdef HAVE_IPV4 - if (a->type == NA_IP || a->type == NA_BROADCAST_IP || a->type == NA_TCP || a->type == NA_TLSV4) + if (a->type == NA_IP) { if ((memcmp(a->address.ip, b->address.ip, sizeof(a->address.ip)) == 0) && a->port == b->port) return true; @@ -295,7 +304,7 @@ qboolean NET_CompareAdr (netadr_t *a, netadr_t *b) #endif #ifdef IPPROTO_IPV6 - if (a->type == NA_IPV6 || a->type == NA_BROADCAST_IP6 || a->type == NA_TCPV6 || a->type == NA_TLSV6) + if (a->type == NA_IPV6) { if ((memcmp(a->address.ip6, b->address.ip6, sizeof(a->address.ip6)) == 0) && a->port == b->port) return true; @@ -304,7 +313,7 @@ qboolean NET_CompareAdr (netadr_t *a, netadr_t *b) #endif #ifdef USEIPX - if (a->type == NA_IPX || a->type == NA_BROADCAST_IPX) + if (a->type == NA_IPX) { if ((memcmp(a->address.ipx, b->address.ipx, sizeof(a->address.ipx)) == 0) && a->port == b->port) return true; @@ -330,10 +339,14 @@ qboolean NET_CompareAdr (netadr_t *a, netadr_t *b) NET_CompareBaseAdr Compares without the port +(udp/tcp/etc must still match) =================== */ qboolean NET_CompareBaseAdr (netadr_t *a, netadr_t *b) { + if (a->prot != b->prot) + return false; + if (a->type != b->type) return false; @@ -341,7 +354,7 @@ qboolean NET_CompareBaseAdr (netadr_t *a, netadr_t *b) return true; #ifdef HAVE_IPV4 - if (a->type == NA_IP || a->type == NA_TCP || a->type == NA_TLSV4) + if (a->type == NA_IP) { if ((memcmp(a->address.ip, b->address.ip, sizeof(a->address.ip)) == 0)) return true; @@ -349,7 +362,7 @@ qboolean NET_CompareBaseAdr (netadr_t *a, netadr_t *b) } #endif #ifdef IPPROTO_IPV6 - if (a->type == NA_IPV6 || a->type == NA_TCPV6 || a->type == NA_TLSV6) + if (a->type == NA_IPV6) { if ((memcmp(a->address.ip6, b->address.ip6, 16) == 0)) return true; @@ -373,6 +386,15 @@ qboolean NET_CompareBaseAdr (netadr_t *a, netadr_t *b) } #endif +#ifdef HAVE_WEBSOCKCL + if (a->type == NA_WEBSOCKET) + { + if (!strcmp(a->address.websocketurl, b->address.websocketurl)) + return true; + return false; + } +#endif + Sys_Error("NET_CompareBaseAdr: Bad address type"); return false; } @@ -387,7 +409,6 @@ qboolean NET_AddressSmellsFunny(netadr_t *a) switch(a->type) { #ifdef HAVE_IPV4 - case NA_BROADCAST_IP: case NA_IP: //reject localhost if (a->address.ip[0] == 127)// && a->address.ip[1] == 0 && a->address.ip[2] == 0 && a->address.ip[3] == 1 ) @@ -403,7 +424,6 @@ qboolean NET_AddressSmellsFunny(netadr_t *a) #endif #ifdef IPPROTO_IPV6 - case NA_BROADCAST_IP6: case NA_IPV6: //reject [::XXXX] (this includes obsolete ipv4-compatible (not ipv4 mapped), and localhost) for (i = 0; i < 12; i++) @@ -416,7 +436,6 @@ qboolean NET_AddressSmellsFunny(netadr_t *a) #ifdef USEIPX //no idea how this protocol's addresses work - case NA_BROADCAST_IPX: case NA_IPX: return false; #endif @@ -463,33 +482,53 @@ void NET_AdrToStringResolve (netadr_t *adr, void (*resolved)(void *ctx, void *da char *NET_AdrToString (char *s, int len, netadr_t *a) { char *rs = s; + char *prot = ""; #ifdef IPPROTO_IPV6 qboolean doneblank; #endif + switch(a->prot) + { + case NP_DGRAM: prot = ""; break; + case NP_DTLS: prot = "dtls://"; break; + case NP_STREAM: prot = "tcp://"; break; //not strictly true for ipx, but whatever. + case NP_TLS: prot = "tls://"; break; + case NP_WS: prot = "ws://"; break; + case NP_WSS: prot = "wss://"; break; + case NP_IRC: prot = "irc://"; break; + case NP_NATPMP: prot = "natpmp://"; break; + } + switch(a->type) { #ifdef HAVE_WEBSOCKCL - case NA_WEBSOCKET: - Q_strncpyz(s, a->address.websocketurl, len); + case NA_WEBSOCKET: //ws / wss is part of the url + { + char *url = a->address.websocketurl; + prot = ""; + if (a->prot == NP_DTLS && !strncmp(url, "ws://", 5)) + { + url+=5; + prot = "rtc://"; + } + if (a->prot == NP_DTLS && !strncmp(url, "wss://", 6)) + { + url+=6; + prot = "rtcs://"; + } + if (a->port) + Q_snprintfz(s, len, "%s%s#%i", prot, url, ntohs(a->port)); + else + Q_snprintfz(s, len, "%s%s", prot, url); + } break; #endif -#ifdef TCPCONNECT - case NA_TLSV4: - case NA_TCP: - if (len < 7) - return "?"; - snprintf (s, len, (a->type == NA_TLSV4)?"tls://":"tcp://"); - s += 6; - len -= 6; - //fallthrough -#endif #ifdef HAVE_IPV4 - case NA_BROADCAST_IP: case NA_IP: if (a->port) { - snprintf (s, len, "%i.%i.%i.%i:%i", + Q_snprintfz(s, len, "%s%i.%i.%i.%i:%i", + prot, a->address.ip[0], a->address.ip[1], a->address.ip[2], @@ -498,7 +537,8 @@ char *NET_AdrToString (char *s, int len, netadr_t *a) } else { - snprintf (s, len, "%i.%i.%i.%i", + snprintf (s, len, "%s%i.%i.%i.%i", + prot, a->address.ip[0], a->address.ip[1], a->address.ip[2], @@ -506,18 +546,7 @@ char *NET_AdrToString (char *s, int len, netadr_t *a) } break; #endif -#ifdef TCPCONNECT - case NA_TLSV6: - case NA_TCPV6: - if (len < 7) - return "?"; - snprintf (s, len, (a->type == NA_TLSV4)?"tls://":"tcp://"); - s += 6; - len -= 6; - //fallthrough -#endif #ifdef IPPROTO_IPV6 - case NA_BROADCAST_IP6: case NA_IPV6: { char *p; @@ -528,14 +557,16 @@ char *NET_AdrToString (char *s, int len, netadr_t *a) *(short*)&a->address.ip6[10] == (short)0xffff) { if (a->port) - snprintf (s, len, "%i.%i.%i.%i:%i", + snprintf (s, len, "%s%i.%i.%i.%i:%i", + prot, a->address.ip6[12], a->address.ip6[13], a->address.ip6[14], a->address.ip6[15], ntohs(a->port)); else - snprintf (s, len, "%i.%i.%i.%i", + snprintf (s, len, "%s%i.%i.%i.%i", + prot, a->address.ip6[12], a->address.ip6[13], a->address.ip6[14], @@ -546,10 +577,10 @@ char *NET_AdrToString (char *s, int len, netadr_t *a) doneblank = false; p = s; if (a->port) - { - snprintf (s, len-strlen(s), "["); - p += strlen(p); - } + snprintf (s, len-strlen(s), "%s[", prot); + else + snprintf (s, len-strlen(s), "%s", prot); + p += strlen(p); for (i = 0; i < 16; i+=2) { @@ -600,9 +631,9 @@ char *NET_AdrToString (char *s, int len, netadr_t *a) break; #endif #ifdef USEIPX - case NA_BROADCAST_IPX: case NA_IPX: - snprintf (s, len, "%02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x:%i", + snprintf (s, len, "%s%02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x:%i", + prot, a->address.ipx[0], a->address.ipx[1], a->address.ipx[2], @@ -617,7 +648,7 @@ char *NET_AdrToString (char *s, int len, netadr_t *a) break; #endif case NA_LOOPBACK: - snprintf (s, len, "QLoopBack:%i", a->port); + snprintf (s, len, "%sQLoopBack:%i", prot, a->port); break; #ifdef IRCCONNECT @@ -639,38 +670,50 @@ char *NET_AdrToString (char *s, int len, netadr_t *a) char *NET_BaseAdrToString (char *s, int len, netadr_t *a) { + char *prot = ""; + switch(a->prot) + { + case NP_DGRAM: prot = ""; break; + case NP_DTLS: prot = "dtls://"; break; + case NP_STREAM: prot = "tcp://"; break; //not strictly true for ipx, but whatever. + case NP_TLS: prot = "tls://"; break; + case NP_WS: prot = "ws://"; break; + case NP_WSS: prot = "wss://"; break; + case NP_IRC: prot = "irc://"; break; + case NP_NATPMP: prot = "natpmp://"; break; + } + switch(a->type) { - case NA_BROADCAST_IP: +#ifdef HAVE_WEBSOCKCL + case NA_WEBSOCKET: //ws / wss is part of the url + { + char *url = a->address.websocketurl; + prot = ""; + if (a->prot == NP_DTLS && !strncmp(url, "ws://", 5)) + { + url+=5; + prot = "rtc://"; + } + if (a->prot == NP_DTLS && !strncmp(url, "wss://", 6)) + { + url+=6; + prot = "rtcs://"; + } + Q_snprintfz(s, len, "%s%s", prot, url); + } + break; +#endif + case NA_IP: - snprintf (s, len, "%i.%i.%i.%i", + snprintf (s, len, "%s%i.%i.%i.%i", + prot, a->address.ip[0], a->address.ip[1], a->address.ip[2], a->address.ip[3]); break; - case NA_TLSV4: - case NA_TLSV6: - snprintf (s, len, "tls://"); - if (len > 6) - { - a->type = (a->type-NA_TLSV4)+NA_IP; - NET_BaseAdrToString(s+6, len-6, a); - a->type = (a->type-NA_IP)+NA_TLSV4; - } - break; - case NA_TCP: - case NA_TCPV6: - snprintf (s, len, "tcp://"); - if (len > 6) - { - a->type = (a->type-NA_TCP)+NA_IP; - NET_BaseAdrToString(s+6, len-6, a); - a->type = (a->type-NA_IP)+NA_TCP; - } - break; #ifdef IPPROTO_IPV6 - case NA_BROADCAST_IP6: case NA_IPV6: { char *p; @@ -680,7 +723,8 @@ char *NET_BaseAdrToString (char *s, int len, netadr_t *a) !*(short*)&a->address.ip6[8] && *(short*)&a->address.ip6[10] == (short)0xffff) { - snprintf (s, len, "%i.%i.%i.%i", + snprintf (s, len, "%s%i.%i.%i.%i", + prot, a->address.ip6[12], a->address.ip6[13], a->address.ip6[14], @@ -728,9 +772,9 @@ char *NET_BaseAdrToString (char *s, int len, netadr_t *a) break; #endif #ifdef USEIPX - case NA_BROADCAST_IPX: case NA_IPX: - snprintf (s, len, "%02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x", + snprintf (s, len, "%s%02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x", + prot, a->address.ipx[0], a->address.ipx[1], a->address.ipx[2], @@ -744,7 +788,7 @@ char *NET_BaseAdrToString (char *s, int len, netadr_t *a) break; #endif case NA_LOOPBACK: - snprintf (s, len, "QLoopBack"); + snprintf (s, len, "%sQLoopBack", prot); break; #ifdef IRCCONNECT @@ -838,7 +882,7 @@ size_t NET_StringToSockaddr2 (const char *s, int defaultport, struct sockaddr_qs 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; + udp6hint.ai_protocol = 0; if (*s == '[') { @@ -1015,9 +1059,54 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num Con_DPrintf("Resolving address: %s\n", s); #ifdef HAVE_WEBSOCKCL - if (!strncmp (s, "ws://", 5) || !strncmp (s, "wss://", 6)) + if (!strncmp (s, "/", 1)) + { + char *prefix = ""; + if (!fs_manifest->rtcbroker || !*fs_manifest->rtcbroker) + { //FIXME: use referrer? or the website's host? + Con_DPrintf("No default rtc broker\n"); + return false; //can't accept it + } + if (!strstr(fs_manifest->rtcbroker, "://")) + prefix = "ws://"; + Q_snprintfz(a->address.websocketurl, sizeof(a->address.websocketurl), "%s%s%s", prefix, fs_manifest->rtcbroker, s); + return true; + } + else if (!strncmp (s, "rtc://", 6) || !strncmp (s, "rtcs://", 7)) + { + const char *prot, *host, *path; + a->type = NA_WEBSOCKET; + a->prot = NP_DTLS; + if (!strncmp (s, "rtcs://", 7)) + { + prot = "wss://"; + path = s+7; + } + else + { + prot = "ws://"; + path = s+6; + } + if (*path == '/') + { + host = fs_manifest->rtcbroker; + if (!host || !*host) //can't guess the host + return false; + if (strstr(host, "://")) + prot = ""; + } + else + host = ""; + Q_snprintfz(a->address.websocketurl, sizeof(a->address.websocketurl), "%s%s%s", prot, host, path); + return true; + } + else if (!strncmp (s, "ws://", 5) || !strncmp (s, "wss://", 6)) { a->type = NA_WEBSOCKET; + if (!strncmp (s, "wss://", 6)) + a->prot = NP_WSS; + else + a->prot = NP_WS; Q_strncpyz(a->address.websocketurl, s, sizeof(a->address.websocketurl)); return true; } @@ -1027,10 +1116,11 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num static float warned; if (warned < realtime) { - Con_Printf("Note: Assuming ws:// prefix\n"); + Con_DPrintf("Note: Assuming ws:// prefix\n"); warned = realtime + 1; } a->type = NA_WEBSOCKET; + a->prot = NP_WS; memcpy(a->address.websocketurl, "ws://", 5); Q_strncpyz(a->address.websocketurl+5, s, sizeof(a->address.websocketurl)-5); return true; @@ -1048,18 +1138,50 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num } SockadrToNetadr (&sadr[0], a); + a->prot = NP_STREAM; + return true; + } + if (!strncmp (s, "ws://", 7)) + { + //make sure that the rest of the address is a valid ip address (4 or 6) - if (a->type == NA_IP) + if (!NET_StringToSockaddr (s+6, defaultport, &sadr[0], NULL, NULL)) { - a->type = NA_TCP; - return true; + a->type = NA_INVALID; + return false; } - if (a->type == NA_IPV6) + + SockadrToNetadr (&sadr[0], a); + a->prot = NP_WS; + return true; + } + if (!strncmp (s, "wss://", 7)) + { + //make sure that the rest of the address is a valid ip address (4 or 6) + + if (!NET_StringToSockaddr (s+6, defaultport, &sadr[0], NULL, NULL)) { - a->type = NA_TCPV6; - return true; + a->type = NA_INVALID; + return false; } - return false; + + SockadrToNetadr (&sadr[0], a); + a->prot = NP_WSS; + return true; + } + if (!strncmp (s, "dtls://", 7)) + { + //make sure that the rest of the address is a valid ip address (4 or 6) + + if (!NET_StringToSockaddr (s+6, defaultport, &sadr[0], NULL, NULL)) + { + a->type = NA_INVALID; + return false; + } + + SockadrToNetadr (&sadr[0], a); + a->prot = NP_DTLS; + return true; } if (!strncmp (s, "tls://", 6)) { @@ -1072,18 +1194,8 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num } SockadrToNetadr (&sadr[0], a); - - if (a->type == NA_IP) - { - a->type = NA_TLSV4; - return true; - } - if (a->type == NA_IPV6) - { - a->type = NA_TLSV6; - return true; - } - return false; + a->prot = NP_TLS; + return true; } #endif #ifdef IRCCONNECT @@ -1121,11 +1233,9 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num #endif #ifdef HAVE_NATPMP if (!strncmp (s, "natpmp://", 9)) - { - NET_PortToAdr(NA_NATPMP, s+9, a); - if (a->type == NA_IP) - a->type = NA_NATPMP; - if (a->type != NA_NATPMP) + { //our natpmp thing omits the host part. FIXME: host should be the NAT that we're sending to + NET_PortToAdr(NA_IP, NP_NATPMP, s+9, a); + if (a->prot != NP_NATPMP) return false; return true; } @@ -1135,14 +1245,6 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num for (i = 0; i < result; i++) { SockadrToNetadr (&sadr[i], &a[i]); - -#if !defined(HAVE_PACKET) && defined(HAVE_TCP) - //bump over protocols that cannot work in the first place. - if (a[i].type == NA_IP) - a[i].type = NA_TCP; - if (a[i].type == NA_IPV6) - a[i].type = NA_TCPV6; -#endif } //invalidate any others @@ -1172,10 +1274,7 @@ void NET_IntegerToMask (netadr_t *a, netadr_t *amask, int bits) { case NA_INVALID: break; - case NA_TCP: - case NA_TLSV4: case NA_IP: - case NA_BROADCAST_IP: n = amask->address.ip; if (i > 32) i = 32; @@ -1193,10 +1292,7 @@ void NET_IntegerToMask (netadr_t *a, netadr_t *amask, int bits) *n = i; } break; - case NA_TCPV6: - case NA_TLSV6: case NA_IPV6: - case NA_BROADCAST_IP6: #ifdef IPPROTO_IPV6 n = amask->address.ip6; if (i > 128) @@ -1217,7 +1313,6 @@ void NET_IntegerToMask (netadr_t *a, netadr_t *amask, int bits) #endif break; case NA_IPX: - case NA_BROADCAST_IPX: #ifdef USEIPX n = amask->address.ipx; if (i > 80) @@ -1240,9 +1335,12 @@ void NET_IntegerToMask (netadr_t *a, netadr_t *amask, int bits) case NA_LOOPBACK: break; // warning: enumeration value âNA_*â not handled in switch - case NA_NATPMP: +#ifdef HAVE_WEBSOCKCL case NA_WEBSOCKET: +#endif +#ifdef IRCCONNECT case NA_IRC: +#endif break; } @@ -1423,6 +1521,7 @@ qboolean NET_StringToAdrMasked (const char *s, qboolean allowdns, netadr_t *a, n // returns true or false if they match //WARNING: a is typically an ipv6 address, even if its an ipv4-mapped address. //so ipv4ify first. +//this is not intended to identify any specific connection, so we can ignore udp/tcp distinctions (especially as this is usually used for bans). qboolean NET_CompareAdrMasked(netadr_t *a, netadr_t *b, netadr_t *mask) { int i; @@ -1478,7 +1577,6 @@ qboolean NET_CompareAdrMasked(netadr_t *a, netadr_t *b, netadr_t *mask) { case NA_LOOPBACK: return true; - case NA_BROADCAST_IP: case NA_IP: for (i = 0; i < 4; i++) { @@ -1487,7 +1585,6 @@ qboolean NET_CompareAdrMasked(netadr_t *a, netadr_t *b, netadr_t *mask) } break; #ifdef IPPROTO_IPV6 - case NA_BROADCAST_IP6: case NA_IPV6: for (i = 0; i < 16; i++) { @@ -1497,7 +1594,6 @@ qboolean NET_CompareAdrMasked(netadr_t *a, netadr_t *b, netadr_t *mask) break; #endif #ifdef USEIPX - case NA_BROADCAST_IPX: case NA_IPX: for (i = 0; i < 10; i++) { @@ -1532,7 +1628,6 @@ int UniformMaskedBits(netadr_t *mask) switch (mask->type) { - case NA_BROADCAST_IP: case NA_IP: bits = 32; for (b = 3; b >= 0; b--) @@ -1562,7 +1657,6 @@ int UniformMaskedBits(netadr_t *mask) } break; #ifdef IPPROTO_IPV6 - case NA_BROADCAST_IP6: case NA_IPV6: bits = 128; for (b = 15; b >= 0; b--) @@ -1593,7 +1687,6 @@ int UniformMaskedBits(netadr_t *mask) break; #endif #ifdef USEIPX - case NA_BROADCAST_IPX: case NA_IPX: bits = 80; for (b = 9; b >= 0; b--) @@ -1772,7 +1865,7 @@ qboolean FTENET_Loop_GetPacket(ftenet_generic_connection_t *con) #ifdef HAVE_PACKET //just a null function so we don't pass bad things to select. -int FTENET_Loop_SetReceiveFDSet(ftenet_generic_connection_t *gcon, fd_set *fdset) +int FTENET_Loop_SetFDSets(ftenet_generic_connection_t *gcon, fd_set *readfdset, fd_set *writefdset) { return 0; } @@ -1823,7 +1916,7 @@ static ftenet_generic_connection_t *FTENET_Loop_EstablishConnection(qboolean iss newcon->SendPacket = FTENET_Loop_SendPacket; newcon->Close = FTENET_Loop_Close; #ifdef HAVE_PACKET - newcon->SetReceiveFDSet = FTENET_Loop_SetReceiveFDSet; + newcon->SetFDSets = FTENET_Loop_SetFDSets; #endif newcon->islisten = isserver; @@ -1847,20 +1940,11 @@ ftenet_connections_t *FTENET_CreateCollection(qboolean listen) #if !defined(SERVERONLY) && !defined(CLIENTONLY) static ftenet_generic_connection_t *FTENET_Loop_EstablishConnection(qboolean isserver, const char *address, netadr_t adr); #endif -#ifdef HAVE_IPV4 -static ftenet_generic_connection_t *FTENET_UDP4_EstablishConnection(qboolean isserver, const char *address, netadr_t adr); -#endif -#ifdef IPPROTO_IPV6 -static ftenet_generic_connection_t *FTENET_UDP6_EstablishConnection(qboolean isserver, const char *address, netadr_t adr); +#ifdef HAVE_PACKET +static ftenet_generic_connection_t *FTENET_Datagram_EstablishConnection(qboolean isserver, const char *address, netadr_t adr); #endif #ifdef TCPCONNECT -static ftenet_generic_connection_t *FTENET_TCP4Connect_EstablishConnection(qboolean isserver, const char *address, netadr_t adr); -static ftenet_generic_connection_t *FTENET_TCP6Connect_EstablishConnection(qboolean isserver, const char *address, netadr_t adr); -static ftenet_generic_connection_t *FTENET_TLS4Connect_EstablishConnection(qboolean isserver, const char *address, netadr_t adr); -static ftenet_generic_connection_t *FTENET_TLS6Connect_EstablishConnection(qboolean isserver, const char *address, netadr_t adr); -#endif -#ifdef USEIPX -static ftenet_generic_connection_t *FTENET_IPX_EstablishConnection(qboolean isserver, const char *address, netadr_t adr); +static ftenet_generic_connection_t *FTENET_TCPConnect_EstablishConnection(qboolean isserver, const char *address, netadr_t adr); #endif #ifdef HAVE_WEBSOCKCL static ftenet_generic_connection_t *FTENET_WebSocket_EstablishConnection(qboolean isserver, const char *address, netadr_t adr); @@ -2109,8 +2193,8 @@ ftenet_generic_connection_t *FTENET_NATPMP_EstablishConnection(qboolean isserver { pmpcon_t *pmp; - if (pmpadr.type == NA_NATPMP) - pmpadr.type = NA_IP; + if (pmpadr.prot == NP_NATPMP) + pmpadr.prot = NP_DGRAM; if (pmpadr.type != NA_IP) return NULL; @@ -2179,7 +2263,7 @@ static qboolean FTENET_AddToCollection_Ptr(ftenet_connections_t *col, const char } return count > 0; } -qboolean FTENET_AddToCollection(ftenet_connections_t *col, const char *name, const char *addresslist, netadrtype_t addrtype, qboolean islisten) +qboolean FTENET_AddToCollection(ftenet_connections_t *col, const char *name, const char *addresslist, netadrtype_t addrtype, netproto_t addrprot, qboolean islisten) { netadr_t adr[8]; ftenet_generic_connection_t *(*establish[countof(adr)])(qboolean isserver, const char *address, netadr_t adr); @@ -2197,52 +2281,47 @@ qboolean FTENET_AddToCollection(ftenet_connections_t *col, const char *name, con if (!*address[i]) adr[i].type = NA_INVALID; else if (islisten) - NET_PortToAdr(addrtype, address[i], &adr[i]); + NET_PortToAdr(addrtype, addrprot, address[i], &adr[i]); else { if (!NET_StringToAdr(address[i], 0, &adr[i])) return false; } - switch(adr[i].type) - { - default: establish[i] = NULL; break; +#ifdef HAVE_WEBSOCKCL + if (adr[i].prot == NP_WS && adr[i].type == NA_WEBSOCKET) establish[i] = FTENET_WebSocket_EstablishConnection; else + if (adr[i].prot == NP_WSS && adr[i].type == NA_WEBSOCKET) establish[i] = FTENET_WebSocket_EstablishConnection; else + if (adr[i].prot == NP_DTLS && adr[i].type == NA_WEBSOCKET) establish[i] = FTENET_WebSocket_EstablishConnection; else +#endif #ifdef HAVE_NATPMP - case NA_NATPMP: establish[i] = FTENET_NATPMP_EstablishConnection; break; + if (adr[i].prot == NP_NATPMP&& adr[i].type == NA_IP) establish[i] = FTENET_NATPMP_EstablishConnection; else #endif #if !defined(CLIENTONLY) && !defined(SERVERONLY) - case NA_LOOPBACK: establish[i] = FTENET_Loop_EstablishConnection; break; + if (adr[i].prot == NP_DGRAM && adr[i].type == NA_LOOPBACK) establish[i] = FTENET_Loop_EstablishConnection; else #endif #ifdef HAVE_IPV4 - case NA_IP: establish[i] = FTENET_UDP4_EstablishConnection; break; + if (adr[i].prot == NP_DGRAM && adr[i].type == NA_IP) establish[i] = FTENET_Datagram_EstablishConnection; else #endif -#ifdef IPPROTO_IPV6 - case NA_IPV6: establish[i] = FTENET_UDP6_EstablishConnection; break; +#ifdef HAVE_IPV6 + if (adr[i].prot == NP_DGRAM && adr[i].type == NA_IPV6) establish[i] = FTENET_Datagram_EstablishConnection; else #endif #ifdef USEIPX - case NA_IPX: establish[i] = FTENET_IPX_EstablishConnection; break; + if (adr[i].prot == NP_DGRAM && adr[i].type == NA_IPX) establish[i] = FTENET_Datagram_EstablishConnection; else #endif - case NA_WEBSOCKET: -#ifdef HAVE_WEBSOCKCL - if (!islisten) - establish[i] = FTENET_WebSocket_EstablishConnection; +#if defined(TCPCONNECT) && defined(HAVE_IPV4) + if (adr[i].prot == NP_WS && adr[i].type == NA_IP) establish[i] = FTENET_TCPConnect_EstablishConnection; else + if (adr[i].prot == NP_STREAM&& adr[i].type == NA_IP) establish[i] = FTENET_TCPConnect_EstablishConnection; else + if (adr[i].prot == NP_TLS && adr[i].type == NA_IP) establish[i] = FTENET_TCPConnect_EstablishConnection; else #endif -#ifdef TCPCONNECT - establish[i] = FTENET_TCP4Connect_EstablishConnection; +#if defined(TCPCONNECT) && defined(HAVE_IPV6) + if (adr[i].prot == NP_WS && adr[i].type == NA_IPV6) establish[i] = FTENET_TCPConnect_EstablishConnection; else + if (adr[i].prot == NP_STREAM&& adr[i].type == NA_IPV6) establish[i] = FTENET_TCPConnect_EstablishConnection; else + if (adr[i].prot == NP_TLS && adr[i].type == NA_IPV6) establish[i] = FTENET_TCPConnect_EstablishConnection; else #endif - break; #ifdef IRCCONNECT - case NA_IRC: establish[i] = FTENET_IRCConnect_EstablishConnection; break; + if (adr[i].prot == NP_TLS) establish[i] = FTENET_IRCConnect_EstablishConnection; else #endif -#ifdef TCPCONNECT - case NA_TCP: establish[i] = FTENET_TCP4Connect_EstablishConnection; break; - case NA_TLSV4: establish[i] = FTENET_TLS4Connect_EstablishConnection; break; -#endif -#if defined(TCPCONNECT) && defined(IPPROTO_IPV6) - case NA_TCPV6: establish[i] = FTENET_TCP6Connect_EstablishConnection; break; - case NA_TLSV6: establish[i] = FTENET_TLS6Connect_EstablishConnection; break; -#endif - } + establish[i] = NULL; } if (i == 1) @@ -2475,7 +2554,7 @@ int FTENET_Generic_GetLocalAddresses(struct ftenet_generic_connection_s *con, un #ifdef USE_GETHOSTNAME_LOCALLISTING //if its bound to 'any' address, ask the system what addresses it actually accepts. - if (adr.type == NA_IPV6 && + if ((adr.type == NA_IPV6) && !*(int*)&adr.address.ip6[0] && !*(int*)&adr.address.ip6[4] && !*(short*)&adr.address.ip6[8] && @@ -2552,7 +2631,7 @@ int FTENET_Generic_GetLocalAddresses(struct ftenet_generic_connection_s *con, un #endif } -qboolean FTENET_Generic_GetPacket(ftenet_generic_connection_t *con) +qboolean FTENET_Datagram_GetPacket(ftenet_generic_connection_t *con) { #ifndef HAVE_PACKET return false; @@ -2632,7 +2711,7 @@ qboolean FTENET_Generic_GetPacket(ftenet_generic_connection_t *con) #endif } -neterr_t FTENET_Generic_SendPacket(ftenet_generic_connection_t *con, int length, const void *data, netadr_t *to) +neterr_t FTENET_Datagram_SendPacket(ftenet_generic_connection_t *con, int length, const void *data, netadr_t *to) { #ifndef HAVE_PACKET return NETERR_DISCONNECTED; @@ -2641,6 +2720,9 @@ neterr_t FTENET_Generic_SendPacket(ftenet_generic_connection_t *con, int length, int size; int ret; + if (to->prot != NP_DGRAM) + return NETERR_NOROUTE; + for (size = 0; size < FTENET_ADDRTYPES; size++) if (to->type == con->addrtype[size]) break; @@ -2661,30 +2743,7 @@ neterr_t FTENET_Generic_SendPacket(ftenet_generic_connection_t *con, int length, else #endif { - NetadrToSockadr (to, &addr); - - switch(to->type) - { - default: - Con_Printf("Bad address type\n"); - break; -#ifdef USEIPX //who uses ipx nowadays anyway? - case NA_BROADCAST_IPX: - case NA_IPX: - size = sizeof(struct sockaddr_ipx); - break; -#endif - case NA_BROADCAST_IP: - case NA_IP: - size = sizeof(struct sockaddr_in); - break; -#ifdef IPPROTO_IPV6 - case NA_BROADCAST_IP6: - case NA_IPV6: - size = sizeof(struct sockaddr_in6); - break; -#endif - } + size = NetadrToSockadr (to, &addr); } ret = sendto (con->thesocket, data, length, 0, (struct sockaddr*)&addr, size ); @@ -2724,16 +2783,12 @@ neterr_t FTENET_Generic_SendPacket(ftenet_generic_connection_t *con, int length, #endif } -qboolean NET_PortToAdr (int adrfamily, const char *s, netadr_t *a) +qboolean NET_PortToAdr (netadrtype_t adrfamily, netproto_t adrprot, const char *s, netadr_t *a) { char *e; if (net_enabled.ival || adrfamily == NA_LOOPBACK) { - int port; - if (!strncmp(s, "natpmp:", 7)) - return NET_StringToAdr2(s, 0, a, 1); - - port = strtoul(s, &e, 10); + int port = strtoul(s, &e, 10); if (*e) //if *e then its not just a single number in there, so treat it as a proper address. return NET_StringToAdr(s, 0, a); else if (e != s) //if we actually read something (even a 0) @@ -2741,6 +2796,7 @@ qboolean NET_PortToAdr (int adrfamily, const char *s, netadr_t *a) memset(a, 0, sizeof(*a)); a->port = htons((unsigned short)port); a->type = adrfamily; + a->prot = adrprot; return a->type != NA_INVALID; } @@ -2750,7 +2806,7 @@ qboolean NET_PortToAdr (int adrfamily, const char *s, netadr_t *a) } /*just here to prevent the client from spamming new sockets, which can be a problem with certain q2 servers*/ -qboolean FTENET_Generic_ChangeLocalAddress(struct ftenet_generic_connection_s *con, netadr_t *adr) +qboolean FTENET_Datagram_ChangeLocalAddress(struct ftenet_generic_connection_s *con, netadr_t *adr) { if (adr->type == con->addrtype[0] || adr->type == con->addrtype[1]) if (adr->port == 0) @@ -2758,7 +2814,7 @@ qboolean FTENET_Generic_ChangeLocalAddress(struct ftenet_generic_connection_s *c return false; } -ftenet_generic_connection_t *FTENET_Generic_EstablishConnection(int adrfamily, int protocol, qboolean isserver, const char *address, netadr_t adr) +ftenet_generic_connection_t *FTENET_Datagram_EstablishConnection(qboolean isserver, const char *address, netadr_t adr) { #ifndef HAVE_PACKET return NULL; @@ -2775,11 +2831,30 @@ ftenet_generic_connection_t *FTENET_Generic_EstablishConnection(int adrfamily, i int bindtries; int bufsz; qboolean hybrid = false; + int protocol; - if (adr.type != adrfamily) + switch(adr.type) { - if (adr.type == NA_INVALID) - Con_Printf("unable to resolve local address %s\n", address); +#if defined(HAVE_IPV4) || defined(HAVE_IPV6) + case NA_IP: + case NA_IPV6: + protocol = IPPROTO_UDP; + break; +#endif +#ifdef USEIPX + case NA_IPX: + protocol = NSPROTO_IPX; + break; +#endif + default: + protocol = 0; + break; + } + + + if (adr.type == NA_INVALID) + { + Con_Printf("unable to resolve local address %s\n", address); return NULL; //couldn't resolve the name } temp = NetadrToSockadr(&adr, &qs); @@ -2891,10 +2966,10 @@ ftenet_generic_connection_t *FTENET_Generic_EstablishConnection(int adrfamily, i if (newcon) { newcon->GetLocalAddresses = FTENET_Generic_GetLocalAddresses; - newcon->GetPacket = FTENET_Generic_GetPacket; - newcon->SendPacket = FTENET_Generic_SendPacket; + newcon->GetPacket = FTENET_Datagram_GetPacket; + newcon->SendPacket = FTENET_Datagram_SendPacket; newcon->Close = FTENET_Generic_Close; - newcon->ChangeLocalAddress = FTENET_Generic_ChangeLocalAddress; + newcon->ChangeLocalAddress = FTENET_Datagram_ChangeLocalAddress; newcon->islisten = isserver; if (hybrid) @@ -2920,25 +2995,6 @@ ftenet_generic_connection_t *FTENET_Generic_EstablishConnection(int adrfamily, i #endif } -#ifdef IPPROTO_IPV6 -ftenet_generic_connection_t *FTENET_UDP6_EstablishConnection(qboolean isserver, const char *address, netadr_t adr) -{ - return FTENET_Generic_EstablishConnection(NA_IPV6, IPPROTO_UDP, isserver, address, adr); -} -#endif -#ifdef HAVE_IPV4 -ftenet_generic_connection_t *FTENET_UDP4_EstablishConnection(qboolean isserver, const char *address, netadr_t adr) -{ - return FTENET_Generic_EstablishConnection(NA_IP, IPPROTO_UDP, isserver, address, adr); -} -#endif -#ifdef USEIPX -ftenet_generic_connection_t *FTENET_IPX_EstablishConnection(qboolean isserver, const char *address, netadr_t adr) -{ - return FTENET_Generic_EstablishConnection(NA_IPX, NSPROTO_IPX, isserver, address, adr); -} -#endif - #ifdef TCPCONNECT typedef struct ftenet_tcpconnect_stream_s { vfsfile_t *clientstream; @@ -2948,23 +3004,43 @@ typedef struct ftenet_tcpconnect_stream_s { enum { TCPC_UNKNOWN, //waiting to see what they send us. - TCPC_UNFRAMED, //something else is doing the framing (ie: we're running in emscripten and over some hidden websocket connection) - TCPC_HTTPCLIENT, //we're sending a file to this victim. TCPC_QIZMO, //'qizmo\n' handshake, followed by packets prefixed with a 16bit packet length. TCPC_WEBSOCKETU, //utf-8 encoded data. TCPC_WEBSOCKETB, //binary encoded data (subprotocol = 'binary') TCPC_WEBSOCKETNQ, //raw nq msg buffers with no encapsulation or handshake +#ifndef CLIENTONLY + TCPC_HTTPCLIENT, //we're sending a file to this victim. + TCPC_WEBRTC_CLIENT, //for brokering webrtc connections, doesn't carry any actual game data itself. + TCPC_WEBRTC_HOST //for brokering webrtc connections, doesn't carry any actual game data itself. +#endif } clienttype; - char inbuffer[3000]; - char outbuffer[3000]; + qbyte inbuffer[MAX_OVERALLMSGLEN]; + qbyte outbuffer[MAX_OVERALLMSGLEN]; vfsfile_t *dlfile; //if the client looked like an http client, this is the file that they're downloading. float timeouttime; + qboolean pinging; netadr_t remoteaddr; struct ftenet_tcpconnect_stream_s *next; - SOCKET socketnum; //for select. + SOCKET socketnum; //for select. not otherwise used. int fakesequence; //TCPC_WEBSOCKETNQ + +#ifndef CLIENTONLY + struct + { + qboolean connection_close; + } httpstate; + qtvpendingstate_t qtvstate; + struct + { + char resource[32]; + int clientnum; +#ifdef SUPPORT_RTC_ICE + struct icestate_s *ice; +#endif + } webrtc; +#endif } ftenet_tcpconnect_stream_t; typedef struct { @@ -3088,7 +3164,898 @@ neterr_t FTENET_TCPConnect_WebSocket_Splurge(ftenet_tcpconnect_stream_t *st, qby return NETERR_SENT; } +#ifndef CLIENTONLY +enum +{ + WCATTR_METHOD, + WCATTR_URL, + WCATTR_HTTP, + WCATTR_HOST, + WCATTR_CONNECTION, + + WCATTR_UPGRADE, + WCATTR_WSKEY, + //WCATTR_ORIGIN, + WCATTR_WSPROTO, + //WCATTR_WSEXT, + + WCATTR_IFNONEMATCH, + + WCATTR_COUNT, + + WCATTR_WSVER, + WCATTR_CONTENT_LENGTH, + WCATTR_ACCEPT_ENCODING, + WCATTR_TRANSFER_ENCODING +}; +typedef char httparg_t[64]; #include "fs.h" +#ifdef _WIN32 +#include "resource.h" +#endif +void SV_UserCmdMVDList_HTML (vfsfile_t *pipe); +qboolean FTENET_TCPConnect_HTTPResponse(ftenet_tcpconnect_stream_t *st, httparg_t arg[WCATTR_COUNT], qboolean allowgzip) +{ + char adr[256]; + int i; + const char *filetype = NULL; + char *resp = NULL; //response headers (no length/gap) + char *body = NULL; //response body + int method; + if (!strcmp(arg[WCATTR_METHOD], "GET")) + method = 0; + else if (!strcmp(arg[WCATTR_METHOD], "HEAD")) + method = 1; + else //if (!strcmp(arg[WCATTR_METHOD], "POST") || !strcmp(arg[WCATTR_METHOD], "PUT") || !strcmp(arg[WCATTR_METHOD], "OPTIONS")) + { + method = 404; + resp = "HTTP/1.1 405 Method Not Allowed\r\n"; + body = NULL; + } + + //FIXME: demonum/ + + st->dlfile = NULL; + if (!resp && *arg[WCATTR_URL] == '/') + { //'can't use SV_LocateDownload, as that assumes an active client. + char *name = arg[WCATTR_URL]+1; + char *extraheaders = ""; + time_t modificationtime = 0; + char *query = strchr(arg[WCATTR_URL]+1, '?'); + if (query) + *query++ = 0; + + //FIXME: remove ? + //FIXME: any path that ends with / should be sent to index.html or something + if (!*name) + name = "index.html"; + + //FIXME: provide some resource->filename mapping that allows various misc files. + + /*if (!strcmp(name, "live.html")) + { + resp = "HTTP/1.1 200 Ok\r\n" + "Content-Type: text/html\r\n"; + body = + "" + "" + "" + "
" + "" + "" + "" + "" + "Please install a plugin first.
" + "
" + "
" + "
" + "" + ; + } + else */ + if (!strcmp(name, "index.html")) + { + resp = "HTTP/1.1 200 Ok\r\n" + "Content-Type: text/html\r\n"; + + body = va( + "" + "" + "" + "" + "" +#ifdef _WIN32 + "" +#else + "" +#endif + "%s - %s" + "" + "" + "" + "" + "" + "" + ,fs_manifest->formalname, hostname.string, ENGINEWEBSITE, fs_manifest->installation, (st->remoteaddr.prot==NP_TLS)?"wss://":"ws://", arg[WCATTR_HOST]); + } +#ifdef _WIN32 + else if (!strcmp(name, "favicon.ico")) + { //we can serve up the icon from the exe. we just have to reformat it a little. + st->dlfile = VFSPIPE_Open(1, false); + if (st->dlfile) + { + struct + { + short reserved; + short type; + short count; + struct + { + qbyte width; + qbyte height; + qbyte colours; + qbyte reserved; + short planes; + short bitcount; + unsigned short bytes[2]; + unsigned short fileoffset[2]; + } entry[1]; + } icohdr; + struct + { + short reserved; + short type; + short count; + struct + { + qbyte width; + qbyte height; + qbyte colours; + qbyte reserved; + short planes; + short bitcount; + unsigned short bytes[2]; + unsigned short id; + } entry[1]; + } *grphdr = LockResource(LoadResource(NULL, FindResource(NULL, MAKEINTRESOURCE(IDI_ICON1), RT_GROUP_ICON))); + void *blob; + //fixme: scan for the best icon size to use. + + icohdr.reserved = 0; + icohdr.type = 1;//type + icohdr.count = countof(icohdr.entry);//count + icohdr.entry[0].width = grphdr->entry[0].width; + icohdr.entry[0].height = grphdr->entry[0].height; + icohdr.entry[0].colours = grphdr->entry[0].colours; + icohdr.entry[0].reserved = grphdr->entry[0].reserved; + icohdr.entry[0].planes = grphdr->entry[0].planes; + icohdr.entry[0].bitcount = grphdr->entry[0].bitcount; + icohdr.entry[0].bytes[0] = grphdr->entry[0].bytes[0]; + icohdr.entry[0].bytes[1] = grphdr->entry[0].bytes[1]; + icohdr.entry[0].fileoffset[0] = sizeof(icohdr); + icohdr.entry[0].fileoffset[1] = sizeof(icohdr)>>16; + VFS_WRITE(st->dlfile, &icohdr, sizeof(icohdr)); + + blob = LockResource(LoadResource(NULL, FindResource(NULL, MAKEINTRESOURCE(grphdr->entry[0].id), RT_ICON))); + + VFS_WRITE(st->dlfile, blob, grphdr->entry[0].bytes[0] | (grphdr->entry[0].bytes[1]<<16)); + + resp = NULL; + body = NULL; + } + } +#endif + /*else if (!strcmp(name, "default.fmf") && (st->dlfile = FS_OpenVFS("default.fmf", "rb", FS_ROOT))) + { + resp = "HTTP/1.1 200 Ok\r\n" + "Content-Type: application/x-ftemanifest\r\n"; + body = NULL; + }*/ + else if (!Q_strncasecmp(name, "demolist", 8)) + { + filetype = "text/html"; + st->dlfile = VFSPIPE_Open(1, false); + if (st->dlfile) + SV_UserCmdMVDList_HTML(st->dlfile); + } + else if (!Q_strncasecmp(name, "demonum/", 8)) + { + char *mvdname = SV_MVDNum(arg[WCATTR_METHOD], sizeof(arg[WCATTR_METHOD]), atoi(name+8)); + if (mvdname) + { + Con_Printf("Redirect %s to %s (copyrighted)\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + resp = va( "HTTP/1.1 302 Found\r\n" + "Location: /demos/%s\r\n" + "Content-Type: text/html\r\n" + , mvdname); + body = NULL; + } + } + else if (!SV_AllowDownload(name)) + { + Con_Printf("Denied download of %s to %s\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + resp = "HTTP/1.1 403 Forbidden\r\n" + "Content-Type: text/html\r\n"; + body = va("File \"%s\" may not be downloaded" + , name); + } + else if (!Q_strncasecmp(name, "package/", 8)) + { + if (FS_GetPackageDownloadable(name+8)) + st->dlfile = FS_OpenVFS(name+8, "rb", FS_ROOT); + else + { + Con_Printf("Unable to download %s to %s (copyrighted)\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + resp = "HTTP/1.1 403 Forbidden\r\n" + "Content-Type: text/html\r\n"; + body = "File is flagged as copyrighted"; + } + } + else + { + flocation_t gzloc; + flocation_t rawloc; + extern cvar_t sv_demoDir; + if (!Q_strncasecmp(name, "demos/", 6)) + name = va("%s/%s", sv_demoDir.string, name+6); + + if (FS_FLocateFile(name, FSLF_IFFOUND, &rawloc)) + { + char gzname[MAX_QPATH]; + time_t rt; + FS_GetLocMTime(&rawloc, &rt); + Q_snprintfz(gzname, sizeof(gzname), "%s.gz", name); + if (allowgzip && FS_FLocateFile(gzname, FSLF_IFFOUND, &gzloc)) + { + time_t gt; + if (rawloc.search == gzloc.search && FS_GetLocMTime(&gzloc, >) && gt >= rt) + { //must be in the same gamedir, and not older + extraheaders = "Content-Encoding: gzip\r\n"; + rawloc = gzloc; + rt = gt; + Con_DPrintf("HTTP: Serving %s instead\n", gzname); + } + else + Con_DPrintf("HTTP: Ignoring %s, outdated\n", gzname); + } + + modificationtime = rt; + + if (rawloc.search->flags & SPF_COPYPROTECTED) + { + Con_Printf("Unable to download %s to %s (copyrighted)\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + resp = "HTTP/1.1 403 Forbidden\r\n" + "Content-Type: text/html\r\n"; + + body = va("File %s inside a package
Download" + , name, FS_GetPackageDownloadFilename(&rawloc)); + } + else + st->dlfile = rawloc.search->handle->OpenVFS(rawloc.search->handle, &rawloc, "rb"); + } + else + st->dlfile = NULL; + } + if (st->dlfile) + { + char etag[64]; + if (!filetype) + { + char ext[64]; + int i; + static const char *mimes[] = + { + "html", "text/html", + "htm", "text/html", + "png", "image/png", + "jpeg", "image/jpeg", + "jpg", "image/jpeg", + "ico", "image/vnd.microsoft.icon", + "pk3", "application/zip", + "fmf", "application/x-ftemanifest", + "qtv", "application/x-qtv", + + "mvd", "application/x-multiviewdemo", + "mvd.gz", "application/x-multiviewdemo", + "qwd", "application/x-multiviewdemo", + "qwd.gz", "application/x-multiviewdemo", + "dem", "application/x-multiviewdemo", + "dem.gz", "application/x-multiviewdemo", + }; + COM_FileExtension (name, ext, sizeof(ext)); + for (i = 0; i < countof(mimes); i+=2) + { + if (!Q_strcasecmp(ext, mimes[i])) + { + filetype = mimes[i+1]; + break; + } + } + } + + if (modificationtime) + { + Q_snprintfz(etag, sizeof(etag), "W/\"%0"PRIxQOFS"\"", (qofs_t)modificationtime); + if (!strcmp(arg[WCATTR_IFNONEMATCH], etag)) + { + resp = "HTTP/1.1 304 Not Modified \r\n"; + body = NULL; + } + + Q_snprintfz(etag, sizeof(etag), "ETag: W/\"%0"PRIxQOFS"\"\r\n", (qofs_t)modificationtime); + } + + if (resp) + { + VFS_CLOSE(st->dlfile); + st->dlfile = NULL; + } + else + { + Con_Printf("Downloading %s to %s\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + if (filetype) + { + resp = va("HTTP/1.1 200 Ok\r\n" + "Content-Type: %s\r\n" + "%s%s", + filetype, + etag,extraheaders); + } + else + resp = va("HTTP/1.1 200 Ok\r\n" + "%s%s", + etag,extraheaders); + body = NULL; + } + } + else if (!resp) + { + Con_Printf("Unable to download %s to %s\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + resp = "HTTP/1.1 404 File Not Found\r\n" + "Content-Type: text/html\r\n"; + body = "File not found"; + } + } + if (!resp) + { + Con_Printf("Invalid download request %s to %s\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + resp = "HTTP/1.1 404 File Not Found\r\n" + "Content-Type: text/html\r\n"; + body = "This is a Quake WebSocket server, not an http server.
\r\n" + ""FULLENGINENAME""; + } + + st->clienttype = TCPC_HTTPCLIENT; + + i = strlen(resp); + if (st->outlen + i > sizeof(st->outbuffer)) + return false; + memcpy(st->outbuffer+st->outlen, resp, i); + st->outlen+= i; + + resp = "Access-Control-Allow-Origin: *\r\n"; + i = strlen(resp); + if (st->outlen + i > sizeof(st->outbuffer)) + return false; + memcpy(st->outbuffer+st->outlen, resp, i); + st->outlen+= i; + + if (st->dlfile || body) + { + qofs_t size; + if (body) + size = strlen(body); + else + size = VFS_GETLEN(st->dlfile); + resp = adr; + Q_snprintfz(adr, sizeof(adr), "Content-Length: %"PRIuQOFS"\r\n", size); + } + else + resp = "Content-Length: 0\r\n"; + i = strlen(resp); + if (st->outlen + i > sizeof(st->outbuffer)) + return false; + memcpy(st->outbuffer+st->outlen, resp, i); + st->outlen+= i; + + if (st->outlen + 2 > sizeof(st->outbuffer)) + return false; + memcpy(st->outbuffer+st->outlen, "\r\n", 2); + st->outlen+= 2; + + if (method == 1) + { //body is not included in HEAD responses + body = NULL; + if (st->dlfile) + VFS_CLOSE(st->dlfile); + st->dlfile = NULL; + } + else if (body) + { + i = strlen(body); + if (st->outlen + i > sizeof(st->outbuffer)) + return false; + memcpy(st->outbuffer+st->outlen, body, i); + st->outlen+= i; + } + return true; +} + +void FTENET_TCPConnect_WebRTCServerAssigned(ftenet_tcpconnect_stream_t *list, ftenet_tcpconnect_stream_t *client, ftenet_tcpconnect_stream_t *server) +{ + qbyte buffer[5]; + int trynext = 0; + ftenet_tcpconnect_stream_t *o; + if (client->webrtc.clientnum < 0) + client->webrtc.clientnum = 0; + for(;;) + { + for (o = list; o; o = o->next) + { + if (o != client && o->clienttype == TCPC_WEBRTC_CLIENT && !strcmp(o->webrtc.resource, client->webrtc.resource) && client->webrtc.clientnum == o->webrtc.clientnum) + break; + } + if (!o) + break; + client->webrtc.clientnum = trynext++; + } + + if (server) + { //and tell them both, if the server is actually up + buffer[0] = 2; + buffer[1] = (client->webrtc.clientnum>>0)&0xff; + buffer[2] = (client->webrtc.clientnum>>8)&0xff; + buffer[3] = (client->webrtc.clientnum>>16)&0xff; + buffer[4] = (client->webrtc.clientnum>>24)&0xff; + FTENET_TCPConnect_WebSocket_Splurge(server, 2, buffer, 5); + FTENET_TCPConnect_WebSocket_Splurge(client, 2, "\x02\xff\xff\xff\xff", 5); + } +} + +qboolean FTENET_TCP_ParseHTTPRequest(ftenet_tcpconnect_connection_t *con, ftenet_tcpconnect_stream_t *st) +{ + char *resp; + char adr[256]; + int i, j; + int attr = 0; + int alen = 0; + qboolean headerscomplete = false; + int contentlen = 0; + int websocketver = 0; + qboolean acceptsgzip = false; + qboolean sendingweirdness = false; + char arg[WCATTR_COUNT][64]; + + if (!net_enable_http.ival && !net_enable_websockets.ival && !net_enable_webrtcbroker.ival) + { + //we need to respond, firefox will create 10 different connections if we just close it + resp = va( "HTTP/1.1 403 Forbidden\r\n" + "Connection: close\r\n" //let the client know that any pipelining it was doing will have been ignored + "\r\n"); + VFS_WRITE(st->clientstream, resp, strlen(resp)); + return false; + } + + for (i = 0; i < WCATTR_COUNT; i++) + arg[i][0] = 0; + for (i = 0; i < st->inlen; i++) + { + if (alen == 63) + { + Con_Printf("http request overflow from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + //we need to respond, firefox will create 10 different connections if we just close it + resp = va( "HTTP/1.1 414 URI Too Long\r\n" + "Connection: close\r\n" //let the client know that any pipelining it was doing will have been ignored + "\r\n"); + VFS_WRITE(st->clientstream, resp, strlen(resp)); + return false; //overflow... + } + if (st->inbuffer[i] == ' ' || st->inbuffer[i] == '\t' || st->inbuffer[i] == '\r') + { + arg[attr][alen++] = 0; + alen=0; + if (attr++ == WCATTR_HTTP) + break; + + for (; i < st->inlen && (st->inbuffer[i] == ' ' || st->inbuffer[i] == '\t' || st->inbuffer[i] == '\r'); i++) + ; + if (i == st->inlen) + break; + } + if (st->inbuffer[i] == '\n') + { + arg[attr][alen++] = 0; + alen=0; + break; + } + if (st->inbuffer[i] < ' ' && st->inbuffer[i] != '\t') + { + Con_Printf("http request contained control codes\n"); + return false; + } + arg[attr][alen++] = st->inbuffer[i]; + } + if (!*arg[WCATTR_URL]) //don't bug out if it was truncated. + strcpy(arg[WCATTR_URL], "/"); + + if (st->inbuffer[i] == '\r') + i++; + if (st->inbuffer[i] == '\n') + { //okay, we have at least a line... try scanning the rest of the header for known key:value pairs, and see if we can reach the end + i++; + + attr = 0; + j = i; + for (; i < st->inlen; i++) + { + if ((i+1 < st->inlen && st->inbuffer[i] == '\r' && st->inbuffer[i+1] == '\n') || + (i < st->inlen && st->inbuffer[i] == '\n')) + { + if (st->inbuffer[i] == '\n') + i++; + else + i+=2; + headerscomplete = true; + break; + } + + for (; i < st->inlen && (st->inbuffer[i] == ' ' || st->inbuffer[i] == '\t'); i++) + ; + if (i == st->inlen) + break; + + for (j = i; j < st->inlen; j++) + { + if (st->inbuffer[j] == ':' || st->inbuffer[j] == '\n') + { + /*set j to the end of the word, going back past whitespace*/ + while (j > i && (st->inbuffer[j-1] == ' ' || st->inbuffer[i-1] == '\t')) + j--; + break; + } + } + if (j-i == 4 && !strnicmp(&st->inbuffer[i], "Host", 4)) + attr = WCATTR_HOST; + else if (j-i == 7 && !strnicmp(&st->inbuffer[i], "Upgrade", 7)) + attr = WCATTR_UPGRADE; + else if (j-i == 10 && !strnicmp(&st->inbuffer[i], "Connection", 10)) + attr = WCATTR_CONNECTION; + //websocket stuff + else if (j-i == 17 && !strnicmp(&st->inbuffer[i], "Sec-WebSocket-Key", 17)) + attr = WCATTR_WSKEY; + else if (j-i == 21 && !strnicmp(&st->inbuffer[i], "Sec-WebSocket-Version", 21)) + attr = WCATTR_WSVER; +// else if (j-i == 6 && !strnicmp(&st->inbuffer[i], "Origin", j-i)) +// attr = WCATTR_ORIGIN; + else if (j-i == 22 && !strnicmp(&st->inbuffer[i], "Sec-WebSocket-Protocol", 22)) + attr = WCATTR_WSPROTO; +// else if (j-i == 24 && !strnicmp(&st->inbuffer[i], "Sec-WebSocket-Extensions", 24)) +// attr = WCATTR_WSEXT; + //http stuff + else if (j-i == 14 && !strnicmp(&st->inbuffer[i], "Content-Length", 14)) + attr = WCATTR_CONTENT_LENGTH; //in case they're trying to post/put stuff + else if (j-i == 15 && !strnicmp(&st->inbuffer[i], "Accept-Encoding", 15)) + attr = WCATTR_ACCEPT_ENCODING; //for gzip + else if (j-i == 17 && !strnicmp(&st->inbuffer[i], "Transfer-Encoding", 17)) + attr = WCATTR_TRANSFER_ENCODING;//in case they're trying to post/put complex stuff + else if (j-i == 13 && !strnicmp(&st->inbuffer[i], "If-None-Match", 13)) + attr = WCATTR_IFNONEMATCH;//for clientside caches + else + attr = 0; + + i = j; + /*skip over the whitespace at the end*/ + for (; i < st->inlen && (st->inbuffer[i] == ' ' || st->inbuffer[i] == '\t'); i++) + ; + if (i < st->inlen && st->inbuffer[i] == ':') + { + i++; + for (; i < st->inlen && (st->inbuffer[i] == ' ' || st->inbuffer[i] == '\t'); i++) + ; + j = i; + + //FIXME: check for control codes. although probably not a problem in this part + for (; i < st->inlen && st->inbuffer[i] != '\n'; i++) + ; + if (i > j && st->inbuffer[i-1] == '\r') + i--; + if (attr) + { + switch(attr) + { + case WCATTR_CONTENT_LENGTH: + contentlen = atoi(&st->inbuffer[j]); + break; + case WCATTR_ACCEPT_ENCODING: + while (j < i) + { + if (st->inbuffer[j] == ' ' || st->inbuffer[j] == '\t') + { + j++; + continue; + } + else if (j+4 <= i && !strncmp(&st->inbuffer[j], "gzip", 4) && (j+4==i || st->inbuffer[j+4] == ';' || st->inbuffer[j+4] == ',')) + acceptsgzip = true; + + while (j < i && st->inbuffer[j] != ',') + j++; + if (j < i && st->inbuffer[j] == ',') + j++; + } + break; + case WCATTR_TRANSFER_ENCODING: + sendingweirdness = true; //doesn't matter what it is, we can't handle it. + break; + case WCATTR_WSVER: + websocketver = atoi(&st->inbuffer[j]); + break; + default: + Q_strncpyz(arg[attr], &st->inbuffer[j], (i-j > 63)?64:(i - j + 1)); + break; + } + } + if (i < st->inlen && st->inbuffer[i] == '\r') + i++; + } + else + { + /*just a word on the line on its own. that would be invalid in http*/ + return false; + } + } + } + + if (!headerscomplete) + { + Con_Printf("http header parsing failed\n"); + return false; //the caller said it was complete! something's fucked if we're here + } + + //okay, the above code parsed all the headers that we care about. + + if (contentlen && i+contentlen > st->inlen) + { //request isn't complete yet + if (i+contentlen > sizeof(st->inbuffer)-1) + { + resp = va( "HTTP/1.1 413 Payload Too Large \r\n" + "Connection: close\r\n" //let the client know that any pipelining it was doing will have been ignored + "\r\n"); + VFS_WRITE(st->clientstream, resp, strlen(resp)); + Con_Printf("http oversize request\n"); + return false; //can never be completed. + } + return true; + } + + //clients uploading chunked stuff is bad/unsupported. + if (sendingweirdness) + { + resp = va( "HTTP/1.1 413 Payload Too Large \r\n" + "Connection: close\r\n" //let the client know that any pipelining it was doing will have been ignored + "\r\n"); + VFS_WRITE(st->clientstream, resp, strlen(resp)); + Con_Printf("http encoded request\n"); + return false; //can't handle the request, so discard the connection + } + + memmove(st->inbuffer, st->inbuffer+i, st->inlen - (i)); + st->inlen -= i; + + //for websocket connections: + //must be a Host, Upgrade=websocket, Connection=Upgrade, Sec-WebSocket-Key=base64(randbytes(16)), Sec-WebSocket-Version=13 + //optionally will be Origin=url, Sec-WebSocket-Protocol=FTEWebSocket, Sec-WebSocket-Extensions + //other fields will be ignored. + if (!stricmp(arg[WCATTR_UPGRADE], "websocket") && (!stricmp(arg[WCATTR_CONNECTION], "Upgrade") || !stricmp(arg[WCATTR_CONNECTION], "keep-alive, Upgrade"))) + { + if (!net_enable_websockets.ival && !net_enable_webrtcbroker.ival) + return false; + if (websocketver != 13) + { + Con_Printf("Outdated websocket request for \"%s\" from \"%s\". got version %i, expected version 13\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), websocketver); + + resp = va( "HTTP/1.1 426 Upgrade Required\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Connection: close\r\n" //let the client know that any pipelining it was doing will have been ignored + "\r\n"); + VFS_WRITE(st->clientstream, resp, strlen(resp)); + return false; + } + else + { + qboolean fail = false; + char acceptkey[20*2]; + unsigned char sha1digest[20]; + char *blurgh; + char *protoname = ""; + + blurgh = va("%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", arg[WCATTR_WSKEY]); + tobase64(acceptkey, sizeof(acceptkey), sha1digest, SHA1(sha1digest, sizeof(sha1digest), blurgh, strlen(blurgh))); + + if (st->remoteaddr.prot == NP_TLS) + st->remoteaddr.prot = NP_WSS; + else + st->remoteaddr.prot = NP_WS; + Con_Printf("Websocket request for %s from %s (%s)\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), arg[WCATTR_WSPROTO]); + + protoname = va("Sec-WebSocket-Protocol: %s\r\n", arg[WCATTR_WSPROTO]); + + //the choice of protocol affects the type of response that we give rather than anything mystic + if (!strcmp(arg[WCATTR_WSPROTO], "quake")) + st->clienttype = TCPC_WEBSOCKETNQ; //raw nq data, all reliable, for compat with webquake + else if (!strcmp(arg[WCATTR_WSPROTO], "rtc_client")) + st->clienttype = TCPC_WEBRTC_CLIENT;//not a real client. + else if (!strcmp(arg[WCATTR_WSPROTO], "rtc_host")) + st->clienttype = TCPC_WEBRTC_HOST;//not a real client, but a competing server! oh noes! + else if (!strcmp(arg[WCATTR_WSPROTO], "binary")) + st->clienttype = TCPC_WEBSOCKETB; //emscripten's networking libraries insists on 'binary', but we stopped using that a while back because its hostname->ip stuff was flawed. + else if (!strcmp(arg[WCATTR_WSPROTO], "fteqw")) + st->clienttype = TCPC_WEBSOCKETB; //specific custom protocol name to avoid ambiguities. + else + { + st->clienttype = TCPC_WEBSOCKETU; //nacl supports only utf-8 encoded data, at least at the time I implemented it. + protoname = ""; + } + + switch(st->clienttype) + { + case TCPC_WEBSOCKETNQ: + case TCPC_WEBSOCKETU: + case TCPC_WEBSOCKETB: + if (!net_enable_websockets.ival) + return false; + break; + case TCPC_WEBRTC_HOST: + case TCPC_WEBRTC_CLIENT: + if (!net_enable_webrtcbroker.ival) + return false; + break; + } + + if (*arg[WCATTR_URL] == '/') + Q_strncpyz(st->webrtc.resource, arg[WCATTR_URL]+1, sizeof(st->webrtc.resource)); + else + Q_strncpyz(st->webrtc.resource, arg[WCATTR_URL], sizeof(st->webrtc.resource)); + st->webrtc.clientnum = -1; +#ifndef SUPPORT_RTC_ICE + if (st->clienttype == TCPC_WEBRTC_CLIENT && !*st->webrtc.resource) + fail = true; +#endif + + resp = va( "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Access-Control-Allow-Origin: *\r\n" //allow cross-origin requests. this means you can use any domain to play on any public server. + "Sec-WebSocket-Accept: %s\r\n" + "%s" + "\r\n", acceptkey, protoname); + //send the websocket handshake response. + VFS_WRITE(st->clientstream, resp, strlen(resp)); + + if (st->clienttype == TCPC_WEBRTC_CLIENT && !*st->webrtc.resource) + { //client should be connected to us rather than any impostors. tell it to start its ICE handshake. + FTENET_TCPConnect_WebSocket_Splurge(st, 2, "\x02\xff\xff\xff\xff", 5); + } + else if (st->clienttype == TCPC_WEBRTC_HOST || st->clienttype == TCPC_WEBRTC_CLIENT) + { + ftenet_tcpconnect_stream_t *o; + if (st->clienttype == TCPC_WEBRTC_HOST) + { //if its a server, then let it know its final resource name + if (!*st->webrtc.resource) + { //webrtc servers need some unique resource address. lets use their ip+port for now. we should probably be randomising this + Q_snprintfz(st->webrtc.resource, sizeof(st->webrtc.resource), "%s", adr); + } + + for (o = con->tcpstreams; o; o = o->next) + { + if (o != st && o->clienttype == TCPC_WEBRTC_HOST && !strcmp(st->webrtc.resource, o->webrtc.resource)) + return false; //conflict! can't have two servers listening on the same url + } + + net_message_buffer[0] = 1; + net_message_buffer[1] = 0xff; + net_message_buffer[2] = 0xff; + strcpy(net_message_buffer+3, st->webrtc.resource); + FTENET_TCPConnect_WebSocket_Splurge(st, 2, net_message_buffer, strlen(net_message_buffer)); + + //if we have (inactive) clients connected, assign them (and let them know that they need to start handshaking) + for (o = con->tcpstreams; o; o = o->next) + { + if (o->clienttype == TCPC_WEBRTC_CLIENT && !strcmp(st->webrtc.resource, o->webrtc.resource)) + FTENET_TCPConnect_WebRTCServerAssigned(con->tcpstreams, o, st); + } + } + else + { //find its server, if we can + for (o = con->tcpstreams; o; o = o->next) + { + if (o->clienttype == TCPC_WEBRTC_HOST && !strcmp(st->webrtc.resource, o->webrtc.resource)) + break; + } + //and assign it to this client + FTENET_TCPConnect_WebRTCServerAssigned(con->tcpstreams, st, o); + } + } + + //and the connection is okay + + if (st->clienttype == TCPC_WEBSOCKETNQ) + { + //inject a connection request so that our server actually accepts them... + net_message.cursize = 0; + net_message.packing = SZ_RAWBYTES; + net_message.currentbit = 0; + net_from = st->remoteaddr; + MSG_WriteLong(&net_message, LongSwap(NETFLAG_CTL | (strlen(NQ_NETCHAN_GAMENAME)+7))); + MSG_WriteByte(&net_message, CCREQ_CONNECT); + MSG_WriteString(&net_message, NQ_NETCHAN_GAMENAME); + MSG_WriteByte(&net_message, NQ_NETCHAN_VERSION); + } + return true; + } + } + else + { + if (!net_enable_http.ival) + return false; + return FTENET_TCPConnect_HTTPResponse(st, arg, acceptsgzip); + } +} + +static int QDECL TLSPromoteRead (struct vfsfile_s *file, void *buffer, int bytestoread) +{ + if (bytestoread > net_message.cursize) + bytestoread = net_message.cursize; + memcpy(buffer, net_message_buffer, bytestoread); + net_message.cursize = 0; + return bytestoread; +} +#endif +void FTENET_TCPConnect_PrintStatus(ftenet_generic_connection_t *gcon) +{ + ftenet_tcpconnect_connection_t *con = (ftenet_tcpconnect_connection_t*)gcon; + ftenet_tcpconnect_stream_t *st; + char adr[MAX_QPATH]; + if (!con->tcpstreams) + return; + for (st = con->tcpstreams; st; st = st->next) + { + NET_AdrToString(adr, sizeof(adr), &st->remoteaddr); + switch(st->clienttype) + { + case TCPC_UNKNOWN: //note: this is often a pending http client that's waiting on the off-chance of having more requests to send + Con_Printf("handshaking %s\n", adr); + break; + case TCPC_QIZMO: + Con_Printf("qizmo %s\n", adr); + break; + case TCPC_WEBSOCKETU: + case TCPC_WEBSOCKETB: + case TCPC_WEBSOCKETNQ: + Con_Printf("websocket %s\n", adr); + break; +#ifndef CLIENTONLY + case TCPC_HTTPCLIENT: + Con_Printf("http %s\n", adr); + break; + case TCPC_WEBRTC_CLIENT: + Con_Printf("webrtc client %s/%s\n", adr, st->webrtc.resource); + break; + case TCPC_WEBRTC_HOST: + Con_Printf("webrtc host %s/%s\n", adr, st->webrtc.resource); + break; +#endif + } + } +} qboolean FTENET_TCPConnect_GetPacket(ftenet_generic_connection_t *gcon) { ftenet_tcpconnect_connection_t *con = (ftenet_tcpconnect_connection_t*)gcon; @@ -3125,11 +4092,23 @@ qboolean FTENET_TCPConnect_GetPacket(ftenet_generic_connection_t *gcon) if (st->timeouttime < timeval) { - Con_Printf ("tcp peer %s timed out\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - goto closesvstream; +#ifndef CLIENTONLY + if (!st->pinging && (st->clienttype==TCPC_WEBRTC_CLIENT||st->clienttype==TCPC_WEBRTC_HOST) && *st->webrtc.resource) + { //ping broker clients. there usually shouldn't be any data flow to keep it active otherwise. + st->timeouttime = timeval + 30; + st->pinging = true; + + FTENET_TCPConnect_WebSocket_Splurge(st, 0x9, "ping", 4); + } + else +#endif + { + Con_Printf ("tcp peer %s timed out\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + goto closesvstream; + } } - ret = VFS_READ(st->clientstream, st->inbuffer+st->inlen, sizeof(st->inbuffer)-st->inlen); + ret = VFS_READ(st->clientstream, st->inbuffer+st->inlen, sizeof(st->inbuffer)-1-st->inlen); if (ret < 0) { Con_Printf ("tcp peer %s closed connection\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); @@ -3146,289 +4125,116 @@ closesvstream: if (st->inlen < 6) continue; +#ifdef HAVE_SSL //if its non-ascii, then try and upgrade the connection to tls + if (net_enable_tls.ival && con->generic.islisten && st->remoteaddr.prot == NP_STREAM && st->clientstream && !((st->inbuffer[0] >= 'a' && st->inbuffer[0] <= 'z') || (st->inbuffer[0] >= 'A' && st->inbuffer[0] <= 'Z'))) + { + //copy off our buffer so we can read it into the tls stream's buffer instead. + vfsfile_t *stream = st->clientstream; + int (QDECL *realread) (struct vfsfile_s *file, void *buffer, int bytestoread); + realread = stream->ReadBytes; + stream->ReadBytes = TLSPromoteRead; + memcpy(net_message_buffer, st->inbuffer, st->inlen); + net_message.cursize = st->inlen; + //wrap the stream now + st->clientstream = FS_OpenSSL(NULL, st->clientstream, true, false); + st->remoteaddr.prot = NP_TLS; + //try and reclaim it all + st->inlen = VFS_READ(st->clientstream, st->inbuffer, sizeof(st->inbuffer)-1); + //make sure we actually read from the proper stream again + stream->ReadBytes = realread; + if (net_message.cursize) + goto closesvstream; //something cocked up. we didn't give the tls stream all the data. + net_message.cursize = 0; + continue; + } +#endif + if (!strncmp(st->inbuffer, "qizmo\n", 6)) { - memmove(st->inbuffer, st->inbuffer+6, st->inlen - (6)); - st->inlen -= 6; - st->clienttype = TCPC_QIZMO; - if (con->generic.islisten) +#ifdef CLIENTONLY + if (1) +#else + if (net_enable_qizmo.ival || !con->generic.islisten) +#endif { - //send the qizmo handshake response. - VFS_WRITE(st->clientstream, "qizmo\n", 6); + memmove(st->inbuffer, st->inbuffer+6, st->inlen - (6)); + st->inlen -= 6; + st->clienttype = TCPC_QIZMO; + if (con->generic.islisten) + { + //send the qizmo handshake response. + VFS_WRITE(st->clientstream, "qizmo\n", 6); + } } + else + goto closesvstream; } - else if (con->generic.islisten && !strncmp(st->inbuffer, "GET ", 4)) +#ifndef CLIENTONLY + else if (con->generic.islisten)// && !strncmp(st->inbuffer, "GET ", 4)) { - int i, j; - int attr = 0; - int alen = 0; + //qtv or http request header. these terminate with a blank line. + int i = 0; qboolean headerscomplete = false; - enum - { - WCATTR_METHOD, - WCATTR_URL, - WCATTR_HTTP, - WCATTR_HOST, - WCATTR_UPGRADE, - WCATTR_CONNECTION, - WCATTR_WSKEY, - WCATTR_WSVER, - //WCATTR_ORIGIN, - WCATTR_WSPROTO, - //WCATTR_WSEXT, - WCATTR_COUNT - }; - char arg[WCATTR_COUNT][64]; - for (i = 0; i < WCATTR_COUNT; i++) - arg[i][0] = 0; - for (i = 0; i < st->inlen; i++) - { - if (alen == 63) - goto handshakeerror; - if (st->inbuffer[i] == ' ' || st->inbuffer[i] == '\t') - { - arg[attr][alen++] = 0; - alen=0; - if (attr++ == WCATTR_HTTP) - break; - for (; i < st->inlen && (st->inbuffer[i] == ' ' || st->inbuffer[i] == '\t'); i++) - ; - if (i == st->inlen) - break; - } - arg[attr][alen++] = st->inbuffer[i]; - if (st->inbuffer[i] == '\n') - { - arg[attr][alen++] = 0; - alen=0; - break; - } - } - i++; - attr = 0; - j = i; for (; i < st->inlen; i++) { + //we're at the start of a line, so if its a \r\n or a \n then its a blank line, and the headers are complete if ((i+1 < st->inlen && st->inbuffer[i] == '\r' && st->inbuffer[i+1] == '\n') || (i < st->inlen && st->inbuffer[i] == '\n')) { - i+=2; + if (st->inbuffer[i] == '\n') + i++; + else + i+=2; headerscomplete = true; break; } - for (; i < st->inlen && (st->inbuffer[i] == ' ' || st->inbuffer[i] == '\t'); i++) + for (; i < st->inlen && st->inbuffer[i] != '\n'; i++) ; - if (i == st->inlen) - break; - - for (j = i; j < st->inlen; j++) - { - if (st->inbuffer[j] == ':' || st->inbuffer[j] == '\n') - { - /*set j to the end of the word, going back past whitespace*/ - while (j > i && (st->inbuffer[j-1] == ' ' || st->inbuffer[i-1] == '\t')) - j--; - break; - } - } - if (!strnicmp(&st->inbuffer[i], "Host", j-i)) - attr = WCATTR_HOST; - else if (!strnicmp(&st->inbuffer[i], "Upgrade", j-i)) - attr = WCATTR_UPGRADE; - else if (!strnicmp(&st->inbuffer[i], "Connection", j-i)) - attr = WCATTR_CONNECTION; - else if (!strnicmp(&st->inbuffer[i], "Sec-WebSocket-Key", j-i)) - attr = WCATTR_WSKEY; - else if (!strnicmp(&st->inbuffer[i], "Sec-WebSocket-Version", j-i)) - attr = WCATTR_WSVER; -// else if (!strnicmp(&st->inbuffer[i], "Origin", j-i)) -// attr = WCATTR_ORIGIN; - else if (!strnicmp(&st->inbuffer[i], "Sec-WebSocket-Protocol", j-i)) - attr = WCATTR_WSPROTO; -// else if (!strnicmp(&st->inbuffer[i], "Sec-WebSocket-Extensions", j-i)) -// attr = WCATTR_WSEXT; - else - attr = 0; - - i = j; - /*skip over the whitespace at the end*/ - for (; i < st->inlen && (st->inbuffer[i] == ' ' || st->inbuffer[i] == '\t'); i++) - ; - if (i < st->inlen && st->inbuffer[i] == ':') - { - i++; - for (; i < st->inlen && (st->inbuffer[i] == ' ' || st->inbuffer[i] == '\t'); i++) - ; - j = i; - - for (; i < st->inlen && st->inbuffer[i] != '\n'; i++) - ; - if (i > j && st->inbuffer[i-1] == '\r') - i--; - if (attr) - Q_strncpyz(arg[attr], &st->inbuffer[j], (i-j > 63)?64:(i - j + 1)); - if (i < st->inlen && st->inbuffer[i] == '\r') - i++; - } - else - { - /*just a word on the line on its own*/ - goto handshakeerror; - } } if (headerscomplete) { - char *resp; - //must be a Host, Upgrade=websocket, Connection=Upgrade, Sec-WebSocket-Key=base64(randbytes(16)), Sec-WebSocket-Version=13 - //optionally will be Origin=url, Sec-WebSocket-Protocol=FTEWebSocket, Sec-WebSocket-Extensions - //other fields will be ignored. - - if (!stricmp(arg[WCATTR_UPGRADE], "websocket") && (!stricmp(arg[WCATTR_CONNECTION], "Upgrade") || !stricmp(arg[WCATTR_CONNECTION], "keep-alive, Upgrade"))) + //for QTV connections, we just need the method and a blank line. our qtv parser will parse the actual headers. + if (!Q_strncasecmp(st->inbuffer, "QTV", 3)) { - if (atoi(arg[WCATTR_WSVER]) != 13) + int r = net_enable_qtv.ival?SV_MVD_GotQTVRequest(st->clientstream, st->inbuffer, st->inbuffer+st->inlen, &st->qtvstate):-1; + i = st->inlen; + memmove(st->inbuffer, st->inbuffer+i, st->inlen - (i)); + st->inlen -= i; + switch(r) { - Con_Printf("Outdated websocket request for \"%s\" from \"%s\". got version %i, expected version 13\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), atoi(arg[WCATTR_WSVER])); - - memmove(st->inbuffer, st->inbuffer+i, st->inlen - (i)); - st->inlen -= i; - resp = va( "HTTP/1.1 426 Upgrade Required\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n"); - //send the websocket handshake rejection. - VFS_WRITE(st->clientstream, resp, strlen(resp)); - + case -1: goto closesvstream; - } - else - { - char acceptkey[20*2]; - unsigned char sha1digest[20]; - char *blurgh; - char *protoname = ""; - memmove(st->inbuffer, st->inbuffer+i, st->inlen - (i)); - st->inlen -= i; - - blurgh = va("%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", arg[WCATTR_WSKEY]); - tobase64(acceptkey, sizeof(acceptkey), sha1digest, SHA1(sha1digest, sizeof(sha1digest), blurgh, strlen(blurgh))); - - Con_Printf("Websocket request for %s from %s (%s)\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), arg[WCATTR_WSPROTO]); - - if (!strcmp(arg[WCATTR_WSPROTO], "quake")) - { - st->clienttype = TCPC_WEBSOCKETNQ; //emscripten doesn't give us a choice, but its compact. - protoname = "Sec-WebSocket-Protocol: quake\r\n"; - } - else if (!strcmp(arg[WCATTR_WSPROTO], "binary")) - { - st->clienttype = TCPC_WEBSOCKETB; //emscripten doesn't give us a choice, but its compact. - protoname = "Sec-WebSocket-Protocol: binary\r\n"; //emscripten is a bit limited - } - else - { - st->clienttype = TCPC_WEBSOCKETU; //nacl supports only utf-8 encoded data, at least at the time I implemented it. - protoname = va("Sec-WebSocket-Protocol: %s\r\n", arg[WCATTR_WSPROTO]); //emscripten is a bit limited - } - - resp = va( "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" - "Upgrade: websocket\r\n" - "Connection: Upgrade\r\n" - "Access-Control-Allow-Origin: *\r\n" //allow cross-origin requests. this means you can use any domain to play on any public server. - "Sec-WebSocket-Accept: %s\r\n" - "%s" - "\r\n", acceptkey, protoname); - //send the websocket handshake response. - VFS_WRITE(st->clientstream, resp, strlen(resp)); - - //and the connection is okay - - if (st->clienttype == TCPC_WEBSOCKETNQ) - { - //hide a connection request in there... - net_message.cursize = 0; - net_message.packing = SZ_RAWBYTES; - net_message.currentbit = 0; - net_from = st->remoteaddr; - MSG_WriteLong(&net_message, LongSwap(NETFLAG_CTL | (strlen(NQ_NETCHAN_GAMENAME)+7))); - MSG_WriteByte(&net_message, CCREQ_CONNECT); - MSG_WriteString(&net_message, NQ_NETCHAN_GAMENAME); - MSG_WriteByte(&net_message, NQ_NETCHAN_VERSION); - return true; - } + case 0: + continue; + case 1: + st->clientstream = NULL; + continue; } } else { - memmove(st->inbuffer, st->inbuffer+i, st->inlen - (i)); - st->inlen -= i; - if (!strcmp(arg[WCATTR_URL], "/live.html")) - { - resp = va( "HTTP/1.1 200 Ok\r\n" - "Connection: Close\r\n" - "Content-Type: text/html\r\n" - "\r\n" - "" - "" - "" - "
" - "" - "" - "" - "" - "Please install a plugin first.
" - "
" - "
" - "
" - "" - ); - } -/* else if ((!strcmp(arg[WCATTR_URL], "/ftewebgl.html") || !strcmp(arg[WCATTR_URL], "/ftewebgl.html.fmf") || !strcmp(arg[WCATTR_URL], "/pak0.pak")) && ((st->file = VFSOS_Open(va("C:/Incoming/vm%s", arg[WCATTR_URL]), "rb")))) - { - Con_Printf("Downloading %s to %s\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), st->remoteaddr)); - resp = va( "HTTP/1.1 200 Ok\r\n" - "Content-Type: text/html\r\n" - "Content-Length: %i\r\n" - "\r\n", - VFS_GETLEN(st->file) - ); - send(st->socketnum, resp, strlen(resp), 0); - st->clienttype = TCPC_HTTPCLIENT; - continue; - } -*/ - else - { - Con_Printf("Invalid download request %s to %s\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - resp = va( "HTTP/1.1 404 Ok\r\n" - "Connection: Close\r\n" - "Content-Type: text/html\r\n" - "\r\n" + net_message.cursize = 0; + if (!FTENET_TCP_ParseHTTPRequest(con, st)) + goto closesvstream; - "This is a Quake WebSocket server, not an http server.
\r\n" - ""FULLENGINENAME"" - ); - } - - //send the websocket handshake rejection. - VFS_WRITE(st->clientstream, resp, strlen(resp)); - - goto closesvstream; + if (net_message.cursize > 0) + return true; } + continue; } } +#endif else { -handshakeerror: Con_Printf ("Unknown TCP handshake from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); goto closesvstream; } break; +#ifndef CLIENTONLY case TCPC_HTTPCLIENT: if (st->outlen) { /*try and flush the old data*/ @@ -3444,16 +4250,23 @@ handshakeerror: } if (!st->outlen) { - st->outlen = VFS_READ(st->dlfile, st->outbuffer, sizeof(st->outbuffer)); + if (st->dlfile) + st->outlen = VFS_READ(st->dlfile, st->outbuffer, sizeof(st->outbuffer)); + else + st->outlen = 0; if (st->outlen <= 0) { - VFS_CLOSE(st->dlfile); + if (st->dlfile) + VFS_CLOSE(st->dlfile); st->dlfile = NULL; st->clienttype = TCPC_UNKNOWN; Con_Printf ("Outgoing file transfer complete\n"); + if (st->httpstate.connection_close) + goto closesvstream; } } continue; +#endif case TCPC_QIZMO: if (st->inlen < 2) continue; @@ -3477,28 +4290,21 @@ handshakeerror: net_message.currentbit = 0; net_from = st->remoteaddr; - return true; - case TCPC_UNFRAMED: - if (!st->inlen) - continue; - net_message.cursize = st->inlen; - memcpy(net_message_buffer, st->inbuffer, net_message.cursize); - st->inlen = 0; - - net_message.packing = SZ_RAWBYTES; - net_message.currentbit = 0; - net_from = st->remoteaddr; return true; case TCPC_WEBSOCKETU: case TCPC_WEBSOCKETB: case TCPC_WEBSOCKETNQ: +#ifndef CLIENTONLY + case TCPC_WEBRTC_HOST: + case TCPC_WEBRTC_CLIENT: +#endif while (st->inlen >= 2) { unsigned short ctrl = ((unsigned char*)st->inbuffer)[0]<<8 | ((unsigned char*)st->inbuffer)[1]; unsigned long paylen; unsigned int payoffs = 2; unsigned int mask = 0; - st->inbuffer[st->inlen]=0; +// st->inbuffer[st->inlen]=0; if (ctrl & 0x7000) { Con_Printf ("%s: reserved bits set\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); @@ -3531,7 +4337,7 @@ handshakeerror: Con_Printf ("%s: payload size (%"PRIu64") encoded badly\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), ullpaylen); goto closesvstream; } - if (ullpaylen > 0x10000) + if (ullpaylen > 0x40000) { Con_Printf ("%s: payload size (%"PRIu64") is abusive\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), ullpaylen); goto closesvstream; @@ -3571,7 +4377,14 @@ handshakeerror: } /*if there isn't space, try again next time around*/ if (payoffs + paylen > st->inlen) + { + if (payoffs + paylen >= sizeof(st->inbuffer)-1) + { + Con_TPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + goto closesvstream; //can't ever complete + } break; + } if (mask) { @@ -3624,15 +4437,56 @@ handshakeerror: } break; case 0x2: /*binary frame*/ - Con_Printf ("websocket binary frame from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); +// Con_Printf ("websocket binary frame from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); net_message.cursize = paylen; if (net_message.cursize+8 >= sizeof(net_message_buffer) ) { - Con_TPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), &net_from)); + Con_TPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); goto closesvstream; } - if (st->clienttype == TCPC_WEBSOCKETNQ) - { +#ifndef CLIENTONLY +#ifdef SUPPORT_RTC_ICE + if (st->clienttype == TCPC_WEBRTC_CLIENT && !*st->webrtc.resource) + { //this is a client that's corrected directly to us via webrtc. + //FIXME: we don't support dtls, so browers will bitch about our sdp. + if (paylen+1 < sizeof(net_message_buffer)) + { + net_message_buffer[paylen] = 0; + memcpy(net_message_buffer, st->inbuffer+payoffs, paylen); + + if (!st->webrtc.ice) //if the ice state isn't established yet, do that now. + st->webrtc.ice = iceapi.ICE_Create(NULL, "test", "rtc://foo", ICEM_ICE, ICEP_QWSERVER); + iceapi.ICE_Set(st->webrtc.ice, "sdp", net_message_buffer); + + if (iceapi.ICE_Get(st->webrtc.ice, "sdp", net_message_buffer, sizeof(net_message_buffer))) + FTENET_TCPConnect_WebSocket_Splurge(st, 2, net_message_buffer, strlen(net_message_buffer)); + } + net_message.cursize = 0; + } + else +#endif + if ((st->clienttype == TCPC_WEBRTC_CLIENT || st->clienttype == TCPC_WEBRTC_HOST) && paylen >= 3) + { //we're brokering a client+server. all messages should be unicasts between a client and its host, matched by resource. + ftenet_tcpconnect_stream_t *o; + int clnum = (st->inbuffer[payoffs+1]<<0)|(st->inbuffer[payoffs+2]<<8); + int type = (st->clienttype != TCPC_WEBRTC_CLIENT)?TCPC_WEBRTC_CLIENT:TCPC_WEBRTC_HOST; + for (o = con->tcpstreams; o; o = o->next) + { + if (o->clienttype == type && clnum == o->webrtc.clientnum && !strcmp(o->webrtc.resource, st->webrtc.resource)) + { + st->inbuffer[payoffs+1] = (st->webrtc.clientnum>>0)&0xff; + st->inbuffer[payoffs+2] = (st->webrtc.clientnum>>8)&0xff; + FTENET_TCPConnect_WebSocket_Splurge(o, 2, st->inbuffer+payoffs, paylen); + break; + } + } + net_message.cursize = 0; + } + else +#endif +#ifdef NQPROT + if (st->clienttype == TCPC_WEBSOCKETNQ) + { //hack in an 8-byte header payoffs+=1; paylen-=1; memcpy(net_message_buffer+8, st->inbuffer+payoffs, paylen); @@ -3641,25 +4495,27 @@ handshakeerror: ((int*)net_message_buffer)[1] = LongSwap(++st->fakesequence); } else +#endif memcpy(net_message_buffer, st->inbuffer+payoffs, paylen); break; case 0x8: /*connection close*/ Con_Printf ("websocket closure %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); goto closesvstream; case 0x9: /*ping*/ - Con_Printf ("websocket ping from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); +// Con_Printf ("websocket ping from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); if (FTENET_TCPConnect_WebSocket_Splurge(st, 0xa, st->inbuffer+payoffs, paylen) != NETERR_SENT) goto closesvstream; break; case 0xa: /*pong*/ - Con_Printf ("websocket pong from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); - goto closesvstream; + st->timeouttime = Sys_DoubleTime() + 30; + st->pinging = false; +// Con_Printf ("websocket pong from %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); + break; default: Con_Printf ("Unsupported websocket opcode (%i) from %s\n", (ctrl>>8) & 0xf, NET_AdrToString (adr, sizeof(adr), &st->remoteaddr)); goto closesvstream; } -// memcpy(net_message_buffer, st->inbuffer+2, net_message.cursize); memmove(st->inbuffer, st->inbuffer+payoffs + paylen, st->inlen - (payoffs + paylen)); st->inlen -= payoffs + paylen; @@ -3703,19 +4559,13 @@ handshakeerror: { st->clientstream = FS_OpenSSL(NULL, st->clientstream, true, false); /*sockadr doesn't contain transport info, so fix that up here*/ - if (st->remoteaddr.type == NA_IP) - st->remoteaddr.type = NA_TLSV4; - else if (st->remoteaddr.type == NA_IPV6) - st->remoteaddr.type = NA_TLSV6; + st->remoteaddr.prot = NP_TLS; } else #endif { /*sockadr doesn't contain transport info, so fix that up here*/ - if (st->remoteaddr.type == NA_IP) - st->remoteaddr.type = NA_TCP; - else if (st->remoteaddr.type == NA_IPV6) - st->remoteaddr.type = NA_TCPV6; + st->remoteaddr.prot = NP_STREAM; } st->timeouttime = timeval + 30; @@ -3761,16 +4611,6 @@ neterr_t FTENET_TCPConnect_SendPacket(ftenet_generic_connection_t *gcon, int len } } break; - case TCPC_UNFRAMED: - if (length > sizeof(st->outbuffer)) - { - if (length > 0xffff) - return NETERR_MTU; - Con_DPrintf("FTENET_TCPConnect_SendPacket: outgoing overflow\n"); - } - memcpy(st->outbuffer, data, length); - st->outlen = length; - break; case TCPC_WEBSOCKETNQ: if (length < 8 || ((char*)data)[0] & 0x80) break; @@ -3810,6 +4650,99 @@ neterr_t FTENET_TCPConnect_SendPacket(ftenet_generic_connection_t *gcon, int len return NETERR_NOROUTE; } +int FTENET_TCPConnect_GetLocalAddresses(struct ftenet_generic_connection_s *gcon, unsigned int *adrflags, netadr_t *addresses, int maxaddresses) +{ + ftenet_tcpconnect_connection_t *con = (ftenet_tcpconnect_connection_t*)gcon; + netproto_t prot = con->tls?NP_TLS:NP_STREAM; + int i, r = FTENET_Generic_GetLocalAddresses(gcon, adrflags, addresses, maxaddresses); + for (i = 0; i < r; i++) + { + addresses[i].prot = prot; + } + return r; +} + +qboolean FTENET_TCPConnect_ChangeLocalAddress(struct ftenet_generic_connection_s *con, netadr_t *adr) +{ + //if we're a server, we want to try switching listening tcp port without shutting down all other connections. + //yes, this might mean we leave a connection active on the old port, but oh well. + int addrsize, addrsize2; + int family; + struct sockaddr_qstorage qs; + struct sockaddr_qstorage cur; + netadr_t n; + SOCKET newsocket; + unsigned long _true = true; + + addrsize = NetadrToSockadr(adr, &qs); + family = ((struct sockaddr*)&qs)->sa_family; + + if (con->thesocket != INVALID_SOCKET) + { + addrsize2 = sizeof(cur); + getsockname(con->thesocket, (struct sockaddr *)&cur, &addrsize2); + + if (addrsize == addrsize2) + { + SockadrToNetadr(&cur, &n); + if (NET_CompareAdr(adr, &n)) //the address+port we're trying is already current, apparently. + return true; + } + + closesocket(con->thesocket); + con->thesocket = INVALID_SOCKET; + } + +#if defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY) + if (family == AF_INET && net_hybriddualstack.ival && !((struct sockaddr_in*)&qs)->sin_addr.s_addr) + { //hybrid sockets pathway takes over when INADDR_ANY + unsigned long _false = false; + if ((newsocket = socket (AF_INET6, SOCK_STREAM, IPPROTO_TCP)) != INVALID_SOCKET) + { + if (0 == setsockopt(newsocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&_false, sizeof(_false))) + { + memset(&n, 0, sizeof(n)); + n.type = NA_IPV6; + n.port = adr->port; + n.scopeid = adr->scopeid; + addrsize2 = NetadrToSockadr(&n, &cur); + + if ((bind(newsocket, (struct sockaddr *)&cur, addrsize2) != INVALID_SOCKET) && + (listen(newsocket, 2) != INVALID_SOCKET)) + { + if (ioctlsocket (newsocket, FIONBIO, &_true) != -1) + { + setsockopt(newsocket, IPPROTO_TCP, TCP_NODELAY, (char *)&_true, sizeof(_true)); + + con->addrtype[0] = NA_IP; + con->addrtype[1] = NA_IPV6; + con->thesocket = newsocket; + return true; + } + } + } + closesocket(newsocket); + } + } +#endif + + if ((newsocket = socket (family, SOCK_STREAM, IPPROTO_TCP)) != INVALID_SOCKET) + { + if ((bind(newsocket, (struct sockaddr *)&qs, addrsize) != INVALID_SOCKET) && + (listen(newsocket, 2) != INVALID_SOCKET)) + { + if (ioctlsocket (newsocket, FIONBIO, &_true) != -1) + { + setsockopt(newsocket, IPPROTO_TCP, TCP_NODELAY, (char *)&_true, sizeof(_true)); + + con->thesocket = newsocket; + return true; + } + } + closesocket(newsocket); + } + return false; +} void FTENET_TCPConnect_Close(ftenet_generic_connection_t *gcon) { ftenet_tcpconnect_connection_t *con = (ftenet_tcpconnect_connection_t*)gcon; @@ -3831,7 +4764,7 @@ void FTENET_TCPConnect_Close(ftenet_generic_connection_t *gcon) } #ifdef HAVE_PACKET -int FTENET_TCPConnect_SetReceiveFDSet(ftenet_generic_connection_t *gcon, fd_set *fdset) +int FTENET_TCPConnect_SetFDSets(ftenet_generic_connection_t *gcon, fd_set *readfdset, fd_set *writefdset) { int maxfd = 0; ftenet_tcpconnect_connection_t *con = (ftenet_tcpconnect_connection_t*)gcon; @@ -3839,15 +4772,29 @@ int FTENET_TCPConnect_SetReceiveFDSet(ftenet_generic_connection_t *gcon, fd_set for (st = con->tcpstreams; st; st = st->next) { +#ifdef SUPPORT_RTC_ICE + if (st->webrtc.ice) + { + while(iceapi.ICE_GetLCandidateSDP(st->webrtc.ice, net_message_buffer, sizeof(net_message_buffer))) + { + FTENET_TCPConnect_WebSocket_Splurge(st, 2, net_message_buffer, strlen(net_message_buffer)); + } + continue; + } +#endif if (st->clientstream == NULL || st->socketnum == INVALID_SOCKET) continue; - FD_SET(st->socketnum, fdset); // network socket +#ifndef CLIENTONLY + if (st->clienttype == TCPC_HTTPCLIENT) + FD_SET(st->socketnum, writefdset); // network socket +#endif + FD_SET(st->socketnum, readfdset); // network socket if (maxfd < st->socketnum) maxfd = st->socketnum; } if (con->generic.thesocket != INVALID_SOCKET) { - FD_SET(con->generic.thesocket, fdset); // network socket + FD_SET(con->generic.thesocket, readfdset); // network socket if (maxfd < con->generic.thesocket) maxfd = con->generic.thesocket; } @@ -3855,112 +4802,76 @@ int FTENET_TCPConnect_SetReceiveFDSet(ftenet_generic_connection_t *gcon, fd_set } #endif -ftenet_generic_connection_t *FTENET_TCPConnect_EstablishConnection(int affamily, qboolean isserver, const char *address) +ftenet_generic_connection_t *FTENET_TCPConnect_EstablishConnection(qboolean isserver, const char *address, netadr_t adr) { //this is written to support either ipv4 or ipv6, depending on the remote addr. ftenet_tcpconnect_connection_t *newcon; unsigned long _true = true; int newsocket; - int temp; - netadr_t adr; - struct sockaddr_qstorage qs; - int family; + qboolean tls = (adr.prot == NP_TLS || adr.prot == NP_WSS); #ifndef HAVE_SSL - if ((affamily == NA_TLSV4 || affamily == NA_TLSV6)) + if (tls) { Con_Printf("tls not supported in this build\n"); return NULL; } #endif + newcon = Z_Malloc(sizeof(*newcon)); + newcon->generic.thesocket = INVALID_SOCKET; + newsocket = INVALID_SOCKET; + + newcon->generic.addrtype[0] = adr.type; + newcon->generic.addrtype[1] = NA_INVALID; + if (isserver) { -#ifndef HAVE_PACKET - //unable to listen on tcp if we have no packet interface - return NULL; -#else - - if (!strncmp(address, "tls://", 6)) - address += 6; - if (!strncmp(address, "tcp://", 6)) - address += 6; - if (!strncmp(address, "ws://", 5)) - address += 5; - if (!strncmp(address, "wss://", 6)) - address += 6; - - if (!NET_PortToAdr(affamily, address, &adr)) - return NULL; //couldn't resolve the name - if (adr.type == NA_IP) - adr.type = NA_TCP; - else if (adr.type == NA_IPV6) - adr.type = NA_TCPV6; - temp = NetadrToSockadr(&adr, &qs); - family = ((struct sockaddr_in*)&qs)->sin_family; - - if ((newsocket = socket (family, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) +#ifdef HAVE_PACKET //unable to listen on tcp if we have no packet interface + if (!FTENET_TCPConnect_ChangeLocalAddress(&newcon->generic, &adr)) { - Con_Printf("operating system doesn't support that\n"); + Z_Free(newcon); return NULL; } - - if ((bind(newsocket, (struct sockaddr *)&qs, temp) == INVALID_SOCKET) || - (listen(newsocket, 2) == INVALID_SOCKET)) - { - SockadrToNetadr(&qs, &adr); - //mneh, reuse qs. - NET_AdrToString((char*)&qs, sizeof(qs), &adr); - Con_Printf("Unable to listen at %s\n", (char*)&qs); - closesocket(newsocket); - return NULL; - } - - if (ioctlsocket (newsocket, FIONBIO, &_true) == -1) - Sys_Error ("UDP_OpenSocket: ioctl FIONBIO: %s", strerror(neterrno())); #endif } else { - if (!NET_StringToAdr(address, 0, &adr)) - return NULL; //couldn't resolve the name - - if (adr.type == NA_IP) - adr.type = NA_TCP; - else if (adr.type == NA_IPV6) - adr.type = NA_TCPV6; newsocket = TCP_OpenStream(&adr); if (newsocket == INVALID_SOCKET) + { + Z_Free(newcon); return NULL; + } + + //this isn't fatal + setsockopt(newsocket, IPPROTO_TCP, TCP_NODELAY, (char *)&_true, sizeof(_true)); } - //this isn't fatal - setsockopt(newsocket, IPPROTO_TCP, TCP_NODELAY, (char *)&_true, sizeof(_true)); - newcon = Z_Malloc(sizeof(*newcon)); if (newcon) { - newcon->tls = (affamily == NA_TLSV4 || affamily == NA_TLSV6); + newcon->tls = tls; if (isserver) - newcon->generic.GetLocalAddresses = FTENET_Generic_GetLocalAddresses; + { + newcon->generic.GetLocalAddresses = FTENET_TCPConnect_GetLocalAddresses; + newcon->generic.ChangeLocalAddress = FTENET_TCPConnect_ChangeLocalAddress; + } newcon->generic.GetPacket = FTENET_TCPConnect_GetPacket; newcon->generic.SendPacket = FTENET_TCPConnect_SendPacket; newcon->generic.Close = FTENET_TCPConnect_Close; #ifdef HAVE_PACKET - newcon->generic.SetReceiveFDSet = FTENET_TCPConnect_SetReceiveFDSet; + newcon->generic.SetFDSets = FTENET_TCPConnect_SetFDSets; #endif + newcon->generic.PrintStatus = FTENET_TCPConnect_PrintStatus; newcon->generic.islisten = isserver; - newcon->generic.addrtype[0] = adr.type; - newcon->generic.addrtype[1] = NA_INVALID; newcon->active = 0; if (!isserver) { - newcon->generic.thesocket = INVALID_SOCKET; - newcon->active++; newcon->tcpstreams = Z_Malloc(sizeof(*newcon->tcpstreams)); newcon->tcpstreams->next = NULL; @@ -3975,20 +4886,15 @@ ftenet_generic_connection_t *FTENET_TCPConnect_EstablishConnection(int affamily, newcon->tcpstreams->clientstream = FS_OpenSSL(address, newcon->tcpstreams->clientstream, false, false); #endif -#ifdef FTE_TARGET_WEB - newcon->tcpstreams->clienttype = TCPC_UNFRAMED; -#else //send the qizmo greeting. newcon->tcpstreams->clienttype = TCPC_UNKNOWN; VFS_WRITE(newcon->tcpstreams->clientstream, "qizmo\n", 6); -#endif newcon->tcpstreams->timeouttime = Sys_DoubleTime() + 30; } else { newcon->tcpstreams = NULL; - newcon->generic.thesocket = newsocket; } return &newcon->generic; @@ -4000,26 +4906,6 @@ ftenet_generic_connection_t *FTENET_TCPConnect_EstablishConnection(int affamily, } } -#ifdef IPPROTO_IPV6 -ftenet_generic_connection_t *FTENET_TCP6Connect_EstablishConnection(qboolean isserver, const char *address, netadr_t adr) -{ - return FTENET_TCPConnect_EstablishConnection(NA_TCPV6, isserver, address); -} -ftenet_generic_connection_t *FTENET_TLS6Connect_EstablishConnection(qboolean isserver, const char *address, netadr_t adr) -{ - return FTENET_TCPConnect_EstablishConnection(NA_TLSV6, isserver, address); -} -#endif - -ftenet_generic_connection_t *FTENET_TCP4Connect_EstablishConnection(qboolean isserver, const char *address, netadr_t adr) -{ - return FTENET_TCPConnect_EstablishConnection(NA_TCP, isserver, address); -} -ftenet_generic_connection_t *FTENET_TLS4Connect_EstablishConnection(qboolean isserver, const char *address, netadr_t adr) -{ - return FTENET_TCPConnect_EstablishConnection(NA_TLSV4, isserver, address); -} - #endif @@ -4613,60 +5499,296 @@ struct ftenet_generic_connection_s *FTENET_IRCConnect_EstablishConnection(qboole typedef struct { ftenet_generic_connection_t generic; - int sock; + int brokersock; //only if rtc netadr_t remoteadr; qboolean failed; + + int datasock; //only if we're a client + + size_t numclients; + struct + { + netadr_t remoteadr; + int datasock; + } *clients; } ftenet_websocket_connection_t; static void FTENET_WebSocket_Close(ftenet_generic_connection_t *gcon) { ftenet_websocket_connection_t *wsc = (void*)gcon; - emscriptenfte_ws_close(wsc->sock); + size_t i; + if (wsc->brokersock != INVALID_SOCKET) + emscriptenfte_ws_close(wsc->brokersock); + if (wsc->datasock != INVALID_SOCKET) + emscriptenfte_ws_close(wsc->datasock); + for (i = 0; i < wsc->numclients; i++) + { + if (wsc->clients[i].datasock != INVALID_SOCKET) + emscriptenfte_ws_close(wsc->clients[i].datasock); + } + free(wsc->clients); } static qboolean FTENET_WebSocket_GetPacket(ftenet_generic_connection_t *gcon) { ftenet_websocket_connection_t *wsc = (void*)gcon; - net_message.cursize = emscriptenfte_ws_recv(wsc->sock, net_message_buffer, sizeof(net_message_buffer)); + net_message.cursize = emscriptenfte_ws_recv(wsc->datasock, net_message_buffer, sizeof(net_message_buffer)); if (net_message.cursize > 0) { net_from = wsc->remoteadr; return true; } + if ((int)net_message.cursize < 0) + wsc->failed = true; net_message.cursize = 0;//just incase return false; } static neterr_t FTENET_WebSocket_SendPacket(ftenet_generic_connection_t *gcon, int length, const void *data, netadr_t *to) { ftenet_websocket_connection_t *wsc = (void*)gcon; + if (wsc->failed) + return NETERR_DISCONNECTED; if (NET_CompareAdr(to, &wsc->remoteadr)) { - if (emscriptenfte_ws_send(wsc->sock, data, length) < 0) + if (emscriptenfte_ws_send(wsc->datasock, data, length) < 0) return NETERR_CLOGGED; return NETERR_SENT; } return NETERR_NOROUTE; } +//called from the javascript when there was some ice event. just forwards over the broker connection. +static void FTENET_WebRTC_Callback(void *ctxp, int ctxi, int evtype, const char *data) +{ + ftenet_websocket_connection_t *wcs = ctxp; + size_t dl = strlen(data); + qbyte *o = net_message_buffer; + *o++ = evtype; + *o++ = (ctxi>>0)&0xff; + *o++ = (ctxi>>8)&0xff; + memcpy(o, data, dl); + o+=dl; + +// Con_Printf("To Broker: %i %i\n", evtype, ctxi); + emscriptenfte_ws_send(wcs->brokersock, net_message_buffer, o-net_message_buffer); +} +static qboolean FTENET_WebRTC_GetPacket(ftenet_generic_connection_t *gcon) +{ + ftenet_websocket_connection_t *wsc = (void*)gcon; + size_t i; + if (!wsc->generic.islisten) + { + if (wsc->datasock != INVALID_SOCKET && FTENET_WebSocket_GetPacket(gcon)) + return true; + } + else + { + for (i = 0; i < wsc->numclients; i++) + { + net_message.cursize = emscriptenfte_ws_recv(wsc->clients[i].datasock, net_message_buffer, sizeof(net_message_buffer)); + if (net_message.cursize > 0) + { + net_from = wsc->clients[i].remoteadr; + return true; + } + } + } + + net_message.cursize = emscriptenfte_ws_recv(wsc->brokersock, net_message_buffer, sizeof(net_message_buffer)); + if (net_message.cursize > 0) + { + int cmd; + short cl; + const char *s; + char *p; + + MSG_BeginReading(msg_nullnetprim); + cmd = MSG_ReadByte(); + cl = MSG_ReadShort(); + +//Con_Printf("From Broker: %i %i\n", cmd, cl); + + switch(cmd) + { + case 0: //connection closing... + if (cl == -1) + { + wsc->failed = true; + Con_Printf("Broker closing connection: %s\n", MSG_ReadString()); + } + else if (cl >= 0 && cl < wsc->numclients) + { + wsc->clients[cl].remoteadr.type = NA_INVALID; + if (wsc->clients[cl].datasock != INVALID_SOCKET) + emscriptenfte_ws_close(wsc->clients[cl].datasock); + wsc->clients[cl].datasock = INVALID_SOCKET; + Con_Printf("Broker closing connection: %s\n", MSG_ReadString()); + } + break; + case 1: //reports the trailing url we're 'listening' on. anyone else using that url will connect to us. + s = MSG_ReadString(); + if (*s == '/') + s++; + p = wsc->remoteadr.address.websocketurl; + while (*p) + { + if (p[0] == ':' && p[1] == '/' && p[2] == '/') + p+=3; + else if (p[0] == '/') + { + *p = 0; + break; + } + else + p++; + } + Q_strncatz(wsc->remoteadr.address.websocketurl, "/", sizeof(wsc->remoteadr.address.websocketurl)); + Q_strncatz(wsc->remoteadr.address.websocketurl, s, sizeof(wsc->remoteadr.address.websocketurl)); + Con_Printf("Listening on %s\n", wsc->remoteadr.address.websocketurl); + break; + case 2: //connection established with a new peer + if (wsc->generic.islisten) + { + if (cl < 1024 && cl >= wsc->numclients) + { //looks like a new one... but don't waste memory + size_t nm; + nm = cl+1; + wsc->clients = realloc(wsc->clients, sizeof(*wsc->clients)*nm); + while(wsc->numclients < nm) + { + memset(&wsc->clients[i].remoteadr, 0, sizeof(wsc->clients[i].remoteadr)); + wsc->clients[wsc->numclients++].datasock = INVALID_SOCKET; + } + } + if (cl < wsc->numclients) + { + if (wsc->clients[cl].datasock != INVALID_SOCKET) + emscriptenfte_ws_close(wsc->clients[cl].datasock); + memcpy(&wsc->clients[cl].remoteadr, &wsc->remoteadr, sizeof(netadr_t)); + wsc->clients[cl].remoteadr.port = htons(cl+1); + wsc->clients[cl].datasock = emscriptenfte_rtc_create(false, wsc, cl, FTENET_WebRTC_Callback); + } + } + else + { + if (wsc->datasock != INVALID_SOCKET) + emscriptenfte_ws_close(wsc->datasock); + wsc->datasock = emscriptenfte_rtc_create(true, wsc, cl, FTENET_WebRTC_Callback); + } + break; + case 3: //we received an offer from a client + s = MSG_ReadString(); + if (wsc->generic.islisten) + { + if (cl < wsc->numclients && wsc->clients[cl].datasock != INVALID_SOCKET) + emscriptenfte_rtc_offer(wsc->clients[cl].datasock, s, "offer"); + } + else + { + if (wsc->datasock != INVALID_SOCKET) + emscriptenfte_rtc_offer(wsc->datasock, s, "answer"); + } + break; + case 4: + s = MSG_ReadString(); + if (wsc->generic.islisten) + { + if (cl < wsc->numclients && wsc->clients[cl].datasock != INVALID_SOCKET) + emscriptenfte_rtc_candidate(wsc->clients[cl].datasock, s); + } + else + { + if (wsc->datasock != INVALID_SOCKET) + emscriptenfte_rtc_candidate(wsc->datasock, s); + } + break; + } + } + + net_message.cursize = 0;//just incase + return false; +} +static neterr_t FTENET_WebRTC_SendPacket(ftenet_generic_connection_t *gcon, int length, const void *data, netadr_t *to) +{ + ftenet_websocket_connection_t *wsc = (void*)gcon; + size_t i; + if (!wsc->generic.islisten) + { +// if (wsc->failed) +// return NETERR_DISCONNECTED; + if (NET_CompareAdr(to, &wsc->remoteadr)) + { + if (wsc->datasock == INVALID_SOCKET) + return NETERR_CLOGGED; //we're still waiting for the broker to give us a server... or for a server to become available. + else + { + if (emscriptenfte_ws_send(wsc->datasock, data, length) <= 0) + return NETERR_CLOGGED; + return NETERR_SENT; + } + } + } + else + { + for (i = 0; i < wsc->numclients; i++) + { + if (NET_CompareAdr(to, &wsc->clients[i].remoteadr)) + { + if (emscriptenfte_ws_send(wsc->clients[i].datasock, data, length) <= 0) + return NETERR_CLOGGED; + return NETERR_SENT; + } + } + } + return NETERR_NOROUTE; +} + +int FTENET_WebRTC_GetAddresses(struct ftenet_generic_connection_s *con, unsigned int *adrflags, netadr_t *addresses, int maxaddresses) +{ + ftenet_websocket_connection_t *wsc = (void*)con; + if (maxaddresses) + { + *addresses = wsc->remoteadr; + *adrflags = 0; + return 1; + } + return 0; +} static ftenet_generic_connection_t *FTENET_WebSocket_EstablishConnection(qboolean isserver, const char *address, netadr_t adr) { ftenet_websocket_connection_t *newcon; - int newsocket; + int brokersocket = INVALID_SOCKET; + int datasocket = INVALID_SOCKET; - if (isserver) - { - return NULL; - } - newsocket = emscriptenfte_ws_connect(address); - if (newsocket < 0) - return NULL; newcon = Z_Malloc(sizeof(*newcon)); - if (newcon) + if (adr.prot == NP_DTLS) + { //this requires that we create a broker connection + if (isserver) + brokersocket = emscriptenfte_ws_connect(adr.address.websocketurl, "rtc_host"); + else + brokersocket = emscriptenfte_ws_connect(adr.address.websocketurl, "rtc_client"); + + newcon->generic.GetPacket = FTENET_WebRTC_GetPacket; + newcon->generic.SendPacket = FTENET_WebRTC_SendPacket; + newcon->generic.GetLocalAddresses = FTENET_WebRTC_GetAddresses; + } + else { - Q_strncpyz(newcon->generic.name, "WebSocket", sizeof(newcon->generic.name)); + if (!isserver) + datasocket = emscriptenfte_ws_connect(adr.address.websocketurl, "fteqw"); + newcon->generic.GetPacket = FTENET_WebSocket_GetPacket; newcon->generic.SendPacket = FTENET_WebSocket_SendPacket; + } + if (brokersocket == INVALID_SOCKET && datasocket == INVALID_SOCKET) + { + Con_Printf("Unable to create rtc/ws connection\n"); + Z_Free(newcon); + } + else + { + Q_strncpyz(newcon->generic.name, "WebSocket", sizeof(newcon->generic.name)); newcon->generic.Close = FTENET_WebSocket_Close; newcon->generic.islisten = isserver; @@ -4674,15 +5796,16 @@ static ftenet_generic_connection_t *FTENET_WebSocket_EstablishConnection(qboolea newcon->generic.addrtype[1] = NA_INVALID; newcon->generic.thesocket = INVALID_SOCKET; - newcon->sock = newsocket; + newcon->brokersock = brokersocket; + newcon->datasock = datasocket; + adr.port = 0; newcon->remoteadr = adr; return &newcon->generic; } return NULL; } - #endif @@ -4897,13 +6020,13 @@ static neterr_t FTENET_NaClWebSocket_SendPacket(ftenet_generic_connection_t *gco if (wsc->failed) return NETERR_DISCONNECTED; -#if 0 +#if 1 struct PP_Var str = ppb_vararraybuffer_interface->Create(length); - void *out = ppb_vararraybuffer_interface->Map(wsc->incomingpacket); + void *out = ppb_vararraybuffer_interface->Map(str); if (!out) return NETERR_MTU; memcpy(out, data, length); - ppb_vararraybuffer_interface->Unmap(wsc->incomingpacket); + ppb_vararraybuffer_interface->Unmap(str); #else int outchars = 0; unsigned char outdata[length*2+1]; @@ -4950,11 +6073,14 @@ static ftenet_generic_connection_t *FTENET_WebSocket_EstablishConnection(qboolea newcon = Z_Malloc(sizeof(*newcon)); if (newcon) { +#define WEBSOCKETPROTOCOL "fteqw" struct PP_CompletionCallback ccb = {websocketconnected, newcon, PP_COMPLETIONCALLBACK_FLAG_NONE}; newsocket = ppb_websocket_interface->Create(pp_instance); struct PP_Var str = ppb_var_interface->VarFromUtf8(adr.address.websocketurl, strlen(adr.address.websocketurl)); - ppb_websocket_interface->Connect(newsocket, str, NULL, 0, ccb); + struct PP_Var protocols[1] = {ppb_var_interface->VarFromUtf8(WEBSOCKETPROTOCOL, strlen(WEBSOCKETPROTOCOL))}; + ppb_websocket_interface->Connect(newsocket, str, protocols, countof(protocols), ccb); ppb_var_interface->Release(str); + ppb_var_interface->Release(protocols[0]); Q_strncpyz(newcon->generic.name, "WebSocket", sizeof(newcon->generic.name)); newcon->generic.GetPacket = FTENET_NaClWebSocket_GetPacket; newcon->generic.SendPacket = FTENET_NaClWebSocket_SendPacket; @@ -5211,15 +6337,15 @@ qboolean NET_EnsureRoute(ftenet_connections_t *collection, char *routename, char if (NET_StringToAdr(host, 0, &adr)) { - switch(adr.type) + switch(adr.prot) { - case NA_WEBSOCKET: - case NA_TLSV4: - case NA_TLSV6: - case NA_TCP: - case NA_TCPV6: - case NA_IRC: - if (!FTENET_AddToCollection(collection, routename, host, adr.type, islisten)) + case NP_DTLS: + case NP_WS: + case NP_WSS: + case NP_TLS: + case NP_STREAM: + case NP_IRC: + if (!FTENET_AddToCollection(collection, routename, host, adr.type, adr.prot, islisten)) return false; Con_Printf("Establishing connection to %s\n", host); break; @@ -5231,7 +6357,7 @@ qboolean NET_EnsureRoute(ftenet_connections_t *collection, char *routename, char return true; } -int NET_EnumerateAddresses(ftenet_connections_t *collection, struct ftenet_generic_connection_s **con, int *adrflags, netadr_t *addresses, int maxaddresses) +int NET_EnumerateAddresses(ftenet_connections_t *collection, struct ftenet_generic_connection_s **con, unsigned int *adrflags, netadr_t *addresses, int maxaddresses) { unsigned int found = 0, c, i, j; for (i = 0; i < MAX_CONNECTIONS; i++) @@ -5239,7 +6365,10 @@ int NET_EnumerateAddresses(ftenet_connections_t *collection, struct ftenet_gener if (!collection->conn[i]) continue; - c = collection->conn[i]->GetLocalAddresses(collection->conn[i], adrflags, addresses, maxaddresses); + if (collection->conn[i]->GetLocalAddresses) + c = collection->conn[i]->GetLocalAddresses(collection->conn[i], adrflags, addresses, maxaddresses); + else + c = 0; if (maxaddresses && !c) { @@ -5274,7 +6403,7 @@ enum addressscope_e NET_ClassifyAddress(netadr_t *adr, char **outdesc) //we don't list 127.0.0.1 or ::1, so don't bother with this either. its not interesting. scope = ASCOPE_PROCESS, desc = "internal"; } - else if (adr->type == NA_IPV6 || adr->type == NA_BROADCAST_IP6 || adr->type == NA_TCPV6 || adr->type == NA_TLSV6) + else if (adr->type == NA_IPV6) { if ((*(int*)adr->address.ip6&BigLong(0xffc00000)) == BigLong(0xfe800000)) //fe80::/10 scope = ASCOPE_LAN, desc = "link-local"; @@ -5289,7 +6418,7 @@ enum addressscope_e NET_ClassifyAddress(netadr_t *adr, char **outdesc) else if (memcmp(adr->address.ip6, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16) == 0) //:: scope = ASCOPE_NET, desc = "any"; } - else if (adr->type == NA_IP || adr->type == NA_BROADCAST_IP || adr->type == NA_TCP || adr->type == NA_TLSV4) + else if (adr->type == NA_IP) { if ((*(int*)adr->address.ip&BigLong(0xffff0000)) == BigLong(0xA9FE0000)) //169.254.x.x/16 scope = ASCOPE_LAN, desc = "link-local"; @@ -5350,6 +6479,18 @@ void NET_PrintAddresses(ftenet_connections_t *collection) Con_Printf("net address: no addresses\n"); } +void NET_PrintConnectionsStatus(ftenet_connections_t *collection) +{ + unsigned int i; + for (i = 0; i < MAX_CONNECTIONS; i++) + { + if (!collection->conn[i]) + continue; + if (collection->conn[i]->PrintStatus) + collection->conn[i]->PrintStatus(collection->conn[i]); + } +} + //============================================================================= int TCP_OpenStream (netadr_t *remoteaddr) @@ -5485,7 +6626,7 @@ int maxport = port + 100; */ #if defined(SV_MASTER) || defined(CL_MASTER) -int UDP_OpenSocket (int port, qboolean bcast) +int UDP_OpenSocket (int port) { SOCKET newsocket; struct sockaddr_in address; @@ -5499,16 +6640,6 @@ int maxport = port + 100; if (ioctlsocket (newsocket, FIONBIO, &_true) == -1) Sys_Error ("UDP_OpenSocket: ioctl FIONBIO: %s", strerror(neterrno())); - if (bcast) - { - _true = true; - if (setsockopt(newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&_true, sizeof(_true)) == -1) - { - Con_Printf("Cannot create broadcast socket\n"); - return (int)INVALID_SOCKET; - } - } - address.sin_family = AF_INET; //ZOID -- check for interface binding option if ((i = COM_CheckParm("-ip")) != 0 && i < com_argc) { @@ -5541,7 +6672,7 @@ int maxport = port + 100; } #ifdef IPPROTO_IPV6 -int UDP6_OpenSocket (int port, qboolean bcast) +int UDP6_OpenSocket (int port) { int err; SOCKET newsocket; @@ -5561,18 +6692,6 @@ int maxport = port + 100; if (ioctlsocket (newsocket, FIONBIO, &_true) == -1) Sys_Error ("UDP_OpenSocket: ioctl FIONBIO: %s", strerror(neterrno())); - if (bcast) - { -// address.sin6_addr -// _true = true; -// if (setsockopt(newsocket, SOL_SOCKET, IP_ADD_MEMBERSHIP, (char *)&_true, sizeof(_true)) == -1) -// { -// Con_Printf("Cannot create broadcast socket\n"); -// closesocket(newsocket); -// return (int)INVALID_SOCKET; -// } - } - #ifdef IPV6_V6ONLY setsockopt(newsocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&_true, sizeof(_true)); #endif @@ -5624,7 +6743,7 @@ void UDP_CloseSocket (int socket) closesocket(socket); } -int IPX_OpenSocket (int port, qboolean bcast) +int IPX_OpenSocket (int port) { #ifndef USEIPX return 0; @@ -5648,16 +6767,6 @@ int IPX_OpenSocket (int port, qboolean bcast) return INVALID_SOCKET; } - if (bcast) - { - // make it broadcast capable - if (setsockopt(newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&_true, sizeof(_true)) == -1) - { - Con_Printf ("WARNING: IPX_Socket: setsockopt SO_BROADCAST: %i\n", neterrno()); - return INVALID_SOCKET; - } - } - address.sa_family = AF_IPX; memset (address.sa_netnum, 0, 4); memset (address.sa_nodenum, 0, 6); @@ -5693,17 +6802,19 @@ qboolean NET_Sleep(float seconds, qboolean stdinissocket) { #ifdef HAVE_PACKET struct timeval timeout; - fd_set fdset; + fd_set readfdset; + fd_set writefdset; qintptr_t maxfd = -1; int con, sock; unsigned int usec; - FD_ZERO(&fdset); + FD_ZERO(&readfdset); + FD_ZERO(&writefdset); if (stdinissocket) { sock = 0; //stdin tends to be socket/filehandle 0 in unix - FD_SET(sock, &fdset); + FD_SET(sock, &readfdset); maxfd = sock; } @@ -5712,9 +6823,9 @@ qboolean NET_Sleep(float seconds, qboolean stdinissocket) { if (!svs.sockets->conn[con]) continue; - if (svs.sockets->conn[con]->SetReceiveFDSet) + if (svs.sockets->conn[con]->SetFDSets) { - sock = svs.sockets->conn[con]->SetReceiveFDSet(svs.sockets->conn[con], &fdset); + sock = svs.sockets->conn[con]->SetFDSets(svs.sockets->conn[con], &readfdset, &writefdset); if (sock > maxfd) maxfd = sock; } @@ -5723,7 +6834,7 @@ qboolean NET_Sleep(float seconds, qboolean stdinissocket) sock = svs.sockets->conn[con]->thesocket; if (sock != INVALID_SOCKET) { - FD_SET(sock, &fdset); // network socket + FD_SET(sock, &readfdset); // network socket if (sock > maxfd) maxfd = sock; } @@ -5740,11 +6851,11 @@ qboolean NET_Sleep(float seconds, qboolean stdinissocket) usec += 1000; //slight extra delay, to ensure we don't wake up with nothing to do. timeout.tv_sec = usec/(1000*1000); timeout.tv_usec = usec; - select(maxfd+1, &fdset, NULL, NULL, &timeout); + select(maxfd+1, &readfdset, &writefdset, NULL, &timeout); } if (stdinissocket) - return FD_ISSET(0, &fdset); + return FD_ISSET(0, &readfdset); #endif return true; } @@ -5840,11 +6951,11 @@ void SVNET_AddPort_f(void) { svs.sockets = FTENET_CreateCollection(true); #ifndef SERVERONLY - FTENET_AddToCollection(svs.sockets, "SVLoopback", STRINGIFY(PORT_QWSERVER), NA_LOOPBACK, true); + FTENET_AddToCollection(svs.sockets, "SVLoopback", STRINGIFY(PORT_QWSERVER), NA_LOOPBACK, NP_DGRAM, true); #endif } - FTENET_AddToCollection(svs.sockets, conname, *s?s:NULL, *s?NA_IP:NA_INVALID, true); + FTENET_AddToCollection(svs.sockets, conname, *s?s:NULL, *s?NA_IP:NA_INVALID, NP_DGRAM, true); } #endif @@ -5886,10 +6997,10 @@ qboolean NET_WasSpecialPacket(netsrc_t netsrc) return false; } -static void QDECL NET_UPNPIGP_Callback(cvar_t *var, char *oldval) -{ -} -cvar_t net_upnpigp = CVARCD("net_upnpigp", "0", NET_UPNPIGP_Callback, "If set, enables the use of the upnp-igd protocol to punch holes in your local NAT box."); +//static void QDECL NET_UPNPIGP_Callback(cvar_t *var, char *oldval) +//{ +//} +//cvar_t net_upnpigp = CVARCD("net_upnpigp", "0", NET_UPNPIGP_Callback, "If set, enables the use of the upnp-igd protocol to punch holes in your local NAT box."); void SSL_Init(void); /* @@ -5931,8 +7042,8 @@ void NET_Init (void) Cmd_AddCommand("cl_addport", NET_ClientPort_f); #endif - Cvar_Register (&net_upnpigp, "networking"); - net_upnpigp.restriction = RESTRICT_MAX; +// Cvar_Register (&net_upnpigp, "networking"); +// net_upnpigp.restriction = RESTRICT_MAX; // // init the message buffer @@ -5967,18 +7078,18 @@ void NET_InitClient(qboolean loopbackonly) if (!cls.sockets) cls.sockets = FTENET_CreateCollection(false); #ifndef CLIENTONLY - FTENET_AddToCollection(cls.sockets, "CLLoopback", "1", NA_LOOPBACK, true); + FTENET_AddToCollection(cls.sockets, "CLLoopback", "1", NA_LOOPBACK, NP_DGRAM, true); #endif if (loopbackonly) port = ""; #ifdef HAVE_IPV4 - FTENET_AddToCollection(cls.sockets, "CLUDP4", port, NA_IP, true); + FTENET_AddToCollection(cls.sockets, "CLUDP4", port, NA_IP, NP_DGRAM, true); #endif #ifdef IPPROTO_IPV6 - FTENET_AddToCollection(cls.sockets, "CLUDP6", port, NA_IPV6, true); + FTENET_AddToCollection(cls.sockets, "CLUDP6", port, NA_IPV6, NP_DGRAM, true); #endif #ifdef USEIPX - FTENET_AddToCollection(cls.sockets, "CLIPX", port, NA_IPX, true); + FTENET_AddToCollection(cls.sockets, "CLIPX", port, NA_IPX, NP_DGRAM, true); #endif // Con_TPrintf("Client port Initialized\n"); @@ -5989,42 +7100,49 @@ void NET_InitClient(qboolean loopbackonly) #ifdef HAVE_IPV4 void QDECL SV_Tcpport_Callback(struct cvar_s *var, char *oldvalue) { - FTENET_AddToCollection(svs.sockets, var->name, var->string, NA_TCP, true); + if (!strcmp(var->string, "0")) //qtv_streamport had an old default value of 0. make sure we don't end up listening on random ports. + FTENET_AddToCollection(svs.sockets, var->name, "", NA_IP, NP_STREAM, true); + else + FTENET_AddToCollection(svs.sockets, var->name, var->string, NA_IP, NP_STREAM, true); } -cvar_t sv_port_tcp = CVARC("sv_port_tcp", "", SV_Tcpport_Callback); +cvar_t sv_port_tcp = CVARFC("sv_port_tcp", "", CVAR_SERVERINFO, SV_Tcpport_Callback); +#ifndef NOLEGACY +cvar_t qtv_streamport = CVARAFCD( "qtv_streamport", "", + "mvd_streamport", 0, SV_Tcpport_Callback, "Legacy cvar. Use sv_port_tcp instead."); +#endif #endif #ifdef IPPROTO_IPV6 void QDECL SV_Tcpport6_Callback(struct cvar_s *var, char *oldvalue) { - FTENET_AddToCollection(svs.sockets, var->name, var->string, NA_TCPV6, true); + FTENET_AddToCollection(svs.sockets, var->name, var->string, NA_IPV6, NP_STREAM, true); } cvar_t sv_port_tcp6 = CVARC("sv_port_tcp6", "", SV_Tcpport6_Callback); #endif #ifdef HAVE_IPV4 void QDECL SV_Port_Callback(struct cvar_s *var, char *oldvalue) { - FTENET_AddToCollection(svs.sockets, var->name, var->string, NA_IP, true); + FTENET_AddToCollection(svs.sockets, var->name, var->string, NA_IP, NP_DGRAM, true); } cvar_t sv_port_ipv4 = CVARC("sv_port", STRINGIFY(PORT_QWSERVER), SV_Port_Callback); #endif #ifdef IPPROTO_IPV6 void QDECL SV_PortIPv6_Callback(struct cvar_s *var, char *oldvalue) { - FTENET_AddToCollection(svs.sockets, var->name, var->string, NA_IPV6, true); + FTENET_AddToCollection(svs.sockets, var->name, var->string, NA_IPV6, NP_DGRAM, true); } cvar_t sv_port_ipv6 = CVARCD("sv_port_ipv6", "", SV_PortIPv6_Callback, "Port to use for incoming ipv6 udp connections. Due to hybrid sockets this might not be needed. You can specify an ipv4 address:port for a second ipv4 port if you want."); #endif #ifdef USEIPX void QDECL SV_PortIPX_Callback(struct cvar_s *var, char *oldvalue) { - FTENET_AddToCollection(svs.sockets, var->name, var->string, NA_IPX, true); + FTENET_AddToCollection(svs.sockets, var->name, var->string, NA_IPX, NP_DGRAM, true); } cvar_t sv_port_ipx = CVARC("sv_port_ipx", "", SV_PortIPX_Callback); #endif #ifdef HAVE_NATPMP void QDECL SV_Port_NatPMP_Callback(struct cvar_s *var, char *oldvalue) { - FTENET_AddToCollection(svs.sockets, var->name, va("natpmp://%s", var->string), NA_NATPMP, true); + FTENET_AddToCollection(svs.sockets, var->name, va("natpmp://%s", var->string), NA_IP, NP_NATPMP, true); } #if 1//def SERVERONLY #define NATPMP_DEFAULT_PORT "" //don't fuck with dedicated servers @@ -6034,11 +7152,27 @@ void QDECL SV_Port_NatPMP_Callback(struct cvar_s *var, char *oldvalue) cvar_t sv_port_natpmp = CVARCD("sv_port_natpmp", NATPMP_DEFAULT_PORT, SV_Port_NatPMP_Callback, "If set (typically to 5351), automatically configures your router's port forwarding. You can instead specify the full ip address of your router (192.168.1.1:5351 for example). Your router must have NAT-PMP supported and enabled."); #endif +#ifdef FTE_TARGET_WEB +void QDECL SV_PortRTC_Callback(struct cvar_s *var, char *oldvalue) +{ + FTENET_AddToCollection(svs.sockets, var->name, var->string, NA_WEBSOCKET, NP_DTLS, true); +} +cvar_t sv_port_rtc = CVARCD("sv_port_rtc", "/", SV_PortRTC_Callback, "This specifies the broker url to use to obtain clients from. If the hostname is ommitted, it'll come from the manifest. If omitted, the broker service will randomize the resource part, so be sure to tell your friends the path reported by eg status rather than just this cvar value. Or just set it to 'rtc:///example' and tell clients to connect to the same sservevalue."); +#endif + void SVNET_RegisterCvars(void) { +#ifdef FTE_TARGET_WEB + Cvar_Register (&sv_port_rtc, "networking"); +// sv_port_rtc.restriction = RESTRICT_MAX; +#endif #if defined(TCPCONNECT) && defined(HAVE_IPV4) Cvar_Register (&sv_port_tcp, "networking"); sv_port_tcp.restriction = RESTRICT_MAX; +#ifndef NOLEGACY + Cvar_Register (&qtv_streamport, "networking"); + qtv_streamport.restriction = RESTRICT_MAX; +#endif #endif #if defined(TCPCONNECT) && defined(IPPROTO_IPV6) Cvar_Register (&sv_port_tcp6, "networking"); @@ -6076,10 +7210,13 @@ void NET_InitServer(void) { svs.sockets = FTENET_CreateCollection(true); #ifndef SERVERONLY - FTENET_AddToCollection(svs.sockets, "SVLoopback", STRINGIFY(PORT_QWSERVER), NA_LOOPBACK, true); + FTENET_AddToCollection(svs.sockets, "SVLoopback", STRINGIFY(PORT_QWSERVER), NA_LOOPBACK, NP_DGRAM, true); #endif } +#ifdef FTE_TARGET_WEB + Cvar_ForceCallback(&sv_port_rtc); +#endif #ifdef HAVE_IPV4 Cvar_ForceCallback(&sv_port_ipv4); #endif @@ -6091,6 +7228,9 @@ void NET_InitServer(void) #endif #if defined(TCPCONNECT) && defined(HAVE_TCP) Cvar_ForceCallback(&sv_port_tcp); +#ifndef NOLEGACY + Cvar_ForceCallback(&qtv_streamport); +#endif #ifdef IPPROTO_IPV6 Cvar_ForceCallback(&sv_port_tcp6); #endif @@ -6105,7 +7245,7 @@ void NET_InitServer(void) #ifndef SERVERONLY svs.sockets = FTENET_CreateCollection(true); - FTENET_AddToCollection(svs.sockets, "SVLoopback", STRINGIFY(PORT_QWSERVER), NA_LOOPBACK, true); + FTENET_AddToCollection(svs.sockets, "SVLoopback", STRINGIFY(PORT_QWSERVER), NA_LOOPBACK, NP_DGRAM, true); #endif } } @@ -6269,7 +7409,7 @@ int QDECL VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int byt int len; if (tf->sock == INVALID_SOCKET) - return 0; + return -1; if (tf->conpending) { @@ -6349,7 +7489,7 @@ vfsfile_t *FS_OpenTCPSocket(SOCKET sock, qboolean conpending, const char *peerna newf->funcs.Seek = VFSTCP_Seek; newf->funcs.Tell = VFSTCP_Tell; newf->funcs.WriteBytes = VFSTCP_WriteBytes; - newf->funcs.seekingisabadplan = true; + newf->funcs.seekstyle = SS_UNSEEKABLE; return &newf->funcs; } diff --git a/engine/common/netinc.h b/engine/common/netinc.h index e4d28efd..150692ff 100644 --- a/engine/common/netinc.h +++ b/engine/common/netinc.h @@ -253,19 +253,21 @@ typedef struct { struct icestate_s *(QDECL *ICE_Create)(void *module, const char *conname, const char *peername, enum icemode_e mode, enum iceproto_e proto); //doesn't start pinging anything. qboolean (QDECL *ICE_Set)(struct icestate_s *con, const char *prop, const char *value); - qboolean (QDECL *ICE_Get)(struct icestate_s *con, const char *prop, char *value, int valuesize); + qboolean (QDECL *ICE_Get)(struct icestate_s *con, const char *prop, char *value, size_t valuesize); struct icecandinfo_s *(QDECL *ICE_GetLCandidateInfo)(struct icestate_s *con); //retrieves candidates that need reporting to the peer. void (QDECL *ICE_AddRCandidateInfo)(struct icestate_s *con, struct icecandinfo_s *cand); //stuff that came from the peer. void (QDECL *ICE_Close)(struct icestate_s *con); //bye then. void (QDECL *ICE_CloseModule)(void *module); //closes all unclosed connections, with warning. // struct icestate_s *(QDECL *ICE_Find)(void *module, const char *conname); + qboolean (QDECL *ICE_GetLCandidateSDP)(struct icestate_s *con, char *out, size_t valuesize); //retrieves candidates that need reporting to the peer. } icefuncs_t; extern icefuncs_t iceapi; #endif - +//address flags #define ADDR_NATPMP (1u<<0) #define ADDR_UPNPIGP (1u<<1) +#define ADDR_REFLEX (1u<<2) //as reported by some external server. #define FTENET_ADDRTYPES 2 typedef struct ftenet_generic_connection_s { @@ -277,8 +279,9 @@ typedef struct ftenet_generic_connection_s { neterr_t (*SendPacket)(struct ftenet_generic_connection_s *con, int length, const void *data, netadr_t *to); void (*Close)(struct ftenet_generic_connection_s *con); #ifdef HAVE_PACKET - int (*SetReceiveFDSet) (struct ftenet_generic_connection_s *con, fd_set *fdset); /*set for connections which have multiple sockets (ie: listening tcp connections)*/ + int (*SetFDSets) (struct ftenet_generic_connection_s *con, fd_set *readfdset, fd_set *writefdset); /*set for connections which have multiple sockets (ie: listening tcp connections)*/ #endif + void (*PrintStatus)(struct ftenet_generic_connection_s *con); netadrtype_t addrtype[FTENET_ADDRTYPES]; qboolean islisten; @@ -312,8 +315,8 @@ void QDECL ICE_AddLCandidateInfo(struct icestate_s *con, netadr_t *adr, int adrn ftenet_connections_t *FTENET_CreateCollection(qboolean listen); void FTENET_CloseCollection(ftenet_connections_t *col); -qboolean FTENET_AddToCollection(struct ftenet_connections_s *col, const char *name, const char *address, netadrtype_t addrtype, qboolean islisten); -int NET_EnumerateAddresses(ftenet_connections_t *collection, struct ftenet_generic_connection_s **con, int *adrflags, netadr_t *addresses, int maxaddresses); +qboolean FTENET_AddToCollection(struct ftenet_connections_s *col, const char *name, const char *address, netadrtype_t addrtype, netproto_t addrprot, qboolean islisten); +int NET_EnumerateAddresses(ftenet_connections_t *collection, struct ftenet_generic_connection_s **con, unsigned int *adrflags, netadr_t *addresses, int maxaddresses); vfsfile_t *FS_OpenSSL(const char *hostname, vfsfile_t *source, qboolean server, qboolean datagram); #ifdef HAVE_PACKET diff --git a/engine/common/plugin.c b/engine/common/plugin.c index 084e96f1..1852b522 100644 --- a/engine/common/plugin.c +++ b/engine/common/plugin.c @@ -1170,7 +1170,7 @@ qintptr_t VARGS Plug_FS_Open(void *offset, quintptr_t mask, const qintptr_t *arg handle = Plug_NewStreamHandle(STREAM_WEB); pluginstreamarray[handle].dl = HTTP_CL_Get(fname, NULL, Plug_DownloadComplete); pluginstreamarray[handle].dl->user_num = handle; - pluginstreamarray[handle].dl->file = pluginstreamarray[handle].vfs = VFSPIPE_Open(); + pluginstreamarray[handle].dl->file = pluginstreamarray[handle].vfs = VFSPIPE_Open(2, true); pluginstreamarray[handle].dl->isquery = true; #ifdef MULTITHREAD DL_CreateThread(pluginstreamarray[handle].dl, NULL, NULL); @@ -1187,6 +1187,7 @@ qintptr_t VARGS Plug_FS_Open(void *offset, quintptr_t mask, const qintptr_t *arg return -1; handle = Plug_NewStreamHandle(STREAM_VFS); pluginstreamarray[handle].vfs = f; + Q_strncpyz(pluginstreamarray[handle].file.filename, fname, sizeof(pluginstreamarray[handle].file.filename)); *ret = handle; return VFS_GETLEN(pluginstreamarray[handle].vfs); } @@ -1325,7 +1326,7 @@ void Plug_Net_Close_Internal(int handle) case STREAM_VFS: if (pluginstreamarray[handle].vfs) { - if (!pluginstreamarray[handle].vfs->seekingisabadplan && pluginstreamarray[handle].vfs->WriteBytes) + if (*pluginstreamarray[handle].file.filename && pluginstreamarray[handle].vfs->WriteBytes) { VFS_CLOSE(pluginstreamarray[handle].vfs); FS_FlushFSHashWritten(pluginstreamarray[handle].file.filename); @@ -1949,7 +1950,7 @@ qboolean Plug_ServerMessage(char *buffer, int messagelevel) qboolean ret = true; Cmd_TokenizeString(buffer, false, false); - Cmd_Args_Set(buffer); + Cmd_Args_Set(buffer, strlen(buffer)); for (currentplug = plugs; currentplug; currentplug = currentplug->next) { @@ -1959,7 +1960,7 @@ qboolean Plug_ServerMessage(char *buffer, int messagelevel) } } - Cmd_Args_Set(NULL); + Cmd_Args_Set(NULL, 0); return ret; // true to display message, false to supress } @@ -1969,7 +1970,7 @@ qboolean Plug_ChatMessage(char *buffer, int talkernum, int tpflags) qboolean ret = true; Cmd_TokenizeString(buffer, false, false); - Cmd_Args_Set(buffer); + Cmd_Args_Set(buffer, strlen(buffer)); for (currentplug = plugs; currentplug; currentplug = currentplug->next) { @@ -1979,7 +1980,7 @@ qboolean Plug_ChatMessage(char *buffer, int talkernum, int tpflags) } } - Cmd_Args_Set(NULL); + Cmd_Args_Set(NULL, 0); return ret; // true to display message, false to supress } @@ -1989,7 +1990,7 @@ qboolean Plug_CenterPrintMessage(char *buffer, int clientnum) qboolean ret = true; Cmd_TokenizeString(buffer, false, false); - Cmd_Args_Set(buffer); + Cmd_Args_Set(buffer, strlen(buffer)); for (currentplug = plugs; currentplug; currentplug = currentplug->next) { @@ -1999,7 +2000,7 @@ qboolean Plug_CenterPrintMessage(char *buffer, int clientnum) } } - Cmd_Args_Set(NULL); + Cmd_Args_Set(NULL, 0); return ret; // true to display message, false to supress } diff --git a/engine/common/pmove.c b/engine/common/pmove.c index 70183707..2e03d91c 100644 --- a/engine/common/pmove.c +++ b/engine/common/pmove.c @@ -1147,7 +1147,9 @@ void PM_NudgePosition (void) VectorCopy (pmove.origin, base); for (i=0 ; i<3 ; i++) - base[i] = ((int)(base[i]*8)) * 0.125; + base[i] = MSG_FromCoord(MSG_ToCoord(base[i], 2), 2); + +// VectorCopy (base, pmove.origin); //if we're moving, allow that spot without snapping to any grid // if (pmove.velocity[0] || pmove.velocity[1] || pmove.velocity[2]) diff --git a/engine/common/sys.h b/engine/common/sys.h index c1ee91e9..57c2ce71 100644 --- a/engine/common/sys.h +++ b/engine/common/sys.h @@ -24,9 +24,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // for the most part, we use stdio. // if your system doesn't have stdio then urm... well. // -void Sys_mkdir (char *path); //not all pre-unix systems have directories (including dos 1) -qboolean Sys_remove (char *path); -qboolean Sys_Rename (char *oldfname, char *newfname); +void Sys_mkdir (const char *path); //not all pre-unix systems have directories (including dos 1) +qboolean Sys_rmdir (const char *path); +qboolean Sys_remove (const char *path); +qboolean Sys_Rename (const char *oldfname, const char *newfname); qboolean Sys_FindGameData(const char *poshname, const char *gamename, char *basepath, int basepathlen, qboolean allowprompts); // diff --git a/engine/common/sys_win_threads.c b/engine/common/sys_win_threads.c index f62c4049..e2f0c40a 100644 --- a/engine/common/sys_win_threads.c +++ b/engine/common/sys_win_threads.c @@ -168,7 +168,16 @@ void Sys_DetachThread(void *thread) void Sys_WaitOnThread(void *thread) { threadctx_t *ctx = thread; +#ifdef SERVERONLY WaitForSingleObject(ctx->handle, INFINITE); +#else + while (WAIT_OBJECT_0+1 == MsgWaitForMultipleObjects(1, &ctx->handle, false, INFINITE, QS_SENDMESSAGE)) + { + MSG msg; + if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + DispatchMessage (&msg); + } +#endif CloseHandle(ctx->handle); free(ctx); } diff --git a/engine/common/zone.c b/engine/common/zone.c index 33bfeca9..0b457a9d 100644 --- a/engine/common/zone.c +++ b/engine/common/zone.c @@ -212,6 +212,18 @@ void *Z_Malloc(int size) } #endif +void Z_StrCat(char **ptr, char *append) +{ + size_t oldlen = *ptr?strlen(*ptr):0; + size_t newlen = strlen(append); + char *newptr = BZ_Malloc(oldlen+newlen+1); + memcpy(newptr, *ptr, oldlen); + memcpy(newptr+oldlen, append, newlen); + newptr[oldlen+newlen] = 0; + BZ_Free(*ptr); + *ptr = newptr; +} + void VARGS Z_TagFree(void *mem) { zone_t *zone = ((zone_t *)mem) - 1; diff --git a/engine/common/zone.h b/engine/common/zone.h index 45d8527e..76fceb56 100644 --- a/engine/common/zone.h +++ b/engine/common/zone.h @@ -133,6 +133,8 @@ void ZG_FreeGroup(zonegroup_t *ctx); #endif #define Z_StrDup(s) strcpy(Z_Malloc(strlen(s)+1), s) +void Z_StrCat(char **ptr, char *append); + /* void *Hunk_Alloc (int size); // returns 0 filled memory void *Hunk_AllocName (int size, char *name); diff --git a/engine/dotnet2005/botlib.vcproj b/engine/dotnet2005/botlib.vcproj index 72da3532..0eefd441 100644 --- a/engine/dotnet2005/botlib.vcproj +++ b/engine/dotnet2005/botlib.vcproj @@ -10,6 +10,9 @@ + @@ -136,6 +139,130 @@ Name="VCPostBuildEventTool" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/dotnet2005/droid.vcproj b/engine/dotnet2005/droid.vcproj index bb21b60c..484aac29 100644 --- a/engine/dotnet2005/droid.vcproj +++ b/engine/dotnet2005/droid.vcproj @@ -23,7 +23,7 @@ > backbuf->presentsemaphore); + + //tell the gl driver to copy it over now + qglDrawVkImageNV(theframe->backbuf->colour.image, theframe->backbuf->colour.sampler, + 0, 0, vid.pixelwidth, vid.pixelheight, //xywh (window coords) + 0, //z + 0, 1, 1, 0); //stst (remember that gl textures are meant to be upside down) + + //and tell our code to expect it. + vk.acquirebufferidx[vk.aquirelast%ACQUIRELIMIT] = vk.aquirelast%vk.backbuf_count; + fence = vk.acquirefences[vk.aquirelast%ACQUIRELIMIT]; + vk.aquirelast++; + //and actually signal it, so our code can wake up. + qglSignalVkFenceNV(fence); + + + //and the gl driver has its final image and should do something with it now. + qSwapBuffers(maindc); + +// qwglMakeCurrent(maindc, NULL); + + RSpeedEnd(RSPEED_PRESENT); +} +static qboolean WGL_CheckExtension(char *extname); +static qboolean Win32NVVK_AttachVulkan (rendererstate_t *info) +{ //make sure we can get a valid renderer. + + if (!GL_CheckExtension("GL_NV_draw_vulkan_image")) + { + Con_Printf("GL_NV_draw_vulkan_image is not supported. Try using real vulkan instead.\n"); + return false; + } + qglGetVkProcAddrNV = getglfunc("glGetVkProcAddrNV"); + qglDrawVkImageNV = getglfunc("glDrawVkImageNV"); + qglWaitVkSemaphoreNV = getglfunc("glWaitVkSemaphoreNV"); + qglSignalVkSemaphoreNV = getglfunc("glSignalVkSemaphoreNV"); + qglSignalVkFenceNV = getglfunc("glSignalVkFenceNV"); + + vkGetInstanceProcAddr = nvvkGetInstanceProcAddr; +// qwglMakeCurrent(maindc, NULL); + return VK_Init(info, NULL, Win32NVVK_CreateSurface, Win32NVVK_Present); +} +#endif + /*doesn't consider parent offsets*/ static RECT centerrect(unsigned int parentleft, unsigned int parenttop, unsigned int parentwidth, unsigned int parentheight, unsigned int cwidth, unsigned int cheight) { @@ -1343,6 +1434,9 @@ static int GLVID_SetMode (rendererstate_t *info, unsigned char *palette) { #ifdef USE_WGL case MODE_WGL: +#ifdef VKQUAKE + case MODE_NVVULKAN: +#endif // Set either the fullscreen or windowed mode qwglChoosePixelFormatARB = NULL; qwglCreateContextAttribsARB = NULL; @@ -1383,6 +1477,13 @@ static int GLVID_SetMode (rendererstate_t *info, unsigned char *palette) return false; } } + +#ifdef VKQUAKE + if (platform_rendermode == MODE_NVVULKAN && stat) + { + stat = Win32NVVK_AttachVulkan(info); + } +#endif break; #endif #ifdef USE_EGL @@ -1490,6 +1591,13 @@ void VID_UnSetMode (void) switch(platform_rendermode) { +#if defined(VKQUAKE) && defined(USE_WGL) + case MODE_NVVULKAN: + qwglMakeCurrent(maindc, baseRC); + VK_Shutdown(); + ReleaseGL(); + break; +#endif #ifdef USE_WGL case MODE_WGL: ReleaseGL(); @@ -1596,10 +1704,10 @@ static void VID_UpdateWindowStatus (HWND hWnd) switch(platform_rendermode) { #ifdef VKQUAKE + case MODE_NVVULKAN: case MODE_VULKAN: if (vid.pixelwidth != window_width || vid.pixelheight != window_height) vk.neednewswapchain = true; - break; #endif default: vid.pixelwidth = window_width; @@ -2708,6 +2816,11 @@ static LONG WINAPI GLMainWndProc ( case WM_USER+7: VK_DoPresent((struct vkframe*)lParam); break; +#endif +#if defined(VKQUAKE) && defined(USE_WGL) + case WM_USER+8: + Win32NVVK_DoPresent((struct vkframe*)lParam); + break; #endif case WM_USER+4: PostQuitMessage(0); @@ -2815,6 +2928,31 @@ static LONG WINAPI GLMainWndProc ( } break; + case WM_DROPFILES: + { + HDROP p = (HDROP)wParam; + wchar_t fnamew[MAX_PATH]; + char fname[MAX_PATH]; + vfsfile_t *f; + int i, count = DragQueryFile(p, ~0, NULL, 0); + for(i = 0; i < count; i++) + { + if (WinNT) + { + DragQueryFileW(p, i, fnamew, countof(fnamew)); + narrowen(fname, sizeof(fname), fnamew); + } + else + DragQueryFileA(p, i, fname, countof(fname)); + f = FS_OpenVFS(fname, "rb", FS_SYSTEM); + if (f) + Host_RunFile(fname, strlen(fname), f); + } + DragFinish(p); + return 0; //An application should return zero if it processes this message. + } + break; + #ifdef HAVE_CDPLAYER case MM_MCINOTIFY: #ifdef WTHREAD @@ -2935,6 +3073,8 @@ qboolean Win32VID_Init (rendererstate_t *info, unsigned char *palette, int mode) vid_initialized = true; vid_initializing = false; + WIN_WindowCreated(mainwindow); + return true; } @@ -3084,6 +3224,69 @@ rendererinfo_t vkrendererinfo = "no more" }; + + + + +static qboolean NVVKVID_Init (rendererstate_t *info, unsigned char *palette) +{ + return Win32VID_Init(info, palette, MODE_NVVULKAN); +} +rendererinfo_t nvvkrendererinfo = +{ + "Vulkan (nvidia workaround)", + { + "nvvk", + }, + QR_VULKAN, + + VK_Draw_Init, + VK_Draw_Shutdown, + + VK_UpdateFiltering, + VK_LoadTextureMips, + VK_DestroyTexture, + + VK_R_Init, + VK_R_DeInit, + VK_R_RenderView, + + NVVKVID_Init, + GLVID_DeInit, + GLVID_SwapBuffers, + GLVID_ApplyGammaRamps, + WIN_CreateCursor, + WIN_SetCursor, + WIN_DestroyCursor, + GLVID_SetCaption, + VKVID_GetRGBInfo, + + VK_SCR_UpdateScreen, + + VKBE_SelectMode, + VKBE_DrawMesh_List, + VKBE_DrawMesh_Single, + VKBE_SubmitBatch, + VKBE_GetTempBatch, + VKBE_DrawWorld, + VKBE_Init, + VKBE_GenBrushModelVBO, + VKBE_ClearVBO, + VKBE_UploadAllLightmaps, + VKBE_SelectEntity, + VKBE_SelectDLight, + VKBE_Scissor, + VKBE_LightCullModel, + + VKBE_VBO_Begin, + VKBE_VBO_Data, + VKBE_VBO_Finish, + VKBE_VBO_Destroy, + + VKBE_RenderToTextureUpdate2d, + + "no more" +}; #endif #endif diff --git a/engine/gl/r_bishaders.h b/engine/gl/r_bishaders.h index d6c29348..37c6e6bc 100644 --- a/engine/gl/r_bishaders.h +++ b/engine/gl/r_bishaders.h @@ -10004,7 +10004,7 @@ YOU SHOULD NOT EDIT THIS FILE BY HAND "#define tcbase tcoffsetmap\n" "#endif\n" "#if defined(FLAT)\n" -"vec4 bases = vec3(FLAT, 1.0);\n" +"vec4 bases = vec4(FLAT, FLAT, FLAT, 1.0);\n" "#else\n" "vec4 bases = texture2D(s_diffuse, tcbase);\n" "#ifdef VERTEXCOLOURS\n" diff --git a/engine/http/ftpserver.c b/engine/http/ftpserver.c index 2c8df090..384f6368 100644 --- a/engine/http/ftpserver.c +++ b/engine/http/ftpserver.c @@ -22,28 +22,39 @@ static iwboolean ftpserverinitied = false; static SOCKET ftpserversocket = INVALID_SOCKET; +static int ftpserverport = 0; qboolean ftpserverfailed; typedef struct FTPclient_s{ + char peername[256]; char name[64]; char pwd[64]; int auth; //has it got auth? char path[256]; + char renamefrom[256]; + char commandbuffer[256]; char messagebuffer[256]; int cmdbuflen; int msgbuflen; + int controlaf; SOCKET controlsock; SOCKET datasock; //FTP only allows one transfer per connection. int dataislisten; int datadir; //0 no data, 1 reading, 2 writing vfsfile_t *file; + qboolean brieflist; //incoming list command was an nlist + + qofs_t restartpos; + unsigned long blocking; + void *transferthread; + struct FTPclient_s *next; } FTPclient_t; @@ -57,11 +68,34 @@ SOCKET FTP_BeginListening(int aftype, int port) int i; SOCKET sock; + int af; + int prot; + + if (!port) + port = IWebGetSafeListeningPort(); + + switch(aftype) + { + case 0: #ifdef IPPROTO_IPV6 - if ((sock = socket ((aftype!=1)?PF_INET6:PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) -#else - if ((sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) + case 2: + af = AF_INET6; + prot = IPPROTO_TCP; + break; #endif + case 1: + af = AF_INET; + prot = IPPROTO_TCP; + break; +// case 11: +// af = AF_IPX; +// prot = NSPROTO_SPX; +// break; + default: + return INVALID_SOCKET; + } + + if ((sock = socket (af, SOCK_STREAM, prot)) == -1) { IWebPrintf ("FTP_BeginListening: socket: %s\n", strerror(neterrno())); return INVALID_SOCKET; @@ -74,7 +108,7 @@ SOCKET FTP_BeginListening(int aftype, int port) } #ifdef IPPROTO_IPV6 - if (aftype != 1) + if (aftype == 0 || aftype == 2) { //0=ipv4+ipv6 //2=ipv6 only @@ -87,6 +121,8 @@ SOCKET FTP_BeginListening(int aftype, int port) return FTP_BeginListening(1, port); } } + else + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&_true, sizeof(_true)); memset(&address, 0, sizeof(address)); ((struct sockaddr_in6*)&address)->sin6_family = AF_INET6; @@ -129,14 +165,42 @@ SOCKET FTP_BeginListening(int aftype, int port) void FTP_ServerShutdown(void) { closesocket(ftpserversocket); + ftpserversocket = INVALID_SOCKET; ftpserverinitied = false; IWebPrintf("FTP server is deactivated\n"); } +static iwboolean FTP_AllowUpLoad(const char *fname, FTPclient_t *cl) +{ + if (cl->auth & IWEBACC_FULL) + return true; + if (!(cl->auth & IWEBACC_WRITE)) + return false; + + return IWebAllowUpLoad(fname, cl->name); +} +static iwboolean FTP_AllowDownLoad(const char *fname, FTPclient_t *cl) +{ + if (cl->auth & IWEBACC_FULL) + return true; + if (!(cl->auth & IWEBACC_READ)) + return false; + + if (FTP_AllowUpLoad(fname, cl)) + return true; + + return SV_AllowDownload(fname); +} +static iwboolean FTP_AllowList(const char *fname, FTPclient_t *cl) +{ + return FTP_AllowDownLoad(fname, cl) || FTP_AllowUpLoad(fname, cl); +} + //we ought to filter this to remove duplicates. static int QDECL SendFileNameTo(const char *rawname, qofs_t size, time_t mtime, void *param, searchpathfuncs_t *spath) { - SOCKET socket = *(SOCKET*)param; + FTPclient_t *cl = param; + SOCKET socket = cl->datasock; // int i; char buffer[256+1]; char *slash; @@ -146,7 +210,7 @@ static int QDECL SendFileNameTo(const char *rawname, qofs_t size, time_t mtime, #ifndef WEBSVONLY //copy protection of the like that QWSV normally has. if (!isdir) - if (!SV_AllowDownload(rawname)) //don't advertise if we're going to disallow it + if (!FTP_AllowList(rawname, cl)) return true; #endif @@ -158,10 +222,22 @@ static int QDECL SendFileNameTo(const char *rawname, qofs_t size, time_t mtime, while((slash = strchr(fname, '/'))) fname = slash+1; - if (isdir) - sprintf(buffer, "drw-r--r--\t1\troot\troot\t%8u Jan 1 12:00 %s\r\n", (unsigned int)size, fname); + if (cl->brieflist) + Q_snprintfz(buffer, sizeof(buffer), "%s\r\n", fname); else - sprintf(buffer, "-rw-r--r--\t1\troot\troot\t%8u Jan 1 12:00 %s\r\n", (unsigned int)size, fname); + { + char timestamp[32]; + if (1) + strftime(timestamp, sizeof(timestamp), "%b %d %Y", gmtime(&mtime)); + else + strftime(timestamp, sizeof(timestamp), "%b %d %H:%M", gmtime(&mtime)); + + Q_snprintfz(buffer, sizeof(buffer), "%c%c%c-------\t1\troot\troot\t%8"PRIuQOFS" %s %s\r\n", + isdir?'d':'-', + FTP_AllowDownLoad(rawname, cl)?'r':'-', + FTP_AllowUpLoad(rawname, cl)?'w':'-', + size, timestamp, fname); + } // strcpy(buffer, fname); // for (i = strlen(buffer); i < 40; i+=8) @@ -211,7 +287,7 @@ SOCKET FTP_SV_makelistensocket(unsigned long nblocking) return sock; } -iwboolean FTP_SVSocketPortToString (SOCKET socket, char *s) +int FTP_SVGetSocketPort (SOCKET socket) { struct sockaddr_qstorage addr; int adrlen = sizeof(addr); @@ -220,58 +296,107 @@ iwboolean FTP_SVSocketPortToString (SOCKET socket, char *s) return false; if (((struct sockaddr_in*)&addr)->sin_family == AF_INET6) - sprintf(s, "%i", ntohs(((struct sockaddr_in6*)&addr)->sin6_port)); + return ntohs(((struct sockaddr_in6*)&addr)->sin6_port); + else if (((struct sockaddr_in*)&addr)->sin_family == AF_INET) + return ntohs(((struct sockaddr_in*)&addr)->sin_port); else - sprintf(s, "%i", ntohs(((struct sockaddr_in*)&addr)->sin_port)); - return true; + return 0; //no idea } //only to be used for ipv4 sockets. -iwboolean FTP_SVSocketToString (SOCKET socket, char *s) +iwboolean FTP_SVSocketToV4String (SOCKET socket, char *s) { - struct sockaddr_in addr; + struct sockaddr_qstorage addr; qbyte *baddr; int adrlen = sizeof(addr); char name[256]; + unsigned short port; - //get the port. if (getsockname(socket, (struct sockaddr*)&addr, &adrlen) == -1) return false; - - baddr = (qbyte *)&addr.sin_addr; - - if (gethostname(name, sizeof(name)) != -1) + if (((struct sockaddr_in*)&addr)->sin_family == AF_INET6) { - struct hostent *hent = gethostbyname(name); - if (hent) - baddr = hent->h_addr_list[0]; + port = ((struct sockaddr_in6*)&addr)->sin6_port; + baddr = ((struct sockaddr_in6*)&addr)->sin6_addr.s6_addr; + if (memcmp(baddr, "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12)) + return false; //must be ipv4-mapped for this ipv4 function + baddr += 12; } - - sprintf(s, "%i,%i,%i,%i,%i,%i", baddr[0], baddr[1], baddr[2], baddr[3], ((qbyte *)&addr.sin_port)[0], ((qbyte *)&addr.sin_port)[1]); - return true; -} -iwboolean FTP_SVRemoteSocketToString (SOCKET socket, char *s, int slen) -{ - struct sockaddr_qstorage addr; - netadr_t na; - int adrlen = sizeof(addr); - -// addr.sin_family = AF_INET; - if (getpeername(socket, (struct sockaddr*)&addr, &adrlen) == -1) + else if (((struct sockaddr_in*)&addr)->sin_family == AF_INET) { - *s = 0; + port = ((struct sockaddr_in*)&addr)->sin_port; + baddr = (qbyte*)&((struct sockaddr_in*)&addr)->sin_addr; + } + else return false; + + if (!*(int*)baddr) + { + //FIXME doesn't work on anything but windows. + if (gethostname(name, sizeof(name)) != -1) + { + struct hostent *hent = gethostbyname(name); + if (hent) + baddr = hent->h_addr_list[0]; + } } - SockadrToNetadr(&addr, &na); - NET_AdrToString(s, slen, &na); - -// if (((struct sockaddr_in*)&addr)->sin_family == AF_INET6) -// { -// } -// else -// sprintf(s, "%i,%i,%i,%i,%i,%i", ((qbyte *)&addr.sin_addr)[0], ((qbyte *)&addr.sin_addr)[1], ((qbyte *)&addr.sin_addr)[2], ((qbyte *)&addr.sin_addr)[3], ((qbyte *)&addr.sin_port)[0], ((qbyte *)&addr.sin_port)[1]); + if (!baddr) + return false; + sprintf(s, "%i,%i,%i,%i,%i,%i", baddr[0], baddr[1], baddr[2], baddr[3], ((qbyte *)&port)[0], ((qbyte *)&port)[1]); return true; } +iwboolean FTP_V4StringToAdr (const char *s, struct sockaddr_in *addr) +{ + ((qbyte*)&addr->sin_addr)[0] = strtol(s, (char**)&s, 0); + if (*s++ != ',') return false; + ((qbyte*)&addr->sin_addr)[1] = strtol(s, (char**)&s, 0); + if (*s++ != ',') return false; + ((qbyte*)&addr->sin_addr)[2] = strtol(s, (char**)&s, 0); + if (*s++ != ',') return false; + ((qbyte*)&addr->sin_addr)[3] = strtol(s, (char**)&s, 0); + if (*s++ != ',') return false; + ((qbyte*)&addr->sin_port)[0] = strtol(s, (char**)&s, 0); + if (*s++ != ',') return false; + ((qbyte*)&addr->sin_port)[1] = strtol(s, (char**)&s, 0); + + return true; +} +iwboolean FTP_HostToSockaddr(int prot, char *host, int port, struct sockaddr_qstorage *addr, size_t *addrsize) +{ + iwboolean r = false; + struct addrinfo *res, hint; + char service[16]; + + *addrsize = 0; + + memset(&hint, 0, sizeof(hint)); + hint.ai_flags = AI_NUMERICHOST; + switch(prot) + { + case 1: + hint.ai_family = AF_INET; + break; + case 2: + hint.ai_family = AF_INET6; + break; + } + Q_snprintfz(service, sizeof(service), "%i", port); + if (getaddrinfo(host, service, &hint, &res)) + return false; + if (res && res->ai_addr && res->ai_addrlen <= sizeof(*addr)) + { + *addrsize = res->ai_addrlen; + memcpy(addr, res->ai_addr, res->ai_addrlen); + r = true; + } + freeaddrinfo(res); + return r; +#if 0 + host = va("[%s]", host); + return NET_StringToSockaddr(host, port, addr, NULL, NULL); +#endif +} + /* * Responsable for sending all control server -> client messages. * Queues the message if it cannot send now. @@ -279,10 +404,14 @@ iwboolean FTP_SVRemoteSocketToString (SOCKET socket, char *s, int slen) */ void QueueMessage(FTPclient_t *cl, char *msg) { + IWebDPrintf("FTP> %s", msg); if (send (cl->controlsock, msg, strlen(msg), 0) == -1) { //wasn't sent if (strlen(msg) + strlen(cl->messagebuffer) >= sizeof(cl->messagebuffer)-1) + { closesocket(cl->controlsock); //but don't mark it as closed, so we get errors later (for this is how we shall tell). + return; + } strcat(cl->messagebuffer, msg); } } @@ -297,17 +426,154 @@ void VARGS QueueMessageva(FTPclient_t *cl, char *fmt, ...) msg[sizeof(msg)-1] = 0; va_end (argptr); + IWebDPrintf("FTP> %s", msg); if (send (cl->controlsock, msg, strlen(msg), 0) == -1) { //wasn't sent if (strlen(msg) + strlen(cl->messagebuffer) >= sizeof(cl->messagebuffer)-1) - closesocket(cl->controlsock); //but don't mark it as closed, so we get errors later (for this is how we shall tell). + { + closesocket(cl->controlsock); + cl->controlsock = INVALID_SOCKET; + } strcat(cl->messagebuffer, msg); } } + +//if eg: "RETR Some File Name.txt \r\n", and the RETR was already parsed, we'll read out the Some File Name until the \r\n +//returns a directly-usable game path. +qboolean FTP_ReadToAbsFilename(FTPclient_t *cl, const char *msg, char *out, size_t outsize) +{ + size_t len = 0; + const char *end; + if (*msg == ' ') + msg++; + else + { + *out = 0; + return false; + } + end = msg; + while(*end) + { + if (*end == '\r' || *end == '\n') + break; + end++; + } + //trim stupid trailing space that windows insists on fucking shit up with + if (end > msg && end[-1] == ' ') + end--; + + //figure out the root + if (*msg == '/') + msg++; + else + { //FIXME: nuke the +1s + len = strlen(cl->path); + if (len >= outsize) + { //too long... + *out = 0; + return false; + } + memcpy(out, cl->path, len); + } + + while (msg < end) + { + if (*msg == '/') + msg++; //double slashes will be silently ignored. also simplifies ../ etc. + else if (!strncmp(msg, "..", 2) && (msg+2 == end || msg[2] == '/')) + { // "/foo/../bar" should end up as just "bar" + msg+=2; + if (!len) + { //can't go above root... + *out = 0; + return false; + } + while (len > 0) + { + if (out[--len] == '/') + break; + } + } + else if (!strncmp(msg, ".", 1) && (msg+1 == end || msg[1] == '/')) + { //filenames relative to the working directory are stupid, but whatever + msg+=1; + while (len > 0) + { + if (out[--len] == '/') + break; + } + } + else + { + const char *s; + for (s = msg; s < end; s++) + { + if (*s == '/') + break; + } + if (s == msg) + { //error... + *out = 0; + return false; + } + if (len + s-msg + 2 > outsize) + break; + if (len) + out[len++] = '/'; + memcpy(out+len, msg, s-msg); + len += s-msg; + msg = s; + } + } + out[len] = 0; + return true; +} + +int FTP_TransferThread(void *vcl) +{ + char resource[8192]; + FTPclient_t *cl = vcl; + u_long _false = false; + ioctlsocket (cl->datasock, FIONBIO, &_false); + + if (cl->datadir == 1) + { + while(1) + { + int ammount = VFS_READ(cl->file, resource, sizeof(resource)); + int chunk, sent; + if (ammount <= 0) + break; + for (sent = 0; sent < ammount; sent += chunk) + { + chunk = send(cl->datasock, resource, ammount-sent, 0); + if (chunk <= 0) + break; + } + if (ammount != sent) + break; + } + } + else if (cl->datadir == 2) + { + while(1) + { + int ammount = recv(cl->datasock, resource, sizeof(resource), 0); + if (ammount <= 0) + break; + if (ammount != VFS_WRITE(cl->file, resource, ammount)) + break; + } + } + cl->transferthread = NULL; + + return 0; +} + iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) { int ret; - struct sockaddr_in from; + struct sockaddr_qstorage from; int fromlen; char *msg, *line; @@ -315,6 +581,16 @@ iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) char resource[8192]; int _true = true; + if (cl->transferthread) + { + if (*cl->messagebuffer) + { + if (send (cl->controlsock, cl->messagebuffer, strlen(cl->messagebuffer), 0) != -1) + *cl->messagebuffer = '\0'; //YAY! It went! + } + return false; + } + if (cl->datadir == 1) { int pos, sent; @@ -405,7 +681,7 @@ iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) if (e == NET_ECONNABORTED || e == NET_ECONNRESET) return true; - Con_Printf ("NET_GetPacket: %s\n", strerror(e)); + IWebPrintf ("NET_GetPacket: %s\n", strerror(e)); return true; } if (*cl->messagebuffer) @@ -440,16 +716,27 @@ iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) } *line = '\0'; line++; - IWebPrintf("FTP: %s\n", msg); + IWebDPrintf("FTP: %s\n", msg); msg = COM_ParseOut(msg, mode, sizeof(mode)); if (!stricmp(mode, "SYST")) { QueueMessage (cl, "215 UNIX Type: L8.\r\n"); //some browsers can be wierd about things. } + else if (!stricmp(mode, "FEAT")) + { + QueueMessage (cl, "211-Extensions supported:\r\n"); + QueueMessage (cl, " SIZE\r\n"); + QueueMessage (cl, " REST\r\n"); + QueueMessage (cl, " EPSV\r\n"); + QueueMessage (cl, " EPRT\r\n"); +// QueueMessage (cl, " MDTM\r\n"); + QueueMessage (cl, "211 End\r\n"); + } else if (!stricmp(mode, "user")) { msg = COM_ParseOut(msg, cl->name, sizeof(cl->name)); + cl->auth = 0; //any access rights go away now, so they can't spoof read access with dodgy filenames. QueueMessage (cl, "331 User name received, will be checked with password.\r\n"); } @@ -493,27 +780,63 @@ iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) QueueMessage (cl, "530 Not logged in.\r\n"); continue; } - QueueMessageva (cl, "257 \"%s\"\r\n", cl->path); + if (*cl->path) + QueueMessageva (cl, "257 \"/%s/\"\r\n", cl->path); + else + QueueMessageva (cl, "257 \"/\"\r\n"); } else if (!stricmp(mode, "CWD")) { - char *p; if (!cl->auth) { QueueMessage (cl, "530 Not logged in.\r\n"); continue; } - Q_strcpyline(cl->path, msg+1, sizeof(cl->path));//path starts after cmd and single space - for (p = cl->path+strlen(cl->path)-1; *p == ' ' && p >= cl->path; p--) - *p = '\0'; + if (!FTP_ReadToAbsFilename(cl, msg, resource, sizeof(resource))) + { + QueueMessage (cl, "550 invalid path.\r\n"); + continue; + } + + Q_strncpyz(cl->path, resource, sizeof(cl->path)); QueueMessage (cl, "200 directory changed.\r\n"); } + else if (!stricmp(mode, "MKD")) + { + //create directory + FTP_ReadToAbsFilename(cl, msg, resource, sizeof(resource)); + if (!cl->auth) + { + QueueMessage (cl, "530 Not logged in.\r\n"); + continue; + } + if (!*resource || !FTP_AllowUpLoad(resource, cl)) + { + IWebPrintf("%s: Denied mkdir request for \"ftp://%s@%s/%s\"\n", cl->peername, cl->name, "", resource); + QueueMessage (cl, "550 Access denied.\r\n"); + continue; + } + + IWebPrintf("%s: Mkdir request for \"ftp://%s@%s/%s\"\n", cl->peername, cl->name, "", resource); + + Q_strncatz(resource, "/", sizeof(resource)); + FS_CreatePath(resource, FS_GAMEONLY); + QueueMessage (cl, "250 Success.\r\n"); + } else if (!stricmp(mode, "EPSV")) { int aftype = 0; - //one argument, "1"=ipv4, "2"=ipv6. if not present, use same as control connection + //one argument, "1"=ipv4, "2"=ipv6, "11"=ipx (by rfc 1700). if not present, use same as control connection //reply: "229 Entering Extended Passive Mode (|||$PORTNUM|)\r\n" + while(*msg == ' ') + msg++; + if (!strncmp(msg, "ALL", 3)) + continue; //rfc2428 'EPSV ALL' is a signal to client NATs that PORT/EPRT/PASV will not be used, and that they can just treat it as a regular TCP connection same as anything else. we 'must' also refuse any of those commands too, but we shouldn't receive them anyway. + aftype = atoi(msg); + if (!aftype) + aftype = cl->controlaf; + if (!cl->auth) { QueueMessage (cl, "530 Not logged in.\r\n"); @@ -530,8 +853,7 @@ iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) QueueMessage (cl, "425 server was unable to make a listen socket\r\n"); else { - FTP_SVSocketPortToString(cl->datasock, resource); - QueueMessageva (cl, "229 Entering Extended Passive Mode (|||%s|).\r\n", resource); + QueueMessageva (cl, "229 Entering Extended Passive Mode (|||%i|).\r\n", FTP_SVGetSocketPort(cl->datasock)); } cl->dataislisten = true; } @@ -553,19 +875,65 @@ iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) QueueMessage (cl, "425 server was unable to make a listen socket\r\n"); else { - FTP_SVSocketToString(cl->datasock, resource); - QueueMessageva (cl, "227 Entering Passive Mode (%s).\r\n", resource); + if (FTP_SVSocketToV4String(cl->datasock, resource)) + QueueMessageva (cl, "227 Entering Passive Mode (%s).\r\n", resource); + else + QueueMessageva (cl, "550 Unable to parse address.\r\n", resource); } cl->dataislisten = true; } -// else if (!stricmp(mode, "EPRT")) -// { + else if (!stricmp(mode, "EPRT")) + { //eg: one of: //EPRT |1|132.235.1.2|6275| //EPRT |2|1080::8:800:200C:417A|5282| //reply: 522 Network protocol not supported, use (1,2) -// } + + char d; + int prot; + int port; + char *eon, *host; + struct sockaddr_qstorage peer; + size_t peersize; + while(*msg == ' ') + msg++; + d = *msg++; + prot = strtol(msg, &msg, 0); + host = ++msg; + eon = strchr(msg, d); + if (eon) + { + *eon++ = 0; + msg = eon; + } + cl->dataislisten = false; + if (cl->datasock != INVALID_SOCKET) + closesocket(cl->datasock); + cl->datasock = INVALID_SOCKET; + + port = strtol(msg, &msg, 0); + if (*msg != d || !eon || !FTP_HostToSockaddr(prot, host, port, &peer, &peersize)) + QueueMessage (cl, "522 Network protocol not supported, use (1,2).\r\n"); + else + { + memset(&from, 0, sizeof(from)); + ((struct sockaddr*)&from)->sa_family = ((struct sockaddr*)&peer)->sa_family; + if ((cl->datasock = socket (((struct sockaddr*)&from)->sa_family, SOCK_STREAM, IPPROTO_TCP)) != -1) + { + if (ioctlsocket (cl->datasock, FIONBIO, (u_long *)&_true) != -1) + if( bind (cl->datasock, (void *)&from, peersize) != -1) + { + connect(cl->datasock, (struct sockaddr *)&peer, peersize); + QueueMessage (cl, "200 Opened data channel.\r\n"); + continue; + } + closesocket(cl->datasock); + cl->datasock=INVALID_SOCKET; + } + QueueMessage (cl, "550 Command not fully implemented.\r\n"); + } + } else if (!stricmp(mode, "PORT")) { if (!cl->auth) @@ -582,7 +950,10 @@ iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) cl->dataislisten = false; - if ((cl->datasock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) + memset(&from, 0, sizeof(from)); + ((struct sockaddr_in*)&from)->sin_family = AF_INET; + + if ((cl->datasock = socket (((struct sockaddr*)&from)->sa_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { Sys_Error ("FTP_ServerThinkForConnection: socket: %s", strerror(neterrno())); } @@ -592,12 +963,6 @@ iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) Sys_Error ("FTP_ServerThinkForConnection: ioctl FIONBIO: %s", strerror(neterrno())); } - from.sin_family = AF_INET; - - from.sin_addr.s_addr = INADDR_ANY; - - from.sin_port = 0; - if( bind (cl->datasock, (void *)&from, sizeof(from)) == -1) { closesocket(cl->datasock); @@ -609,14 +974,24 @@ iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) fromlen = sizeof(from); - FTP_StringToAdr(resource, (qbyte *)&from.sin_addr, (qbyte *)&from.sin_port); - connect(cl->datasock, (struct sockaddr *)&from, fromlen); + if (FTP_V4StringToAdr(resource, (struct sockaddr_in *)&from)) + { + connect(cl->datasock, (struct sockaddr *)&from, fromlen); - QueueMessage (cl, "200 Opened data channel.\r\n"); + QueueMessage (cl, "200 Opened data channel.\r\n"); + } + else + { + closesocket(cl->datasock); + cl->datasock=INVALID_SOCKET; + + QueueMessage (cl, "425 server resolve error.\r\n"); + } } - else if (!stricmp(mode, "LIST")) + else if (!stricmp(mode, "LIST") || !stricmp(mode, "NLST")) { char buffer[256]; + cl->brieflist = !stricmp(mode, "NLST"); if (!cl->auth) { QueueMessage (cl, "530 Not logged in.\r\n"); @@ -630,13 +1005,13 @@ iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) struct sockaddr_qstorage adr; int adrlen = sizeof(adr); temp = accept(cl->datasock, (struct sockaddr *)&adr, &adrlen); - err = neterrno(); closesocket(cl->datasock); cl->datasock = temp; cl->dataislisten = false; if (cl->datasock == INVALID_SOCKET) { + err = neterrno(); QueueMessageva (cl, "425 Can't accept pasv data connection - %i.\r\n", err); continue; } @@ -660,19 +1035,58 @@ iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) strcat(buffer, "*"); QueueMessage (cl, "125 Opening FAKE ASCII mode data connection for file.\r\n"); - COM_EnumerateFiles(buffer, SendFileNameTo, &cl->datasock); + COM_EnumerateFiles(buffer, SendFileNameTo, cl); QueueMessage (cl, "226 Transfer complete.\r\n"); closesocket(cl->datasock); cl->datasock = INVALID_SOCKET; } -// else if (!stricmp(mode, "SIZE")) //why IE can't use the list command to find file length, I've no idea. -// { -// msg = COM_ParseOut(msg, resource, sizeof(resource)); -// } + /*else if (!stricmp(mode, "MDTM")) + { + char ospath[MAX_OSPATH]; + struct tm *t; + FTP_ReadToAbsFilename(cl, msg, resource, sizeof(resource)); + if (FS_NativePath(resource, FS_GAME, ospath, sizeof(ospath))) + { + t = gmtime(Sys_FileTime(path)); + QueueMessageva (cl, "213 %04i%02i%02i%02i%02i%02i\r\n", + 1900+t->tm_year, 1+t->tm->mon, 1+t->tm->mday, + t->tm->hour, t->tm->min, t->tm_sec ); + } + else + QueueMessageva (cl, "550 unavailable.\r\n", size); + }*/ + else if (!stricmp(mode, "SIZE")) //why IE can't use the list command to find file length, I've no idea. + { + //STRU, MODE, and TYPE may change the reported size... + vfsfile_t *f; + qofs_t size = qofs_ErrorValue(); + FTP_ReadToAbsFilename(cl, msg, resource, sizeof(resource)); + + if (*resource && FTP_AllowList(resource, cl)) + { + f = FS_OpenVFS(resource, "rb", FS_GAME); + if (f) + { + size = VFS_GETLEN(f); + VFS_CLOSE(f); + } + } + + if (qofs_Error(size)) + QueueMessageva (cl, "550 Couldn't read file.\r\n", size); + else + QueueMessageva (cl, "213 %"PRIuQOFS"\r\n", size); + } + else if (!stricmp(mode, "REST")) + { + COM_ParseOut(msg, resource, sizeof(resource)); + cl->restartpos = strtoull(resource, NULL, 0); + } else if (!stricmp(mode, "RETR")) { + qboolean waspassive = cl->dataislisten; if (!cl->auth) { QueueMessage (cl, "530 Not logged in.\r\n"); @@ -682,7 +1096,7 @@ iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) { int _true = true; int temp; - struct sockaddr_in adr; + struct sockaddr_qstorage adr; int adrlen = sizeof(adr); temp = accept(cl->datasock, (struct sockaddr *)&adr, &adrlen); closesocket(cl->datasock); @@ -702,43 +1116,15 @@ iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) QueueMessage (cl, "503 Bad sequence of commands.\r\n"); continue; } - msg = COM_ParseOut(msg, resource, sizeof(resource)); - if (!cl->auth & IWEBACC_READ) - { - QueueMessage (cl, "550 No read access.\r\n"); - continue; - } + FTP_ReadToAbsFilename(cl, msg, resource, sizeof(resource)); - if (!(*resource == '/')) - { - int plen = strlen(cl->path); - if (plen && cl->path[plen-1] != '/') - { - memmove(resource+plen+1, resource, strlen(resource)+1); - memcpy(resource, cl->path, plen); - resource[plen] = '/'; - } - else - { - memmove(resource+plen, resource, strlen(resource)+1); - memcpy(resource, cl->path, plen); - } - } - if (*resource == '/') - { - if (SV_AllowDownload(resource+1)) - cl->file = FS_OpenVFS(resource+1, "rb", FS_GAME); - else - cl->file = IWebGenerateFile(resource+1, NULL, 0); - } + IWebPrintf("%s: Download request for \"ftp://%s@%s/%s\"\n", cl->peername, cl->name, "", resource); + + if (FTP_AllowDownLoad(resource, cl)) + cl->file = FS_OpenVFS(resource, "rb", FS_GAME); else - { - if (SV_AllowDownload(resource)) - cl->file = FS_OpenVFS(resource, "rb", FS_GAME); - else - cl->file = IWebGenerateFile(resource, NULL, 0); - } + cl->file = IWebGenerateFile(resource, NULL, 0); if (!cl->file) { @@ -746,23 +1132,31 @@ iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) } else { //send data - QueueMessage (cl, "125 Opening BINARY mode data connection for file.\r\n"); + if (waspassive) + QueueMessage (cl, "150 Opening BINARY mode data connection for file.\r\n"); + else + QueueMessage (cl, "125 Opening BINARY mode data connection for file.\r\n"); cl->datadir = 1; + + if (cl->restartpos) + VFS_SEEK(cl->file, cl->restartpos); } + cl->restartpos = 0; } - else if (!stricmp(mode, "STOR")) + else if (!stricmp(mode, "STOR") || !stricmp(mode, "APPE")) { if (!cl->auth) { QueueMessage (cl, "530 Not logged in.\r\n"); continue; } - Q_strcpyline(mode, msg+1, sizeof(mode)); + FTP_ReadToAbsFilename(cl, msg, resource, sizeof(resource)); - if (!(cl->auth & IWEBACC_FULL) && (((cl->auth & IWEBACC_WRITE && !IWebAllowUpLoad(cl->path+1, cl->name)) || !(cl->auth & IWEBACC_WRITE)))) + if (!*resource || !FTP_AllowUpLoad(resource, cl)) { + IWebPrintf("%s: Denied upload request for \"ftp://%s@%s/%s\"\n", cl->peername, cl->name, "", resource); QueueMessage (cl, "550 Permission denied.\r\n"); } else @@ -771,7 +1165,7 @@ iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) { int _true = true; int temp; - struct sockaddr_in adr; + struct sockaddr_qstorage adr; int adrlen = sizeof(adr); temp = accept(cl->datasock, (struct sockaddr *)&adr, &adrlen); closesocket(cl->datasock); @@ -791,21 +1185,22 @@ iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) QueueMessage (cl, "502 Bad sequence of commands.\r\n"); continue; } -// msg = COM_ParseOut(msg, mode, sizeof(mode)); - if (*mode == '/') - sprintf(resource, "%s%s", cl->path, mode); + IWebPrintf("%s: Upload request for \"ftp://%s@%s/%s\"\n", cl->peername, cl->name, "", resource); + + if (cl->restartpos || !stricmp(mode, "APPE")) //write without truncating. + cl->file = FS_OpenVFS(resource, "w+b", FS_GAMEONLY); else - sprintf(resource, "%s%s", cl->path, mode); - - cl->file = FS_OpenVFS(resource, "rb", FS_GAMEONLY); - if (cl->file) { - VFS_CLOSE(cl->file); - QueueMessage (cl, "550 File already exists.\r\n"); - continue; + cl->file = FS_OpenVFS(resource, "rb", FS_GAMEONLY); + if (cl->file) + { + VFS_CLOSE(cl->file); + QueueMessage (cl, "550 File already exists.\r\n"); + continue; + } + cl->file = FS_OpenVFS(resource, "wb", FS_GAME); } - cl->file = FS_OpenVFS(resource, "wb", FS_GAME); if (!cl->file) { @@ -816,9 +1211,83 @@ iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) QueueMessage (cl, "125 Opening BINARY mode data connection for input.\r\n"); cl->datadir = 2; + + if (cl->restartpos) + VFS_SEEK(cl->file, cl->restartpos); + else if (!stricmp(mode, "APPE")) + VFS_SEEK(cl->file, VFS_GETLEN(cl->file)); } + cl->restartpos = 0; } } + else if (!stricmp(mode, "RNFR")) + { + FTP_ReadToAbsFilename(cl, msg, cl->renamefrom, sizeof(cl->renamefrom)); + + QueueMessage (cl, "350 Success.\r\n"); + } + else if (!stricmp(mode, "RNTO")) + { + FTP_ReadToAbsFilename(cl, msg, resource, sizeof(resource)); + + if (!cl->auth) + { + QueueMessage (cl, "530 Not logged in.\r\n"); + continue; + } + if (!*cl->renamefrom) + + + if (!FTP_AllowUpLoad(cl->renamefrom, cl) || !FTP_AllowUpLoad(resource, cl)) + { + QueueMessage (cl, "550 Access denied.\r\n"); + continue; + } + + IWebPrintf("%s: Rename request from \"ftp://%s@/%s\" to \"/%s\"\n", cl->peername, cl->name, cl->renamefrom, resource); + + if (FS_Rename(cl->renamefrom, resource, FS_GAMEONLY)) + QueueMessage (cl, "250 Success.\r\n"); + else + QueueMessage (cl, "550 Requested action not taken.\r\n"); + + FS_FlushFSHashRemoved(cl->renamefrom); + FS_FlushFSHashWritten(resource); + *cl->renamefrom = 0; + } + else if (!stricmp(mode, "DELE") || !stricmp(mode, "RMD")) + { + FTP_ReadToAbsFilename(cl, msg, resource, sizeof(resource)); + if (!cl->auth) + { + QueueMessage (cl, "530 Not logged in.\r\n"); + continue; + } + if (!*resource || !FTP_AllowUpLoad(resource, cl)) + { + IWebPrintf("%s: Denied delete request for \"ftp://%s@/%s\"\n", cl->peername, cl->name, resource); + QueueMessage (cl, "550 Access denied.\r\n"); + continue; + } + IWebPrintf("%s: Delete request for \"ftp://%s@/%s\"\n", cl->peername, cl->name, resource); + if (!stricmp(mode, "RMD")) + { + char path[MAX_OSPATH]; + if (FS_NativePath(resource, FS_GAMEONLY, path, sizeof(path)) && Sys_rmdir(path)) + QueueMessage (cl, "250 Success.\r\n"); + else + QueueMessage (cl, "550 Requested action not taken.\r\n"); + } + else + { + if (FS_Remove(resource, FS_GAMEONLY)) + QueueMessage (cl, "250 Success.\r\n"); + else + QueueMessage (cl, "550 Requested action not taken.\r\n"); + } + + FS_FlushFSHashRemoved(resource); + } else if (!stricmp(mode, "STRU")) { if (!cl->auth) @@ -827,10 +1296,12 @@ iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) continue; } msg = COM_ParseOut(msg, resource, sizeof(resource)); - if (!strcmp(resource, "F")) + if (!strcmp(resource, "F")) //file { QueueMessage (cl, "200 recordless structure selected.\r\n"); } +// else if (!strcmp(resource, "R")) //record +// else if (!strcmp(resource, "P")) //page else { QueueMessage (cl, "504 not implemented (it's a simple server).\r\n"); @@ -850,6 +1321,9 @@ iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl) QueueMessage (cl, "502 Command not implemented.\r\n"); } } + + if (cl->datadir && !cl->transferthread) + cl->transferthread = Sys_CreateThread("FTP RECV", FTP_TransferThread, cl, 0, 65536); return false; } @@ -889,6 +1363,14 @@ iwboolean FTP_ServerRun(iwboolean ftpserverwanted, int port) SOCKET clientsock; unsigned long _true = true; + if (!port) + port = 21; + if (ftpserverport != port) + { + ftpserverport = port; + ftpserverwanted = false; //forces it to restart if the port is changed. + } + if (!ftpserverinitied) { if (ftpserverwanted) @@ -899,6 +1381,8 @@ unsigned long _true = true; ftpserverfailed = true; IWebPrintf("Unable to establish listening FTP socket\n"); } + else + IWebPrintf("FTP server is running\n"); ftpserverinitied = true; } return false; @@ -961,7 +1445,7 @@ unsigned long _true = true; } - Con_Printf ("NET_GetPacket: %s\n", strerror(e)); + IWebPrintf ("NET_GetPacket: %s\n", strerror(e)); return false; } @@ -978,16 +1462,23 @@ unsigned long _true = true; closesocket(clientsock); //try to forget this ever happend return true; } - { - char resource[256]; - FTP_SVRemoteSocketToString(clientsock, resource, sizeof(resource)); - IWebPrintf("FTP connect from %s\n", resource); - } + NET_SockadrToString(cl->peername, sizeof(cl->peername), &from); + IWebPrintf("%s: New FTP connection\n", cl->peername); + //RFC1700 + if (((struct sockaddr *)&from)->sa_family == AF_INET) + cl->controlaf = 1; + else if (((struct sockaddr *)&from)->sa_family == AF_INET6) + cl->controlaf = 2; + else if (((struct sockaddr *)&from)->sa_family == AF_IPX) + cl->controlaf = 11; + else + cl->controlaf = 0; + cl->controlsock = clientsock; cl->datasock = INVALID_SOCKET; cl->next = FTPclient; cl->blocking = false; - strcpy(cl->path, "/"); + strcpy(cl->path, ""); QueueMessage(cl, "220-" FULLENGINENAME " FTP Server.\r\n220 Welcomes all new users.\r\n"); diff --git a/engine/http/httpclient.c b/engine/http/httpclient.c index fd2679de..6e03f4f0 100644 --- a/engine/http/httpclient.c +++ b/engine/http/httpclient.c @@ -20,15 +20,18 @@ static struct dl_download *activedownloads; #include #endif +vfsfile_t *FSWEB_OpenTempHandle(int f); + static void DL_Cancel(struct dl_download *dl) { //FIXME: clear out the callbacks somehow dl->ctx = NULL; } -static void DL_OnLoad(void *c, void *data, int datasize) +static void DL_OnLoad(void *c, int buf) { //also fires from 404s. struct dl_download *dl = c; + vfsfile_t *tempfile = FSWEB_OpenTempHandle(buf); //make sure the file is 'open'. if (!dl->file) @@ -40,20 +43,38 @@ static void DL_OnLoad(void *c, void *data, int datasize) } else { - //emscripten does not close the file. plus we seem to end up with infinite loops. - dl->file = FS_OpenTemp(); + dl->file = tempfile; + tempfile = NULL; } } if (dl->file) { - VFS_WRITE(dl->file, data, datasize); - VFS_SEEK(dl->file, 0); dl->status = DL_FINISHED; + if (tempfile) + { + qofs_t datasize = VFS_GETLEN(tempfile); + char *data = malloc(datasize); //grab a temp buffer so we can do the entire file at once... + if (!data) + dl->status = DL_FAILED; + else + { + VFS_READ(tempfile, data, datasize); + + VFS_WRITE(dl->file, data, datasize); + if (dl->file->seekstyle < SS_PIPE) + VFS_SEEK(dl->file, 0); + + free(data); + } + } } else dl->status = DL_FAILED; + if (tempfile) + VFS_CLOSE(tempfile); + dl->replycode = 200; #if !MYJS dl->completed += datasize; @@ -1504,7 +1525,7 @@ void DL_Close(struct dl_download *dl) if (dl->threadctx) Sys_WaitOnThread(dl->threadctx); #endif - if (dl->file && dl->file->Seek) + if (dl->file && dl->file->seekstyle < SS_PIPE) VFS_SEEK(dl->file, 0); if (dl->notifycomplete) dl->notifycomplete(dl); @@ -1706,17 +1727,27 @@ typedef struct char *data; int maxlen; - int writepos; - int readpos; + unsigned int writepos; + unsigned int readpos; void *mutex; + int refs; + qboolean terminate; //one end has closed, make the other report failures now that its no longer needed. } vfspipe_t; static qboolean QDECL VFSPIPE_Close(vfsfile_t *f) { + int r; vfspipe_t *p = (vfspipe_t*)f; - free(p->data); - Sys_DestroyMutex(p->mutex); - free(p); + Sys_LockMutex(p->mutex); + r = --p->refs; + p->terminate = true; + Sys_UnlockMutex(p->mutex); + if (!r) + { + free(p->data); + Sys_DestroyMutex(p->mutex); + free(p); + } return true; } static qofs_t QDECL VFSPIPE_GetLen(vfsfile_t *f) @@ -1724,21 +1755,33 @@ static qofs_t QDECL VFSPIPE_GetLen(vfsfile_t *f) vfspipe_t *p = (vfspipe_t*)f; return p->writepos - p->readpos; } -//static unsigned long QDECL VFSPIPE_Tell(vfsfile_t *f) -//{ -// return 0; -//} -//static qboolean QDECL VFSPIPE_Seek(vfsfile_t *f, unsigned long offset) -//{ -// Con_Printf("Seeking is a bad plan, mmkay?\n"); -// return false; -//} +static qofs_t QDECL VFSPIPE_Tell(vfsfile_t *f) +{ + vfspipe_t *p = (vfspipe_t*)f; + return p->readpos; +} +static qboolean QDECL VFSPIPE_Seek(vfsfile_t *f, qofs_t offset) +{ + vfspipe_t *p = (vfspipe_t*)f; + p->readpos = offset; + return true; +} static int QDECL VFSPIPE_ReadBytes(vfsfile_t *f, void *buffer, int len) { vfspipe_t *p = (vfspipe_t*)f; Sys_LockMutex(p->mutex); + if (p->readpos > p->writepos) + len = 0; if (len > p->writepos - p->readpos) + { len = p->writepos - p->readpos; + if (!len && p->terminate) + { //if we reached eof, we started with two refs and are down to 1 and we're reading, then its the writer side that disconnected. + //eof is now fatal rather than 'try again later'. + Sys_UnlockMutex(p->mutex); + return -1; + } + } memcpy(buffer, p->data+p->readpos, len); p->readpos += len; Sys_UnlockMutex(p->mutex); @@ -1748,8 +1791,14 @@ static int QDECL VFSPIPE_WriteBytes(vfsfile_t *f, const void *buffer, int len) { vfspipe_t *p = (vfspipe_t*)f; Sys_LockMutex(p->mutex); - if (p->readpos > 8192) + if (p->terminate) + { //if we started with 2 refs, and we're down to one, and we're writing, then its the reader that closed. that means writing is redundant and we should signal an error. + Sys_UnlockMutex(p->mutex); + return -1; + } + if (p->readpos > 8192 && !p->funcs.Seek) { //don't grow infinitely if we're reading+writing at the same time + //if we're seekable, then we have to buffer the ENTIRE file. memmove(p->data, p->data+p->readpos, p->writepos-p->readpos); p->writepos -= p->readpos; p->readpos = 0; @@ -1759,6 +1808,16 @@ static int QDECL VFSPIPE_WriteBytes(vfsfile_t *f, const void *buffer, int len) p->maxlen = p->writepos + len; if (p->maxlen < (p->writepos-p->readpos)*2) //over-allocate a little p->maxlen = (p->writepos-p->readpos)*2; + if (p->maxlen > 0x8000000) + { + p->maxlen = 0x8000000; + len = p->maxlen - p->writepos; + if (!len) + { + Sys_UnlockMutex(p->mutex); + return -1; //try and get the caller to stop + } + } p->data = realloc(p->data, p->maxlen); } memcpy(p->data+p->writepos, buffer, len); @@ -1767,10 +1826,12 @@ static int QDECL VFSPIPE_WriteBytes(vfsfile_t *f, const void *buffer, int len) return len; } -vfsfile_t *VFSPIPE_Open(void) +vfsfile_t *VFSPIPE_Open(int refs, qboolean seekable) { vfspipe_t *newf; newf = malloc(sizeof(*newf)); + newf->refs = refs; + newf->terminate = false; newf->mutex = Sys_CreateMutex(); newf->data = NULL; newf->maxlen = 0; @@ -1780,10 +1841,20 @@ vfsfile_t *VFSPIPE_Open(void) newf->funcs.Flush = NULL; newf->funcs.GetLen = VFSPIPE_GetLen; newf->funcs.ReadBytes = VFSPIPE_ReadBytes; - newf->funcs.Seek = NULL;//VFSPIPE_Seek; - newf->funcs.Tell = NULL;//VFSPIPE_Tell; newf->funcs.WriteBytes = VFSPIPE_WriteBytes; - newf->funcs.seekingisabadplan = true; + + if (seekable) + { //if this is set, then we allow changing the readpos at the expense of buffering the ENTIRE file. no more fifo. + newf->funcs.Seek = VFSPIPE_Seek; + newf->funcs.Tell = VFSPIPE_Tell; + newf->funcs.seekstyle = SS_PIPE; + } + else + { //periodically reclaim read data to avoid memory wastage. this means that seeking can't work. + newf->funcs.Seek = NULL; + newf->funcs.Tell = NULL; + newf->funcs.seekstyle = SS_UNSEEKABLE; + } return &newf->funcs; } diff --git a/engine/http/httpserver.c b/engine/http/httpserver.c index 1d94eabc..1e583d9d 100644 --- a/engine/http/httpserver.c +++ b/engine/http/httpserver.c @@ -129,17 +129,43 @@ typedef enum {HTTP_WAITINGFORREQUEST,HTTP_SENDING} http_mode_t; qboolean HTTP_ServerInit(int port) { - struct sockaddr_in address; + struct sockaddr_qstorage address; unsigned long _true = true; int i; - if ((httpserversocket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) + memset(&address, 0, sizeof(address)); + //check for interface binding option. this also forces ipv4, oh well. + if ((i = COM_CheckParm("-ip")) != 0 && i < com_argc) + { + ((struct sockaddr_in*)&address)->sin_addr.s_addr = inet_addr(com_argv[i+1]); + Con_TPrintf("Binding to IP Interface Address of %s\n", + inet_ntoa(((struct sockaddr_in*)&address)->sin_addr)); + + + ((struct sockaddr_in*)&address)->sin_family = AF_INET; + if (port != PORT_ANY) + ((struct sockaddr_in*)&address)->sin_port = htons((short)port); + } + else + { //otherwise just use ipv6 + ((struct sockaddr_in6*)&address)->sin6_family = AF_INET6; + if (port != PORT_ANY) + ((struct sockaddr_in6*)&address)->sin6_port = htons((short)port); + } + + if ((httpserversocket = socket (((struct sockaddr*)&address)->sa_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { IWebPrintf ("HTTP_ServerInit: socket: %s\n", strerror(neterrno())); httpserverfailed = true; return false; } + if (((struct sockaddr_in6*)&address)->sin6_family == AF_INET6 && !memcmp(((struct sockaddr_in6*)&address)->sin6_addr.s6_addr, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16)) + { //in6addr_any, allow ipv4 too, if we can do hybrid sockets. + unsigned long v6only = false; + setsockopt(httpserversocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&v6only, sizeof(v6only)); + } + if (ioctlsocket (httpserversocket, FIONBIO, &_true) == -1) { IWebPrintf ("HTTP_ServerInit: ioctl FIONBIO: %s\n", strerror(neterrno())); @@ -147,22 +173,6 @@ qboolean HTTP_ServerInit(int port) return false; } - address.sin_family = AF_INET; -//check for interface binding option - if ((i = COM_CheckParm("-ip")) != 0 && i < com_argc) - { - address.sin_addr.s_addr = inet_addr(com_argv[i+1]); - Con_TPrintf("Binding to IP Interface Address of %s\n", - inet_ntoa(address.sin_addr)); - } - else - address.sin_addr.s_addr = INADDR_ANY; - - if (port == PORT_ANY) - address.sin_port = 0; - else - address.sin_port = htons((short)port); - if( bind (httpserversocket, (void *)&address, sizeof(address)) == -1) { closesocket(httpserversocket); @@ -697,23 +707,6 @@ notimplemented: } } -#ifdef WEBSVONLY -void VARGS Q_snprintfz (char *dest, size_t size, const char *fmt, ...) -{ - va_list args; - va_start (args, fmt); -#ifdef _WIN32 -#undef _vsnprintf - _vsnprintf (dest, size-1, fmt, args); -#else - vsnprintf (dest, size-1, fmt, args); -#endif - va_end (args); - //make sure its terminated. - dest[size-1] = 0; -} -#endif - qboolean HTTP_ServerPoll(qboolean httpserverwanted, int portnum) //loop while true { struct sockaddr_qstorage from; @@ -773,16 +766,7 @@ qboolean HTTP_ServerPoll(qboolean httpserverwanted, int portnum) //loop while tr } cl = IWebMalloc(sizeof(HTTP_active_connections_t)); - -#ifndef WEBSVONLY - { - netadr_t na; - SockadrToNetadr(&from, &na); - NET_AdrToString(cl->peername, sizeof(cl->peername), &na); - } -#else - Q_snprintfz(cl->peername, sizeof(cl->peername), "%s:%i", inet_ntoa(((struct sockaddr_in*)&from)->sin_addr), ntohs(((struct sockaddr_in*)&from)->sin_port)); -#endif + NET_SockadrToString(cl->peername, sizeof(cl->peername), &from); IWebPrintf("%s: New http connection\n", cl->peername); cl->datasock = clientsock; diff --git a/engine/http/iweb.h b/engine/http/iweb.h index d32c4c7e..2176d342 100644 --- a/engine/http/iweb.h +++ b/engine/http/iweb.h @@ -4,10 +4,9 @@ #ifdef WEBSERVER #ifdef WEBSVONLY - -#include "quakedef.h" - +//When running standalone #define Con_TPrintf IWebPrintf +void VARGS IWebDPrintf(char *fmt, ...) LIKEPRINTF(1); #define IWebPrintf printf #define com_gamedir "." //current dir. @@ -15,6 +14,16 @@ #define IWebMalloc(x) calloc(x, 1) #define IWebRealloc(x, y) realloc(x, y) #define IWebFree free +#else +//Inside FTE +#define IWebDPrintf Con_DPrintf +#define IWebPrintf Con_Printf + +#define IWebMalloc Z_Malloc +#define IWebRealloc BZF_Realloc +#define IWebFree Z_Free + +void VARGS IWebWarnPrintf(char *fmt, ...) LIKEPRINTF(1); #endif #define IWEBACC_READ 1 @@ -30,13 +39,6 @@ qboolean SV_AllowDownload (const char *name); typedef qboolean iwboolean; -//it's not allowed to error. -#ifndef WEBSVONLY -void VARGS IWebDPrintf(char *fmt, ...) LIKEPRINTF(1); -void VARGS IWebPrintf(char *fmt, ...) LIKEPRINTF(1); -void VARGS IWebWarnPrintf(char *fmt, ...) LIKEPRINTF(1); -#endif - typedef struct { float gentime; //useful for generating a new file (if too old, removes reference) int references; //freed if 0 @@ -44,18 +46,11 @@ typedef struct { int len; } IWeb_FileGen_t; -#ifndef WEBSVONLY -void *IWebMalloc(int size); -void *IWebRealloc(void *old, int size); -void IWebFree(void *mem); -#define IWebFree Z_Free -#endif - -int IWebAuthorize(char *name, char *password); -iwboolean IWebAllowUpLoad(char *fname, char *uname); - -vfsfile_t *IWebGenerateFile(char *name, char *content, int contentlength); +int IWebAuthorize(const char *name, const char *password); +iwboolean IWebAllowUpLoad(const char *fname, const char *uname); +vfsfile_t *IWebGenerateFile(const char *name, const char *content, int contentlength); +int IWebGetSafeListeningPort(void); //char *COM_ParseOut (const char *data, char *out, int outlen); //struct searchpath_s; @@ -65,10 +60,6 @@ vfsfile_t *IWebGenerateFile(char *name, char *content, int contentlength); char *Q_strcpyline(char *out, const char *in, int maxlen); - - -iwboolean FTP_StringToAdr (const char *s, qbyte ip[4], qbyte port[2]); - //server tick/control functions iwboolean FTP_ServerRun(iwboolean ftpserverwanted, int port); qboolean HTTP_ServerPoll(qboolean httpserverwanted, int port); @@ -145,7 +136,7 @@ struct dl_download void (*notifycomplete) (struct dl_download *dl); }; -vfsfile_t *VFSPIPE_Open(void); +vfsfile_t *VFSPIPE_Open(int refs, qboolean seekable); //refs should be 1 or 2, to say how many times it must be closed before its actually closed, so both ends can close separately void HTTP_CL_Think(void); void HTTP_CL_Terminate(void); //kills all active downloads unsigned int HTTP_CL_GetActiveDownloads(void); diff --git a/engine/http/iwebiface.c b/engine/http/iwebiface.c index 73f7e2d0..cf602fa6 100644 --- a/engine/http/iwebiface.c +++ b/engine/http/iwebiface.c @@ -7,6 +7,47 @@ #ifdef WEBSVONLY //we need some functions from quake +char *NET_SockadrToString(char *s, int slen, struct sockaddr_qstorage *addr) +{ + switch(((struct sockaddr*)addr)->sa_family) + { + case AF_INET: + Q_snprintfz(s, slen, "%s:%u", inet_ntoa(((struct sockaddr_in*)addr)->sin_addr), ntohs(((struct sockaddr_in*)addr)->sin_port)); + break; + case AF_INET6: + if (!memcmp(((struct sockaddr_in6*)addr)->sin6_addr.s6_bytes, "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12)) + { //ipv4-mapped + Q_snprintfz(s, slen, "[::ffff:%u.%u.%u.%u]:%u", + ((struct sockaddr_in6*)addr)->sin6_addr.s6_bytes[12], + ((struct sockaddr_in6*)addr)->sin6_addr.s6_bytes[13], + ((struct sockaddr_in6*)addr)->sin6_addr.s6_bytes[14], + ((struct sockaddr_in6*)addr)->sin6_addr.s6_bytes[15], + + ntohs(((struct sockaddr_in6*)addr)->sin6_port)); + } + else + { + Q_snprintfz(s, slen, "[%x:%x:%x:%x:%x:%x:%x:%x]:%u", + ntohs(((struct sockaddr_in6*)addr)->sin6_addr.s6_words[0]), + ntohs(((struct sockaddr_in6*)addr)->sin6_addr.s6_words[1]), + ntohs(((struct sockaddr_in6*)addr)->sin6_addr.s6_words[2]), + ntohs(((struct sockaddr_in6*)addr)->sin6_addr.s6_words[3]), + + ntohs(((struct sockaddr_in6*)addr)->sin6_addr.s6_words[4]), + ntohs(((struct sockaddr_in6*)addr)->sin6_addr.s6_words[5]), + ntohs(((struct sockaddr_in6*)addr)->sin6_addr.s6_words[6]), + ntohs(((struct sockaddr_in6*)addr)->sin6_addr.s6_words[7]), + + ntohs(((struct sockaddr_in6*)addr)->sin6_port)); + } + break; + default: + *s = 0; + break; + } + return s; +} + qboolean SV_AllowDownload (const char *name) { if (strstr(name, "..")) @@ -22,7 +63,7 @@ com_tokentype_t com_tokentype; int com_argc; const char **com_argv; -vfsfile_t *IWebGenerateFile(char *name, char *content, int contentlength) +vfsfile_t *IWebGenerateFile(const char *name, const char *content, int contentlength) { return NULL; } @@ -47,6 +88,21 @@ void Q_strncpyz(char *d, const char *s, int n) *d='\0'; } +void VARGS Q_snprintfz (char *dest, size_t size, const char *fmt, ...) +{ + va_list args; + va_start (args, fmt); +#ifdef _WIN32 +#undef _vsnprintf + _vsnprintf (dest, size-1, fmt, args); +#else + vsnprintf (dest, size-1, fmt, args); +#endif + va_end (args); + //make sure its terminated. + dest[size-1] = 0; +} + /*char *va(char *format, ...) { #define VA_BUFFERS 2 //power of two @@ -91,23 +147,82 @@ int COM_CheckParm(const char *parm) char *authedusername; char *autheduserpassword; +int lport_min, lport_max; +int anonaccess = IWEBACC_READ; +iwboolean verbose; int main(int argc, char **argv) { int httpport = 80; + int ftpport = 21; int arg = 1; #ifdef _WIN32 WSADATA pointlesscrap; WSAStartup(2, &pointlesscrap); #endif + while (arg < argc) + { + char *a = argv[arg]; + if (!a) + continue; + if (*a != '-') + break; //other stuff + while (*a == '-') + a++; + arg++; + + if (!strcmp(a, "help")) + { + printf("%s -http 80 -ftp 21 -user steve -pass swordfish -ports 5000 6000\n", argv[0]); + printf("runs a simple http server\n"); + printf(" -http specifies the port to listen on for http\n"); + printf(" -ftp specifies the port to listen on for ftp\n"); + printf(" -ports specifies a port range for incoming ftp connections, to work around firewall rules\n"); + printf(" -user specifies the username that has full access. if not supplied noone can write.\n"); + printf(" -pass specifies the password to go with that username\n"); + printf(" -noanon will refuse to serve files to anyone but the authed user\n"); + return; + } + else if (!strcmp(a, "port") || !strcmp(a, "p")) + { + httpport = atoi(argv[arg++]); + ftpport = 0; + } + else if (!strcmp(a, "http") || !strcmp(a, "h")) + httpport = atoi(argv[arg++]); + else if (!strcmp(a, "ftp") || !strcmp(a, "f")) + ftpport = atoi(argv[arg++]); + else if (!strcmp(a, "noanon")) + anonaccess = 0; + else if (!strcmp(a, "ports")) + { + lport_min = atoi(argv[arg++]); + lport_max = atoi(argv[arg++]); + if (lport_max < lport_min) + lport_max = lport_min; + } + else if (!strcmp(a, "verbose") || !strcmp(a, "v")) + verbose = true; + else if (!strcmp(a, "user")) + authedusername = argv[arg++]; + else if (!strcmp(a, "pass")) + autheduserpassword = argv[arg++]; + else + printf("Unknown argument: %s\n", a); + } + if (arg < argc && atoi(argv[arg])) + { httpport = atoi(argv[arg++]); + ftpport = 0; + } if (arg < argc) authedusername = argv[arg++]; if (arg < argc) autheduserpassword = argv[arg++]; printf("http port %i\n", httpport); + printf("ftp port %i\n", ftpport); if (authedusername || autheduserpassword) printf("Username = \"%s\"\nPassword = \"%s\"\n", authedusername, autheduserpassword); else @@ -115,7 +230,8 @@ int main(int argc, char **argv) while(1) { -// FTP_ServerRun(1, 21); + if (ftpport) + FTP_ServerRun(1, ftpport); if (httpport) HTTP_ServerPoll(1, httpport); #ifdef _WIN32 @@ -126,6 +242,22 @@ int main(int argc, char **argv) } } +int IWebGetSafeListeningPort(void) +{ + static int sequence; + return lport_min + (sequence++ % (lport_max+1-lport_min)); +} +void VARGS IWebDPrintf(char *fmt, ...) +{ + va_list args; + if (!verbose) + return; + va_start (args, fmt); + vprintf (fmt, args); + va_end (args); +} + + #ifdef _WIN32 #ifdef _MSC_VER #define ULL(x) x##ui64 @@ -253,7 +385,7 @@ skipwhite: return (char*)data; } -#undef COM_ParseToken +/*#undef COM_ParseToken char *COM_ParseToken (const char *data, const char *punctuation) { int c; @@ -335,7 +467,7 @@ skipwhite: com_token[len] = 0; return (char*)data; -} +}*/ /* IWEBFILE *IWebFOpenRead(char *name) //fread(name, "rb"); @@ -371,7 +503,7 @@ IWEBFILE *IWebFOpenRead(char *name) //fread(name, "rb"); #else -#ifndef CLIENTONLY +#ifdef WEBSERVER cvar_t ftpserver = CVAR("sv_ftp", "0"); cvar_t ftpserver_port = CVAR("sv_ftp_port", "21"); cvar_t httpserver = CVAR("sv_http", "0"); @@ -379,35 +511,31 @@ cvar_t httpserver_port = CVAR("sv_http_port", "80"); cvar_t sv_readlevel = CVAR("sv_readlevel", "0"); //default to allow anyone cvar_t sv_writelevel = CVAR("sv_writelevel", "35"); //allowed to write to uploads/uname cvar_t sv_fulllevel = CVAR("sv_fulllevel", "51"); //allowed to write anywhere, replace any file... +cvar_t sv_ftp_port_range = CVARD("sv_ftp_port_range", "0", "Specifies the port range for the server to create listening sockets for 'active' ftp connections, to work around NAT/firewall issues.\nMost FTP clients should use passive connections, but there's still some holdouts like windows."); + +int IWebGetSafeListeningPort(void) +{ + char *e; + int base, range; + static int sequence; + if (!*sv_ftp_port_range.string) + return 0; //lets the OS pick. + base = strtol(sv_ftp_port_range.string, &e, 0); + while(*e == ' ') + e++; + if (*e == '-') + e++; + while(*e == ' ') + e++; + range = strtol(e, NULL, 0); + if (range < base) + range = base; + return base + (sequence++ % (range+1-base)); +} #endif //this file contains functions called from each side. -void VARGS IWebDPrintf(char *fmt, ...) -{ - va_list argptr; - char msg[4096]; - - if (!developer.value) - return; - - va_start (argptr,fmt); - vsnprintf (msg,sizeof(msg)-10, fmt,argptr); //catch any nasty bugs... (this is hopefully impossible) - va_end (argptr); - - Con_Printf("%s", msg); -} -void VARGS IWebPrintf(char *fmt, ...) -{ - va_list argptr; - char msg[4096]; - - va_start (argptr,fmt); - vsnprintf (msg,sizeof(msg)-10, fmt,argptr); //catch any nasty bugs... (this is hopefully impossible) - va_end (argptr); - - Con_Printf("%s", msg); -} void VARGS IWebWarnPrintf(char *fmt, ...) { va_list argptr; @@ -429,6 +557,7 @@ void IWebInit(void) Cvar_Register(&ftpserver, "Internet Server Access"); Cvar_Register(&ftpserver_port, "Internet Server Access"); + Cvar_Register(&sv_ftp_port_range, "Internet Server Access"); Cvar_Register(&httpserver, "Internet Server Access"); Cvar_Register(&httpserver_port, "Internet Server Access"); @@ -467,39 +596,81 @@ void IWebShutdown(void) } #endif -#ifndef WEBSVONLY -//replacement for Z_Malloc. It simply allocates up to a reserve ammount. -void *IWebMalloc(int size) +#ifdef WEBSVONLY +void *Sys_CreateThread(char *name, int (*func)(void *), void *args, int priority, int stacksize) { - void *mem = BZF_Malloc(size); - memset(mem, 0, size); - return mem; + return NULL; } +qboolean FS_Remove(const char *fname, enum fs_relative relativeto) +{ + return false; +} +qboolean FS_NativePath(const char *fname, enum fs_relative relativeto, char *out, int outlen) +{ + Q_strncpyz(out, fname, outlen); + if (*out == '/' || strstr(out, "..")) + { + *out = 0; + return false; + } + return strlen(fname) == strlen(out); +} +void FS_FlushFSHashWritten(const char *fname) {} +void FS_FlushFSHashRemoved(const char *fname) {} +qboolean FS_Rename(const char *oldf, const char *newf, enum fs_relative relativeto) +{ + return rename(oldf, newf) != -1; +} +#ifdef _WIN32 +#include +void FS_CreatePath(const char *pname, enum fs_relative relativeto) +{ + _mkdir(pname); +} +qboolean Sys_rmdir (const char *path) +{ + return _rmdir(path) != -1; +} +#else +#include +void FS_CreatePath(const char *pname, enum fs_relative relativeto) +{ + mkdir(pname); +} +qboolean Sys_rmdir (const char *path) +{ + return rmdir(path) != -1; +} +#endif -void *IWebRealloc(void *old, int size) -{ - return BZF_Realloc(old, size); -} #endif -int IWebAuthorize(char *name, char *password) +int IWebAuthorize(const char *name, const char *password) { #ifdef WEBSVONLY if (authedusername) + { if (!strcmp(name, authedusername)) + { if (!strcmp(password, autheduserpassword)) return IWEBACC_FULL; - return IWEBACC_READ; + } + } + + //if they tried giving some other username, don't give them any access (prevents them from reading actual user files). + if (*name && stricmp(name, "anonymous")) + return 0; + return anonaccess; #else #ifndef CLIENTONLY int id = Rank_GetPlayerID(NULL, name, atoi(password), false, true); rankinfo_t info; if (!id) { - if (!sv_readlevel.value) + if (!sv_readlevel.value && !*name || !stricmp(name, "anonymous")) return IWEBACC_READ; //read only anywhere return 0; } @@ -517,7 +688,7 @@ int IWebAuthorize(char *name, char *password) #endif } -iwboolean IWebAllowUpLoad(char *fname, char *uname) //called for partial write access +iwboolean IWebAllowUpLoad(const char *fname, const char *uname) //called for partial write access { if (strstr(fname, "..")) return false; @@ -530,50 +701,6 @@ iwboolean IWebAllowUpLoad(char *fname, char *uname) //called for partial write a return false; } -iwboolean FTP_StringToAdr (const char *s, qbyte ip[4], qbyte port[2]) -{ - s = COM_ParseToken(s, NULL); - ip[0] = atoi(com_token); - - s = COM_ParseToken(s, NULL); - if (*com_token != ',') - return false; - - s = COM_ParseToken(s, NULL); - ip[1] = atoi(com_token); - - s = COM_ParseToken(s, NULL); - if (*com_token != ',') - return false; - - s = COM_ParseToken(s, NULL); - ip[2] = atoi(com_token); - - s = COM_ParseToken(s, NULL); - if (*com_token != ',') - return false; - - s = COM_ParseToken(s, NULL); - ip[3] = atoi(com_token); - - s = COM_ParseToken(s, NULL); - if (*com_token != ',') - return false; - - s = COM_ParseToken(s, NULL); - port[0] = atoi(com_token); - - s = COM_ParseToken(s, NULL); - if (*com_token != ',') - return false; - - s = COM_ParseToken(s, NULL); - port[1] = atoi(com_token); - - - return true; -} - char *Q_strcpyline(char *out, const char *in, int maxlen) { char *w = out; diff --git a/engine/http/webgen.c b/engine/http/webgen.c index 542f9b8c..ef5257cc 100644 --- a/engine/http/webgen.c +++ b/engine/http/webgen.c @@ -212,7 +212,7 @@ static void IWeb_GenerateRankingsFileCallback(const rankinfo_t *ri) IWeb_Generate(""); } -static void IWeb_GenerateRankingsFile (char *parms, char *content, int contentlength) +static void IWeb_GenerateRankingsFile (const char *parms, const char *content, int contentlength) { IWeb_Generate(""); @@ -243,7 +243,7 @@ static void IWeb_GenerateRankingsFile (char *parms, char *content, int contentle IWeb_Generate(""); } -static void IWeb_GenerateIndexFile (char *parms, char *content, int contentlength) +static void IWeb_GenerateIndexFile (const char *parms, const char *content, int contentlength) { extern cvar_t rcon_password; char *s, *o; @@ -338,7 +338,7 @@ static void IWeb_GenerateIndexFile (char *parms, char *content, int contentlengt typedef struct { char *name; - void (*GenerationFunction) (char *parms, char *content, int contentlength); + void (*GenerationFunction) (const char *parms, const char *content, int contentlength); float lastgenerationtime; float oldbysecs; IWeb_FileGen_t *buffer; @@ -440,10 +440,10 @@ static vfsfile_t *VFSGen_Create(IWeb_FileGen_t *gen) return (vfsfile_t*)ret; } -vfsfile_t *IWebGenerateFile(char *name, char *content, int contentlength) +vfsfile_t *IWebGenerateFile(const char *name, const char *content, int contentlength) { int fnum; - char *parms; + const char *parms; int len; if (!sv.state) diff --git a/engine/nacl/fs_ppapi.c b/engine/nacl/fs_ppapi.c index ea260518..eb61fc80 100644 --- a/engine/nacl/fs_ppapi.c +++ b/engine/nacl/fs_ppapi.c @@ -292,7 +292,7 @@ vfsfile_t *FSPPAPI_OpenTemp(void) return &r->funcs; } -qboolean Sys_remove (char *path) +qboolean Sys_remove (const char *path) { mfile_t *f; for (f = mfiles; f; f = f->next) @@ -308,7 +308,7 @@ qboolean Sys_remove (char *path) } return false; } -qboolean Sys_Rename (char *oldfname, char *newfname) +qboolean Sys_Rename (const char *oldfname, const char *newfname) { mfile_t *f; for (f = mfiles; f; f = f->next) @@ -322,9 +322,13 @@ qboolean Sys_Rename (char *oldfname, char *newfname) return false; } //no concept of directories. -void Sys_mkdir (char *path) +void Sys_mkdir (const char *path) { } +qboolean Sys_rmdir (const char *path) +{ + return false; +} vfsfile_t *VFSPPAPI_Open(const char *osname, const char *mode) { diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index efa52ebc..40859e0c 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -9305,6 +9305,7 @@ static void QCBUILTIN PF_runclientphys(pubprogfuncs_t *prinst, struct globalvars extern cvar_t sv_gravity; edict_t *ent = G_EDICT(prinst, OFS_PARM0); edict_t *touched; + client_t *client; if (!ent || ent->readonly) { @@ -9317,7 +9318,11 @@ static void QCBUILTIN PF_runclientphys(pubprogfuncs_t *prinst, struct globalvars else pmove.sequence = 0; - pmove.pm_type = SV_PMTypeForClient((host_client && host_client->edict == ent)?host_client:NULL, ent); + if (ent->entnum >= 1 && ent->entnum <= sv.allocated_client_slots) + client = &svs.clients[ent->entnum-1]; + else + client = NULL; + pmove.pm_type = SV_PMTypeForClient(client, ent); pmove.jump_msec = 0; diff --git a/engine/server/pr_q1qvm.c b/engine/server/pr_q1qvm.c index d3551bac..8ae35594 100755 --- a/engine/server/pr_q1qvm.c +++ b/engine/server/pr_q1qvm.c @@ -1508,11 +1508,11 @@ static void QVM_uri_query_callback(struct dl_download *dl) char *buffer = malloc(len+1); buffer[len] = 0; VFS_READ(dl->file, buffer, len); - Cmd_Args_Set(buffer); + Cmd_Args_Set(buffer, strlen(buffer)); free(buffer); } else - Cmd_Args_Set(NULL); + Cmd_Args_Set(NULL, 0); VM_Call(q1qvm, cb_entry, cb_context, dl->replycode, 0, 0, 0); } diff --git a/engine/server/savegame.c b/engine/server/savegame.c index 2a0c8624..45999126 100644 --- a/engine/server/savegame.c +++ b/engine/server/savegame.c @@ -1226,6 +1226,7 @@ void SV_Savegame (const char *savename, qboolean mapchange) FS_Remove(savefilename, FS_GAMEONLY); if (cls.state == ca_active && qrenderer > QR_NONE && qrenderer != QR_VULKAN/*FIXME*/) { + int stride; int width; int height; void *rgbbuffer; @@ -1252,11 +1253,11 @@ void SV_Savegame (const char *savename, qboolean mapchange) if (okay) { enum uploadfmt fmt; - rgbbuffer = VID_GetRGBInfo(&width, &height, &fmt); + rgbbuffer = VID_GetRGBInfo(&stride, &width, &height, &fmt); if (rgbbuffer) { // extern cvar_t scr_sshot_type; - SCR_ScreenShot(savefilename, FS_GAMEONLY, &rgbbuffer, 1, width, height, fmt); + SCR_ScreenShot(savefilename, FS_GAMEONLY, &rgbbuffer, 1, stride, width, height, fmt); BZ_Free(rgbbuffer); diff --git a/engine/server/server.h b/engine/server/server.h index 34a85383..606165ff 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -319,8 +319,6 @@ typedef struct float move_msecs; // int packetsizein; //amount of data received for this frame int packetsizeout; //amount of data that was sent in the frame - vec3_t playerpositions[MAX_CLIENTS]; //where each player was in this frame, for antilag - qboolean playerpresent[MAX_CLIENTS]; //whether the player was actually present packet_entities_t entities; //package containing entity states that were sent in this frame, for deltaing struct resendinfo_s { @@ -330,6 +328,19 @@ typedef struct } *resend; unsigned short resendstats[32];//the number of each entity that was sent in this frame unsigned int numresendstats; //the bits of each entity that were sent in this frame + + //antilag + //these are to recalculate the player's origin without old knockbacks nor teleporters, to give more accurate weapon start positions (post-command). + vec3_t pmorigin; + vec3_t pmvelocity; + int pmtype; + unsigned int pmjumpheld:1; + unsigned int pmonladder:1; + float pmwaterjumptime; + usercmd_t cmd; + //these are old positions of players, to give more accurate victim positions + vec3_t playerpositions[MAX_CLIENTS]; //where each player was in this frame, for antilag + qboolean playerpresent[MAX_CLIENTS]; //whether the player was actually present } client_frame_t; #ifdef Q2SERVER @@ -779,7 +790,6 @@ typedef struct int forceFrame; struct mvddest_s *dest; - struct mvdpendingdest_s *pendingdest; } demo_t; @@ -1333,6 +1343,11 @@ typedef struct { //stats info qbyte trustlevel; char pad2; char pad3; + +#if NUM_RANK_SPAWN_PARMS>32 + quint64_t created; + quint64_t lastseen; +#endif } rankstats_t; typedef struct { //name, identity and order. @@ -1417,7 +1432,7 @@ void SV_ChatThink(client_t *client); - +/* // // sv_mvd.c // @@ -1442,7 +1457,7 @@ typedef struct mvdpendingdest_s { int outsize; struct mvdpendingdest_s *nextdest; -} mvdpendingdest_t; +} mvdpendingdest_t;*/ typedef struct mvddest_s { qboolean error; //disables writers, quit ASAP. @@ -1450,11 +1465,6 @@ typedef struct mvddest_s { enum {DEST_NONE, DEST_FILE, DEST_BUFFEREDFILE, DEST_THREADEDFILE, DEST_STREAM} desttype; -#ifdef _WIN32 - quintptr_t socket; //gah -#else - int socket; -#endif vfsfile_t *file; char filename[MAX_QPATH]; //demos/foo.mvd @@ -1500,6 +1510,7 @@ extern cvar_t sv_demoMaxSize; extern cvar_t sv_demoMaxDirSize; char *SV_Demo_CurrentOutput(void); +void SV_Demo_PrintOutputs(void); void SV_MVDInit(void); char *SV_MVDNum(char *buffer, int bufferlen, int num); const char *SV_MVDLastNum(unsigned int num); @@ -1509,6 +1520,14 @@ qboolean SV_ReadMVD (void); void SV_FlushDemoSignon (void); void DestFlush(qboolean compleate); +typedef struct +{ + qboolean hasauthed; + qboolean isreverse; + char challenge[32]; +} qtvpendingstate_t; +int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *headerend, qtvpendingstate_t *p); + // savegame.c void SV_LegacySavegame_f(void); void SV_Savegame_f (void); diff --git a/engine/server/sv_ccmds.c b/engine/server/sv_ccmds.c index 950f2c95..df652e5e 100644 --- a/engine/server/sv_ccmds.c +++ b/engine/server/sv_ccmds.c @@ -1846,8 +1846,9 @@ static void SV_Status_f (void) Con_Printf("gamedir : %s\n", FS_GetGamedir(true)); if (sv.csqcdebug) Con_Printf("csqc debug : true\n"); - if (sv.mvdrecording) - Con_Printf("recording : %s\n", SV_Demo_CurrentOutput()); + SV_Demo_PrintOutputs(); + NET_PrintConnectionsStatus(svs.sockets); + // min fps lat drp if (columns < 80) @@ -1855,7 +1856,7 @@ static void SV_Status_f (void) // most remote clients are 40 columns // 0123456789012345678901234567890123456789 Con_Printf ("name userid frags\n"); - Con_Printf (" address rate ping drop\n"); + Con_Printf (" address rate ping drop\n"); Con_Printf (" ---------------- ---- ---- -----\n"); for (i=0,cl=svs.clients ; iprotocol != SCP_QUAKEWORLD || cl->spectator || !(cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)) columns |= 1<<9; - if (cl->netchan.remote_address.type == NA_IPV6 || cl->netchan.remote_address.type == NA_TCPV6 || cl->netchan.remote_address.type == NA_TLSV6) + if (cl->netchan.remote_address.type == NA_IPV6) columns = (columns & ~(1<<2)) | (1<<10); } diff --git a/engine/server/sv_cluster.c b/engine/server/sv_cluster.c index 7b141c1f..9cae2152 100644 --- a/engine/server/sv_cluster.c +++ b/engine/server/sv_cluster.c @@ -836,7 +836,7 @@ void SSV_UpdateAddresses(void) char buf[256]; netadr_t addr[64]; struct ftenet_generic_connection_s *con[sizeof(addr)/sizeof(addr[0])]; - int flags[sizeof(addr)/sizeof(addr[0])]; + unsigned int flags[sizeof(addr)/sizeof(addr[0])]; int count; sizebuf_t send; qbyte send_buf[MAX_QWMSGLEN]; diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index c08db060..9eaf254f 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -485,6 +485,9 @@ void SV_DropClient (client_t *drop) for (j=0 ; jspawnparamglobals[j]) rs.parm[j] = *pr_global_ptrs->spawnparamglobals[j]; +#if NUM_RANK_SPAWN_PARMS>32 + rs.lastseen = time(NULL); +#endif Rank_SetPlayerStats(drop->rankid, &rs); } } @@ -3681,13 +3684,13 @@ qboolean SVNQ_ConnectionlessPacket(void) flags = MSG_ReadByte(); passwd = MSG_ReadLong(); - if (!strncmp(MSG_ReadString(), "getchallenge", 12) && (sv_listen_qw.ival || sv_listen_dp.ival)) + if (SV_ChallengeRecent()) + return true; + else if (!strncmp(MSG_ReadString(), "getchallenge", 12) && (sv_listen_qw.ival || sv_listen_dp.ival)) { /*dual-stack client, supporting either DP or QW protocols*/ SVC_GetChallenge (true); } - else if (SV_ChallengeRecent()) - return true; else { if (progstype == PROG_H2) @@ -4575,11 +4578,6 @@ float SV_Frame (void) SVM_Think(PORT_QWMASTER); } - { -void SV_MVDStream_Poll(void); - SV_MVDStream_Poll(); - } - #ifdef PLUGINS if (isDedicated) Plug_Tick(); diff --git a/engine/server/sv_mvd.c b/engine/server/sv_mvd.c index 86639f72..a7ef61f3 100644 --- a/engine/server/sv_mvd.c +++ b/engine/server/sv_mvd.c @@ -45,10 +45,8 @@ cvar_t sv_demoExtensions = CVARD("sv_demoExtensions", "", "Enables protocol exte cvar_t sv_demoAutoCompress = CVARD("sv_demoAutoCompress", "", "Specifies whether to compress demos as they're recorded.\n0 = no compression.\n1 = gzip compression."); cvar_t qtv_password = CVAR( "qtv_password", ""); -cvar_t qtv_streamport = CVARAF( "qtv_streamport", "0", - "mvd_streamport", 0); -cvar_t qtv_maxstreams = CVARAF( "qtv_maxstreams", "1", - "mvd_maxstreams", 0); +cvar_t qtv_maxstreams = CVARAFD( "qtv_maxstreams", "0", + "mvd_maxstreams", 0, "This is the maximum number of QTV clients/proxies that may be directly connected to the server. If empty then there is no limit. 0 disallows any streaming."); cvar_t sv_demoPrefix = CVAR("sv_demoPrefix", ""); cvar_t sv_demoSuffix = CVAR("sv_demoSuffix", ""); @@ -79,9 +77,7 @@ static char demomsgbuf[MAX_OVERALLMSGLEN]; static mvddest_t *singledest; //used when a stream is starting up so redundant data doesn't get dumped into other streams -#ifdef HAVE_TCP -static mvddest_t *SV_MVD_InitStream(int socket); -#endif +static mvddest_t *SV_MVD_InitStream(vfsfile_t *stream); qboolean SV_MVD_Record (mvddest_t *dest); char *SV_MVDName2Txt(char *name); extern cvar_t qtv_password; @@ -102,10 +98,6 @@ static void DestClose(mvddest_t *d, enum mvdclosereason_e reason) VFS_CLOSE(d->file); FS_FlushFSHashWritten(d->filename); } -#ifdef HAVE_TCP - if (d->socket != INVALID_SOCKET) - closesocket(d->socket); -#endif if (reason == MVD_CLOSE_CANCEL) { @@ -203,28 +195,17 @@ void DestFlush(qboolean compleate) break; case DEST_STREAM: -#ifndef HAVE_TCP - d->error = true; -#else if (d->cacheused && !d->error) { - len = send(d->socket, d->cache, d->cacheused, 0); - if (len == 0) //client died + len = VFS_WRITE(d->file, d->cache, d->cacheused); + if (len < 0) //client died d->error = true; else if (len > 0) //we put some data through { //move up the buffer d->cacheused -= len; memmove(d->cache, d->cache+len, d->cacheused); } - else - { //error of some kind. would block or something - int e; - e = neterrno(); - if (e != NET_EWOULDBLOCK) - d->error = true; - } } -#endif break; case DEST_NONE: @@ -244,146 +225,61 @@ void DestFlush(qboolean compleate) } } -void SV_MVD_RunPendingConnections(void) +enum qtvstatus_e +{ + QTV_ERROR = -1, //corrupt/bad request that should be dropped. + QTV_RETRY = 0, //still handshaking. + QTV_ACCEPT = 1 //stream is now owned by the qtv code +}; +int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *headerend, qtvpendingstate_t *p) { -#ifndef HAVE_TCP - if (demo.pendingdest) - Sys_Error("demo.pendingdest not null"); -#else unsigned short ushort_result; char *e; - int len; - mvdpendingdest_t *p; - mvdpendingdest_t *np; - if (!demo.pendingdest) - return; + qboolean server = false; + char *start, *lineend; + int versiontouse = 0; + int raw = 0; + char password[256] = ""; + enum { + QTVAM_NONE, + QTVAM_PLAIN, + QTVAM_CCITT, + QTVAM_MD4, + QTVAM_MD5, + } authmethod = QTVAM_NONE; - while (demo.pendingdest && demo.pendingdest->error) - { - np = demo.pendingdest->nextdest; + start = headerstart; - if (demo.pendingdest->socket != INVALID_SOCKET) - closesocket(demo.pendingdest->socket); - Z_Free(demo.pendingdest); - demo.pendingdest = np; + lineend = strchr(start, '\n'); + if (!lineend) + return QTV_ERROR; + + *lineend = '\0'; + COM_ParseToken(start, NULL); + start = lineend+1; + if (strcmp(com_token, "QTV")) + { //it's an error if it's not qtv. + if (!strcmp(com_token, "QTVSV")) + server = true; + else + return QTV_ERROR; } - for (p = demo.pendingdest; p && p->nextdest; p = p->nextdest) - { - if (p->nextdest->error) - { - np = p->nextdest->nextdest; - if (p->nextdest->socket != INVALID_SOCKET) - closesocket(p->nextdest->socket); - Z_Free(p->nextdest); - p->nextdest = np; - } + if (server != p->isreverse) + { //just a small check + return QTV_ERROR; } - for (p = demo.pendingdest; p; p = p->nextdest) + for(;;) { - if (p->outsize && !p->error) + lineend = strchr(start, '\n'); + if (!lineend) + break; + *lineend = '\0'; + start = COM_ParseToken(start, NULL); + if (start && *start == ':') { - len = send(p->socket, p->outbuffer, p->outsize, 0); - if (len == 0) //client died - p->error = true; - else if (len > 0) //we put some data through - { //move up the buffer - p->outsize -= len; - memmove(p->outbuffer, p->outbuffer+len, p->outsize ); - } - else - { //error of some kind. would block or something - int e; - e = neterrno(); - if (e != NET_EWOULDBLOCK) - p->error = true; - } - } - if (!p->error) - { - len = recv(p->socket, p->inbuffer + p->insize, sizeof(p->inbuffer) - p->insize - 1, 0); - if (len > 0) - {//fixme: cope with extra \rs - char *end; - p->insize += len; - p->inbuffer[p->insize] = 0; - - for (end = p->inbuffer; ; end++) - { - if (*end == '\0') - { - end = NULL; - break; //not enough data - } - - if (end[0] == '\n') - { - if (end[1] == '\n') - { - end[1] = '\0'; - break; - } - } - } - if (end) - { //we found the end of the header - qboolean server = false; - char *start, *lineend; - int versiontouse = 0; - int raw = 0; - char password[256] = ""; - enum { - QTVAM_NONE, - QTVAM_PLAIN, - QTVAM_CCITT, - QTVAM_MD4, - QTVAM_MD5, - } authmethod = QTVAM_NONE; - - start = p->inbuffer; - - lineend = strchr(start, '\n'); - if (!lineend) - { -// char *e; -// e = "This is a QTV server."; -// send(p->socket, e, strlen(e), 0); - - p->error = true; - continue; - } - *lineend = '\0'; - COM_ParseToken(start, NULL); - start = lineend+1; - if (strcmp(com_token, "QTV")) - { //it's an error if it's not qtv. - if (!strcmp(com_token, "QTVSV")) - server = true; - else - { - p->error = true; - lineend = strchr(start, '\n'); - continue; - } - } - - if (server != p->isreverse) - { //just a small check - p->error = true; - return; - } - - for(;;) - { - lineend = strchr(start, '\n'); - if (!lineend) - break; - *lineend = '\0'; - start = COM_ParseToken(start, NULL); - if (*start == ':') - { //VERSION: a list of the different qtv protocols supported. Multiple versions can be specified. The first is assumed to be the prefered version. //RAW: if non-zero, send only a raw mvd with no additional markup anywhere (for telnet use). Doesn't work with challenge-based auth, so will only be accepted when proxy passwords are not required. //AUTH: specifies an auth method, the exact specs varies based on the method @@ -395,243 +291,242 @@ void SV_MVD_RunPendingConnections(void) //SOURCE: which stream to play from, DEFAULT is special. Without qualifiers, it's assumed to be a tcp address. //COMPRESSION: Suggests a compression method (multiple are allowed). You'll get a COMPRESSION response, and compression will begin with the binary data. - start = start+1; - while(*start == ' ' || *start == '\t') - start++; - Con_DPrintf("qtv, got (%s) (%s)\n", com_token, start); - if (!strcmp(com_token, "VERSION")) - { - start = COM_ParseToken(start, NULL); - if (atoi(com_token) == 1) - versiontouse = 1; - } - else if (!strcmp(com_token, "RAW")) - { - start = COM_ParseToken(start, NULL); - raw = atoi(com_token); - } - else if (!strcmp(com_token, "PASSWORD")) - { - start = COM_ParseToken(start, NULL); - Q_strncpyz(password, com_token, sizeof(password)); - } - else if (!strcmp(com_token, "AUTH")) - { - int thisauth; - start = COM_ParseToken(start, NULL); - if (!strcmp(com_token, "NONE")) - thisauth = QTVAM_PLAIN; - else if (!strcmp(com_token, "PLAIN")) - thisauth = QTVAM_PLAIN; - else if (!strcmp(com_token, "CCIT")) - thisauth = QTVAM_CCITT; - else if (!strcmp(com_token, "MD4")) - thisauth = QTVAM_MD4; + start = start+1; + while(*start == ' ' || *start == '\t') + start++; + Con_DPrintf("qtv, got (%s) (%s)\n", com_token, start); + if (!strcmp(com_token, "VERSION")) + { + start = COM_ParseToken(start, NULL); + if (atoi(com_token) == 1) + versiontouse = 1; + } + else if (!strcmp(com_token, "RAW")) + { + start = COM_ParseToken(start, NULL); + raw = atoi(com_token); + } + else if (!strcmp(com_token, "PASSWORD")) + { + start = COM_ParseToken(start, NULL); + Q_strncpyz(password, com_token, sizeof(password)); + } + else if (!strcmp(com_token, "AUTH")) + { + int thisauth; + start = COM_ParseToken(start, NULL); + if (!strcmp(com_token, "NONE")) + thisauth = QTVAM_PLAIN; + else if (!strcmp(com_token, "PLAIN")) + thisauth = QTVAM_PLAIN; + else if (!strcmp(com_token, "CCIT")) + thisauth = QTVAM_CCITT; + else if (!strcmp(com_token, "MD4")) + thisauth = QTVAM_MD4; // else if (!strcmp(com_token, "MD5")) // thisauth = QTVAM_MD5; - else - { - thisauth = QTVAM_NONE; - Con_DPrintf("qtv: received unrecognised auth method (%s)\n", com_token); - } - - if (authmethod < thisauth) - authmethod = thisauth; - } - else if (!strcmp(com_token, "SOURCE")) - { - //servers don't support source, and ignore it. - //source is only useful for qtv proxy servers. - } - else if (!strcmp(com_token, "COMPRESSION")) - { - //compression not supported yet - } - else if (!strcmp(com_token, "QTV_EZQUAKE_EXT")) - { - //if we were treating this as a regular client over tcp (qizmo...) - } - else if (!strcmp(com_token, "USERINFO")) - { - //if we were treating this as a regular client over tcp (qizmo...) - } - else - { - //not recognised. - } - } - start = lineend+1; - } - - len = (end - p->inbuffer)+2; - p->insize -= len; - memmove(p->inbuffer, p->inbuffer + len, p->insize); - p->inbuffer[p->insize] = 0; - - e = NULL; - if (p->hasauthed) - { - } - else if (p->isreverse) - p->hasauthed = true; //reverse connections do not need to auth. - else if (!*qtv_password.string) - p->hasauthed = true; //no password, no need to auth. - else if (*password) - { - switch (authmethod) - { - case QTVAM_NONE: - e = ("QTVSV 1\n" - "PERROR: You need to provide a password.\n\n"); - break; - case QTVAM_PLAIN: - p->hasauthed = !strcmp(qtv_password.string, password); - break; - case QTVAM_CCITT: - QCRC_Init(&ushort_result); - QCRC_AddBlock(&ushort_result, p->challenge, strlen(p->challenge)); - QCRC_AddBlock(&ushort_result, qtv_password.string, strlen(qtv_password.string)); - p->hasauthed = (ushort_result == strtoul(password, NULL, 0)); - break; - case QTVAM_MD4: - { - char hash[512]; - int md4sum[4]; - - snprintf(hash, sizeof(hash), "%s%s", p->challenge, qtv_password.string); - Com_BlockFullChecksum (hash, strlen(hash), (unsigned char*)md4sum); - sprintf(hash, "%X%X%X%X", md4sum[0], md4sum[1], md4sum[2], md4sum[3]); - p->hasauthed = !strcmp(password, hash); - } - break; - case QTVAM_MD5: - default: - e = ("QTVSV 1\n" - "PERROR: FTEQWSV bug detected.\n\n"); - break; - } - if (!p->hasauthed && !e) - { - if (raw) - e = ""; - else - e = ("QTVSV 1\n" - "PERROR: Bad password.\n\n"); - } - } - else - { - //no password, and not automagically authed - switch (authmethod) - { - case QTVAM_NONE: - if (raw) - e = ""; - else - e = ("QTVSV 1\n" - "PERROR: You need to provide a common auth method.\n\n"); - break; - case QTVAM_PLAIN: - p->hasauthed = !strcmp(qtv_password.string, password); - break; - - if (0) - { - case QTVAM_CCITT: - e = ("QTVSV 1\n" - "AUTH: CCITT\n" - "CHALLENGE: "); - } - else if (0) - { - case QTVAM_MD4: - e = ("QTVSV 1\n" - "AUTH: MD4\n" - "CHALLENGE: "); - } - else - { - case QTVAM_MD5: - e = ("QTVSV 1\n" - "AUTH: MD5\n" - "CHALLENGE: "); - } - - send(p->socket, e, strlen(e), 0); - send(p->socket, p->challenge, strlen(p->challenge), 0); - e = "\n\n"; - send(p->socket, e, strlen(e), 0); - continue; - - default: - e = ("QTVSV 1\n" - "PERROR: FTEQWSV bug detected.\n\n"); - break; - } - } - - if (e) - { - } - else if (!versiontouse) - { - e = ("QTVSV 1\n" - "PERROR: Incompatible version (valid version is v1)\n\n"); - } - else if (raw) - { - if (p->hasauthed == false) - { - e = ""; - } - else - { - SV_MVD_Record(SV_MVD_InitStream(p->socket)); - p->socket = INVALID_SOCKET; //so it's not cleared wrongly. - } - p->error = true; - } - else - { - if (p->hasauthed == true) - { - mvddest_t *dst; - e = ("QTVSV 1\n" - "BEGIN\n" - "\n"); - send(p->socket, e, strlen(e), 0); - e = NULL; - dst = SV_MVD_InitStream(p->socket); - dst->droponmapchange = p->isreverse; - SV_MVD_Record(dst); - p->socket = INVALID_SOCKET; //so it's not cleared wrongly. - } - else - { - e = ("QTVSV 1\n" - "PERROR: You need to provide a password.\n\n"); - } - p->error = true; - } - - if (e) - { - send(p->socket, e, strlen(e), 0); - p->error = true; - } + else + { + thisauth = QTVAM_NONE; + Con_DPrintf("qtv: received unrecognised auth method (%s)\n", com_token); } + + if (authmethod < thisauth) + authmethod = thisauth; + } + else if (!strcmp(com_token, "SOURCE")) + { + //servers don't support source, and ignore it. + //source is only useful for qtv proxy servers. + } + else if (!strcmp(com_token, "COMPRESSION")) + { + //compression not supported yet + } + else if (!strcmp(com_token, "QTV_EZQUAKE_EXT")) + { + //if we were treating this as a regular client over tcp (qizmo...) + } + else if (!strcmp(com_token, "USERINFO")) + { + //if we were treating this as a regular client over tcp (qizmo...) } - else if (len == 0) - p->error = true; else - { //error of some kind. would block or something - int e = neterrno(); - if (e != NET_EWOULDBLOCK) - p->error = true; + { + //not recognised. } } + start = lineend+1; } -#endif + + /*len = (headerend - headerstart)+2; + p->insize -= len; + memmove(p->inbuffer, p->inbuffer + len, p->insize); + p->inbuffer[p->insize] = 0; + */ + + e = NULL; + if (p->hasauthed) + { + } + else if (p->isreverse) + p->hasauthed = true; //reverse connections do not need to auth. + else if (!*qtv_password.string) + p->hasauthed = true; //no password, no need to auth. + else if (*password) + { + switch (authmethod) + { + case QTVAM_NONE: + e = ("QTVSV 1\n" + "PERROR: You need to provide a password.\n\n"); + break; + case QTVAM_PLAIN: + p->hasauthed = !strcmp(qtv_password.string, password); + break; + case QTVAM_CCITT: + QCRC_Init(&ushort_result); + QCRC_AddBlock(&ushort_result, p->challenge, strlen(p->challenge)); + QCRC_AddBlock(&ushort_result, qtv_password.string, strlen(qtv_password.string)); + p->hasauthed = (ushort_result == strtoul(password, NULL, 0)); + break; + case QTVAM_MD4: + { + char hash[512]; + int md4sum[4]; + + snprintf(hash, sizeof(hash), "%s%s", p->challenge, qtv_password.string); + Com_BlockFullChecksum (hash, strlen(hash), (unsigned char*)md4sum); + sprintf(hash, "%X%X%X%X", md4sum[0], md4sum[1], md4sum[2], md4sum[3]); + p->hasauthed = !strcmp(password, hash); + } + break; + case QTVAM_MD5: + default: + e = ("QTVSV 1\n" + "PERROR: FTEQWSV bug detected.\n\n"); + break; + } + if (!p->hasauthed && !e) + { + if (raw) + e = ""; + else + e = ("QTVSV 1\n" + "PERROR: Bad password.\n\n"); + } + } + else + { + //no password, and not automagically authed + switch (authmethod) + { + case QTVAM_NONE: + if (raw) + e = ""; + else + e = ("QTVSV 1\n" + "PERROR: You need to provide a common auth method.\n\n"); + break; + case QTVAM_PLAIN: + p->hasauthed = !strcmp(qtv_password.string, password); + break; + + if (0) + { + case QTVAM_CCITT: + e = ("QTVSV 1\n" + "AUTH: CCITT\n" + "CHALLENGE: "); + } + else if (0) + { + case QTVAM_MD4: + e = ("QTVSV 1\n" + "AUTH: MD4\n" + "CHALLENGE: "); + } + else + { + case QTVAM_MD5: + e = ("QTVSV 1\n" + "AUTH: MD5\n" + "CHALLENGE: "); + } + + VFS_WRITE(clientstream, e, strlen(e)); + VFS_WRITE(clientstream, p->challenge, strlen(p->challenge)); + e = "\n\n"; + VFS_WRITE(clientstream, e, strlen(e)); + return QTV_RETRY; + + default: + e = ("QTVSV 1\n" + "PERROR: FTEQWSV bug detected.\n\n"); + break; + } + } + + if (*qtv_maxstreams.string) + { + int count = 0; + mvddest_t *dest; + for (dest = demo.dest; dest; dest = dest->nextdest) + { + if (dest->desttype == DEST_STREAM) + count++; + } + + if (count >= qtv_maxstreams.value) //sorry + { + if (!qtv_maxstreams.value) + e = "QTVSV 1\nTERROR: QTV streaming from this server is blocked by qtv_maxstreams.\n\n"; + else + e = "QTVSV 1\nTERROR: This server enforces a limit on the number of proxies connected at any one time. Please try again later.\n\n"; + } + } + + if (e) + { + } + else if (!versiontouse) + { + e = ("QTVSV 1\n" + "PERROR: Incompatible version (valid version is v1)\n\n"); + } + else if (raw) + { + if (p->hasauthed == true) + { + SV_MVD_Record(SV_MVD_InitStream(clientstream)); + return QTV_ACCEPT; + } + } + else + { + if (p->hasauthed == true) + { + mvddest_t *dst; + e = ("QTVSV 1\n" + "BEGIN\n" + "\n"); + VFS_WRITE(clientstream, e, strlen(e)); + e = NULL; + dst = SV_MVD_InitStream(clientstream); + dst->droponmapchange = p->isreverse; + SV_MVD_Record(dst); + return QTV_ACCEPT; + } + else + { + e = ("QTVSV 1\n" + "PERROR: You need to provide a password.\n\n"); + } + } + + if (e && !raw) //don't write any error messages to raw requests. that would confuse stuff. + VFS_WRITE(clientstream, e, strlen(e)); + return QTV_ERROR; } static int DestCloseAllFlush(enum mvdclosereason_e reason, qboolean mvdonly) @@ -1368,7 +1263,6 @@ mvddest_t *SV_MVD_InitRecordFile (char *name) #endif dst = Z_Malloc(sizeof(mvddest_t)); - dst->socket = INVALID_SOCKET; strcpy(dst->filename, name); #ifdef LOADERTHREAD @@ -1467,43 +1361,41 @@ char *SV_Demo_CurrentOutput(void) } return "QTV"; } +void SV_Demo_PrintOutputs(void) +{ + mvddest_t *d; + for (d = demo.dest; d; d = d->nextdest) + { + if (d->desttype == DEST_FILE || d->desttype == DEST_BUFFEREDFILE || d->desttype == DEST_THREADEDFILE) + Con_Printf("recording : %s\n", d->simplename); + else if (d->desttype == DEST_STREAM) + Con_Printf("streaming : %s\n", d->simplename); + } +} -#ifdef HAVE_TCP -static mvddest_t *SV_MVD_InitStream(int socket) +static mvddest_t *SV_MVD_InitStream(vfsfile_t *stream) { mvddest_t *dst; + for (dst = demo.dest; dst; dst = dst->nextdest) + { + if (dst->desttype == DEST_STREAM) + break; + } + if (!dst) + SV_BroadcastPrintf (PRINT_CHAT, "Smile, you're on QTV!\n"); + dst = Z_Malloc(sizeof(mvddest_t)); dst->desttype = DEST_STREAM; - dst->socket = socket; + dst->file = stream; dst->maxcachesize = 0x8000; //is this too small? dst->cache = BZ_Malloc(dst->maxcachesize); dst->droponmapchange = false; - SV_BroadcastPrintf (PRINT_CHAT, "Smile, you're on QTV!\n"); - return dst; } -static mvdpendingdest_t *SV_MVD_InitPendingStream(int socket, char *ip) -{ - mvdpendingdest_t *dst; - int i; - dst = Z_Malloc(sizeof(mvdpendingdest_t)); - dst->socket = socket; - - Q_strncpyz(dst->challenge, ip, sizeof(dst->challenge)); - for (i = strlen(dst->challenge); i < sizeof(dst->challenge)-1; i++) - dst->challenge[i] = rand()%(127-33) + 33; //generate a random challenge - - dst->nextdest = demo.pendingdest; - demo.pendingdest = dst; - - return dst; -} -#endif - /* ==================== SV_Stop @@ -2041,7 +1933,7 @@ void SV_MVD_Record_f (void) void SV_MVD_QTVReverse_f (void) { -#ifndef HAVE_TCP +#if 1//ndef HAVE_TCP Con_Printf ("%s is not supported in this build\n", Cmd_Argv(0)); #else char *ip; @@ -2342,132 +2234,6 @@ void SV_MVDEasyRecord_f (void) SV_MVD_Record (SV_MVD_InitRecordFile(name2)); } -#ifdef HAVE_TCP -static SOCKET MVD_StreamStartListening(int port) -{ - SOCKET sock; - - struct sockaddr_in address; -// int fromlen; - - unsigned int nonblocking = true; - - address.sin_family = AF_INET; - address.sin_addr.s_addr = INADDR_ANY; - address.sin_port = htons((u_short)port); - - - - if ((sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) - { - Sys_Error ("MVD_StreamStartListening: socket: %s", strerror(neterrno())); - } - - if (ioctlsocket (sock, FIONBIO, (u_long *)&nonblocking) == INVALID_SOCKET) - { - Sys_Error ("FTP_TCP_OpenSocket: ioctl FIONBIO: %s", strerror(neterrno())); - } - - if( bind (sock, (void *)&address, sizeof(address)) == INVALID_SOCKET) - { - closesocket(sock); - return INVALID_SOCKET; - } - - listen(sock, 2); - - return sock; -} -#endif - -void SV_MVDStream_Poll(void) -{ -#ifdef HAVE_TCP - static SOCKET listensocket=INVALID_SOCKET; - static int listenport; - int _true = true; - - int client; - netadr_t na; - struct sockaddr_qstorage addr; - int addrlen; - int count; - qboolean wanted; - mvddest_t *dest; - char *ip; - char adrbuf[MAX_ADR_SIZE]; - - if (!sv.state || !qtv_streamport.ival) - wanted = false; - else if (listenport && qtv_streamport.ival != listenport) //easy way to switch... disable for a frame. :) - { - listenport = qtv_streamport.ival; - wanted = false; - } - else - { - listenport = qtv_streamport.ival; - wanted = true; - } - - if (wanted && listensocket==INVALID_SOCKET) - { - listensocket = MVD_StreamStartListening(listenport); - if (listensocket==INVALID_SOCKET && qtv_streamport.modified) - { - Con_Printf("Cannot open TCP port %i for QTV\n", listenport); - qtv_streamport.modified = false; - } - - } - else if (!wanted && listensocket!=INVALID_SOCKET) - { - closesocket(listensocket); - listensocket = INVALID_SOCKET; - return; - } - if (listensocket==INVALID_SOCKET) - return; - - addrlen = sizeof(addr); - client = accept(listensocket, (struct sockaddr *)&addr, &addrlen); - - if (client == INVALID_SOCKET) - return; - - ioctlsocket(client, FIONBIO, (u_long *)&_true); - - if (qtv_maxstreams.value > 0) - { - count = 0; - for (dest = demo.dest; dest; dest = dest->nextdest) - { - if (dest->desttype == DEST_STREAM) - { - count++; - } - } - - if (count > qtv_maxstreams.value) - { //sorry - char *goawaymessage = "QTVSV 1\nTERROR: This server enforces a limit on the number of proxies connected at any one time. Please try again later\n\n"; - - send(client, goawaymessage, strlen(goawaymessage), 0); - closesocket(client); - return; - } - } - - SockadrToNetadr(&addr, &na); - ip = NET_AdrToString(adrbuf, sizeof(adrbuf), &na); - Con_Printf("MVD streaming client attempting to connect from %s\n", ip); - - SV_MVD_InitPendingStream(client, ip); - -// SV_MVD_Record (SV_InitStream(client)); -#endif -} - //console command for servers/admins void SV_MVDList_f (void) { @@ -2496,7 +2262,7 @@ void SV_MVDList_f (void) { for (d = demo.dest; d; d = d->nextdest) { - if (!strcmp(list->name, d->simplename)) + if (d->desttype != DEST_STREAM && !strcmp(list->name, d->simplename)) Con_Printf("*%d: ^[^7%s\\demo\\%s/%s^] %dk\n", i, list->name, sv_demoDir.string, list->name, d->totalsize/1024); } if (!d) @@ -2547,7 +2313,7 @@ void SV_UserCmdMVDList_f (void) { for (d = demo.dest; d; d = d->nextdest) { - if (!strcmp(list->name, d->simplename)) + if (d->desttype != DEST_STREAM && !strcmp(list->name, d->simplename)) SV_ClientPrintf(host_client, PRINT_HIGH, "*%d: %s %dk\n", i, list->name, d->totalsize/1024); } if (!d) @@ -2578,6 +2344,80 @@ void SV_UserCmdMVDList_f (void) Sys_freedir(dir); } +void SV_UserCmdMVDList_HTML (vfsfile_t *pipe) +{ + mvddest_t *d; + dir_t *dir; + file_t *list; + float f; + int i; + + VFS_PRINTF(pipe, + "" + "" + "" + "" + "" + "" + "" + "
\n" + , hostname); + + VFS_PRINTF(pipe, "available demos:
\n"); + dir = Sys_listdir(sv_demoDir.string, ".mvd", SORT_BY_DATE); + list = dir->files; + if (!list->name[0]) + { + VFS_PRINTF(pipe, "no demos
\n"); + } + + for (i = 1; i <= dir->numfiles; i++, list++) + { + for (d = demo.dest; d; d = d->nextdest) + { + if (d->desttype != DEST_STREAM && !strcmp(list->name, d->simplename)) + VFS_PRINTF(pipe, "*%d: %s %dk
\n", i, list->name, d->totalsize/1024); + } + if (!d) + VFS_PRINTF(pipe, "%d: %s %dk play
\n", i, list->name, list->name, list->size/1024, list->name); + } + + for (d = demo.dest; d; d = d->nextdest) + dir->size += d->totalsize; + + VFS_PRINTF(pipe, "
\ndirectory size: %.1fMB
\n",(float)dir->size/(1024*1024)); + if (sv_demoMaxDirSize.value) + { + f = (sv_demoMaxDirSize.value*1024 - dir->size)/(1024*1024); + if ( f < 0) + f = 0; + VFS_PRINTF(pipe, "space available: %.1fMB
\n", f); + } + + VFS_PRINTF(pipe, + "
" + "
" + "" + "
" + "\n" + "\n"); + + Sys_freedir(dir); +} + const char *SV_MVDLastNum(unsigned int num) { if (!num || num > DEMOLOG_LENGTH) @@ -2958,7 +2798,6 @@ void SV_MVDInit(void) Cmd_AddCommand ("rmdemo", SV_MVDRemove_f); Cmd_AddCommand ("rmdemonum", SV_MVDRemoveNum_f); - Cvar_Register(&qtv_streamport, "MVD Streaming"); Cvar_Register(&qtv_maxstreams, "MVD Streaming"); Cvar_Register(&qtv_password, "MVD Streaming"); } diff --git a/engine/server/sv_rankin.c b/engine/server/sv_rankin.c index 5665166c..4f5db06f 100644 --- a/engine/server/sv_rankin.c +++ b/engine/server/sv_rankin.c @@ -1,6 +1,10 @@ #include "quakedef.h" #ifndef CLIENTONLY +//FIXME: this is shitty old code. +//possible improvements: using a hash table for player names for faster logons +//threading logins +//using a real database... #ifdef SVRANKING @@ -13,8 +17,14 @@ typedef struct { } rankfileheader_t; //endian +#define NOENDIAN +#ifdef NOENDIAN +#define swaplong(l) l +#define swapfloat(f) f +#else #define swaplong LittleLong #define swapfloat LittleFloat +#endif rankfileheader_t rankfileheader; vfsfile_t *rankfile; @@ -31,7 +41,9 @@ char rank_cvargroup[] = "server rankings"; static void READ_PLAYERSTATS(int x, rankstats_t *os) { +#ifndef NOENDIAN int i; +#endif size_t result; VFS_SEEK(rankfile, sizeof(rankfileheader_t)+sizeof(rankheader_t)+((x-1)*sizeof(rankinfo_t))); @@ -40,6 +52,7 @@ static void READ_PLAYERSTATS(int x, rankstats_t *os) if (result != sizeof(rankstats_t)) Con_Printf("READ_PLAYERSTATS() fread: expected %lu, result was %u\n",(long unsigned int)sizeof(rankstats_t),(unsigned int)result); +#ifndef NOENDIAN os->kills = swaplong(os->kills); os->deaths = swaplong(os->deaths); for (i = 0; i < NUM_RANK_SPAWN_PARMS; i++) @@ -49,15 +62,17 @@ static void READ_PLAYERSTATS(int x, rankstats_t *os) // os->trustlevel = (os->trustlevel); // os->pad2 = (os->pad2); // os->pad3 = (os->pad3); +#endif } static void WRITE_PLAYERSTATS(int x, rankstats_t *os) { +#ifdef NOENDIAN + VFS_SEEK(rankfile, sizeof(rankfileheader_t)+sizeof(rankheader_t)+((x-1)*sizeof(rankinfo_t))); + VFS_WRITE(rankfile, os, sizeof(rankstats_t)); +#else rankstats_t ns; int i; - - VFS_SEEK(rankfile, sizeof(rankfileheader_t)+sizeof(rankheader_t)+((x-1)*sizeof(rankinfo_t))); - ns.kills = swaplong(os->kills); ns.deaths = swaplong(os->deaths); for (i = 0; i < NUM_RANK_SPAWN_PARMS; i++) @@ -68,7 +83,9 @@ static void WRITE_PLAYERSTATS(int x, rankstats_t *os) ns.pad2 = (os->pad2); ns.pad3 = (os->pad3); + VFS_SEEK(rankfile, sizeof(rankfileheader_t)+sizeof(rankheader_t)+((x-1)*sizeof(rankinfo_t))); VFS_WRITE(rankfile, &ns, sizeof(rankstats_t)); +#endif } static void READ_PLAYERHEADER(int x, rankheader_t *oh) @@ -82,26 +99,31 @@ static void READ_PLAYERHEADER(int x, rankheader_t *oh) if (result != sizeof(rankheader_t)) Con_Printf("READ_PLAYERHEADER() fread: expected %lu, result was %u\n",(long unsigned int)sizeof(rankheader_t),(unsigned int)result); +#ifndef NOENDIAN oh->prev = swaplong(oh->prev); //score is held for convineance. oh->next = swaplong(oh->next); // strcpy(oh->name, oh->name); oh->pwd = swaplong(oh->pwd); oh->score = swapfloat(oh->score); +#endif } static void WRITE_PLAYERHEADER(int x, rankheader_t *oh) { - rankheader_t nh; - +#ifdef NOENDIAN VFS_SEEK(rankfile, sizeof(rankfileheader_t)+((x-1)*sizeof(rankinfo_t))); - + VFS_WRITE(rankfile, &oh, sizeof(rankheader_t)); +#else + rankheader_t nh; nh.prev = swaplong(oh->prev); //score is held for convineance. nh.next = swaplong(oh->next); Q_strncpyz(nh.name, oh->name, sizeof(nh.name)); nh.pwd = swaplong(oh->pwd); nh.score = swapfloat(oh->score); + VFS_SEEK(rankfile, sizeof(rankfileheader_t)+((x-1)*sizeof(rankinfo_t))); VFS_WRITE(rankfile, &nh, sizeof(rankheader_t)); +#endif } static void READ_PLAYERINFO(int x, rankinfo_t *inf) @@ -469,6 +491,7 @@ void Rank_AddUser_f (void) char *name = Cmd_Argv(1); int pwd = atoi(Cmd_Argv(2)); int userlevel = atoi(Cmd_Argv(3)); + char fixed[80]; if (Cmd_Argc() < 2) { @@ -492,7 +515,8 @@ void Rank_AddUser_f (void) return; } - SV_FixupName(name, name, sizeof(name)); + SV_FixupName(name, fixed, sizeof(fixed)); + name = fixed; if (!Rank_OpenRankings()) { @@ -548,6 +572,9 @@ void Rank_AddUser_f (void) memset(&rs, 0, sizeof(rs)); rs.trustlevel = userlevel; +#if NUM_RANK_SPAWN_PARMS>32 + rs.created = rs.lastseen = time(NULL); +#endif WRITE_PLAYERSTATS(id, &rs); Rank_SetPlayerStats(id, &rs); @@ -560,6 +587,7 @@ void Rank_SetPass_f (void) rankheader_t rh; char *name = Cmd_Argv(1); int newpass = atoi(Cmd_Argv(2)); + char fixed[80]; int id; @@ -575,7 +603,8 @@ void Rank_SetPass_f (void) return; } - SV_FixupName(name, name, sizeof(name)); + SV_FixupName(name, fixed, sizeof(fixed)); + name = fixed; id = rankfileheader.leader; while(id) @@ -595,6 +624,7 @@ void Rank_SetPass_f (void) int Rank_GetPass (char *name) { rankheader_t rh; + char fixed[80]; int id; @@ -604,7 +634,8 @@ int Rank_GetPass (char *name) return 0; } - SV_FixupName(name, name, sizeof(name)); + SV_FixupName(name, fixed, sizeof(fixed)); + name = fixed; id = rankfileheader.leader; while(id) @@ -651,14 +682,11 @@ int Rank_Enumerate (unsigned int first, unsigned int last, void (*callback) (con void Rank_RankingList_f (void) { -#if 1 - Con_Printf("Fixme\n"); -#else rankinfo_t ri; int id; int num; - FILE *outfile; + vfsfile_t *outfile; if (!Rank_OpenRankings()) { @@ -666,9 +694,14 @@ void Rank_RankingList_f (void) return; } - outfile = fopen("list.txt", "wb"); + outfile = FS_OpenVFS("list.txt", "wb", FS_GAMEONLY); + if (!outfile) + { + Con_Printf("Couldn't open list.txt\n"); + return; + } - fprintf(outfile, "%5s: %32s, %5s %5s\r\n", "", "Name", "Kills", "Deaths"); + VFS_PRINTF(outfile, "%5s: %32s, %5s %5s\r\n", "", "Name", "Kills", "Deaths"); id = rankfileheader.leader; //start at the leaders num=1; @@ -676,17 +709,16 @@ void Rank_RankingList_f (void) { READ_PLAYERINFO(id, &ri); - fprintf(outfile, "%5i: %32s, %5i %5i\r\n", num, ri.h.name, ri.s.kills, ri.s.deaths); + VFS_PRINTF(outfile, "%5i: %32s, %5i %5i\r\n", num, ri.h.name, ri.s.kills, ri.s.deaths); num++; id = ri.h.next; } - fclose(outfile); -#endif + VFS_CLOSE(outfile); } -void Rank_Remove_f (void) +void Rank_RemoveID_f (void) { rankinfo_t ri; int id; @@ -770,7 +802,7 @@ void Rank_Find_f (void) rankinfo_t ri; int id; - char *match = Cmd_Argv(1); + char *match = Q_strlwr(Cmd_Argv(1)); if (!Rank_OpenRankings()) { @@ -784,7 +816,7 @@ void Rank_Find_f (void) { READ_PLAYERINFO(id, &ri); - if (strstr(ri.h.name, match)) + if (wildcmp(match, Q_strlwr(ri.h.name))) { Con_Printf("%i %s\n", id, ri.h.name); } @@ -889,7 +921,7 @@ void Rank_RegisterCommands(void) Cmd_AddCommand("ranklist", Rank_RankingList_f); Cmd_AddCommand("ranktopten", Rank_ListTop10_f); Cmd_AddCommand("rankfind", Rank_Find_f); - Cmd_AddCommand("rankremove", Rank_Remove_f); + Cmd_AddCommand("rankremove", Rank_RemoveID_f); Cmd_AddCommand("rankrefresh", Rank_Refresh_f); Cmd_AddCommand("rankrconlevel", Rank_RCon_f); diff --git a/engine/server/sv_send.c b/engine/server/sv_send.c index cdc5a52f..be97095c 100644 --- a/engine/server/sv_send.c +++ b/engine/server/sv_send.c @@ -1171,7 +1171,7 @@ void SV_MulticastCB(vec3_t origin, multicast_t to, int dimension_mask, void (*ca break; if (to == MULTICAST_PHS_R || to == MULTICAST_PHS) - { + { //phs is always 'visible' within 1024qu vec3_t delta; VectorSubtract(origin, split->edict->v->origin, delta); if (DotProduct(delta, delta) <= 1024*1024) @@ -1294,7 +1294,7 @@ struct startsoundcontext_s float attenuation; float ratemul; unsigned int chflags; - unsigned int timeofs; + int timeofs; }; static void SV_SoundMulticast(client_t *client, sizebuf_t *msg, void *vctx) { @@ -1305,7 +1305,7 @@ static void SV_SoundMulticast(client_t *client, sizebuf_t *msg, void *vctx) if (ctx->ent >= client->max_net_ents) return; - field_mask |= (ctx->chflags & (CF_NOSPACIALISE|CF_NOREVERB|CF_FOLLOW)) << 8; + field_mask |= (ctx->chflags & CF_NETWORKED) << 8; if (ctx->volume != DEFAULT_SOUND_PACKET_VOLUME) field_mask |= NQSND_VOLUME; if (ctx->attenuation != DEFAULT_SOUND_PACKET_ATTENUATION) @@ -3422,8 +3422,6 @@ void SV_SendMVDMessage(void) // extern cvar_t sv_demoMaxSize; sizebuf_t *dmsg; - SV_MVD_RunPendingConnections(); - if (!sv.mvdrecording) return; diff --git a/engine/server/sv_sys_unix.c b/engine/server/sv_sys_unix.c index 28d296d0..a4e89e5d 100644 --- a/engine/server/sv_sys_unix.c +++ b/engine/server/sv_sys_unix.c @@ -92,20 +92,28 @@ Sys_mkdir ============ */ -void Sys_mkdir (char *path) +void Sys_mkdir (const char *path) { - if (mkdir (path, 0777) != -1) + if (mkdir (path, 0755) != -1) return; // if (errno != EEXIST) // Sys_Error ("mkdir %s: %s",path, strerror(errno)); } +qboolean Sys_rmdir (const char *path) +{ + if (rmdir (path) == 0) + return true; + if (errno == ENOENT) + return true; + return false; +} -qboolean Sys_remove (char *path) +qboolean Sys_remove (const char *path) { return system(va("rm \"%s\"", path)); } -qboolean Sys_Rename (char *oldfname, char *newfname) +qboolean Sys_Rename (const char *oldfname, const char *newfname) { return !rename(oldfname, newfname); } diff --git a/engine/server/sv_sys_win.c b/engine/server/sv_sys_win.c index 451a6c87..3e4e7e64 100644 --- a/engine/server/sv_sys_win.c +++ b/engine/server/sv_sys_win.c @@ -25,6 +25,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #include +#include #ifdef MULTITHREAD #include @@ -483,19 +484,22 @@ char *narrowen(char *out, size_t outlen, wchar_t *wide); Sys_mkdir ================ */ -int _mkdir(const char *path);; -void Sys_mkdir (char *path) +void Sys_mkdir (const char *path) { _mkdir(path); } +qboolean Sys_rmdir (const char *path) +{ + return 0==_rmdir(path); +} -qboolean Sys_remove (char *path) +qboolean Sys_remove (const char *path) { remove(path); return true; } -qboolean Sys_Rename (char *oldfname, char *newfname) +qboolean Sys_Rename (const char *oldfname, const char *newfname) { return !rename(oldfname, newfname); } diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index bc28ae84..b2170602 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -47,7 +47,7 @@ cvar_t sv_spectalk = CVAR("sv_spectalk", "1"); cvar_t sv_mapcheck = CVAR("sv_mapcheck", "1"); cvar_t sv_fullredirect = CVARD("sv_fullredirect", "", "This is the ip:port to redirect players to when the server is full"); -cvar_t sv_antilag = CVARFD("sv_antilag", "", CVAR_SERVERINFO, "Attempt to backdate impacts to compensate for lag. 0=completely off. 1=mod-controlled. 2=forced, which might break certain uses of traceline."); +cvar_t sv_antilag = CVARFD("sv_antilag", "", CVAR_SERVERINFO, "Attempt to backdate impacts to compensate for lag via the MOVE_ANTILAG feature.\n0=completely off.\n1=mod-controlled (default).\n2=forced, which might break certain uses of traceline.\n3=Also attempt to recalculate trace start positions to avoid lagged knockbacks."); cvar_t sv_antilag_frac = CVARF("sv_antilag_frac", "", CVAR_SERVERINFO); #ifndef NEWSPEEDCHEATPROT cvar_t sv_cheatpc = CVARD("sv_cheatpc", "125", "If the client tried to claim more than this percentage of time within any speed-cheat period, the client will be deemed to have cheated."); @@ -69,7 +69,9 @@ cvar_t sv_nqplayerphysics = CVARAD("sv_nqplayerphysics", "0", "sv_nomsec", "Disa cvar_t sv_edgefriction = CVARAF("sv_edgefriction", "2", "edgefriction", 0); +#ifndef NOLEGACY cvar_t sv_brokenmovetypes = CVARD("sv_brokenmovetypes", "0", "Emulate vanilla quakeworld by forcing MOVETYPE_WALK on all players. Shouldn't be used for any games other than QuakeWorld."); +#endif cvar_t sv_chatfilter = CVAR("sv_chatfilter", "0"); @@ -3328,7 +3330,7 @@ void SV_BeginDownload_f(void) #ifdef PEXT_CHUNKEDDOWNLOADS if (host_client->fteprotocolextensions & PEXT_CHUNKEDDOWNLOADS) { - if (host_client->download->seekingisabadplan) + if (host_client->download->seekstyle != SS_SEEKABLE) { //if seeking is a bad plan (for whatever reason - usually because of zip files) //create a temp file instead int i, len; @@ -6432,6 +6434,7 @@ int SV_PMTypeForClient (client_t *cl, edict_t *ent) } #endif +#ifndef NOLEGACY if (sv_brokenmovetypes.value) //this is to mimic standard qw servers, which don't support movetypes other than MOVETYPE_FLY. { //it prevents bugs from being visible in unsuspecting mods. if (cl && cl->spectator) @@ -6445,6 +6448,7 @@ int SV_PMTypeForClient (client_t *cl, edict_t *ent) return PM_DEAD; return PM_NORMAL; } +#endif switch((int)ent->v->movetype) { @@ -8172,7 +8176,9 @@ void SV_UserInit (void) Cvar_Register (&votepercent, sv_votinggroup); Cvar_Register (&votetime, sv_votinggroup); +#ifndef NOLEGACY Cvar_Register (&sv_brokenmovetypes, "Backwards compatability"); +#endif Cvar_Register (&sv_edgefriction, "netquake compatability"); } diff --git a/engine/server/world.c b/engine/server/world.c index b96d9075..b137e73a 100644 --- a/engine/server/world.c +++ b/engine/server/world.c @@ -1863,6 +1863,59 @@ boxmaxs[0] = boxmaxs[1] = boxmaxs[2] = 9999; #endif } +#if !defined(CLIENTONLY) +qboolean SV_AntiKnockBack(world_t *w, client_t *client) +{ + int seq = client->netchan.incoming_acknowledged; //our outgoing sequence that was last acked (in qw, this matches the last known-good input frame) + client_frame_t *frame; + edict_t *ent = client->edict; + if (client->protocol != SCP_QUAKEWORLD || !client->frameunion.frames || !ent) + return false; //FIXME: support nq protocols too + + //reload player state from the journal (the input frame should already have been applied) + frame = &client->frameunion.frames[seq&UPDATE_MASK]; + VectorCopy(frame->pmorigin, pmove.origin); + VectorCopy(frame->pmvelocity, pmove.velocity); + pmove.pm_type = frame->pmtype; + pmove.jump_held = frame->pmjumpheld; + pmove.waterjumptime = frame->pmwaterjumptime; + pmove.onladder = frame->pmonladder; + + //stuff not regenerated properly, shouldn't really be changing much or not very significant. + pmove.world = w; + VectorCopy(ent->v->mins, pmove.player_mins); + VectorCopy(ent->v->maxs, pmove.player_maxs); + pmove.capsule = (ent->xv->geomtype == GEOMTYPE_CAPSULE); + if (ent->xv->gravitydir[2] || ent->xv->gravitydir[1] || ent->xv->gravitydir[0]) + VectorCopy(ent->xv->gravitydir, pmove.gravitydir); + else + VectorCopy(w->g.defaultgravitydir, pmove.gravitydir); + + //FIXME + VectorCopy(ent->v->oldorigin, pmove.safeorigin); + pmove.safeorigin_known = false; + pmove.jump_msec = 0; + VectorClear(pmove.basevelocity); + + //and apply each more recent frame + while (++seq <= client->netchan.incoming_sequence) + { + if (frame->sequence != seq) + continue; //FIXME: lost + + pmove.sequence = seq; + pmove.cmd = frame->cmd; + +// pmove.angles; + +// pmove.numphysent/physents; + + PM_PlayerMove(sv.gamespeed); + } + return true; +} +#endif + /* ================== SV_Move @@ -1911,6 +1964,22 @@ trace_t World_Move (world_t *w, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t e } #endif +#if !defined(CLIENTONLY) + //figure out where the firing player was, and re-run their input frames to calculate their position without any velocity/knockback changes. + //then update the start position to compensate. + if ((clip.type & MOVE_LAGGED) && w == &sv.world && passedict->entnum && passedict->entnum <= sv.allocated_client_slots && sv_antilag.ival==3) + { + vec3_t nudge; + if (SV_AntiKnockBack(w, &svs.clients[passedict->entnum-1])) + { + VectorSubtract(pmove.origin, passedict->v->origin, nudge); + + VectorAdd(start, nudge, start); + VectorAdd(end, nudge, end); + } + } +#endif + if (passedict->xv->hitcontentsmaski) clip.hitcontentsmask = passedict->xv->hitcontentsmaski; /*#ifndef NOLEGACY diff --git a/engine/shaders/glsl/rtlight.glsl b/engine/shaders/glsl/rtlight.glsl index 915f5b97..729e9727 100644 --- a/engine/shaders/glsl/rtlight.glsl +++ b/engine/shaders/glsl/rtlight.glsl @@ -240,7 +240,7 @@ void main () #define tcbase tcoffsetmap #endif #if defined(FLAT) - vec4 bases = vec3(FLAT, 1.0); + vec4 bases = vec4(FLAT, FLAT, FLAT, 1.0); #else vec4 bases = texture2D(s_diffuse, tcbase); #ifdef VERTEXCOLOURS diff --git a/engine/vk/vk_init.c b/engine/vk/vk_init.c index 12e9eed0..a889d01d 100644 --- a/engine/vk/vk_init.c +++ b/engine/vk/vk_init.c @@ -155,7 +155,8 @@ static void VK_DestroySwapChain(void) Sys_WaitOnThread(vk.submitthread); vk.submitthread = NULL; } - vk.dopresent(NULL); + if (vk.dopresent) + vk.dopresent(NULL); while (vk.aquirenext < vk.aquirelast) { VkWarnAssert(vkWaitForFences(vk.device, 1, &vk.acquirefences[vk.aquirenext%ACQUIRELIMIT], VK_FALSE, UINT64_MAX)); @@ -199,7 +200,8 @@ static void VK_DestroySwapChain(void) VK_DestroyVkTexture(&vk.backbufs[i].depth); } - vk.dopresent(NULL); + if (vk.dopresent) + vk.dopresent(NULL); while (vk.aquirenext < vk.aquirelast) { VkWarnAssert(vkWaitForFences(vk.device, 1, &vk.acquirefences[vk.aquirenext%ACQUIRELIMIT], VK_FALSE, UINT64_MAX)); @@ -249,13 +251,78 @@ static qboolean VK_CreateSwapChain(void) uint32_t i, curpri; VkSwapchainKHR newvkswapchain; VkImage *images; + VkImage *memories; VkImageView attachments[2]; VkFramebufferCreateInfo fb_info = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; vk.dopresent(NULL); //make sure they're all pushed through. - if (!vk.surface) - return true; + if (vk.headless) + { + if (vk.swapchain || vk.backbuf_count) + VK_DestroySwapChain(); + + vk.backbufformat = vid_srgb.ival?VK_FORMAT_B8G8R8A8_SRGB:VK_FORMAT_B8G8R8A8_UNORM; + vk.backbuf_count = 4; + + swapinfo.imageExtent.width = vid.pixelwidth; + swapinfo.imageExtent.height = vid.pixelheight; + + images = malloc(sizeof(VkImage)*vk.backbuf_count); + memset(images, 0, sizeof(VkImage)*vk.backbuf_count); + memories = malloc(sizeof(VkDeviceMemory)*vk.backbuf_count); + memset(memories, 0, sizeof(VkDeviceMemory)*vk.backbuf_count); + + vk.aquirelast = vk.aquirenext = 0; + for (i = 0; i < ACQUIRELIMIT; i++) + { + VkFenceCreateInfo fci = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; + fci.flags = VK_FENCE_CREATE_SIGNALED_BIT; + VkAssert(vkCreateFence(vk.device,&fci,vkallocationcb,&vk.acquirefences[i])); + + vk.acquirebufferidx[vk.aquirelast%ACQUIRELIMIT] = vk.aquirelast%vk.backbuf_count; + vk.aquirelast++; + } + + for (i = 0; i < vk.backbuf_count; i++) + { + VkMemoryRequirements mem_reqs; + VkMemoryAllocateInfo memAllocInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + VkImageCreateInfo ici = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + + ici.flags = 0; + ici.imageType = VK_IMAGE_TYPE_2D; + ici.format = vk.backbufformat; + ici.extent.width = vid.pixelwidth; + ici.extent.height = vid.pixelheight; + ici.extent.depth = 1; + ici.mipLevels = 1; + ici.arrayLayers = 1; + ici.samples = VK_SAMPLE_COUNT_1_BIT; + ici.tiling = VK_IMAGE_TILING_OPTIMAL; + ici.usage = VK_IMAGE_USAGE_SAMPLED_BIT|VK_IMAGE_USAGE_TRANSFER_SRC_BIT|VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + ici.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + ici.queueFamilyIndexCount = 0; + ici.pQueueFamilyIndices = NULL; + ici.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAssert(vkCreateImage(vk.device, &ici, vkallocationcb, &images[i])); + + vkGetImageMemoryRequirements(vk.device, images[i], &mem_reqs); + + memAllocInfo.allocationSize = mem_reqs.size; + memAllocInfo.memoryTypeIndex = vk_find_memory_try(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT|VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (memAllocInfo.memoryTypeIndex == ~0) + memAllocInfo.memoryTypeIndex = vk_find_memory_try(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (memAllocInfo.memoryTypeIndex == ~0) + memAllocInfo.memoryTypeIndex = vk_find_memory_try(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + if (memAllocInfo.memoryTypeIndex == ~0) + memAllocInfo.memoryTypeIndex = vk_find_memory_require(mem_reqs.memoryTypeBits, 0); + + VkAssert(vkAllocateMemory(vk.device, &memAllocInfo, vkallocationcb, &memories[i])); + VkAssert(vkBindImageMemory(vk.device, images[i], memories[i], 0)); + } + } else { VkAssert(vkGetPhysicalDeviceSurfaceFormatsKHR(vk.gpu, vk.surface, &fmtcount, NULL)); @@ -267,129 +334,130 @@ static qboolean VK_CreateSwapChain(void) VkAssert(vkGetPhysicalDeviceSurfacePresentModesKHR(vk.gpu, vk.surface, &presentmodes, presentmode)); vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vk.gpu, vk.surface, &surfcaps); - } - swapinfo.surface = vk.surface; - swapinfo.minImageCount = surfcaps.minImageCount+vk.triplebuffer; - if (swapinfo.minImageCount > surfcaps.maxImageCount) - swapinfo.minImageCount = surfcaps.maxImageCount; - if (swapinfo.minImageCount < surfcaps.minImageCount) - swapinfo.minImageCount = surfcaps.minImageCount; - swapinfo.imageExtent.width = surfcaps.currentExtent.width; - swapinfo.imageExtent.height = surfcaps.currentExtent.height; - swapinfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT|VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - swapinfo.preTransform = surfcaps.currentTransform;//VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; - if (surfcaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) - swapinfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - else if (surfcaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR) - swapinfo.compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR; - else if (surfcaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR) - swapinfo.compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR; - else - swapinfo.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; //erk? - swapinfo.imageArrayLayers = /*(r_stereo_method.ival==1)?2:*/1; - swapinfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; - swapinfo.queueFamilyIndexCount = 0; - swapinfo.pQueueFamilyIndices = NULL; - swapinfo.oldSwapchain = vk.swapchain; - swapinfo.clipped = vid_isfullscreen?VK_FALSE:VK_TRUE; //allow fragment shaders to be skipped on parts that are obscured by another window. screenshots might get weird, so use proper captures if required/automagic. + swapinfo.surface = vk.surface; + swapinfo.minImageCount = surfcaps.minImageCount+vk.triplebuffer; + if (swapinfo.minImageCount > surfcaps.maxImageCount) + swapinfo.minImageCount = surfcaps.maxImageCount; + if (swapinfo.minImageCount < surfcaps.minImageCount) + swapinfo.minImageCount = surfcaps.minImageCount; + swapinfo.imageExtent.width = surfcaps.currentExtent.width; + swapinfo.imageExtent.height = surfcaps.currentExtent.height; + swapinfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT|VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + swapinfo.preTransform = surfcaps.currentTransform;//VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + if (surfcaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) + swapinfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + else if (surfcaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR) + swapinfo.compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR; + else if (surfcaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR) + swapinfo.compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR; + else + swapinfo.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; //erk? + swapinfo.imageArrayLayers = /*(r_stereo_method.ival==1)?2:*/1; + swapinfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + swapinfo.queueFamilyIndexCount = 0; + swapinfo.pQueueFamilyIndices = NULL; + swapinfo.oldSwapchain = vk.swapchain; + swapinfo.clipped = vid_isfullscreen?VK_FALSE:VK_TRUE; //allow fragment shaders to be skipped on parts that are obscured by another window. screenshots might get weird, so use proper captures if required/automagic. - swapinfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; //supposed to be guarenteed support. - for (i = 0, curpri = 0; i < presentmodes; i++) - { - uint32_t priority = 0; - switch(presentmode[i]) + swapinfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; //supposed to be guarenteed support. + for (i = 0, curpri = 0; i < presentmodes; i++) { - default://ignore it. - break; - case VK_PRESENT_MODE_IMMEDIATE_KHR: - priority = (vk.vsync?0:2) + 2; //for most quake players, latency trumps tearing. - break; - case VK_PRESENT_MODE_MAILBOX_KHR: - priority = (vk.vsync?0:2) + 1; - break; - case VK_PRESENT_MODE_FIFO_KHR: - priority = (vk.vsync?2:0) + 1; - break; - case VK_PRESENT_MODE_FIFO_RELAXED_KHR: - priority = (vk.vsync?2:0) + 2; //strict vsync results in weird juddering if rtlights etc caues framerates to drop below the refreshrate - break; + uint32_t priority = 0; + switch(presentmode[i]) + { + default://ignore it. + break; + case VK_PRESENT_MODE_IMMEDIATE_KHR: + priority = (vk.vsync?0:2) + 2; //for most quake players, latency trumps tearing. + break; + case VK_PRESENT_MODE_MAILBOX_KHR: + priority = (vk.vsync?0:2) + 1; + break; + case VK_PRESENT_MODE_FIFO_KHR: + priority = (vk.vsync?2:0) + 1; + break; + case VK_PRESENT_MODE_FIFO_RELAXED_KHR: + priority = (vk.vsync?2:0) + 2; //strict vsync results in weird juddering if rtlights etc caues framerates to drop below the refreshrate + break; + } + if (priority > curpri) + { + curpri = priority; + swapinfo.presentMode = presentmode[i]; + } } - if (priority > curpri) + + swapinfo.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; + swapinfo.imageFormat = vid_srgb.ival?VK_FORMAT_B8G8R8A8_SRGB:VK_FORMAT_B8G8R8A8_UNORM; + for (i = 0, curpri = 0; i < fmtcount; i++) { - curpri = priority; - swapinfo.presentMode = presentmode[i]; + uint32_t priority = 0; + switch(surffmts[i].format) + { + case VK_FORMAT_B8G8R8A8_UNORM: + case VK_FORMAT_R8G8B8A8_UNORM: + priority = 4+!vid_srgb.ival; + break; + case VK_FORMAT_B8G8R8A8_SRGB: + case VK_FORMAT_R8G8B8A8_SRGB: + priority = 4+!!vid_srgb.ival; + break; + case VK_FORMAT_R16G16B16A16_SFLOAT: //16bit per-channel formats + case VK_FORMAT_R16G16B16A16_SNORM: + priority = 3; + break; + case VK_FORMAT_R32G32B32A32_SFLOAT: //32bit per-channel formats + priority = 2; + break; + default: //16 bit formats (565). + priority = 1; + break; + } + if (priority > curpri) + { + curpri = priority; + swapinfo.imageColorSpace = surffmts[i].colorSpace; + swapinfo.imageFormat = surffmts[i].format; + } } - } - swapinfo.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; - swapinfo.imageFormat = vid_srgb.ival?VK_FORMAT_B8G8R8A8_SRGB:VK_FORMAT_B8G8R8A8_UNORM; - for (i = 0, curpri = 0; i < fmtcount; i++) - { - uint32_t priority = 0; - switch(surffmts[i].format) + if (vk.backbufformat != swapinfo.imageFormat) { - case VK_FORMAT_B8G8R8A8_UNORM: - case VK_FORMAT_R8G8B8A8_UNORM: - priority = 4+!vid_srgb.ival; - break; - case VK_FORMAT_B8G8R8A8_SRGB: - case VK_FORMAT_R8G8B8A8_SRGB: - priority = 4+!!vid_srgb.ival; - break; - case VK_FORMAT_R16G16B16A16_SFLOAT: //16bit per-channel formats - case VK_FORMAT_R16G16B16A16_SNORM: - priority = 3; - break; - case VK_FORMAT_R32G32B32A32_SFLOAT: //32bit per-channel formats - priority = 2; - break; - default: //16 bit formats (565). - priority = 1; - break; + VK_DestroyRenderPass(); + reloadshaders = true; } - if (priority > curpri) + vk.backbufformat = swapinfo.imageFormat; + + free(presentmode); + free(surffmts); + + VkAssert(vkCreateSwapchainKHR(vk.device, &swapinfo, vkallocationcb, &newvkswapchain)); + if (!newvkswapchain) + return false; + if (vk.swapchain) { - curpri = priority; - swapinfo.imageColorSpace = surffmts[i].colorSpace; - swapinfo.imageFormat = surffmts[i].format; + VK_DestroySwapChain(); } - } + vk.swapchain = newvkswapchain; - if (vk.backbufformat != swapinfo.imageFormat) - { - VK_DestroyRenderPass(); - reloadshaders = true; - } - vk.backbufformat = swapinfo.imageFormat; + VkAssert(vkGetSwapchainImagesKHR(vk.device, vk.swapchain, &vk.backbuf_count, NULL)); + images = malloc(sizeof(VkImage)*vk.backbuf_count); + memories = NULL; + VkAssert(vkGetSwapchainImagesKHR(vk.device, vk.swapchain, &vk.backbuf_count, images)); - free(presentmode); - free(surffmts); - - VkAssert(vkCreateSwapchainKHR(vk.device, &swapinfo, vkallocationcb, &newvkswapchain)); - if (!newvkswapchain) - return false; - if (vk.swapchain) - { - VK_DestroySwapChain(); - } - vk.swapchain = newvkswapchain; - - VkAssert(vkGetSwapchainImagesKHR(vk.device, vk.swapchain, &vk.backbuf_count, NULL)); - images = malloc(sizeof(VkImage)*vk.backbuf_count); - VkAssert(vkGetSwapchainImagesKHR(vk.device, vk.swapchain, &vk.backbuf_count, images)); - - vk.aquirelast = vk.aquirenext = 0; - for (i = 0; i < ACQUIRELIMIT; i++) - { - VkFenceCreateInfo fci = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; - VkAssert(vkCreateFence(vk.device,&fci,vkallocationcb,&vk.acquirefences[i])); - } - /*-1 to hide any weird thread issues*/ - while (vk.aquirelast < ACQUIRELIMIT-1 && vk.aquirelast < vk.backbuf_count && vk.aquirelast <= vk.backbuf_count-surfcaps.minImageCount) - { - VkAssert(vkAcquireNextImageKHR(vk.device, vk.swapchain, UINT64_MAX, VK_NULL_HANDLE, vk.acquirefences[vk.aquirelast%ACQUIRELIMIT], &vk.acquirebufferidx[vk.aquirelast%ACQUIRELIMIT])); - vk.aquirelast++; + vk.aquirelast = vk.aquirenext = 0; + for (i = 0; i < ACQUIRELIMIT; i++) + { + VkFenceCreateInfo fci = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; + VkAssert(vkCreateFence(vk.device,&fci,vkallocationcb,&vk.acquirefences[i])); + } + /*-1 to hide any weird thread issues*/ + while (vk.aquirelast < ACQUIRELIMIT-1 && vk.aquirelast < vk.backbuf_count && vk.aquirelast <= vk.backbuf_count-surfcaps.minImageCount) + { + VkAssert(vkAcquireNextImageKHR(vk.device, vk.swapchain, UINT64_MAX, VK_NULL_HANDLE, vk.acquirefences[vk.aquirelast%ACQUIRELIMIT], &vk.acquirebufferidx[vk.aquirelast%ACQUIRELIMIT])); + vk.aquirelast++; + } } VK_CreateRenderPass(); @@ -415,7 +483,7 @@ static qboolean VK_CreateSwapChain(void) for (i = 0; i < vk.backbuf_count; i++) { VkImageViewCreateInfo ivci = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; - ivci.format = swapinfo.imageFormat; + ivci.format = vk.backbufformat; ivci.components.r = VK_COMPONENT_SWIZZLE_R; ivci.components.g = VK_COMPONENT_SWIZZLE_G; ivci.components.b = VK_COMPONENT_SWIZZLE_B; @@ -429,6 +497,10 @@ static qboolean VK_CreateSwapChain(void) ivci.flags = 0; ivci.image = images[i]; vk.backbufs[i].colour.image = images[i]; + if (memories) + vk.backbufs[i].colour.memory = memories[i]; + vk.backbufs[i].colour.width = swapinfo.imageExtent.width; + vk.backbufs[i].colour.height = swapinfo.imageExtent.height; VkAssert(vkCreateImageView(vk.device, &ivci, vkallocationcb, &vk.backbufs[i].colour.view)); vk.backbufs[i].firstuse = true; @@ -492,6 +564,7 @@ static qboolean VK_CreateSwapChain(void) } } free(images); + free(memories); vid.pixelwidth = swapinfo.imageExtent.width; vid.pixelheight = swapinfo.imageExtent.height; @@ -1873,14 +1946,100 @@ void VK_R_RenderView (void) vk.sourcecolour = r_nulltex; } -char *VKVID_GetRGBInfo (int *truevidwidth, int *truevidheight, enum uploadfmt *fmt) + +typedef struct +{ + struct vk_fencework w; + + uint32_t imageformat; + uint32_t imagestride; + uint32_t imagewidth; + uint32_t imageheight; + VkBuffer buffer; + size_t memsize; + VkDeviceMemory memory; + void (*gotrgbdata) (void *rgbdata, intptr_t bytestride, size_t width, size_t height, enum uploadfmt fmt); +} vkscreencapture_t; + +static void VKVID_CopiedRGBData (void*ctx) +{ //some fence got hit, we did our copy, data is now cpu-visible, cache-willing. + vkscreencapture_t *capt = ctx; + void *imgdata; + VkAssert(vkMapMemory(vk.device, capt->memory, 0, capt->memsize, 0, &imgdata)); + capt->gotrgbdata(imgdata, capt->imagestride, capt->imagewidth, capt->imageheight, capt->imageformat); + vkUnmapMemory(vk.device, capt->memory); + vkDestroyBuffer(vk.device, capt->buffer, vkallocationcb); + vkFreeMemory(vk.device, capt->memory, vkallocationcb); +} +void VKVID_QueueGetRGBData (void (*gotrgbdata) (void *rgbdata, intptr_t bytestride, size_t width, size_t height, enum uploadfmt fmt)) +{ + //should be half way through rendering + vkscreencapture_t *capt; + + VkBufferImageCopy icpy; + VkImageSubresource subres = {0}; + + VkMemoryRequirements mem_reqs; + VkMemoryAllocateInfo memAllocInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + VkBufferCreateInfo bci = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; + + + if (!VK_SCR_GrabBackBuffer()) + return; + + if (!vk.frame->backbuf->colour.width || !vk.frame->backbuf->colour.height) + return; //erm, some kind of error? + + capt = VK_AtFrameEnd(VKVID_CopiedRGBData, sizeof(*capt)); + capt->gotrgbdata = gotrgbdata; + + capt->imageformat = TF_BGRA32; + capt->imagestride = vk.frame->backbuf->colour.width*4; //vulkan is top-down, so this should be positive. + capt->imagewidth = vk.frame->backbuf->colour.width; + capt->imageheight = vk.frame->backbuf->colour.height; + + bci.flags = 0; + bci.size = capt->memsize = capt->imagewidth*capt->imageheight*4; + bci.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT; + bci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + bci.queueFamilyIndexCount = 0; + bci.pQueueFamilyIndices = NULL; + + VkAssert(vkCreateBuffer(vk.device, &bci, vkallocationcb, &capt->buffer)); + vkGetBufferMemoryRequirements(vk.device, capt->buffer, &mem_reqs); + memAllocInfo.allocationSize = mem_reqs.size; + memAllocInfo.memoryTypeIndex = vk_find_memory_require(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + VkAssert(vkAllocateMemory(vk.device, &memAllocInfo, vkallocationcb, &capt->memory)); + VkAssert(vkBindBufferMemory(vk.device, capt->buffer, capt->memory, 0)); + + set_image_layout(vk.frame->cbuf, vk.frame->backbuf->colour.image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_TRANSFER_READ_BIT); + + icpy.bufferOffset = 0; + icpy.bufferRowLength = 0; //packed + icpy.bufferImageHeight = 0; //packed + icpy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + icpy.imageSubresource.mipLevel = 0; + icpy.imageSubresource.baseArrayLayer = 0; + icpy.imageSubresource.layerCount = 1; + icpy.imageOffset.x = 0; + icpy.imageOffset.y = 0; + icpy.imageOffset.z = 0; + icpy.imageExtent.width = capt->imagewidth; + icpy.imageExtent.height = capt->imageheight; + icpy.imageExtent.depth = 1; + + vkCmdCopyImageToBuffer(vk.frame->cbuf, vk.frame->backbuf->colour.image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, capt->buffer, 1, &icpy); + + set_image_layout(vk.frame->cbuf, vk.frame->backbuf->colour.image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_TRANSFER_READ_BIT, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT); +} + +char *VKVID_GetRGBInfo (int *bytestride, int *truevidwidth, int *truevidheight, enum uploadfmt *fmt) { //with vulkan, we need to create a staging image to write into, submit a copy, wait for completion, map the copy, copy that out, free the staging. //its enough to make you pitty anyone that writes opengl drivers. if (VK_SCR_GrabBackBuffer()) { void *imgdata, *outdata; - uint32_t y; struct vk_fencework *fence = VK_FencedBegin(NULL, 0); VkImageCopy icpy; VkImage tempimage; @@ -1944,8 +2103,9 @@ char *VKVID_GetRGBInfo (int *truevidwidth, int *truevidheight, enum uploadfmt VK_FencedSync(fence); - outdata = BZ_Malloc(4*vid.pixelwidth*vid.pixelheight); + outdata = BZ_Malloc(4*vid.pixelwidth*vid.pixelheight); //FIXME: this sucks. *fmt = PTI_BGRA8; + *bytestride = vid.pixelwidth*4; *truevidwidth = vid.pixelwidth; *truevidheight = vid.pixelheight; @@ -1954,8 +2114,7 @@ char *VKVID_GetRGBInfo (int *truevidwidth, int *truevidheight, enum uploadfmt subres.arrayLayer = 0; vkGetImageSubresourceLayout(vk.device, tempimage, &subres, &layout); VkAssert(vkMapMemory(vk.device, tempmemory, 0, mem_reqs.size, 0, &imgdata)); - for (y = 0; y < vid.pixelheight; y++) - memcpy((char*)outdata + (vid.pixelheight-1-y)*vid.pixelwidth*4, (char*)imgdata + layout.offset + y*layout.rowPitch, vid.pixelwidth*4); + memcpy(outdata, imgdata, 4*vid.pixelwidth*vid.pixelheight); vkUnmapMemory(vk.device, tempmemory); vkDestroyImage(vk.device, tempimage, vkallocationcb); @@ -2121,13 +2280,6 @@ qboolean VK_SCR_GrabBackBuffer(void) VK_FencedCheck(); - if (!vk.surface) - { -// if (Media_Capturing() != 2) -// Cmd_ExecuteString("quit force\n", RESTRICT_LOCAL); - return false; //headless... - } - if (!vk.unusedframes) { struct vkframe *newframe = Z_Malloc(sizeof(*vk.frame)); @@ -2139,6 +2291,9 @@ qboolean VK_SCR_GrabBackBuffer(void) while (vk.aquirenext == vk.aquirelast) { //we're still waiting for the render thread to increment acquirelast. Sys_Sleep(0); //o.O +#ifdef _WIN32 + Sys_SendKeyEvents(); +#endif } //wait for the queued acquire to actually finish @@ -2332,12 +2487,13 @@ void VK_DebugFramerate(void) qboolean VK_SCR_UpdateScreen (void) { + uint32_t fblayout; VkCommandBuffer bufs[1]; VK_FencedCheck(); //a few cvars need some extra work if they're changed - if (vk_submissionthread.modified || vid_vsync.modified || vid_triplebuffer.modified || vid_srgb.modified) + if ((vk.allowsubmissionthread && vk_submissionthread.modified) || vid_vsync.modified || vid_triplebuffer.modified || vid_srgb.modified) { vid_vsync.modified = false; vid_triplebuffer.modified = false; @@ -2371,7 +2527,7 @@ qboolean VK_SCR_UpdateScreen (void) VK_CreateSwapChain(); vk.neednewswapchain = false; - if (vk_submissionthread.ival || !*vk_submissionthread.string) + if (vk.allowsubmissionthread && (vk_submissionthread.ival || !*vk_submissionthread.string)) { vk.submitthread = Sys_CreateThread("vksubmission", VK_Submit_Thread, NULL, THREADP_HIGHEST, 0); } @@ -2390,12 +2546,49 @@ qboolean VK_SCR_UpdateScreen (void) vkCmdEndRenderPass(vk.frame->cbuf); + fblayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + /*if (0) + { + vkscreencapture_t *capt = VK_AtFrameEnd(atframeend, sizeof(vkscreencapture_t)); + VkImageMemoryBarrier imgbarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; + VkBufferImageCopy region; + imgbarrier.pNext = NULL; + imgbarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + imgbarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + imgbarrier.oldLayout = fblayout; + imgbarrier.newLayout = fblayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + imgbarrier.image = vk.frame->backbuf->colour.image; + imgbarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imgbarrier.subresourceRange.baseMipLevel = 0; + imgbarrier.subresourceRange.levelCount = 1; + imgbarrier.subresourceRange.baseArrayLayer = 0; + imgbarrier.subresourceRange.layerCount = 1; + imgbarrier.srcQueueFamilyIndex = vk.queuefam[0]; + imgbarrier.dstQueueFamilyIndex = vk.queuefam[0]; + vkCmdPipelineBarrier(vk.frame->cbuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 1, &imgbarrier); + + region.bufferOffset = 0; + region.bufferRowLength = 0; //tightly packed + region.bufferImageHeight = 0; //tightly packed + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset.x = 0; + region.imageOffset.y = 0; + region.imageOffset.z = 0; + region.imageExtent.width = capt->imagewidth = vk.frame->backbuf->colour.width; + region.imageExtent.height = capt->imageheight = vk.frame->backbuf->colour.height; + region.imageExtent.depth = 1; + vkCmdCopyImageToBuffer(vk.frame->cbuf, vk.frame->backbuf->colour.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer, 1, ®ion); + }*/ + { VkImageMemoryBarrier imgbarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; imgbarrier.pNext = NULL; - imgbarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + imgbarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT|VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; imgbarrier.dstAccessMask = 0; - imgbarrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + imgbarrier.oldLayout = fblayout; imgbarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; imgbarrier.image = vk.frame->backbuf->colour.image; imgbarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; @@ -2758,14 +2951,15 @@ qboolean VK_Init(rendererstate_t *info, const char *sysextname, qboolean (*creat const char *extensions[8]; qboolean nvglsl = false; uint32_t extensions_count = 0; - qboolean headless = false; + vk.allowsubmissionthread = true; + vk.headless = false; if (sysextname) { extensions[extensions_count++] = sysextname; extensions[extensions_count++] = VK_KHR_SURFACE_EXTENSION_NAME; } else - headless = true; + vk.headless = true; if (vk_debug.ival) extensions[extensions_count++] = VK_EXT_DEBUG_REPORT_EXTENSION_NAME; @@ -2896,7 +3090,7 @@ qboolean VK_Init(rendererstate_t *info, const char *sysextname, qboolean (*creat vkGetPhysicalDeviceProperties(devs[i], &props); vkGetPhysicalDeviceQueueFamilyProperties(devs[i], &queue_count, NULL); - if (!headless) + if (!vk.headless) { for (j = 0; j < queue_count; j++) { @@ -3045,7 +3239,7 @@ qboolean VK_Init(rendererstate_t *info, const char *sysextname, qboolean (*creat for (i = 0; i < queue_count; i++) { VkBool32 supportsPresent = false; - if (headless) + if (vk.headless) supportsPresent = true; //won't be used anyway. else VkAssert(vkGetPhysicalDeviceSurfaceSupportKHR(vk.gpu, i, vk.surface, &supportsPresent)); @@ -3124,7 +3318,8 @@ qboolean VK_Init(rendererstate_t *info, const char *sysextname, qboolean (*creat else { queueinf[0].queueCount = 1; - vk.dopresent = VK_DoPresent; //can't split submit+present onto different queues, so do these on a single thread. + if (!vk.headless) + vk.dopresent = VK_DoPresent; //can't split submit+present onto different queues, so do these on a single thread. Con_DPrintf("Using single queue\n"); } } @@ -3242,7 +3437,7 @@ qboolean VK_Init(rendererstate_t *info, const char *sysextname, qboolean (*creat { vk.neednewswapchain = false; - if (vk_submissionthread.ival || !*vk_submissionthread.string) + if (vk.allowsubmissionthread && (vk_submissionthread.ival || !*vk_submissionthread.string)) { vk.submitthread = Sys_CreateThread("vksubmission", VK_Submit_Thread, NULL, THREADP_HIGHEST, 0); } diff --git a/engine/vk/vkrenderer.h b/engine/vk/vkrenderer.h index 868c6e1b..99c9f7b1 100644 --- a/engine/vk/vkrenderer.h +++ b/engine/vk/vkrenderer.h @@ -83,6 +83,7 @@ VKFunc(CmdClearDepthStencilImage) \ VKFunc(CmdCopyImage) \ VKFunc(CmdCopyBuffer) \ + VKFunc(CmdCopyImageToBuffer) \ VKFunc(CmdBlitImage) \ VKFunc(CmdPipelineBarrier) \ VKFunc(CmdSetEvent) \ @@ -231,6 +232,8 @@ extern struct vulkaninfo_s { unsigned short triplebuffer; qboolean vsync; + qboolean headless; + qboolean allowsubmissionthread; VkInstance instance; VkDevice device; @@ -435,8 +438,11 @@ void VK_R_Init (void); void VK_R_DeInit (void); void VK_R_RenderView (void); -char *VKVID_GetRGBInfo (int *truevidwidth, int *truevidheight, enum uploadfmt *fmt); +char *VKVID_GetRGBInfo (int *bytestride, int *truevidwidth, int *truevidheight, enum uploadfmt *fmt); qboolean VK_SCR_UpdateScreen (void); void VKBE_RenderToTextureUpdate2d(qboolean destchanged); + +//improved rgb get that calls the callback when the data is actually available. used for video capture. +void VKVID_QueueGetRGBData (void (*gotrgbdata) (void *rgbdata, qintptr_t bytestride, size_t width, size_t height, enum uploadfmt fmt)); diff --git a/engine/web/ftejslib.h b/engine/web/ftejslib.h index f8143e17..84ef49a6 100644 --- a/engine/web/ftejslib.h +++ b/engine/web/ftejslib.h @@ -1,5 +1,5 @@ //emscripten's download mechanism lacks usable progress indicators. -void emscriptenfte_async_wget_data2(const char *url, void *ctx, void (*onload)(void*ctx,void*buf,int sz), void (*onerror)(void*ctx,int code), void (*onprogress)(void*ctx,int prog,int total)); +void emscriptenfte_async_wget_data2(const char *url, void *ctx, void (*onload)(void*ctx,int buf), void (*onerror)(void*ctx,int code), void (*onprogress)(void*ctx,int prog,int total)); //changes the page away from quake (oh noes!) or downloads something. void emscriptenfte_window_location(const char *url); @@ -18,11 +18,15 @@ int emscriptenfte_buf_read(int handle, int offset, void *data, int len);//read d int emscriptenfte_buf_write(int handle, int offset, const void *data, int len);//write data. no access checks. //websocket is implemented in javascript because there is no usable C api (emscripten's javascript implementation is shite and has fatal errors). -int emscriptenfte_ws_connect(const char *url); //open a websocket connect to a specific host -void emscriptenfte_ws_close(int sockid); //close it again +int emscriptenfte_ws_connect(const char *url, const char *wsprotocol); //open a websocket connection to a specific host +void emscriptenfte_ws_close(int sockid); //close it again int emscriptenfte_ws_cansend(int sockid, int extra, int maxpending); //returns false if we're blocking for some reason. avoids overflowing. everything is otherwise reliable. -int emscriptenfte_ws_send(int sockid, const void *data, int len); //send data to the peer. queues data. never dropped. -int emscriptenfte_ws_recv(int sockid, void *data, int len); //receive data from the peer. +int emscriptenfte_ws_send(int sockid, const void *data, int len); //send data to the peer. queues data. never dropped. +int emscriptenfte_ws_recv(int sockid, void *data, int len); //receive data from the peer. + +int emscriptenfte_rtc_create(int clientside, void *ctxp, int ctxi, void(*cb)(void *ctxp, int ctxi, int type, const char *data)); //open a webrtc connection to a specific broker url +void emscriptenfte_rtc_offer(int sock, const char *offer, const char *sdptype);//sets the remote sdp. +void emscriptenfte_rtc_candidate(int sock, const char *offer); //adds a remote candidate. //misc stuff for printf replacements void emscriptenfte_alert(const char *msg); diff --git a/engine/web/ftejslib.js b/engine/web/ftejslib.js index a1bd4ce8..69ec16e5 100644 --- a/engine/web/ftejslib.js +++ b/engine/web/ftejslib.js @@ -56,8 +56,9 @@ mergeInto(LibraryManager.library, emscriptenfte_buf_createfromarraybuf__deps : ['emscriptenfte_handle_alloc'], emscriptenfte_buf_createfromarraybuf : function(buf) { + buf = new Uint8Array(buf); var len = buf.length; - var b = {h:-1, r:1, l:len,m:len,d:new Uint8Array(buf), n:null}; + var b = {h:-1, r:1, l:len,m:len,d:buf, n:null}; b.h = _emscriptenfte_handle_alloc(b); return b.h; }, @@ -69,6 +70,7 @@ mergeInto(LibraryManager.library, pointerislocked:0, pointerwantlock:0, linebuffer:'', + localstorefailure:false, w: -1, h: -1, donecb:0, @@ -400,6 +402,8 @@ mergeInto(LibraryManager.library, emscriptenfte_setupcanvas__deps: ['$FTEC', '$Browser', 'emscriptenfte_buf_createfromarraybuf'], emscriptenfte_setupcanvas : function(nw,nh,evresize,evmouse,evmbutton,evkey,evfile,evjbutton,evjaxis,evwantfullscreen) { + try + { FTEC.evcb.resize = evresize; FTEC.evcb.mouse = evmouse; FTEC.evcb.button = evmbutton; @@ -499,6 +503,10 @@ mergeInto(LibraryManager.library, } _emscriptenfte_updatepointerlock(false, false); + } catch(e) + { + console.log(e); + } return 1; }, @@ -576,22 +584,35 @@ mergeInto(LibraryManager.library, var r = -1; if (f == null) { - if (window.localStorage && createifneeded != 2) + if (!FTEC.localstorefailure) { - var str = window.localStorage.getItem(name); - if (str != null) + try { -// console.log('read file '+name+': ' + str); + if (localStorage && createifneeded != 2) + { + var str = localStorage.getItem(name); + if (str != null) + { + // console.log('read file '+name+': ' + str); - var len = str.length; - var buf = new Uint8Array(len); - for (var i = 0; i < len; i++) - buf[i] = str.charCodeAt(i); + var len = str.length; + var buf = new Uint8Array(len); + for (var i = 0; i < len; i++) + buf[i] = str.charCodeAt(i); - var b = {h:-1, r:2, l:len,m:len,d:buf, n:name}; - r = b.h = _emscriptenfte_handle_alloc(b); - FTEH.f[name] = b; - return b.h; + var b = {h:-1, r:2, l:len,m:len,d:buf, n:name}; + r = b.h = _emscriptenfte_handle_alloc(b); + FTEH.f[name] = b; + return b.h; + } + } + } + catch(e) + { + console.log('exception while trying to read local storage for ' + name); + console.log(e); + console.log('disabling further attempts to access local storage'); + FTEC.localstorefailure = true; } } @@ -655,16 +676,24 @@ mergeInto(LibraryManager.library, return; var data = b.d; var len = b.l; - if (window.localStorage) + try { - var foo = ""; - //use a divide and conquer implementation instead for speed? - for (var i = 0; i < len; i++) - foo += String.fromCharCode(data[i]); - window.localStorage.setItem(b.n, foo); + if (localStorage) + { + var foo = ""; + //use a divide and conquer implementation instead for speed? + for (var i = 0; i < len; i++) + foo += String.fromCharCode(data[i]); + localStorage.setItem(b.n, foo); + } + else + console.log('local storage not supported'); + } + catch (e) + { + console.log('exception while trying to save ' + b.n); + console.log(e); } - else - console.log('local storage not supported'); }, emscriptenfte_buf_release : function(handle) { @@ -722,13 +751,19 @@ mergeInto(LibraryManager.library, }, emscriptenfte_ws_connect__deps: ['emscriptenfte_handle_alloc'], - emscriptenfte_ws_connect : function(url) + emscriptenfte_ws_connect : function(brokerurl, protocolname) { - var _url = Pointer_stringify(url); + var _url = Pointer_stringify(brokerurl); + var _protocol = Pointer_stringify(protocolname); var s = {ws:null, inq:[], err:0, con:0}; - s.ws = new WebSocket(_url, 'binary'); + try { + s.ws = new WebSocket(_url, _protocol); + } catch(err) { console.log(err); } if (s.ws === undefined) return -1; + if (s.ws == null) + return -1; + s.ws.binaryType = 'arraybuffer'; s.ws.onerror = function(event) {s.con = 0; s.err = 1;}; s.ws.onclose = function(event) {s.con = 0; s.err = 1;}; s.ws.onopen = function(event) {s.con = 1;}; @@ -745,8 +780,26 @@ mergeInto(LibraryManager.library, var s = FTEH.h[sockid]; if (s === undefined) return -1; - s.ws.close(); - s.ws = null; //make sure to avoid circular references + + s.callcb = null; + + if (s.ws != null) + { + s.ws.close(); + s.ws = null; //make sure to avoid circular references + } + + if (s.pc != null) + { + s.pc.close(); + s.pc = null; //make sure to avoid circular references + } + + if (s.broker != null) + { + s.broker.close(); + s.broker = null; //make sure to avoid circular references + } delete FTEH.h[sockid]; //socked is no longer accessible. return 0; }, @@ -763,9 +816,9 @@ mergeInto(LibraryManager.library, var s = FTEH.h[sockid]; if (s === undefined) return -1; - if (s.con == 0 || len < 1 || len > 125) + if (s.con == 0) return 0; //not connected yet - s.ws.send(HEAPU8.subarray(data, data+len).buffer); + s.ws.send(HEAPU8.subarray(data, data+len)); return len; }, emscriptenfte_ws_recv : function(sockid, data, len) @@ -786,7 +839,189 @@ mergeInto(LibraryManager.library, return 0; }, + emscriptenfte_rtc_create__deps: ['emscriptenfte_handle_alloc'], + emscriptenfte_rtc_create : function(clientside, ctxp, ctxi, callback) + { + var pcconfig = { + iceServers: + [{ + url: 'stun:stun.l.google.com:19302' + }] + }; + var dcconfig = {ordered: false, maxRetransmits: 0, reliable:false}; +console.log("emscriptenfte_rtc_create"); + + var s = {pc:null, ws:null, inq:[], err:0, con:0, isclient:clientside, callcb: + function(evtype,stringdata) + { //private helper + +console.log("emscriptenfte_rtc_create callback: " + evtype); + + var stringlen = (stringdata.length*3)+1; + var dataptr = _malloc(stringlen); + stringToUTF8(stringdata, dataptr, stringlen); + Runtime.dynCall('viiii', callback, [ctxp,ctxi,evtype,dataptr]); + _free(dataptr); + } + }; + + if (RTCPeerConnection === undefined) + { //IE or something. + console.log("RTCPeerConnection undefined"); + return -1; + } + + s.pc = new RTCPeerConnection(pcconfig); + if (s.pc === undefined) + { + console.log("webrtc failed to create RTCPeerConnection"); + return -1; + } + +//create the dataconnection + s.ws = s.pc.createDataChannel('quake', dcconfig); + s.ws.binaryType = 'arraybuffer'; + s.ws.onclose = function(event) + { +console.log("webrtc datachannel closed:") +console.log(event); + s.con = 0; + s.err = 1; + }; + s.ws.onopen = function(event) + { +console.log("webrtc datachannel opened:"); +console.log(event); + s.con = 1; + }; + s.ws.onmessage = function(event) + { +//console.log("webrtc datachannel message:"); +//console.log(event); + assert(typeof event.data !== 'string' && event.data.byteLength); + s.inq.push(new Uint8Array(event.data)); + }; + + s.pc.onicecandidate = function(e) + { +console.log("onicecandidate: "); +console.log(e); + var desc; + if (1) + desc = JSON.stringify(e.candidate); + else + desc = e.candidate.candidate; + s.callcb(4, desc); + }; + s.pc.oniceconnectionstatechange = function(e) + { +console.log("oniceconnectionstatechange: "); +console.log(e); + }; + s.pc.onaddstream = function(e) + { +console.log("onaddstream: "); +console.log(e); + }; + s.pc.ondatachannel = function(e) + { +console.log("ondatachannel: "); +console.log(e); + + s.recvchan = e.channel; + s.recvchan.binaryType = 'arraybuffer'; + s.recvchan.onmessage = s.ws.onmessage; + + }; + s.pc.onnegotiationneeded = function(e) + { +console.log("onnegotiationneeded: "); +console.log(e); + }; + + if (clientside) + { + s.pc.createOffer().then( + function(desc) + { + s.pc.setLocalDescription(desc); + console.log("gotlocaldescription: "); + console.log(desc); + + if (1) + desc = JSON.stringify(desc); + else + desc = desc.sdp; + + s.callcb(3, desc); + }, + function(event) + { + console.log("createOffer error:"); + console.log(event); + s.err = 1; + } + ); + } + + return _emscriptenfte_handle_alloc(s); + }, + emscriptenfte_rtc_offer : function(sockid, offer, offertype) + { + var s = FTEH.h[sockid]; + offer = Pointer_stringify(offer); + offertype = Pointer_stringify(offertype); + if (s === undefined) + return -1; + + if (1) + desc = JSON.parse(offer); + else + desc = {sdp:offer, type:offertype}; + + s.pc.setRemoteDescription(desc); + + if (!s.isclient) + { //server must give a response. + s.pc.createAnswer().then( + function(desc) + { + s.pc.setLocalDescription(desc); + console.log("gotlocaldescription: "); + console.log(desc); + + if (1) + desc = JSON.stringify(desc); + else + desc = desc.sdp; + + s.callcb(3, desc); + }, + function(event) + { + console.log("createAnswer error:" + event.toString()); + s.err = 1; + } + ); + } + }, + emscriptenfte_rtc_candidate : function(sockid, offer) + { + var s = FTEH.h[sockid]; + offer = Pointer_stringify(offer); + if (s === undefined) + return -1; + + var desc; + if (1) + desc = JSON.parse(offer); + else + desc = {candidate:offer, sdpMid:null, sdpMLineIndex:0}; +console.log("addIceCandidate:"); +console.log(desc); + s.pc.addIceCandidate(desc); + }, emscriptenfte_async_wget_data2 : function(url, ctx, onload, onerror, onprogress) { @@ -810,11 +1045,8 @@ mergeInto(LibraryManager.library, console.log("onload: " + _url + " status " + http.status); if (http.status == 200) { - var bar = new Uint8Array(http.response); - var buf = _malloc(bar.length); - HEAPU8.set(bar, buf); if (onload) - Runtime.dynCall('viii', onload, [ctx, buf, bar.length]); + Runtime.dynCall('vii', onload, [ctx, _emscriptenfte_buf_createfromarraybuf(http.response)]); } else { diff --git a/engine/web/gl_vidweb.c b/engine/web/gl_vidweb.c index 63d026bb..ffd9b53f 100644 --- a/engine/web/gl_vidweb.c +++ b/engine/web/gl_vidweb.c @@ -1,6 +1,7 @@ #include "quakedef.h" #include "glquake.h" #include "web/ftejslib.h" +vfsfile_t *FSWEB_OpenTempHandle(int f); extern cvar_t gl_lateswap; extern qboolean gammaworks; @@ -156,11 +157,10 @@ static void DOM_ButtonEvent(unsigned int devid, int down, int button) IN_KeyEvent(devid, down, K_MOUSE1+button, 0); } } -vfsfile_t *FSWEB_OpenTempHandle(int f); + void DOM_LoadFile(char *loc, char *mime, int handle) { vfsfile_t *file = NULL; - Con_Printf("DOM_LoadFile: %s %i\n", loc, handle); if (handle != -1) file = FSWEB_OpenTempHandle(handle); else diff --git a/engine/web/prejs.js b/engine/web/prejs.js index 3ee0c5d9..dfedfd21 100644 --- a/engine/web/prejs.js +++ b/engine/web/prejs.js @@ -1,14 +1,23 @@ { - Module['arguments'] = ['-nohome']; + if (!Module["arguments"]) + Module['arguments'] = ['-nohome']; var man = window.location.protocol+'//'+window.location.host+window.location.pathname + '.fmf'; if (window.location.hash != "") man = window.location.hash.substring(1); Module['arguments'] = Module['arguments'].concat(['-manifest', man]); + // use query string in URL as command line - if (!document.referrer) { - qstring = decodeURIComponent(window.location.search.substring(1)).split(" "); - Module['arguments'] = Module['arguments'].concat(qstring); + qstring = decodeURIComponent(window.location.search.substring(1)).split(" "); + for (var i = 0; i < qstring.length; i++) + { + if ((qstring[i] == '+sv_port_rtc' || qstring[i] == '+connect' || qstring[i] == '+join' || qstring[i] == '+observe' || qstring[i] == '+qtvplay') && i+1 < qstring.length) + { + Module['arguments'] = Module['arguments'].concat(qstring[i+0], qstring[i+1]); + i++; + } + else if (!document.referrer) + Module['arguments'] = Module['arguments'].concat(qstring[i]); } } \ No newline at end of file diff --git a/engine/web/sys_web.c b/engine/web/sys_web.c index f3e4e1c1..e41363b0 100644 --- a/engine/web/sys_web.c +++ b/engine/web/sys_web.c @@ -84,18 +84,22 @@ double Sys_DoubleTime (void) } //create a directory -void Sys_mkdir (char *path) +void Sys_mkdir (const char *path) { } +qboolean Sys_rmdir (const char *path) +{ + return true; +} //unlink a file -qboolean Sys_remove (char *path) +qboolean Sys_remove (const char *path) { emscriptenfte_buf_delete(path); return true; } -qboolean Sys_Rename (char *oldfname, char *newfname) +qboolean Sys_Rename (const char *oldfname, const char *newfname) { return emscriptenfte_buf_rename(oldfname, newfname); return false; diff --git a/plugins/Makefile b/plugins/Makefile index 23cbdb5d..eb613a05 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -115,14 +115,21 @@ irc-clean: #linux users are expected to have the library installed locally already. If your version is too old or missing, run the following command to install it (to /usr/local), then delete the gz and directory. #wget http://ffmpeg.org/releases/ffmpeg-1.2.tar.gz && cd tar xvfz ffmpeg-1.2.tar.gz && cd ffmpeg-1.2/ && ./configure --disable-yasm --enable-shared && make && sudo make install #we use ffmpeg's version for some reason, as opposed to libav. not sure what the differences are meant to be, but libav seemed to have non-depricated functions defined, docs that say to use them, and these functions missing. -AV7Z_VER=ffmpeg-3.0.1 -AV7Z_W32=$(AV7Z_VER)-win32-dev.7z -AV7Z_URL32=http://ffmpeg.zeranoe.com/builds/win32/dev/$(AV7Z_W32) -AV7Z_PRE32=$(AV7Z_VER)-win32-dev/ -AV7Z_W64=$(AV7Z_VER)-win64-dev.7z -AV7Z_URL64=http://ffmpeg.zeranoe.com/builds/win64/dev/$(AV7Z_W64) -AV7Z_PRE64=$(AV7Z_VER)-win64-dev/ -AV_BASE=$(OUT_DIR)/../fte_libav_$(AV7Z_VER)/ +AV_VER=ffmpeg-3.2.4 +ifeq (0,1) + AV_ARCHIVEEXT=.z7 + AV_EXTRACT=7z e -y +else + AV_ARCHIVEEXT=.zip + AV_EXTRACT=unzip -j +endif +AV_W32=$(AV_VER)-win32-dev$(AV_ARCHIVEEXT) +AV_URL32=http://ffmpeg.zeranoe.com/builds/win32/dev/$(AV_W32) +AV_PRE32=$(AV_VER)-win32-dev/ +AV_W64=$(AV_VER)-win64-dev$(AV_ARCHIVEEXT) +AV_URL64=http://ffmpeg.zeranoe.com/builds/win64/dev/$(AV_W64) +AV_PRE64=$(AV_VER)-win64-dev/ +AV_BASE=$(OUT_DIR)/../fte_libav_$(AV_VER)/ ifeq ($(FTE_TARGET),win32) @@ -134,21 +141,21 @@ endif $(AV_BASE)libavformat/avformat.h: mkdir -p $(AV_BASE) - wget $(AV7Z_URL32) -O $(AV_BASE)$(AV7Z_W32) - mkdir -p $(AV_BASE)libavformat && cd $(AV_BASE)libavformat && 7z e -y ../$(AV7Z_W32) $(AV7Z_PRE32)include/libavformat/ && cd - - mkdir -p $(AV_BASE)libavcodec && cd $(AV_BASE)libavcodec && 7z e -y ../$(AV7Z_W32) $(AV7Z_PRE32)include/libavcodec/ && cd - - mkdir -p $(AV_BASE)libavutil && cd $(AV_BASE)libavutil && 7z e -y ../$(AV7Z_W32) $(AV7Z_PRE32)include/libavutil/ && cd - - mkdir -p $(AV_BASE)libswscale && cd $(AV_BASE)libswscale && 7z e -y ../$(AV7Z_W32) $(AV7Z_PRE32)include/libswscale/ && cd - - mkdir -p $(AV_BASE)lib32 && cd $(AV_BASE)lib32 && 7z e -y ../$(AV7Z_W32) $(AV7Z_PRE32)lib/avformat.lib $(AV7Z_PRE32)lib/avcodec.lib $(AV7Z_PRE32)lib/avutil.lib $(AV7Z_PRE32)lib/swscale.lib && cd - - rm $(AV_BASE)$(AV7Z_W32) - wget $(AV7Z_URL64) -O $(AV_BASE)$(AV7Z_W64) - mkdir -p $(AV_BASE)lib64 && cd $(AV_BASE)lib64 && 7z e -y ../$(AV7Z_W64) $(AV7Z_PRE64)lib/avformat.lib $(AV7Z_PRE64)lib/avcodec.lib $(AV7Z_PRE64)lib/avutil.lib $(AV7Z_PRE64)lib/swscale.lib && cd - - rm $(AV_BASE)$(AV7Z_W64) + cd $(AV_BASE) && wget -N $(AV_URL32) + mkdir -p $(AV_BASE)libavformat && cd $(AV_BASE)libavformat && $(AV_EXTRACT) ../$(AV_W32) $(AV_PRE32)include/libavformat/* && cd - + mkdir -p $(AV_BASE)libavcodec && cd $(AV_BASE)libavcodec && $(AV_EXTRACT) ../$(AV_W32) $(AV_PRE32)include/libavcodec/* && cd - + mkdir -p $(AV_BASE)libavutil && cd $(AV_BASE)libavutil && $(AV_EXTRACT) ../$(AV_W32) $(AV_PRE32)include/libavutil/* && cd - + mkdir -p $(AV_BASE)libswscale && cd $(AV_BASE)libswscale && $(AV_EXTRACT) ../$(AV_W32) $(AV_PRE32)include/libswscale/* && cd - + mkdir -p $(AV_BASE)lib32 && cd $(AV_BASE)lib32 && $(AV_EXTRACT) ../$(AV_W32) $(AV_PRE32)lib/avformat.lib $(AV_PRE32)lib/avcodec.lib $(AV_PRE32)lib/avutil.lib $(AV_PRE32)lib/swscale.lib && cd - + rm $(AV_BASE)$(AV_W32) + cd $(AV_BASE) && wget -N $(AV_URL64) + mkdir -p $(AV_BASE)lib64 && cd $(AV_BASE)lib64 && $(AV_EXTRACT) ../$(AV_W64) $(AV_PRE64)lib/avformat.lib $(AV_PRE64)lib/avcodec.lib $(AV_PRE64)lib/avutil.lib $(AV_PRE64)lib/swscale.lib && cd - + rm $(AV_BASE)$(AV_W64) distclean: rm $(AV_BASE)libavformat/avformat.h -$(OUT_DIR)/fteplug_avplug$(PLUG_NATIVE_EXT): $(AV_BASE)libavformat/avformat.h +$(OUT_DIR)/fteplug_ffmpeg$(PLUG_NATIVE_EXT): $(AV_BASE)libavformat/avformat.h -$(OUT_DIR)/fteplug_avplug$(PLUG_NATIVE_EXT): avplug/avencode.c avplug/avdecode.c plugin.c qvm_api.c +$(OUT_DIR)/fteplug_ffmpeg$(PLUG_NATIVE_EXT): avplug/avencode.c avplug/avdecode.c avplug/avaudio.c plugin.c qvm_api.c $(CC) $(BASE_CFLAGS) $(CFLAGS) -DFTEPLUGIN -s -o $@ -shared $(PLUG_CFLAGS) -L$(AV_BASE)lib$(BITS) -I$(AV_BASE) $^ $(PLUG_DEFFILE) $(PLUG_LDFLAGS) -lavcodec -lavformat -lavutil -lswscale #small script for ode diff --git a/plugins/avplug/avaudio.c b/plugins/avplug/avaudio.c new file mode 100644 index 00000000..618a5aaa --- /dev/null +++ b/plugins/avplug/avaudio.c @@ -0,0 +1,398 @@ +#include "../plugin.h" +#include "../engine.h" + +#include "libavcodec/avcodec.h" +#include "libavformat/avformat.h" + +static cvar_t *ffmpeg_audiodecoder; + +struct avaudioctx +{ + //raw file + uint8_t *filedata; + size_t fileofs; + size_t filesize; + + //avformat stuff + AVFormatContext *pFormatCtx; + int audioStream; + + AVCodecContext *pACodecCtx; + AVFrame *pAFrame; + + //decoding + int64_t lasttime; + + //output audio + //we throw away data if the format changes. which is awkward, but gah. + int64_t samples_start; + int samples_channels; + int samples_speed; + int samples_width; + qbyte *samples_buffer; + size_t samples_count; + size_t samples_max; +}; + +static void S_AV_Purge(sfx_t *s) +{ + struct avaudioctx *ctx = (struct avaudioctx*)s->decoder.buf; + + s->loadstate = SLS_NOTLOADED; + + // Free the audio decoder + if (ctx->pACodecCtx) + avcodec_close(ctx->pACodecCtx); + av_free(ctx->pAFrame); + + // Close the video file + avformat_close_input(&ctx->pFormatCtx); + + //free the decoded buffer + free(ctx->samples_buffer); + + //file storage will be cleared here too + free(ctx); + + memset(&s->decoder, 0, sizeof(s->decoder)); +} +static sfxcache_t *S_AV_Locate(sfx_t *sfx, sfxcache_t *buf, ssamplepos_t start, int length) +{ //warning: can be called on a different thread. + struct avaudioctx *ctx = (struct avaudioctx*)sfx->decoder.buf; + AVPacket packet; + int64_t curtime; + + if (!buf) + return NULL; + + curtime = start + length; + +// curtime = (mediatime * ctx->denum) / ctx->num; + + while (1) + { + if (ctx->lasttime > curtime) + break; + + // We're ahead of the previous frame. try and read the next. + if (av_read_frame(ctx->pFormatCtx, &packet) < 0) + break; + + // Is this a packet from the video stream? + if(packet.stream_index==ctx->audioStream) + { + int okay; + int len; + void *odata = packet.data; + while (packet.size > 0) + { + okay = false; + len = avcodec_decode_audio4(ctx->pACodecCtx, ctx->pAFrame, &okay, &packet); + if (len < 0) + break; + packet.size -= len; + packet.data += len; + if (okay) + { + int width = 2; + int channels = ctx->pACodecCtx->channels; + unsigned int auddatasize = av_samples_get_buffer_size(NULL, ctx->pACodecCtx->channels, ctx->pAFrame->nb_samples, ctx->pACodecCtx->sample_fmt, 1); + void *auddata = ctx->pAFrame->data[0]; + switch(ctx->pACodecCtx->sample_fmt) + { //we don't support planar audio. we just treat it as mono instead. + default: + auddatasize = 0; + break; + case AV_SAMPLE_FMT_U8P: + auddatasize /= channels; + channels = 1; + case AV_SAMPLE_FMT_U8: + width = 1; + break; + case AV_SAMPLE_FMT_S16P: + auddatasize /= channels; + channels = 1; + case AV_SAMPLE_FMT_S16: + width = 2; + break; + + case AV_SAMPLE_FMT_FLTP: + auddatasize /= channels; + channels = 1; + case AV_SAMPLE_FMT_FLT: + //FIXME: support float audio internally. + { + float *in = (void*)auddata; + signed short *out = (void*)auddata; + int v; + unsigned int i; + for (i = 0; i < auddatasize/sizeof(*in); i++) + { + v = (short)(in[i]*32767); + if (v < -32767) + v = -32767; + else if (v > 32767) + v = 32767; + out[i] = v; + } + auddatasize/=2; + width = 2; + } + + case AV_SAMPLE_FMT_DBLP: + auddatasize /= channels; + channels = 1; + case AV_SAMPLE_FMT_DBL: + { + double *in = (double*)auddata; + signed short *out = (void*)auddata; + int v; + unsigned int i; + for (i = 0; i < auddatasize/sizeof(*in); i++) + { + v = (short)(in[i]*32767); + if (v < -32767) + v = -32767; + else if (v > 32767) + v = 32767; + out[i] = v; + } + auddatasize/=4; + width = 2; + } + break; + } + if (ctx->samples_channels != channels || ctx->samples_speed != ctx->pACodecCtx->sample_rate || ctx->samples_width != width) + { //something changed, update + ctx->samples_channels = channels; + ctx->samples_speed = ctx->pACodecCtx->sample_rate; + ctx->samples_width = width; + + //and discard any decoded audio. this might loose some. + ctx->samples_start += ctx->samples_count; + ctx->samples_count = 0; + } + if (ctx->samples_max < (ctx->samples_count*ctx->samples_width*ctx->samples_channels)+auddatasize) + { + ctx->samples_max = (ctx->samples_count*ctx->samples_width*ctx->samples_channels)+auddatasize; + ctx->samples_max *= 2; //slop + ctx->samples_buffer = realloc(ctx->samples_buffer, ctx->samples_max); + } + if (width == 1) + { //FTE uses signed 8bit audio. ffmpeg uses unsigned 8bit audio. *sigh*. + char *out = (char*)(ctx->samples_buffer + ctx->samples_count*(ctx->samples_width*ctx->samples_channels)); + unsigned char *in = auddata; + int i; + for (i = 0; i < auddatasize; i++) + out[i] = in[i]-128; + } + else + memcpy(ctx->samples_buffer + ctx->samples_count*(ctx->samples_width*ctx->samples_channels), auddata, auddatasize); + ctx->samples_count += auddatasize/(ctx->samples_width*ctx->samples_channels); + } + } + packet.data = odata; + } + + // Free the packet that was allocated by av_read_frame + av_packet_unref(&packet); + } + + buf->length = ctx->samples_count; + buf->speed = ctx->samples_speed; + buf->width = ctx->samples_width; + buf->numchannels = ctx->samples_channels; + buf->soundoffset = ctx->samples_start; + buf->data = ctx->samples_buffer; + + //if we couldn't return any new data, then we're at an eof, return NULL to signal that. + if (start == buf->soundoffset + buf->length && length > 0) + return NULL; + + return buf; +} +static float S_AV_Query(struct sfx_s *sfx, struct sfxcache_s *buf, char *title, size_t titlesize) +{ + struct avaudioctx *ctx = (struct avaudioctx*)sfx->decoder.buf; + if (!ctx) + return -1; + if (buf) + { + buf->data = NULL; + buf->soundoffset = 0; + buf->length = 0; + buf->numchannels = ctx->samples_channels; + buf->speed = ctx->samples_speed; + buf->width = ctx->samples_width; + } + return ctx->pFormatCtx->duration / (float)AV_TIME_BASE; +} + +static int AVIO_Mem_Read(void *opaque, uint8_t *buf, int buf_size) +{ + struct avaudioctx *ctx = opaque; + if (ctx->fileofs > ctx->filesize) + buf_size = 0; + if (buf_size > ctx->filesize-ctx->fileofs) + buf_size = ctx->filesize-ctx->fileofs; + if (buf_size > 0) + { + memcpy(buf, ctx->filedata + ctx->fileofs, buf_size); + ctx->fileofs += buf_size; + return buf_size; + } + return 0; +} +static int64_t AVIO_Mem_Seek(void *opaque, int64_t offset, int whence) +{ + struct avaudioctx *ctx = opaque; + whence &= ~AVSEEK_FORCE; + switch(whence) + { + default: + return -1; + case SEEK_SET: + ctx->fileofs = offset; + break; + case SEEK_CUR: + ctx->fileofs += offset; + break; + case SEEK_END: + ctx->fileofs = ctx->filesize + offset; + break; + case AVSEEK_SIZE: + return ctx->filesize; + } + if (ctx->fileofs < 0) + ctx->fileofs = 0; + return ctx->fileofs; +} + +/*const char *COM_GetFileExtension (const char *in) +{ + const char *dot; + + for (dot = in + strlen(in); dot >= in && *dot != '.'; dot--) + ; + if (dot < in) + return ""; + in = dot+1; + return in; +}*/ +static qboolean QDECL S_LoadAVSound (sfx_t *s, qbyte *data, size_t datalen, int sndspeed) +{ + struct avaudioctx *ctx; + int i; + AVCodec *pCodec; + const int iBufSize = 4 * 1024; + + if (!ffmpeg_audiodecoder) + ffmpeg_audiodecoder = pCvar_GetNVFDG("ffmpeg_audiodecoder_wip", "0", 0, "Enables the use of ffmpeg's decoder for pure audio files.", "ffmpeg"); + if (!ffmpeg_audiodecoder->value /* && *ffmpeg_audiodecoder.string */) + return false; + + + if (!data || !datalen) + return false; + + if (datalen >= 4 && !strncmp(data, "RIFF", 4)) + return false; //ignore it if it looks like a wav file. that means we don't need to figure out how to calculate loopstart. +// if (strcasecmp(COM_GetFileExtension(s->name), "wav")) //don't do .wav - I've no idea how to read the loopstart tag with ffmpeg. +// return false; + + s->decoder.buf = ctx = malloc(sizeof(*ctx) + datalen); + if (!ctx) + return false; //o.O + memset(ctx, 0, sizeof(*ctx)); + + // Create internal io buffer for FFmpeg + ctx->filedata = data; //defer that copy + ctx->filesize = datalen; //defer that copy + ctx->pFormatCtx = avformat_alloc_context(); + ctx->pFormatCtx->pb = avio_alloc_context(av_malloc(iBufSize), iBufSize, 0, ctx, AVIO_Mem_Read, 0, AVIO_Mem_Seek); + + // Open file + if(avformat_open_input(&ctx->pFormatCtx, s->name, NULL, NULL)==0) + { + // Retrieve stream information + if(avformat_find_stream_info(ctx->pFormatCtx, NULL)>=0) + { + ctx->audioStream=-1; + for(i=0; ipFormatCtx->nb_streams; i++) + if(ctx->pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO) + { + ctx->audioStream=i; + break; + } + if(ctx->audioStream!=-1) + { + ctx->pACodecCtx=ctx->pFormatCtx->streams[ctx->audioStream]->codec; + pCodec=avcodec_find_decoder(ctx->pACodecCtx->codec_id); + + ctx->pAFrame=av_frame_alloc(); + if(pCodec!=NULL && ctx->pAFrame && avcodec_open2(ctx->pACodecCtx, pCodec, NULL) >= 0) + { //success + } + else + ctx->audioStream = -1; + } + } + + if (ctx->audioStream != -1) + { + //sucky copy + ctx->filedata = (uint8_t*)(ctx+1); + memcpy(ctx->filedata, data, datalen); + + s->decoder.ended = S_AV_Purge; + s->decoder.purge = S_AV_Purge; + s->decoder.decodedata = S_AV_Locate; + s->decoder.querydata = S_AV_Query; + return true; + } + } + S_AV_Purge(s); + return false; +} +static qboolean AVAudio_Init(void) +{ + if (!pPlug_ExportNative("S_LoadSound", S_LoadAVSound)) + { + Con_Printf("avplug: Engine doesn't support audio decoder plugins\n"); + return false; + } + return true; +} + + +//generic module stuff. this has to go somewhere. +static void AVLogCallback(void *avcl, int level, const char *fmt, va_list vl) +{ //needs to be reenterant +#ifdef _DEBUG + char string[1024]; + Q_vsnprintf (string, sizeof(string), fmt, vl); + pCon_Print(string); +#endif +} + +//get the encoder/decoders to register themselves with the engine, then make sure avformat/avcodec have registered all they have to give. +qboolean AVEnc_Init(void); +qboolean AVDec_Init(void); +qintptr_t Plug_Init(qintptr_t *args) +{ + qboolean okay = false; + + okay |= AVAudio_Init(); + okay |= AVDec_Init(); + okay |= AVEnc_Init(); + if (okay) + { + av_register_all(); + avcodec_register_all(); + + av_log_set_level(AV_LOG_WARNING); + av_log_set_callback(AVLogCallback); + } + return okay; +} + diff --git a/plugins/avplug/avdecode.c b/plugins/avplug/avdecode.c index 16a7a804..4c38f9e9 100644 --- a/plugins/avplug/avdecode.c +++ b/plugins/avplug/avdecode.c @@ -1,7 +1,7 @@ #include "../plugin.h" #include "../engine.h" -#include "libavcodec/avcodec.h" +//#include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" #include "libavutil/imgutils.h" @@ -19,6 +19,8 @@ #define avio_alloc_context av_alloc_put_byte */ +#define DECODERNAME "ffmpeg" + #define PASSFLOAT(f) *(int*)&(f) #define ARGNAMES ,sourceid, data, speed, samples, channels, width, PASSFLOAT(volume) @@ -459,7 +461,7 @@ static qintptr_t AVDec_Shutdown(qintptr_t *args) static media_decoder_funcs_t decoderfuncs = { sizeof(media_decoder_funcs_t), - "avplug", + DECODERNAME, AVDec_Create, AVDec_DisplayFrame, AVDec_Destroy, @@ -472,11 +474,11 @@ static media_decoder_funcs_t decoderfuncs = NULL,//AVDec_ChangeStream }; -static qboolean AVDec_Init(void) +qboolean AVDec_Init(void) { if (!pPlug_ExportNative("Media_VideoDecoder", &decoderfuncs)) { - Con_Printf("avplug: Engine doesn't support media decoder plugins\n"); + Con_Printf(DECODERNAME": Engine doesn't support media decoder plugins\n"); return false; } @@ -485,32 +487,3 @@ static qboolean AVDec_Init(void) return true; } - -static void AVLogCallback(void *avcl, int level, const char *fmt, va_list vl) -{ //needs to be reenterant -#ifdef _DEBUG - char string[1024]; - Q_vsnprintf (string, sizeof(string), fmt, vl); - pCon_Print(string); -#endif -} - -//get the encoder/decoders to register themselves with the engine, then make sure avformat/avcodec have registered all they have to give. -qboolean AVEnc_Init(void); -qintptr_t Plug_Init(qintptr_t *args) -{ - qboolean okay = false; - - okay |= AVDec_Init(); - okay |= AVEnc_Init(); - if (okay) - { - av_register_all(); - avcodec_register_all(); - - av_log_set_level(AV_LOG_WARNING); - av_log_set_callback(AVLogCallback); - } - return okay; -} - diff --git a/plugins/avplug/avencode.c b/plugins/avplug/avencode.c index 4cd7098f..0d97d2ca 100644 --- a/plugins/avplug/avencode.c +++ b/plugins/avplug/avencode.c @@ -2,18 +2,32 @@ #include "../engine.h" #include "libavformat/avformat.h" -#include "libavformat/avio.h" -#include "libavcodec/avcodec.h" +//#include "libavformat/avio.h" +//#include "libavcodec/avcodec.h" #include "libswscale/swscale.h" #include "libavutil/imgutils.h" #include "libavutil/opt.h" //#include +#define ENCODERNAME "ffmpeg" + +#define HAVE_DECOUPLED_API (LIBAVCODEC_VERSION_MAJOR>57 || (LIBAVCODEC_VERSION_MAJOR==57&&LIBAVCODEC_VERSION_MINOR>=36)) + /* Most of the logic in here came from here: http://svn.gnumonks.org/tags/21c3-video/upstream/ffmpeg-0.4.9-pre1/output_example.c */ +static cvar_t *ffmpeg_format_force; +static cvar_t *ffmpeg_videocodec; +static cvar_t *ffmpeg_videobitrate; +static cvar_t *ffmpeg_videoforcewidth; +static cvar_t *ffmpeg_videoforceheight; +static cvar_t *ffmpeg_videopreset; +static cvar_t *ffmpeg_video_crf; +static cvar_t *ffmpeg_audiocodec; +static cvar_t *ffmpeg_audiobitrate; + struct encctx { char abspath[MAX_OSPATH]; @@ -59,14 +73,13 @@ static AVFrame *alloc_frame(enum AVPixelFormat pix_fmt, int width, int height) picture->height = height; return picture; } -AVStream *add_video_stream(struct encctx *ctx, AVCodec *codec, int fps, int width, int height) +static AVStream *add_video_stream(struct encctx *ctx, AVCodec *codec, int fps, int width, int height) { AVCodecContext *c; AVStream *st; - char prof[128]; - int bitrate = (int)pCvar_GetFloat("avplug_videobitrate"); - int forcewidth = (int)pCvar_GetFloat("avplug_videoforcewidth"); - int forceheight = (int)pCvar_GetFloat("avplug_videoforceheight"); + int bitrate = ffmpeg_videobitrate->value; + int forcewidth = ffmpeg_videoforcewidth->value; + int forceheight = ffmpeg_videoforceheight->value; st = avformat_new_stream(ctx->fc, codec); if (!st) @@ -102,17 +115,15 @@ AVStream *add_video_stream(struct encctx *ctx, AVCodec *codec, int fps, int widt if (ctx->fc->oformat->flags & AVFMT_GLOBALHEADER) c->flags |= CODEC_FLAG_GLOBAL_HEADER; - pCvar_GetString("avplug_videopreset", prof, sizeof(prof)); - if (*prof) - av_opt_set(c->priv_data, "preset", prof, AV_OPT_SEARCH_CHILDREN); - pCvar_GetString("avplug_video_crf", prof, sizeof(prof)); - if (*prof) - av_opt_set(c->priv_data, "crf", prof, AV_OPT_SEARCH_CHILDREN); + if (*ffmpeg_videopreset->string) + av_opt_set(c->priv_data, "preset", ffmpeg_videopreset->string, AV_OPT_SEARCH_CHILDREN); + if (*ffmpeg_video_crf->string) + av_opt_set(c->priv_data, "crf", ffmpeg_video_crf->string, AV_OPT_SEARCH_CHILDREN); return st; } -void close_video(struct encctx *ctx) +static void close_video(struct encctx *ctx) { if (!ctx->video_st) return; @@ -125,33 +136,66 @@ void close_video(struct encctx *ctx) } av_free(ctx->video_outbuf); } -static void AVEnc_Video (void *vctx, void *data, int frame, int width, int height, enum uploadfmt qpfmt) + +#if HAVE_DECOUPLED_API +//frame can be null on eof. +static void AVEnc_DoEncode(AVFormatContext *fc, AVStream *stream, AVFrame *frame) +{ + AVPacket pkt; + AVCodecContext *codec = stream->codec; + int err = avcodec_send_frame(codec, frame); + if (err) + { + char buf[512]; + Con_Printf("avcodec_send_frame: error: %s\n", av_make_error_string(buf, sizeof(buf), err)); + } + + av_init_packet(&pkt); + while (!(err=avcodec_receive_packet(codec, &pkt))) + { + av_packet_rescale_ts(&pkt, codec->time_base, stream->time_base); + pkt.stream_index = stream->index; + err = av_interleaved_write_frame(fc, &pkt); + if (err) + { + char buf[512]; + Con_Printf("av_interleaved_write_frame: error: %s\n", av_make_error_string(buf, sizeof(buf), err)); + } + av_packet_unref(&pkt); + } + if (err && err != AVERROR(EAGAIN) && err != AVERROR_EOF) + { + char buf[512]; + Con_Printf("avcodec_receive_packet: error: %s\n", av_make_error_string(buf, sizeof(buf), err)); + } +} +#endif + +static void AVEnc_Video (void *vctx, int frameno, void *data, int bytestride, int width, int height, enum uploadfmt qpfmt) { struct encctx *ctx = vctx; const uint8_t *srcslices[2]; int srcstride[2]; - int success; - AVPacket pkt; int avpfmt; - int inbpp; - int err; if (!ctx->video_st) return; switch(qpfmt) { - case TF_BGRA32: avpfmt = AV_PIX_FMT_BGRA; inbpp = 4; break; - case TF_RGBA32: avpfmt = AV_PIX_FMT_RGBA; inbpp = 4; break; - case TF_BGR24: avpfmt = AV_PIX_FMT_BGR24; inbpp = 3; break; - case TF_RGB24: avpfmt = AV_PIX_FMT_RGB24; inbpp = 3; break; + case TF_BGRA32: avpfmt = AV_PIX_FMT_BGRA; break; + case TF_RGBA32: avpfmt = AV_PIX_FMT_RGBA; break; + case TF_BGRX32: avpfmt = AV_PIX_FMT_BGR0; break; + case TF_RGBX32: avpfmt = AV_PIX_FMT_RGB0; break; + case TF_BGR24: avpfmt = AV_PIX_FMT_BGR24; break; + case TF_RGB24: avpfmt = AV_PIX_FMT_RGB24; break; default: return; } //weird maths to flip it. - srcslices[0] = (uint8_t*)data + (height-1)*width*inbpp; - srcstride[0] = -width*inbpp; + srcslices[0] = (uint8_t*)data; + srcstride[0] = bytestride; srcslices[1] = NULL; srcstride[1] = 0; @@ -160,40 +204,47 @@ static void AVEnc_Video (void *vctx, void *data, int frame, int width, int heigh ctx->scale_ctx = sws_getCachedContext(ctx->scale_ctx, width, height, avpfmt, ctx->picture->width, ctx->picture->height, ctx->video_st->codec->pix_fmt, SWS_POINT, 0, 0, 0); sws_scale(ctx->scale_ctx, srcslices, srcstride, 0, height, ctx->picture->data, ctx->picture->linesize); - av_init_packet(&pkt); - ctx->picture->pts = frame; - success = 0; - pkt.data = ctx->video_outbuf; - pkt.size = ctx->video_outbuf_size; + ctx->picture->pts = frameno; ctx->picture->format = ctx->video_st->codec->pix_fmt; - err = avcodec_encode_video2(ctx->video_st->codec, &pkt, ctx->picture, &success); - if (err) +#if HAVE_DECOUPLED_API + AVEnc_DoEncode(ctx->fc, ctx->video_st, ctx->picture); +#else { - char buf[512]; - Con_Printf("avcodec_encode_video2: error: %s\n", av_make_error_string(buf, sizeof(buf), err)); - } - else if (err == 0 && success) - { -// pkt.pts = ctx->video_st->codec->coded_frame->pts; -// if(ctx->video_st->codec->coded_frame->key_frame) -// pkt.flags |= AV_PKT_FLAG_KEY; - av_packet_rescale_ts(&pkt, ctx->video_st->codec->time_base, ctx->video_st->time_base); - pkt.stream_index = ctx->video_st->index; - err = av_interleaved_write_frame(ctx->fc, &pkt); - + AVPacket pkt; + int success; + int err; + + av_init_packet(&pkt); + pkt.data = ctx->video_outbuf; + pkt.size = ctx->video_outbuf_size; + success = 0; + err = avcodec_encode_video2(ctx->video_st->codec, &pkt, ctx->picture, &success); if (err) { char buf[512]; - Con_Printf("av_interleaved_write_frame: error: %s\n", av_make_error_string(buf, sizeof(buf), err)); + Con_Printf("avcodec_encode_video2: error: %s\n", av_make_error_string(buf, sizeof(buf), err)); + } + else if (err == 0 && success) + { + av_packet_rescale_ts(&pkt, ctx->video_st->codec->time_base, ctx->video_st->time_base); + pkt.stream_index = ctx->video_st->index; + err = av_interleaved_write_frame(ctx->fc, &pkt); + + if (err) + { + char buf[512]; + Con_Printf("av_interleaved_write_frame: error: %s\n", av_make_error_string(buf, sizeof(buf), err)); + } } } +#endif } -AVStream *add_audio_stream(struct encctx *ctx, AVCodec *codec, int samplerate, int *bits, int channels) +static AVStream *add_audio_stream(struct encctx *ctx, AVCodec *codec, int samplerate, int *bits, int channels) { AVCodecContext *c; AVStream *st; - int bitrate = (int)pCvar_GetFloat("avplug_audiobitrate"); + int bitrate = ffmpeg_audiobitrate->value; st = avformat_new_stream(ctx->fc, codec); if (!st) @@ -249,7 +300,7 @@ AVStream *add_audio_stream(struct encctx *ctx, AVCodec *codec, int samplerate, i return st; } -void close_audio(struct encctx *ctx) +static void close_audio(struct encctx *ctx) { if (!ctx->audio_st) return; @@ -259,9 +310,6 @@ void close_audio(struct encctx *ctx) static void AVEnc_Audio (void *vctx, void *data, int bytes) { struct encctx *ctx = vctx; - int success; - AVPacket pkt; - int err; if (!ctx->audio_st) return; @@ -384,36 +432,45 @@ static void AVEnc_Audio (void *vctx, void *data, int bytes) ctx->audio->nb_samples = ctx->audio_outcount; avcodec_fill_audio_frame(ctx->audio, ctx->audio_st->codec->channels, ctx->audio_st->codec->sample_fmt, ctx->audio_outbuf, av_get_bytes_per_sample(ctx->audio_st->codec->sample_fmt)*ctx->audio_outcount*ctx->audio_st->codec->channels, 1); - - av_init_packet(&pkt); - pkt.data = NULL; - pkt.size = 0; - success = 0; ctx->audio->pts = ctx->audio_pts; ctx->audio_pts += ctx->audio_outcount; ctx->audio_outcount = 0; - err = avcodec_encode_audio2(ctx->audio_st->codec, &pkt, ctx->audio, &success); - if (err) +#if HAVE_DECOUPLED_API + AVEnc_DoEncode(ctx->fc, ctx->audio_st, ctx->audio); +#else { - char buf[512]; - Con_Printf("avcodec_encode_audio2: error: %s\n", av_make_error_string(buf, sizeof(buf), err)); - } - else if (success) - { - // pkt.pts = ctx->audio_st->codec->coded_frame->pts; - // if(ctx->audio_st->codec->coded_frame->key_frame) - // pkt.flags |= AV_PKT_FLAG_KEY; + AVPacket pkt; + int success; + int err; + av_init_packet(&pkt); + pkt.data = NULL; + pkt.size = 0; + success = 0; + err = avcodec_encode_audio2(ctx->audio_st->codec, &pkt, ctx->audio, &success); - av_packet_rescale_ts(&pkt, ctx->audio_st->codec->time_base, ctx->audio_st->time_base); - pkt.stream_index = ctx->audio_st->index; - err = av_interleaved_write_frame(ctx->fc, &pkt); if (err) { char buf[512]; - Con_Printf("av_interleaved_write_frame: error: %s\n", av_make_error_string(buf, sizeof(buf), err)); + Con_Printf("avcodec_encode_audio2: error: %s\n", av_make_error_string(buf, sizeof(buf), err)); + } + else if (success) + { + // pkt.pts = ctx->audio_st->codec->coded_frame->pts; + // if(ctx->audio_st->codec->coded_frame->key_frame) + // pkt.flags |= AV_PKT_FLAG_KEY; + + av_packet_rescale_ts(&pkt, ctx->audio_st->codec->time_base, ctx->audio_st->time_base); + pkt.stream_index = ctx->audio_st->index; + err = av_interleaved_write_frame(ctx->fc, &pkt); + if (err) + { + char buf[512]; + Con_Printf("av_interleaved_write_frame: error: %s\n", av_make_error_string(buf, sizeof(buf), err)); + } } } +#endif } } @@ -423,14 +480,11 @@ static void *AVEnc_Begin (char *streamname, int videorate, int width, int height AVOutputFormat *fmt = NULL; AVCodec *videocodec = NULL; AVCodec *audiocodec = NULL; - char formatname[64]; int err; - formatname[0] = 0; - pCvar_GetString("avplug_format_force", formatname, sizeof(formatname)); - if (*formatname) + if (ffmpeg_format_force->string) { - fmt = av_guess_format(formatname, NULL, NULL); + fmt = av_guess_format(ffmpeg_format_force->string, NULL, NULL); if (!fmt) { Con_Printf("Unknown format specified.\n"); @@ -452,18 +506,14 @@ static void *AVEnc_Begin (char *streamname, int videorate, int width, int height if (videorate) { - char codecname[64]; - codecname[0] = 0; - pCvar_GetString("avplug_videocodec", codecname, sizeof(codecname)); - - if (strcmp(codecname, "none")) + if (strcmp(ffmpeg_videocodec->string, "none")) { - if (codecname[0]) + if (ffmpeg_videocodec->string[0]) { - videocodec = avcodec_find_encoder_by_name(codecname); + videocodec = avcodec_find_encoder_by_name(ffmpeg_videocodec->string); if (!videocodec) { - Con_Printf("Unsupported avplug_videocodec \"%s\"\n", codecname); + Con_Printf("Unsupported %s \"%s\"\n", ffmpeg_videocodec->name, ffmpeg_videocodec->string); return NULL; } } @@ -473,18 +523,14 @@ static void *AVEnc_Begin (char *streamname, int videorate, int width, int height } if (*sndkhz) { - char codecname[64]; - codecname[0] = 0; - pCvar_GetString("avplug_audiocodec", codecname, sizeof(codecname)); - - if (strcmp(codecname, "none")) + if (strcmp(ffmpeg_audiocodec->string, "none")) { - if (codecname[0]) + if (ffmpeg_audiocodec->string[0]) { - audiocodec = avcodec_find_encoder_by_name(codecname); + audiocodec = avcodec_find_encoder_by_name(ffmpeg_audiocodec->string); if (!audiocodec) { - Con_Printf("avplug: Unsupported avplug_audiocodec \"%s\"\n", codecname); + Con_Printf(ENCODERNAME": Unsupported %s \"%s\"\n", ffmpeg_audiocodec->name, ffmpeg_audiocodec->string); return NULL; } } @@ -493,19 +539,19 @@ static void *AVEnc_Begin (char *streamname, int videorate, int width, int height } } - Con_DPrintf("avplug: Using format \"%s\"\n", fmt->name); + Con_DPrintf(ENCODERNAME": Using format \"%s\"\n", fmt->name); if (videocodec) - Con_DPrintf("avplug: Using Video Codec \"%s\"\n", videocodec->name); + Con_DPrintf(ENCODERNAME": Using Video Codec \"%s\"\n", videocodec->name); else - Con_DPrintf("avplug: Not encoding video\n"); + Con_DPrintf(ENCODERNAME": Not encoding video\n"); if (audiocodec) - Con_DPrintf("avplug: Using Audio Codec \"%s\"\n", audiocodec->name); + Con_DPrintf(ENCODERNAME": Using Audio Codec \"%s\"\n", audiocodec->name); else - Con_DPrintf("avplug: Not encoding audio\n"); + Con_DPrintf(ENCODERNAME": Not encoding audio\n"); if (!videocodec && !audiocodec) { - Con_DPrintf("avplug: Nothing to encode!\n"); + Con_DPrintf(ENCODERNAME": Nothing to encode!\n"); return NULL; } @@ -536,7 +582,7 @@ static void *AVEnc_Begin (char *streamname, int videorate, int width, int height if (err < 0) { char buf[512]; - Con_Printf("avplug: Could not init codec instance \"%s\" - %s\nMaybe try a different framerate/resolution/bitrate\n", videocodec->name, av_make_error_string(buf, sizeof(buf), err)); + Con_Printf(ENCODERNAME": Could not init codec instance \"%s\" - %s\nMaybe try a different framerate/resolution/bitrate\n", videocodec->name, av_make_error_string(buf, sizeof(buf), err)); AVEnc_End(ctx); return NULL; } @@ -556,7 +602,7 @@ static void *AVEnc_Begin (char *streamname, int videorate, int width, int height if (err < 0) { char buf[512]; - Con_Printf("avplug: Could not init codec instance \"%s\" - %s\n", audiocodec->name, av_make_error_string(buf, sizeof(buf), err)); + Con_Printf(ENCODERNAME": Could not init codec instance \"%s\" - %s\n", audiocodec->name, av_make_error_string(buf, sizeof(buf), err)); AVEnc_End(ctx); return NULL; } @@ -599,6 +645,17 @@ static void AVEnc_End (void *vctx) struct encctx *ctx = vctx; unsigned int i; +#if HAVE_DECOUPLED_API + if (ctx->doneheaders) + { + //terminate the codecs properly, flushing all unwritten packets + if (ctx->video_st) + AVEnc_DoEncode(ctx->fc, ctx->video_st, NULL); + if (ctx->audio_st) + AVEnc_DoEncode(ctx->fc, ctx->audio_st, NULL); + } +#endif + close_video(ctx); //don't write trailers if this is an error case and we never even wrote the headers. @@ -620,8 +677,8 @@ static void AVEnc_End (void *vctx) static media_encoder_funcs_t encoderfuncs = { sizeof(media_encoder_funcs_t), - "avplug", - "Use ffmpeg's various codecs. Various settings are configured with the avplug_* cvars.", + "ffmpeg", + "Use ffmpeg's various codecs. Various settings are configured with the "ENCODERNAME"_* cvars.", ".mp4", AVEnc_Begin, AVEnc_Video, @@ -658,24 +715,24 @@ qboolean AVEnc_Init(void) CHECKBUILTIN(FS_NativePath); if (!BUILTINISVALID(FS_NativePath)) { - Con_Printf("avplug: Engine too old\n"); + Con_Printf(ENCODERNAME": Engine too old\n"); return false; } if (!pPlug_ExportNative("Media_VideoEncoder", &encoderfuncs)) { - Con_Printf("avplug: Engine doesn't support media encoder plugins\n"); + Con_Printf(ENCODERNAME": Engine doesn't support media encoder plugins\n"); return false; } - pCvar_Register("avplug_format_force", "", 0, "avplug"); - - pCvar_Register("avplug_videocodec", "", 0, "avplug"); - pCvar_Register("avplug_videobitrate", "4000000", 0, "avplug"); - pCvar_Register("avplug_videoforcewidth", "", 0, "avplug"); - pCvar_Register("avplug_videoforceheight", "", 0, "avplug"); - pCvar_Register("avplug_videopreset", "veryfast", 0, "avplug"); - pCvar_Register("avplug_audiocodec", "", 0, "avplug"); - pCvar_Register("avplug_audiobitrate", "64000", 0, "avplug"); + ffmpeg_format_force = pCvar_GetNVFDG(ENCODERNAME"_format_force", "", 0, "Forces the output container format. If blank, will guess based upon filename extension.", ENCODERNAME); + ffmpeg_videocodec = pCvar_GetNVFDG(ENCODERNAME"_videocodec", "", 0, "Forces which video encoder to use. If blank, guesses based upon container defaults.", ENCODERNAME); + ffmpeg_videobitrate = pCvar_GetNVFDG(ENCODERNAME"_videobitrate", "4000000", 0, "Specifies the target video bitrate", ENCODERNAME); + ffmpeg_videoforcewidth = pCvar_GetNVFDG(ENCODERNAME"_videoforcewidth", "", 0, "Rescales the input video width. Best to leave blank in order to record the video at the native resolution.", ENCODERNAME); + ffmpeg_videoforceheight = pCvar_GetNVFDG(ENCODERNAME"_videoforceheight", "", 0, "Rescales the input video height. Best to leave blank in order to record the video at the native resolution.", ENCODERNAME); + ffmpeg_videopreset = pCvar_GetNVFDG(ENCODERNAME"_videopreset", "veryfast", 0, "Specifies which codec preset to use, for codecs that support such presets.", ENCODERNAME); + ffmpeg_video_crf = pCvar_GetNVFDG(ENCODERNAME"_video_crf", "", 0, "Specifies the 'Constant Rate Factor' codec setting.", ENCODERNAME); + ffmpeg_audiocodec = pCvar_GetNVFDG(ENCODERNAME"_audiocodec", "", 0, "Forces which audio encoder to use. If blank, guesses based upon container defaults.", ENCODERNAME); + ffmpeg_audiobitrate = pCvar_GetNVFDG(ENCODERNAME"_audiobitrate", "64000", 0, "Specifies the target audio bitrate", ENCODERNAME); // if (Plug_Export("ExecuteCommand", AVEnc_ExecuteCommand)) // Cmd_AddCommand("avcapture"); diff --git a/plugins/ezhud/ezquakeisms.c b/plugins/ezhud/ezquakeisms.c index fad920c5..34e87d36 100644 --- a/plugins/ezhud/ezquakeisms.c +++ b/plugins/ezhud/ezquakeisms.c @@ -12,10 +12,6 @@ float alphamul; cvar_t *scr_newHud; -#define ARGNAMES ,name,defaultval,flags,description,groupname -BUILTINR(cvar_t*, Cvar_GetNVFDG, (const char *name, const char *defaultval, unsigned int flags, const char *description, const char *groupname)); -#undef ARGNAMES - char *Cmd_Argv(int arg) { static char buf[4][128]; @@ -725,7 +721,6 @@ qintptr_t EZHud_MenuEvent(qintptr_t *args) qintptr_t Plug_Init(qintptr_t *args) { - CHECKBUILTIN(Cvar_GetNVFDG); if (BUILTINISVALID(Cvar_GetNVFDG) && BUILTINISVALID(Draw_ImageSize) && BUILTINISVALID(GetTeamInfo) && diff --git a/plugins/odeplug/odeplug.vcproj b/plugins/odeplug/odeplug.vcproj index 4fdebd26..09cc330e 100644 --- a/plugins/odeplug/odeplug.vcproj +++ b/plugins/odeplug/odeplug.vcproj @@ -113,9 +113,8 @@ /> diff --git a/plugins/plugin.c b/plugins/plugin.c index 51b50b25..7fd8aa35 100644 --- a/plugins/plugin.c +++ b/plugins/plugin.c @@ -135,6 +135,11 @@ BUILTINR(qhandle_t, Cvar_Register, (const char *name, const char *defaultval, in #define ARGNAMES ,handle,modificationcount,stringv,floatv BUILTINR(int, Cvar_Update, (qhandle_t handle, int *modificationcount, char *stringv, float *floatv)); //stringv is 256 chars long, don't expect this function to do anything if modification count is unchanged. #undef ARGNAMES +#if !defined(Q3_VM) && defined(FTEPLUGIN) +#define ARGNAMES ,name,defaultval,flags,description,groupname +BUILTINR(cvar_t*, Cvar_GetNVFDG, (const char *name, const char *defaultval, unsigned int flags, const char *description, const char *groupname)); +#undef ARGNAMES +#endif #define ARGNAMES ,pnum,stats,maxstats BUILTINR(int, CL_GetStats, (int pnum, unsigned int *stats, int maxstats)); @@ -440,6 +445,9 @@ void Plug_InitStandardBuiltins(void) CHECKBUILTIN(Cvar_GetFloat); CHECKBUILTIN(Cvar_Register); CHECKBUILTIN(Cvar_Update); +#if !defined(Q3_VM) && defined(FTEPLUGIN) + CHECKBUILTIN(Cvar_GetNVFDG); +#endif //file system #if !defined(Q3_VM) && defined(FTEPLUGIN) diff --git a/plugins/plugin.h b/plugins/plugin.h index 428bb7c3..e7f64be8 100644 --- a/plugins/plugin.h +++ b/plugins/plugin.h @@ -225,6 +225,9 @@ EBUILTIN(qboolean, Cvar_GetString, (const char *name, char *retstring, int sizeo EBUILTIN(float, Cvar_GetFloat, (const char *name)); EBUILTIN(qhandle_t, Cvar_Register, (const char *name, const char *defaultval, int flags, const char *grouphint)); EBUILTIN(int, Cvar_Update, (qhandle_t handle, int *modificationcount, char *stringv, float *floatv)); //stringv is 256 chars long, don't expect this function to do anything if modification count is unchanged. +#ifdef FTEPLUGIN +EBUILTIN(cvar_t*, Cvar_GetNVFDG, (const char *name, const char *defaultval, unsigned int flags, const char *description, const char *groupname)); +#endif EBUILTIN(void, Plug_GetPluginName, (int plugnum, char *buffer, int bufsize)); EBUILTIN(void, LocalSound, (const char *soundname)); diff --git a/quakec/csaddon/src/csaddon.qc b/quakec/csaddon/src/csaddon.qc index 688e550b..14c01ae5 100644 --- a/quakec/csaddon/src/csaddon.qc +++ b/quakec/csaddon/src/csaddon.qc @@ -1,3 +1,5 @@ +//FIXME: slice tool is a bit shite and has no way to change points 1+2, only the last one can be freely changed. + var float autocvar_ca_show = 0; var float autocvar_ca_editormode = MODE_LIGHTEDIT; string autocvar_ca_colourtint; @@ -21,19 +23,36 @@ float pointedsurface; float editorrsd[editornames.length]; +static void() csaddon_savemap = +{ + localcmd(strcat("mod_terrain_save \"", mapname, "\"\n")); +}; + +struct +{ + string text; + string cmd; + void() cb; +} editorcommands[] = +{ + {"Close", "ca_show 0\n"}, + {"Noclip", "noclip\n"}, + {"Notarget", "notarget\n"}, + {"Fullbright", "toggle r_fullbright\n"}, + {"Brush Editor Bindings", "brushedit_binds\n"}, + {"Save", __NULL__, csaddon_savemap}, +}; float(float key, float unic, vector mouse) editor_options_key = { if (key == K_MOUSE1) { int sel = (mouse_y - 16) / 8; - if (sel == 0) + if (sel >= 0 && sel < editorcommands.length) { - cvar_set("ca_show", "0"); - return TRUE; - } - else if (sel == 1) - { - localcmd("brushedit_binds\n"); + if (editorcommands[sel].cb) + editorcommands[sel].cb(); + else + localcmd(editorcommands[sel].cmd); return TRUE; } } @@ -43,8 +62,25 @@ void(vector mouse) editor_options_overlay = { vector pos = '0 16'; int sel = (mouse_y - pos_y) / 8; - drawstring(pos, "Close", '8 8', (sel==0)?'0 0 1':'1 1 1', 1, 0); pos_y += 8; - drawstring(pos, "Brush Editor Bindings", '8 8', (sel==1)?'0 0 1':'1 1 1', 1, 0); + for (int i = 0; i < editorcommands.length; i++) + { + string txt = editorcommands[i].text; + string cmd = editorcommands[i].cmd; + if (substring(cmd, 0, 7) == "toggle ") + { + if (cvar(substring(cmd, 7, -2))) + txt = strcat(txt, " (ON)"); + else + txt = strcat(txt, " (OFF)"); + } + drawstring(pos, txt, '8 8', (sel==i)?'0 0 1':'1 1 1', 1, 0); + pos_y += 8; + } +}; + +static void() perf_renderscene = +{ + renderscene(); }; /*the renderscene builtin in the parent progs is redirected to here*/ @@ -189,7 +225,7 @@ void() wrap_renderscene = else if (autocvar_ca_editormode == MODE_BRUSHEDIT) editor_brushes_addentities(curmousepos); - renderscene(); + perf_renderscene(); for (i = 0, x = 0; i < editornames.length; i+=1) { diff --git a/quakec/csaddon/src/csfixups.qc b/quakec/csaddon/src/csfixups.qc index 0d838bb5..c68680e6 100644 --- a/quakec/csaddon/src/csfixups.qc +++ b/quakec/csaddon/src/csfixups.qc @@ -58,4 +58,4 @@ var float(float vismode) ca_checksave; #define isnull(v) (!(int)(v)) -#define notnull(v) ((int)(v)) \ No newline at end of file +#define notnull(v) ((int)(v)) diff --git a/quakec/csaddon/src/csplat.qc b/quakec/csaddon/src/csplat.qc index 395659e6..d55e6907 100644 --- a/quakec/csaddon/src/csplat.qc +++ b/quakec/csaddon/src/csplat.qc @@ -67,7 +67,6 @@ Available options: #define DP_EF_RED #define DP_ENT_CUSTOMCOLORMAP #define DP_ENT_EXTERIORMODELTOCLIENT -#define DP_ENT_SCALE #define DP_ENT_TRAILEFFECTNUM /* self.traileffectnum=particleeffectnum("myeffectname"); can be used to attach a particle trail to the given server entity. This is equivelent to calling trailparticles each frame. */ #define DP_ENT_VIEWMODEL #define DP_GECKO_SUPPORT @@ -163,6 +162,7 @@ Available options: #define FTE_CALLTIMEOFDAY /* Replication of mvdsv functionality (call calltimeofday to cause 'timeofday' to be called, with arguments that can be saved off to a global). Generally strftime is simpler to use. */ #define FTE_CSQC_ALTCONSOLES /* The engine tracks multiple consoles. These may or may not be directly visible to the user. */ #define FTE_CSQC_BASEFRAME /* Specifies that .basebone, .baseframe2, .baselerpfrac, baseframe1time, etc exist in csqc. These fields affect all bones in the entity's model with a lower index than the .basebone field, allowing you to give separate control to the legs of a skeletal model, without affecting the torso animations. */ +#define FTE_CSQC_HALFLIFE_MODELS #define FTE_CSQC_SERVERBROWSER #define FTE_CSQC_SKELETONOBJECTS /* Provides container objects for skeletal bone data, which can be modified on a per bone basis if needed. This allows you to dynamically generate animations (or just blend them with greater customisation) instead of being limited to a single animation or two. */ #define FTE_CSQC_RAWIMAGES /* Provides raw rgba image access to csqc. With this, the csprogs can read textures into qc-accessible memory, modify it, and then upload it to the renderer. */ @@ -287,7 +287,7 @@ void end_sys_globals; .vector oldorigin; /* This is often used on players to reset the player back to where they were last frame if they somehow got stuck inside something due to fpu precision. Never change a player's oldorigin field to inside a solid, because that might cause them to become pemanently stuck. */ .vector velocity; /* The direction and speed that the entity is moving in world space. */ .vector angles; /* The eular angles the entity is facing in, in pitch, yaw, roll order. Due to a legacy bug, mdl/iqm/etc formats use +x=UP, bsp/spr/etc formats use +x=DOWN. */ -.vector avelocity; /* The amount the entity's angles change by each frame. Note that this is direct eular angles, and thus the angular change is non-linear and often just looks buggy. */ +.vector avelocity; /* The amount the entity's angles change by per second. Note that this is direct eular angles, and thus the angular change is non-linear and often just looks buggy if you're changing more than one angle at a time. */ .float pmove_flags; .string classname; /* Identifies the class/type of the entity. Useful for debugging, also used for loading, but its value is not otherwise significant to the engine, this leaves the mod free to set it to whatever it wants and randomly test strings for values in whatever inefficient way it chooses fit. */ .float renderflags; @@ -357,6 +357,8 @@ int serverid; /* The unique id of this server within the server cluster. */ .float bouncestop; .float idealpitch; .float pitch_speed; +.float drawflags; /* Various flags that affect lighting values and scaling. Typically set to 96 in quake for proper compatibility with DP_QC_SCALE. */ +.float abslight; /* Allows overriding light levels. Use drawflags to state that this field should actually be used. */ .vector color; /* This affects the colour of realtime lights that were enabled via the pflags field. */ .float light_lev; /* This is the radius of an entity's light. This is not normally used by the engine, but is used for realtime lights (ones that are enabled with the pflags field). */ .float style; /* Used by the light util to decide how an entity's light should animate. On an entity with pflags set, this also affects realtime lights. */ @@ -370,6 +372,13 @@ int serverid; /* The unique id of this server within the server cluster. */ .float baseframe1time; /* See basebone */ .float baseframe2time; /* See basebone */ .float baselerpfrac; /* See basebone */ +.float bonecontrol1; /* Halflife model format bone controller. On player models, this typically affects the spine's yaw. */ +.float bonecontrol2; /* Halflife model format bone controller. On player models, this typically affects the spine's yaw. */ +.float bonecontrol3; /* Halflife model format bone controller. On player models, this typically affects the spine's yaw. */ +.float bonecontrol4; /* Halflife model format bone controller. On player models, this typically affects the spine's yaw. */ +.float bonecontrol5; /* Halflife model format bone controller. This typically affects the mouth. */ +.float subblendfrac; /* Weird animation value specific to halflife models. On player models, this typically affects the spine's pitch. */ +.float basesubblendfrac; /* See basebone */ void(float reqid, float responsecode, string resourcebody) URI_Get_Callback; /* Called as an eventual result of the uri_get builtin. */ void(float apilevel, string enginename, float engineversion) CSQC_Init; /* Called at startup. enginename and engineversion are arbitary hints and can take any form. enginename should be consistant between revisions, but this cannot truely be relied upon. */ void() CSQC_WorldLoaded; /* Called after model+sound precaches have been executed. Gives a chance for the qc to read the entity lump from the bsp. */ @@ -755,8 +764,7 @@ Note that any rendertarget textures may be destroyed on video mode changes or so #define TEREDIT_MESH_KILL 16 #define TEREDIT_TINT 17 #define TEREDIT_RESET_SECT 20 -#define TEREDIT_RELOAD_SECT 21 -#define TEREDIT_ENTS_WIPE 22 +#define TEREDIT_RELOAD_SECT 21 #define TEREDIT_ENT_GET 26 #define TEREDIT_ENT_SET 27 #define TEREDIT_ENT_ADD 28 @@ -950,6 +958,12 @@ string(entity e, string key) infokey = #80; /* Part of QW_ENGINE If e is world, returns the field 'key' from either the serverinfo or the localinfo. If e is a player, returns the value of 'key' from the player's userinfo string. There are a few special exceptions, like 'ip' which is not technically part of the userinfo. */ float(string) stof = #81; /* Part of FRIK_FILE, FTE_STRINGS, QW_ENGINE, ZQ_QC_STRINGS*/ +string(float style, optional __out vector rgb) getlightstyle = #0:getlightstyle; /* + Obtains the light style string for the given style. */ + +vector(float style) getlightstylergb = #0:getlightstylergb; /* + Obtains the current rgb value of the specified light style. In csqc, this is correct with regard to the current frame, while ssqc gives no guarentees about time and ignores client cvars. Note: use getlight if you want the actual light value at a point. */ + void(vector start, vector mins, vector maxs, vector end, float nomonsters, entity ent) tracebox = #90; /* Part of DP_QC_TRACEBOX Exactly like traceline, but a box instead of a uselessly thin point. Acceptable sizes are limited by bsp format, q1bsp has strict acceptable size values. */ @@ -1127,6 +1141,7 @@ float(string shadername, optional string defaultshader, ...) shaderforname = #23 void(vector org, optional float count) te_bloodqw = #239; /* Part of FTE_TE_STANDARDEFFECTBUILTINS*/ float(vector viewpos, entity entity) checkpvs = #240; /* Part of FTE_QC_CHECKPVS*/ vector(entity ent, float tagnum) rotatevectorsbytag = #244; +float(float dividend, float divisor) mod = #245; int(string) stoi = #259; /* Part of FTE_QC_INTCONV Converts the given string into a true integer. Base 8, 10, or 16 is determined based upon the format of the string. */ @@ -1193,6 +1208,16 @@ float(float modidx, string framename) frameforname = #276; /* Part of FTE_CSQC_S float(float modidx, float framenum) frameduration = #277; /* Part of FTE_CSQC_SKELETONOBJECTS Retrieves the duration (in seconds) of the specified framegroup. */ +void(float modidx, float framenum, __inout float basetime, float targettime, void(float timestamp, int code, string data) callback) processmodelevents = #0:processmodelevents; /* + Calls a callback for each event that has been reached. Basetime is set to targettime. */ + +float(float modidx, float framenum, __inout float basetime, float targettime, __out int code, __out string data) getnextmodelevent = #0:getnextmodelevent; /* + Reports the next event within a model's animation. Returns a boolean if an event was found between basetime and targettime. Writes to basetime,code,data arguments (if an event was found, basetime is set to the event's time, otherwise to targettime). + WARNING: this builtin cannot deal with multiple events with the same timestamp (only the first will be reported). */ + +float(float modidx, float framenum, int eventidx, __out float timestamp, __out int code, __out string data) getmodeleventidx = #0:getmodeleventidx; /* + Reports an indexed event within a model's animation. Writes to timestamp,code,data arguments on success. Returns false if the animation/event/model was out of range/invalid. Does not consider looping animations (retry from index 0 if it fails and you know that its a looping animation). This builtin is more annoying to use than getnextmodelevent, but can be made to deal with multiple events with the exact same timestamp. */ + #define dotproduct(v1,v2) ((vector)(v1)*(vector)(v2)) vector(vector v1, vector v2) crossproduct = #0:crossproduct; /* Part of FTE_QC_CROSSPRODUCT Small helper function to calculate the crossproduct of two vectors. */ @@ -1284,6 +1309,9 @@ float(string name) checkcommand = #294; /* Part of FTE_QC_CHECKCOMMAND string(string s) argescape = #295; /* Marks up a string so that it can be reliably tokenized as a single argument later. */ +float(float mdlidx) modelframecount = #0:modelframecount; /* + Retrieves the number of frames in the specified model. */ + void() clearscene = #300; /* Forgets all rentities, polygons, and temporary dlights. Resets all view properties to their default values. */ @@ -1296,7 +1324,11 @@ void(float mask) addentities = #301; /* void(entity ent) addentity = #302; /* Copies the entity fields into a new rentity for later rendering via addscene. */ -void(string texturename, float flags, void *verts, int *indexes, int numindexes) addtrisoup_1 = #0:addtrisoup_1; /* +typedef float vec2[2]; +typedef float vec3[3]; +typedef float vec4[4]; +typedef struct trisoup_simple_vert_s {vec3 xyz;vec2 st;vec4 rgba;} trisoup_simple_vert_t; +void(string texturename, int flags, struct trisoup_simple_vert_s *verts, int *indexes, int numindexes) addtrisoup_simple = #0:addtrisoup_simple; /* Adds the specified trisoup into the scene as additional geometry. This permits caching geometry to reduce builtin spam. Indexes are a triangle list (so eg quads will need 6 indicies to form two triangles). NOTE: this is not going to be a speedup over polygons if you're still generating lots of new data every frame. */ #define setviewprop setproperty @@ -1400,12 +1432,13 @@ void(vector pivot, vector mins, vector maxs, string pic, vector rgb, float alpha void(vector pivot, vector mins, vector maxs, string pic, vector txmin, vector txsize, vector rgb, vector alphaandangles) drawrotsubpic = #0:drawrotsubpic; /* Overcomplicated draw function for over complicated people. Positions follow drawrotpic, while texture coords follow drawsubpic. Due to argument count limitations in builtins, the alpha value and angles are combined into separate fields of a vector (tip: use fteqcc's [alpha, angle] feature. */ -float(float stnum) getstati = #330; /* +#define getstati_punf(stnum) (float)(__variant)getstati(stnum) +int(float stnum) getstati = #330; /* Retrieves the numerical value of the given EV_INTEGER or EV_ENTITY stat (converted to a float). */ #define getstatbits getstatf float(float stnum, optional float firstbit, optional float bitcount) getstatf = #331; /* - Retrieves the numerical value of the given EV_FLOAT stat. If firstbit and bitcount are specified, retrieves the upper bits of the STAT_ITEMS stat. */ + Retrieves the numerical value of the given EV_FLOAT stat. If firstbit and bitcount are specified, retrieves the upper bits of the STAT_ITEMS stat (converted into a float, so there are no VM dependancies). */ string(float stnum) getstats = #332; /* Retrieves the value of the given EV_STRING stat, as a tempstring. @@ -2018,8 +2051,8 @@ accessor hashtable : float inline set vector v[string key] = {hash_add(this, key, value, HASH_REPLACE|EV_VECTOR);}; inline get string s[string key] = {return hash_get(this, key, "", EV_STRING);}; inline set string s[string key] = {hash_add(this, key, value, HASH_REPLACE|EV_STRING);}; - inline get string f[string key] = {return hash_get(this, key, 0.0, EV_FLOAT);}; - inline set string f[string key] = {hash_add(this, key, value, HASH_REPLACE|EV_FLOAT);}; + inline get float f[string key] = {return hash_get(this, key, 0.0, EV_FLOAT);}; + inline set float f[string key] = {hash_add(this, key, value, HASH_REPLACE|EV_FLOAT);}; inline get __variant[string key] = {return hash_get(this, key, __NULL__);}; inline set __variant[string key] = {hash_add(this, key, value, HASH_REPLACE);}; }; diff --git a/quakec/csaddon/src/editor_brushes.qc b/quakec/csaddon/src/editor_brushes.qc index 7ab4f94c..86fe6f89 100644 --- a/quakec/csaddon/src/editor_brushes.qc +++ b/quakec/csaddon/src/editor_brushes.qc @@ -6,12 +6,19 @@ to move 50 brushes, mod needs to get+delete+transform+create brush ids are ints. this allows different clients to use different ranges without float problems. */ -int *selectedbrushes; +#define FIXME + +typedef struct +{ + int model; + int id; + int face; + //fixme: do we need an array of faces here? +} selbrush_t; +selbrush_t *selectedbrushes; int selectedbrushcount; -int selectedbrush; -int selectedbrushface; -int selectedbrushmodel; +var int selectedbrushmodel = 1; //by default, the worldmodel. this is only really needed for inserts. vector facepoints[64]; var float autocvar_ca_brush_view = 0; //0=normal, 1=x, 2=y, 3=z @@ -30,61 +37,70 @@ typedef struct int numfaces; int contents; } history_t; -nosave history_t historyring[128]; +nosave history_t historyring[512]; int history_min; //oldest we can go int history; //updated on each change int history_max; //max value for redo -int(int brushid) brush_isselected = +int(int modelidx, int brushid) brush_isselected = { for (int i = 0; i < selectedbrushcount; i++) - if (selectedbrushes[i] == brushid) - return i+1; + if (selectedbrushes[i].id == brushid) + if (selectedbrushes[i].model == modelidx) + return i+1; return 0; }; -int(int brushid) brush_deselect = +int(int modelidx, int brushid) brush_deselect = { - int i = brush_isselected(brushid); + int i = brush_isselected(modelidx, brushid); if (!i) return FALSE; - brush_selected(selectedbrushmodel, brushid, -1, FALSE); - memcpy(&selectedbrushes[i-1], &selectedbrushes[i], sizeof(int)*(selectedbrushcount-i)); + brush_selected(modelidx, brushid, -1, FALSE); + memcpy(&selectedbrushes[i-1], &selectedbrushes[i], sizeof(selbrush_t)*(selectedbrushcount-i)); selectedbrushcount--; return TRUE; }; void() brush_deselectall = { - brush_selected(selectedbrushmodel, selectedbrush, -1, FALSE); - selectedbrush = 0; - selectedbrushface = 0; + for (int i = 0; i < selectedbrushcount; i++) + brush_selected(selectedbrushes[i].model, selectedbrushes[i].id, -1, FALSE); + selectedbrushcount = 0; }; -void(int brushid) brush_select = +void(int modelidx, int brushid) brush_select = { - if (!brush_isselected(brushid)) + if (!brush_isselected(modelidx, brushid)) { - int *n = memalloc(sizeof(int) * (selectedbrushcount+1)); - memcpy(n, selectedbrushes, sizeof(int)*selectedbrushcount); + selbrush_t *n = memalloc(sizeof(selbrush_t) * (selectedbrushcount+1)); + memcpy(n, selectedbrushes, sizeof(selbrush_t)*selectedbrushcount); memfree(selectedbrushes); selectedbrushes = n; - n[selectedbrushcount++] = brushid; - brush_selected(selectedbrushmodel, brushid, -1, TRUE); + n[selectedbrushcount].model = modelidx; + n[selectedbrushcount].id = brushid; + selectedbrushcount++; + brush_selected(modelidx, brushid, -1, TRUE); } - if (!selectedbrush) - selectedbrush = brushid; + selectedbrushmodel = modelidx; }; -//when replaying history, ids get changed, which makes -static void(int old, int new) history_remap = +//when replaying history, ids get changed, which makes things fun +static void(int modelidx, int old, int new) history_remap = { - if (selectedbrush == old) - selectedbrush = new; for (int i = history_min; i < history_max; i++) { history_t *h = &historyring[i & (historyring.length-1)]; if (h->id == old) h->id = new; } + + int i = brush_isselected(modelidx, old); + if (i) + { + i--; + brush_selected(modelidx, old, -1, FALSE); + brush_selected(modelidx, new, -1, TRUE); + selectedbrushes[i].id = new; + } }; void() brush_undo = @@ -99,11 +115,13 @@ void() brush_undo = //we're undoing, so deletes create and creates delete. weird, yes. if (h->wasdelete) - history_remap(h->id, brush_create(h->brushmodel, h->brushdata, h->numfaces, h->contents)); + history_remap(h->brushmodel, h->id, brush_create(h->brushmodel, h->brushdata, h->numfaces, h->contents)); else { brush_delete(h->brushmodel, h->id); } + if (history == history_min) + break; //as far back as we can go, more timestamps are invalid } while (historyring[(history-1) & (historyring.length-1)].timestamp == h->timestamp); }; void() brush_redo = @@ -122,13 +140,15 @@ void() brush_redo = } else { - history_remap(h->id, brush_create(h->brushmodel, h->brushdata, h->numfaces, h->contents)); + history_remap(h->brushmodel, h->id, brush_create(h->brushmodel, h->brushdata, h->numfaces, h->contents)); } history++; + if (history == history_max) + return; //don't poke beyond the end... } while (historyring[history & (historyring.length-1)].timestamp == h->timestamp); }; //create and journal. -int(int mod, brushface_t *faces, int numfaces, int contents) brush_history_create = +int(int mod, brushface_t *faces, int numfaces, int contents, float selectnow) brush_history_create = { history_t *h = &historyring[history & (historyring.length-1)]; @@ -148,14 +168,19 @@ int(int mod, brushface_t *faces, int numfaces, int contents) brush_history_creat history_max = history; //always break any pending redos if (history_min < history_max - historyring.length) history_min = history_max - historyring.length; + + if (selectnow) + brush_select(mod, h->id); return h->id; }; //delete and journal. void(int mod, int id) brush_history_delete = { int numfaces = brush_get(mod, id, __NULL__, 0, __NULL__); + brush_deselect(mod, id); if (!numfaces) return; //doesn't exist or something, some kind of error. we can't log a delete if it didn't delete anything, if only because we can't recreate it if its undone. + history_t *h = &historyring[history & (historyring.length-1)]; h->timestamp = time; @@ -177,15 +202,17 @@ void(int mod, int id) brush_history_delete = int(int mod, int id, brushface_t *faces, int numfaces, int contents) brush_history_edit = { - int wasselected = brush_isselected(id); - int wasmainselected = selectedbrush == id; + int wasselected = brush_isselected(mod, id); + if (wasselected) + selectedbrushes[wasselected-1].id = -id; //hack so that brush_history_delete won't remove this entry. brush_history_delete(mod, id); - id = brush_history_create(mod, faces, numfaces, contents); + id = brush_history_create(mod, faces, numfaces, contents, FALSE); if (wasselected) - selectedbrushes[wasselected-1] = id; - if (wasmainselected) - selectedbrush = id; + { + selectedbrushes[wasselected-1].id = id; + brush_selected(mod, id, -1, TRUE); + } return id; }; @@ -198,6 +225,9 @@ typedef struct int selectedvert1; int selectedvert2; + + int model; + int brush; } vertsoup_t; vertsoup_t vertedit; brushface_t tmp_faces[64]; @@ -580,16 +610,16 @@ int(int model, int brush1, int brush2, int face1, int face2) mergebrushes = brush_history_delete(model, brush1); brush_history_delete(model, brush2); - return brush_history_create(model, tmp_faces, tmp_numfaces, tmp_contents); + return brush_history_create(model, tmp_faces, tmp_numfaces, tmp_contents, TRUE); } }; -void(int id) DrawEngineBrushWireframe = +void(int mod, int id) DrawEngineBrushWireframe = { const vector col = '1 0 0'; for(int facenum = 0;;) { - int points = brush_getfacepoints(selectedbrushmodel, id, ++facenum, &facepoints[0], facepoints.length); + int points = brush_getfacepoints(mod, id, ++facenum, &facepoints[0], facepoints.length); if (!points) break; //end of face list, I guess @@ -716,7 +746,7 @@ void(vertsoup_t *soup, int drawit) Rebrushify = float d; - tmp_numfaces = brush_get(selectedbrushmodel, selectedbrush, tmp_faces, tmp_faces.length, &tmp_contents); + tmp_numfaces = brush_get(soup->model, soup->brush, tmp_faces, tmp_faces.length, &tmp_contents); //if any triangle's neighbour opposes it, reform the edge across the quad to try to keep it convex. for(point = 0; point+2 < soup->numidx; point+=3) @@ -884,10 +914,7 @@ p3p1edge: } else { - if (selectedbrush) - brush_history_delete(selectedbrushmodel, selectedbrush); - selectedbrush = brush_history_create(selectedbrushmodel, faces, numfaces, 1i); - selectedbrushface = 0; + brush_history_edit(soup->model, soup->brush, faces, numfaces, 1i); soup->numidx = 0; soup->numverts = 0; @@ -896,7 +923,7 @@ p3p1edge: //take a brush apart and generate trisoup from it. //note that the trisoup does not have textures. they will be reconciled when reforming the brush. -void(void) Debrushify = +void(vertsoup_t *vertedit, int model, int brush) Debrushify = { int *ni; int i, j, k; @@ -904,21 +931,23 @@ void(void) Debrushify = vector p; vector *np; static int fi[64]; - vertedit.numidx = 0; - vertedit.numverts = 0; + vertedit->model = model; + vertedit->brush = brush; + vertedit->numidx = 0; + vertedit->numverts = 0; for (i = 0; ; ) { - points = brush_getfacepoints(selectedbrushmodel, selectedbrush, ++i, facepoints, facepoints.length); + points = brush_getfacepoints(vertedit->model, vertedit->brush, ++i, facepoints, facepoints.length); if (!points) break; //allocate a few new indexes - ni = memalloc(sizeof(*ni) * (vertedit.numidx+3*(points-2))); - memcpy(ni, vertedit.i, sizeof(*ni) * vertedit.numidx); - memfree(vertedit.i); - vertedit.i = ni; - ni += vertedit.numidx; - vertedit.numidx += 3*(points-2); + ni = memalloc(sizeof(*ni) * (vertedit->numidx+3*(points-2))); + memcpy(ni, vertedit->i, sizeof(*ni) * vertedit->numidx); + memfree(vertedit->i); + vertedit->i = ni; + ni += vertedit->numidx; + vertedit->numidx += 3*(points-2); for (j = 0; j < points; j++) { @@ -926,20 +955,20 @@ void(void) Debrushify = p_x = floor(p_x + 0.5); //gah, bloomin inprecision. p_y = floor(p_y + 0.5); p_z = floor(p_z + 0.5); - for (k = 0; k < vertedit.numverts; k++) + for (k = 0; k < vertedit->numverts; k++) { //try to be nice and re-use verts. - if (vertedit.p[k] == p) + if (vertedit->p[k] == p) break; } - if (k == vertedit.numverts) + if (k == vertedit->numverts) { //if it wasn't found, we need to allocate a new one - np = memalloc(sizeof(*np) * (vertedit.numverts+1)); - memcpy(np, vertedit.p, sizeof(*np) * vertedit.numverts); - memfree(vertedit.p); - vertedit.p = np; - np += vertedit.numverts; - vertedit.numverts += 1; + np = memalloc(sizeof(*np) * (vertedit->numverts+1)); + memcpy(np, vertedit->p, sizeof(*np) * vertedit->numverts); + memfree(vertedit->p); + vertedit->p = np; + np += vertedit->numverts; + vertedit->numverts += 1; *np = p; } fi[j] = k; @@ -995,6 +1024,7 @@ void(brushface_t *faces, int numfaces) DebrushifyLite = } }; +/* void(vector org, vector ang) editor_brushes_simpleclone = { vector midpoint; @@ -1012,8 +1042,9 @@ void(vector org, vector ang) editor_brushes_simpleclone = } else brushface_translate(tmp_faces, tmp_numfaces, org); - selectedbrush = brush_history_create(selectedbrushmodel, tmp_faces, tmp_numfaces, tmp_contents); + brush_history_create(selectedbrushmodel, tmp_faces, tmp_numfaces, tmp_contents, TRUE); }; +*/ void() brushedit_subtract = { @@ -1021,46 +1052,60 @@ void() brushedit_subtract = vector selnormals[tmp_faces.length]; float seldists[tmp_faces.length]; - int planecount = brush_get(selectedbrushmodel, selectedbrush, tmp_faces, tmp_faces.length, &tmp_contents); - for (int i = 0; i < planecount; i++) + for (int sb = 0; sb < selectedbrushcount; sb++) { - selnormals[i] = tmp_faces[i].planenormal; - seldists[i] = tmp_faces[i].planedist; - } - int numbrushes = brush_findinvolume(selectedbrushmodel, selnormals, seldists, planecount, brushlist, __NULL__, brushlist.length); - - while (numbrushes --> 0) - { - int br = brushlist[numbrushes]; - if (br == selectedbrush) - continue; - - int counto = brush_get(selectedbrushmodel, br, tmp_faces, tmp_faces.length, &tmp_contents); - int counts = counto + brush_get(selectedbrushmodel, selectedbrush, tmp_faces+counto, tmp_faces.length-counto, &discard); - - brush_history_delete(selectedbrushmodel, br); - while(counts --> counto) + int mod = selectedbrushes[sb].model; + int brush = selectedbrushes[sb].id; + int planecount = brush_get(mod, brush, tmp_faces, tmp_faces.length, &tmp_contents); + for (int i = 0; i < planecount; i++) { - //only consider the resulting brush if the new face actually contributed anything. - //this reduces dupes. - if (brush_calcfacepoints(1+counts, tmp_faces, counts+1, facepoints, facepoints.length)) + selnormals[i] = tmp_faces[i].planenormal; + seldists[i] = tmp_faces[i].planedist; + } + int numbrushes = brush_findinvolume(mod, selnormals, seldists, planecount, brushlist, __NULL__, brushlist.length); + + while (numbrushes --> 0) + { + int br = brushlist[numbrushes]; + + if (brush_isselected(mod, br)) + continue; + + int counto = brush_get(mod, br, tmp_faces, tmp_faces.length, &tmp_contents); + int counts = counto + brush_get(mod, brush, tmp_faces+counto, tmp_faces.length-counto, &discard); + + brush_history_delete(mod, br); + while(counts --> counto) { - //determine the brush defined by this plane - tmp_faces[counts].planenormal *= -1; - tmp_faces[counts].planedist *= -1; - brush_history_create(selectedbrushmodel, tmp_faces, counts+1, tmp_contents); + //only consider the resulting brush if the new face actually contributed anything. + //this reduces dupes. + if (brush_calcfacepoints(1+counts, tmp_faces, counts+1, facepoints, facepoints.length)) + { + //determine the brush defined by this plane + tmp_faces[counts].planenormal *= -1; + tmp_faces[counts].planedist *= -1; + brush_history_create(mod, tmp_faces, counts+1, tmp_contents, FALSE); + } } } } }; -void() brushedit_resettextures = +static void() brushedit_resettextures = { - int planecount = brush_get(selectedbrushmodel, selectedbrush, tmp_faces, tmp_faces.length, &tmp_contents); - for (int i = 0; i < planecount; i++) - reset_texturecoords(&tmp_faces[i]); + for (int sb = 0; sb < selectedbrushcount; sb++) + { + int model = selectedbrushes[sb].model; + int brush = selectedbrushes[sb].id; + int face = selectedbrushes[sb].face; + + int planecount = brush_get(model, brush, tmp_faces, tmp_faces.length, &tmp_contents); + for (int i = 0; i < planecount; i++) + if (!face || face == planecount+1) + reset_texturecoords(&tmp_faces[i]); - selectedbrush = brush_history_edit(selectedbrushmodel, selectedbrush, tmp_faces, planecount, tmp_contents); + brush_history_edit(model, brush, tmp_faces, planecount, tmp_contents); + } }; //when we're drawing sideviews, we normally need them wireframe in order to actually see anything @@ -1079,23 +1124,66 @@ static void(vector sizes) drawwireframeview = vaxis[i+3] = -vaxis[i]; dists[i+3] = (org*vaxis[i+3]) + sizes[i]; } - + int numbrushes = brush_findinvolume(selectedbrushmodel, vaxis, dists, dists.length, &brushlist[0], __NULL__, brushlist.length); //this can be a bit slow with lots of brushes. would be nice to batch them a bit better. while(numbrushes --> 0) - DrawEngineBrushWireframe(brushlist[numbrushes]); + DrawEngineBrushWireframe(selectedbrushmodel, brushlist[numbrushes]); setviewprop(VF_DRAWWORLD, FALSE); }; +static void() editor_brushes_drawselected = +{ + float intensity = (sin(gettime(5)*4)+1)*0.05; + int facenum, point, points; + vector col; + + //draw all selected brush faces. + for (int sb = 0; sb < selectedbrushcount; sb++) + { + int model = selectedbrushes[sb].model; + int brush = selectedbrushes[sb].id; + int face = selectedbrushes[sb].face; + for(facenum = 0;;) + { + points = brush_getfacepoints(model, brush, ++facenum, facepoints, facepoints.length); + if (!points) + break; //end of face list, I guess + + if (facenum == face) + col = [0,intensity,0]; + else + col = [intensity,0,0]; + + R_BeginPolygon("terrainedit"); + for (point = 0; point < points; point++) + R_PolygonVertex(facepoints[point], '0 0', col, 1); + R_EndPolygon(); + } + } + + for (int sb = 0; sb < selectedbrushcount; sb++) + { + int model = selectedbrushes[sb].model; + int brush = selectedbrushes[sb].id; + + //now draw wireframe + DrawEngineBrushWireframe(model, brush); + } + +// tmp_numfaces = brush_get(selectedbrushmodel, selectedbrush, tmp_faces, tmp_faces.length, &tmp_contents); +// DebrushifyLite(tmp_faces, tmp_numfaces); + DrawAxisExtensions(vertedit.p, vertedit.numverts, "terrainedit", '0 0 0.3', 1); +}; + void(vector mousepos) editor_brushes_addentities = { vector col = '0 0 0'; int points, point; int facenum; float intensity = (sin(gettime(5)*4)+1)*0.05; - vector displace, tmp; - float bestdist, dist; + vector displace, tmp; vector mid; autocvar_ca_brush_view = fabs(autocvar_ca_brush_view) % 4f; @@ -1134,22 +1222,29 @@ void(vector mousepos) editor_brushes_addentities = if ((mousetool == BT_VERTEXEDIT || mousetool == BT_CREATEDRAG || brushtool == BT_PUSHFACE || brushtool == BT_CLONEDISPLACE || brushtool == BT_MOVE || brushtool == BT_MOVETEXTURE) && bt_points) { - vector dir = v_forward; - if (!altdown) //if alt is pressed, we'll axialize later. - dir = axialize(dir); + vector fwd = v_forward; + vector rgt = v_right; + vector up = v_up; + + fwd = axialize(fwd); + rgt -= (rgt * fwd) * rgt; + up -= (up * fwd) * up; + rgt = axialize(rgt); + up = axialize(up); + tmp = normalize(mousefar-mousenear) + mousenear; - tmp = planelinepoint(mousenear, tmp, dir, bt_point[0] * dir); //find where the cursor impacts the screen grid (moved along the view vector to match where the drag was last frame) + tmp = planelinepoint(mousenear, tmp, v_forward, bt_point[0] * v_forward); //find where the cursor impacts the screen grid (moved along the view vector to match where the drag was last frame) displace = tmp - bt_point[0]; if (mousetool != BT_VERTEXEDIT && mousetool != BT_CREATEDRAG) displace = brush_snappoint(displace); - if (altdown) //if alt is held, rotate the move by 90 degrees, and move ONLY in the axial dir instead of the screen's xy plane - bt_displace_z = (displace * v_right + displace * v_up); + if (altdown) + bt_displace_x = displace * fwd; else { - bt_displace_x = displace * axialize(v_right); - bt_displace_y = displace * axialize(v_up); + bt_displace_y = displace * rgt; + bt_displace_z = displace * up; } - displace = bt_displace_x * axialize(v_right) + bt_displace_y * axialize(v_up) - bt_displace_z * axialize(v_forward); + displace = bt_displace_x * fwd + bt_displace_y * rgt + bt_displace_z * up; if (mousetool == BT_VERTEXEDIT) displace = brush_snappoint(displace+bt_point[0]); @@ -1242,6 +1337,8 @@ void(vector mousepos) editor_brushes_addentities = } else if (brushtool == BT_CREATE) { + editor_brushes_drawselected(); + if (bt_points > 2) { tmp_numfaces = BrushFromPoints(tmp_faces, tmp_faces.length, bt_point, bt_points, autocvar_ca_newbrushheight); @@ -1289,8 +1386,7 @@ void(vector mousepos) editor_brushes_addentities = if (!mousedown) { - selectedbrush = brush_history_create(selectedbrushmodel, tmp_faces, tmp_numfaces, tmp_contents); - brush_selected(selectedbrushmodel, selectedbrush, selectedbrushface, TRUE); + brush_history_create(selectedbrushmodel, tmp_faces, tmp_numfaces, tmp_contents, TRUE); bt_points = 0; mousetool = BT_NONE; } @@ -1298,32 +1394,66 @@ void(vector mousepos) editor_brushes_addentities = else mousedown = FALSE; } + else if (brushtool == BT_SLICE) + { + editor_brushes_drawselected(); + if (bt_points) + { + for (int sb = 0; sb < selectedbrushcount; sb++) + { + int model = selectedbrushes[sb].model; + int brush = selectedbrushes[sb].id; + + tmp_numfaces = brush_get(model, brush, tmp_faces, tmp_faces.length, &tmp_contents); + tmp_faces[tmp_numfaces].planenormal = normalize(crossproduct(bt_point[2] - bt_point[0], bt_point[1] - bt_point[0])); + tmp_faces[tmp_numfaces].planedist = bt_point[0] * tmp_faces[tmp_numfaces].planenormal; + + //draw it wireframe + DrawQCBrushWireframe(tmp_faces, tmp_numfaces+1, "chop", '1 0 0', 1); + + //flip the split + tmp_faces[tmp_numfaces].planenormal *= -1; + tmp_faces[tmp_numfaces].planedist *= -1; + + //draw the other side wireframe + DrawQCBrushWireframe(tmp_faces, tmp_numfaces+1, "chop", '0 1 0', 1); + } + } + } else if ((mousetool == BT_PUSHFACE || mousetool == BT_MOVETEXTURE || mousetool == BT_MOVE || mousetool == BT_CLONEDISPLACE || mousetool == BT_ROTATE) && bt_points) { int oldselectedbrushcount = selectedbrushcount; - int *oldselectedbrushes = memalloc(sizeof(int)*selectedbrushcount); - memcpy(oldselectedbrushes, selectedbrushes, selectedbrushcount*sizeof(int)); + selbrush_t *oldselectedbrushes = memalloc(sizeof(selbrush_t)*selectedbrushcount); + memcpy(oldselectedbrushes, selectedbrushes, selectedbrushcount*sizeof(selbrush_t)); for (int sb = 0; sb < oldselectedbrushcount; sb++) { - int brush = oldselectedbrushes[sb]; + int model = oldselectedbrushes[sb].model; + int brush = oldselectedbrushes[sb].id; + int face = oldselectedbrushes[sb].face; if (!brush) continue; - tmp_numfaces = brush_get(selectedbrushmodel, brush, tmp_faces, tmp_faces.length, &tmp_contents); + tmp_numfaces = brush_get(model, brush, tmp_faces, tmp_faces.length, &tmp_contents); - if (mousetool == BT_PUSHFACE && brush == selectedbrush) - tmp_faces[selectedbrushface-1].planedist += tmp_faces[selectedbrushface-1].planenormal * displace; - else if (mousetool == BT_MOVETEXTURE && brush == selectedbrush) + if (mousetool == BT_PUSHFACE) { - tmp_faces[selectedbrushface-1].sbias -= tmp_faces[selectedbrushface-1].sdir * displace; - tmp_faces[selectedbrushface-1].tbias -= tmp_faces[selectedbrushface-1].tdir * displace; + if (!face) + continue; //FIXME: should not happen. + tmp_faces[face-1].planedist += tmp_faces[face-1].planenormal * displace; + } + else if (mousetool == BT_MOVETEXTURE) + { + if (!face) + continue; //FIXME: should not happen. + tmp_faces[face-1].sbias -= tmp_faces[face-1].sdir * displace; + tmp_faces[face-1].tbias -= tmp_faces[face-1].tdir * displace; } else if (mousetool == BT_MOVE || mousetool == BT_CLONEDISPLACE) brushface_translate(tmp_faces, tmp_numfaces, displace); else if (mousetool == BT_ROTATE) { //find the brush's middle (based on its bbox) - brush_getfacepoints(selectedbrushmodel, brush, 0, &tmp, 1); + brush_getfacepoints(model, brush, 0, &tmp, 1); makevectors(col); @@ -1339,25 +1469,25 @@ void(vector mousepos) editor_brushes_addentities = if (mousetool == BT_MOVETEXTURE) { - points = brush_calcfacepoints(selectedbrushface, tmp_faces, tmp_numfaces, facepoints, facepoints.length); + points = brush_calcfacepoints(model, tmp_faces, tmp_numfaces, facepoints, facepoints.length); if (points) { //this is unfortunate. the built in shaders expect to use lightmaps. we don't have those. //because lightmaps are special things, we end up in a real mess. so lets just make sure there's a shader now, because we can. - shaderforname(tmp_faces[selectedbrushface-1].shadername, + shaderforname(tmp_faces[face-1].shadername, sprintf("{" "{\n" "map \"%s.lmp\"\n" "rgbgen vertex\n" "alphagen vertex\n" "}\n" - "}", tmp_faces[selectedbrushface-1].shadername)); + "}", tmp_faces[face-1].shadername)); col = '0.7 0.7 0.7'; //fullbright is typically TOO bright. overbrights? meh! - vector sz = drawgetimagesize(tmp_faces[selectedbrushface-1].shadername); - R_BeginPolygon(tmp_faces[selectedbrushface-1].shadername); + vector sz = drawgetimagesize(tmp_faces[face-1].shadername); + R_BeginPolygon(tmp_faces[face-1].shadername); for (point = 0; point < points; point++) - R_PolygonVertex(facepoints[point] + tmp_faces[selectedbrushface-1].planenormal*0.1, [(facepoints[point] * tmp_faces[selectedbrushface-1].sdir + tmp_faces[selectedbrushface-1].sbias)/sz_x, (facepoints[point] * tmp_faces[selectedbrushface-1].tdir + tmp_faces[selectedbrushface-1].tbias)/sz_y], col, 1); + R_PolygonVertex(facepoints[point] + tmp_faces[face-1].planenormal*0.1, [(facepoints[point] * tmp_faces[face-1].sdir + tmp_faces[face-1].sbias)/sz_x, (facepoints[point] * tmp_faces[face-1].tdir + tmp_faces[face-1].tbias)/sz_y], col, 1); R_EndPolygon(); } } @@ -1370,7 +1500,7 @@ void(vector mousepos) editor_brushes_addentities = if (!points) continue; //should probably warn somehow about this //should we use two colour channels? one depth one not? - if (facenum == selectedbrushface) + if (facenum == face) col = '1 0 0'; else col = '0 0.5 0'; @@ -1399,36 +1529,19 @@ void(vector mousepos) editor_brushes_addentities = if (mousetool == BT_CLONEDISPLACE) //doesn't affect the original brush. { if (displace*displace > 1) - selectedbrush = brush_history_create(selectedbrushmodel, tmp_faces, tmp_numfaces, tmp_contents); + brush_history_create(model, tmp_faces, tmp_numfaces, tmp_contents, TRUE); } else - selectedbrush = brush_history_edit(selectedbrushmodel, brush, tmp_faces, tmp_numfaces, tmp_contents); - brush_selected(selectedbrushmodel, brush, selectedbrushface, TRUE); + brush_history_edit(model, brush, tmp_faces, tmp_numfaces, tmp_contents); bt_points = 0; mousetool = BT_NONE; } } memfree(oldselectedbrushes); } - else if (brushtool == BT_SLICE && bt_points == 3) + else if (selectedbrushcount) { - tmp_numfaces = brush_get(selectedbrushmodel, selectedbrush, tmp_faces, tmp_faces.length, &tmp_contents); - tmp_faces[tmp_numfaces].planenormal = normalize(crossproduct(bt_point[2] - bt_point[0], bt_point[1] - bt_point[0])); - tmp_faces[tmp_numfaces].planedist = bt_point[0] * tmp_faces[tmp_numfaces].planenormal; - - //draw it wireframe - DrawQCBrushWireframe(tmp_faces, tmp_numfaces+1, "chop", '1 0 0', 1); - - //flip the split - tmp_faces[tmp_numfaces].planenormal *= -1; - tmp_faces[tmp_numfaces].planedist *= -1; - - //draw the other side wireframe - DrawQCBrushWireframe(tmp_faces, tmp_numfaces+1, "chop", '0 1 0', 1); - } - else if (selectedbrush) - { - if (brushtool == BT_PUSHFACE) + /*if (brushtool == BT_PUSHFACE) { //selected face (not brush) follows cursor when we're not actively dragging a face for(facenum = 0;;) { @@ -1447,36 +1560,11 @@ void(vector mousepos) editor_brushes_addentities = selectedbrushface = facenum; } } - } - - //draw all selected brush faces. - for (int sb = 0; sb < selectedbrushcount; sb++) - { - int brush = selectedbrushes[sb]; - for(facenum = 0;;) - { - points = brush_getfacepoints(selectedbrushmodel, brush, ++facenum, facepoints, facepoints.length); - if (!points) - break; //end of face list, I guess - - if (facenum == selectedbrushface && brush == selectedbrush) - col = [0,intensity,0]; - else - col = [intensity,0,0]; - - R_BeginPolygon("terrainedit"); - for (point = 0; point < points; point++) - R_PolygonVertex(facepoints[point], '0 0', col, 1); - R_EndPolygon(); - } - } - //now draw wireframe - DrawEngineBrushWireframe(selectedbrush); - - tmp_numfaces = brush_get(selectedbrushmodel, selectedbrush, tmp_faces, tmp_faces.length, &tmp_contents); - DebrushifyLite(tmp_faces, tmp_numfaces); - DrawAxisExtensions(vertedit.p, vertedit.numverts, "terrainedit", '0 0 0.3', 1); + }*/ + + editor_brushes_drawselected(); } + // editor_drawbbox(selectedbrush); vector t = mousefar; @@ -1515,7 +1603,7 @@ void(vector mousepos) editor_brushes_addentities = //draw a line/triangle to show the selection. int showpoints = bt_points; - if (brushtool == BT_SLICE) + if (brushtool == BT_SLICE && bt_points) { // bt_point[showpoints++] = brush_snappoint(trace_endpos); showpoints = 3; @@ -1617,7 +1705,7 @@ void(vector mousepos) editor_brushes_overlay = break; case BT_SLICE: drawrawstring('0 32 0', "Slice Tool", '8 8 0', '1 1 1', 1); - if (!selectedbrush) + if (!selectedbrushcount) brushtool = BT_NONE; break; case BT_PUSHFACE: @@ -1693,7 +1781,7 @@ brusheditormodes case "+brushedit_nogrid": nogrid = TRUE; break; case "-brushedit_nogrid": nogrid = FALSE; break; - case "brushedit_matchface": +/* case "brushedit_matchface": brushtool = BT_NONE; bt_points = 0; @@ -1717,6 +1805,7 @@ brusheditormodes } } break; +*/ case "brushedit_resettexcoords": brushedit_resettextures(); break; @@ -1729,9 +1818,10 @@ brusheditormodes brushedit_subtract(); break; case "brushedit_delete": - if (selectedbrushmodel && selectedbrush) - brush_history_delete(selectedbrushmodel, selectedbrush); - selectedbrush = 0; + while(selectedbrushcount) + { + brush_history_delete(selectedbrushes[0].model, selectedbrushes[0].id); + } break; case "brushedit_binds_default": localcmd("echo Setting default brusheditor bindings\n"); @@ -1786,20 +1876,19 @@ float(float key, float unic, vector mousepos) editor_brushes_key = brushface_t *fa; vector t = mousefar; vector o = mousenear; - int i, p; + int i; if (vlen(o - t) > 8192 && !autocvar_ca_brush_view) t = o + normalize(t)*8192; if (key == K_ESCAPE) { + bt_points = 0; vertedit.numidx = 0; vertedit.numverts = 0; if (brushtool) brushtool = BT_NONE; else - { brush_deselectall(); - } return TRUE; } if (key == K_MOUSE1) @@ -1828,13 +1917,13 @@ float(float key, float unic, vector mousepos) editor_brushes_key = traceline(o, t, TRUE, world); float tracemodel = trace_ent.modelindex; - if (brushtool == BT_CREATEDRAG || (brushtool == BT_PUSHFACE && selectedbrushface)) + /*if (brushtool == BT_CREATEDRAG || (brushtool == BT_PUSHFACE && selectedbrushface)) { trace_brush_faceid = selectedbrushface; trace_brush_id = selectedbrush; tracemodel = selectedbrushmodel; } - else if (brushtool != BT_PUSHFACE && brushtool != BT_MOVETEXTURE) + else*/ if (brushtool != BT_PUSHFACE && brushtool != BT_MOVETEXTURE) trace_brush_faceid = 0; if (brushtool == BT_SLICE || brushtool == BT_CREATE) @@ -1857,10 +1946,8 @@ float(float key, float unic, vector mousepos) editor_brushes_key = int majoraxis; traceline(o, t, TRUE, world); - brush_selected(selectedbrushmodel, selectedbrush, -1, FALSE); - static vector bbox[2]; - p = brush_getfacepoints(selectedbrushmodel, selectedbrush, 0, bbox, bbox.length); + brush_getfacepoints(tracemodel, trace_brush_id, 0, bbox, bbox.length); t[0] = fabs(trace_plane_normal[0]); t[1] = fabs(trace_plane_normal[1]); @@ -1880,27 +1967,79 @@ float(float key, float unic, vector mousepos) editor_brushes_key = } } //FIXME: selecting a brush by face should select either the front or the back. ideally depending on which one is closest to its respective face center, I suppose. - else if (trace_brush_id != selectedbrush || selectedbrushface != trace_brush_faceid || selectedbrushmodel != tracemodel) + else { - brush_selected(selectedbrushmodel, selectedbrush, -1, FALSE); - if (!shiftdown) - { - if (!brush_isselected(trace_brush_id)) - brush_deselectall(); + if (shiftdown) + { //simple selection toggle of the clicked brush + if (!tracemodel || !trace_brush_id) + { + } + else if (brush_isselected(tracemodel, trace_brush_id)) + brush_deselect(tracemodel, trace_brush_id); + else + brush_select(tracemodel, trace_brush_id); } - else if (brush_deselect(trace_brush_id)) - return TRUE; - brush_select(trace_brush_id); - selectedbrush = trace_brush_id; - selectedbrushface = trace_brush_faceid; - selectedbrushmodel = tracemodel; - if (trace_brush_faceid) - brush_selected(selectedbrushmodel, selectedbrush, selectedbrushface, TRUE); + else + { //deselect everything but the targetted brush. + if (!tracemodel || !trace_brush_id) + brush_deselectall(); + else if (!brush_isselected(tracemodel, trace_brush_id)) + { + brush_deselectall(); + brush_select(tracemodel, trace_brush_id); + } + else + { //its already selected, start doing whatever the current mouse action is meant to be + mousedown = TRUE; + mousetool = brushtool; + vertedit.selectedvert1 = -1; + vertedit.selectedvert2 = -1; + bt_point[0] = brush_snappoint(trace_endpos); + bt_points = 1; + bt_displace = '0 0 0'; + } + + if (brushtool == BT_VERTEXEDIT && !vertedit.numidx && trace_brush_id) + { + mousedown = FALSE; + if (brush_isselected(tracemodel, trace_brush_id)) + Debrushify(&vertedit, tracemodel, trace_brush_id); + } + } +/* - vertedit.numidx = 0; - vertedit.numverts = 0; - - if (selectedbrushface && (brushtool == BT_PUSHFACE || brushtool == BT_MOVETEXTURE)) + if (trace_brush_id != selectedbrush || selectedbrushface != trace_brush_faceid || selectedbrushmodel != tracemodel) + { + brush_selected(selectedbrushmodel, selectedbrush, -1, FALSE); + if (!shiftdown) + { + if (!brush_isselected(tracemodel, trace_brush_id)) + brush_deselectall(); + } + else if (brush_deselect(tracemodel, trace_brush_id)) + return TRUE; + brush_select(tracemodel, trace_brush_id); + selectedbrush = trace_brush_id; + selectedbrushface = trace_brush_faceid; + selectedbrushmodel = tracemodel; + if (trace_brush_faceid) + brush_selected(selectedbrushmodel, selectedbrush, selectedbrushface, TRUE); + + vertedit.numidx = 0; + vertedit.numverts = 0; + + if (selectedbrushface && (brushtool == BT_PUSHFACE || brushtool == BT_MOVETEXTURE)) + { + mousedown = TRUE; + mousetool = brushtool; + vertedit.selectedvert1 = -1; + vertedit.selectedvert2 = -1; + bt_point[0] = brush_snappoint(trace_endpos); + bt_points = 1; + bt_displace = '0 0 0'; + } + } + else if (selectedbrush == trace_brush_id && selectedbrushface == trace_brush_faceid && selectedbrushmodel == tracemodel) { mousedown = TRUE; mousetool = brushtool; @@ -1910,22 +2049,7 @@ float(float key, float unic, vector mousepos) editor_brushes_key = bt_points = 1; bt_displace = '0 0 0'; } - } - else if (selectedbrush == trace_brush_id && selectedbrushface == trace_brush_faceid && selectedbrushmodel == tracemodel) - { - mousedown = TRUE; - mousetool = brushtool; - vertedit.selectedvert1 = -1; - vertedit.selectedvert2 = -1; - bt_point[0] = brush_snappoint(trace_endpos); - bt_points = 1; - bt_displace = '0 0 0'; - } - if (brushtool == BT_VERTEXEDIT && !vertedit.numidx && selectedbrush) - { - mousedown = FALSE; - Debrushify(); - brush_selected(selectedbrushmodel, selectedbrush, selectedbrushface, TRUE); +*/ } return TRUE; } @@ -1948,48 +2072,52 @@ float(float key, float unic, vector mousepos) editor_brushes_key = { tmp_numfaces = BrushFromPoints(tmp_faces, tmp_faces.length, bt_point, bt_points, autocvar_ca_newbrushheight); bt_points = 0; - int newbr = brush_history_create(selectedbrushmodel, tmp_faces, tmp_numfaces, 1i); - brush_deselectall(); - brush_select(newbr); + brush_history_create(selectedbrushmodel, tmp_faces, tmp_numfaces, 1i, TRUE); } return TRUE; } else if (brushtool == BT_SLICE) - { - //get the current faces - tmp_numfaces = brush_get(selectedbrushmodel, selectedbrush, tmp_faces, tmp_faces.length-1, &tmp_contents); - - //generate a new face plane - fa = &tmp_faces[tmp_numfaces]; - fa->planenormal = normalize(crossproduct(bt_point[2] - bt_point[0], bt_point[1] - bt_point[0])); - fa->planedist = bt_point[0] * fa->planenormal; - fa->shadername = tmp_faces[0].shadername; //find a neighbour? - - //make sure its okay - for (i = 0; i < tmp_numfaces; i++) - { - if (tmp_faces[i].planenormal == fa->planenormal && tmp_faces[i].planedist == fa->planedist) - { - print("that would be co-planar\n"); - return TRUE; - } - } - bt_points = 0; - tmp_numfaces++; - - reset_texturecoords(fa); - - //delete the old one and insert the new - int newbr1 = brush_history_edit(selectedbrushmodel, selectedbrush, tmp_faces, tmp_numfaces, tmp_contents); - - //and insert another new one too, because inserting a plane like this generates two fragments and I'm too lazy to work out which is the front and which is the back. - fa->planenormal *= -1; - fa->planedist *= -1; - int newbr2 = brush_history_create(selectedbrushmodel, tmp_faces, tmp_numfaces, tmp_contents); - + { //save off the selected brushes so we can butcher the list, and select ONLY the sliced brushes on one side of the slice plane. + int oldselectedbrushcount = selectedbrushcount; + selbrush_t *oldselectedbrushes = memalloc(sizeof(selbrush_t)*selectedbrushcount); + memcpy(oldselectedbrushes, selectedbrushes, selectedbrushcount*sizeof(selbrush_t)); brush_deselectall(); - brush_select(newbr2); + for (int sb = 0; sb < oldselectedbrushcount; sb++) + { + int model = oldselectedbrushes[sb].model; + int brush = oldselectedbrushes[sb].id; + //get the current faces + tmp_numfaces = brush_get(model, brush, tmp_faces, tmp_faces.length-1, &tmp_contents); + + //generate a new face plane + fa = &tmp_faces[tmp_numfaces]; + fa->planenormal = normalize(crossproduct(bt_point[2] - bt_point[0], bt_point[1] - bt_point[0])); + fa->planedist = bt_point[0] * fa->planenormal; + fa->shadername = tmp_faces[0].shadername; //find a neighbour? + + //make sure its not coplanar with some other surface. such slices are silly + for (i = 0; i < tmp_numfaces; i++) + { + if (tmp_faces[i].planenormal == fa->planenormal && tmp_faces[i].planedist == fa->planedist) + break; + } + if (i < tmp_numfaces) + continue; + tmp_numfaces++; + + reset_texturecoords(fa); + + //delete the old one and insert the new + brush_history_edit(model, brush, tmp_faces, tmp_numfaces, tmp_contents); + + //and insert another new one too, because inserting a plane like this generates two fragments and I'm too lazy to work out which is the front and which is the back. + fa->planenormal *= -1; + fa->planedist *= -1; + brush_history_create(model, tmp_faces, tmp_numfaces, tmp_contents, TRUE); + } + memfree(oldselectedbrushes); + bt_points = 0; return TRUE; } else return FALSE; diff --git a/quakec/csaddon/src/editor_ents.qc b/quakec/csaddon/src/editor_ents.qc index c573d468..2d221689 100644 --- a/quakec/csaddon/src/editor_ents.qc +++ b/quakec/csaddon/src/editor_ents.qc @@ -15,15 +15,22 @@ typedef struct vector mins; vector maxs; float scale; + + trisoup_simple_vert_t bboxverts[8]; //full data hashtable fields; } entedit_t; +int bbox_line_idxs[] = +{ + 0,2, 2,3, 3,1, 1,0, + 4,6, 6,7, 7,5, 5,4, + 0,4, 3,7, 2,6, 1,5 +}; + static entedit_t *editents; static int numents; -static int entsdirty; -static float entsapplytime; struct { //FIXME: should probably parse quakeed comments or something. @@ -169,7 +176,8 @@ entedit_t*() editor_ents_new = void(float num) editor_ents_delete = { - if (num >= 0 && num < numents) + //num 0 *could* be deleted, but we really don't want to allow that... + if (num > 0 && num < numents) { hash_destroytab(editents[num].fields); editents[num].fields = 0; @@ -201,24 +209,8 @@ string(entedit_t *ent) reforment = return n; }; -void() updatemodelents = -{ - entsdirty = FALSE; - self = world; - terrain_edit(TEREDIT_ENTS_WIPE); - for (int e = 0; e < numents; e++) - { - local entedit_t *ent = &editents[e]; - string n = reforment(ent); - terrain_edit(TEREDIT_ENT_SET, e, n); - } -}; - void(entedit_t *ent) editor_ents_edited = { - entsdirty = TRUE; - entsapplytime = cltime+2; - string n = reforment(ent); terrain_edit(TEREDIT_ENT_SET, ent-editents, n); } @@ -235,6 +227,7 @@ inline float(string model) modelindexforname = void(entedit_t *nent) editor_ents_updated = { float ang; + int i; nent->alpha = stof(nent->fields["alpha"]); nent->scale = stof(nent->fields["scale"]); @@ -255,15 +248,14 @@ void(entedit_t *nent) editor_ents_updated = if (!nent->modelindex) { - int i; string classn; classn = nent->fields["classname"]; for (i = 0; i < entclasses.length; i++) { if (classn == entclasses[i].classn) { - nent->modelindex = 0;//modelindexforname(entclasses[i].model); - nent->alpha = 0.3; + nent->modelindex = modelindexforname(entclasses[i].model); + nent->alpha = 0.7; nent->colourmod = entclasses[i].colour; nent->mins = entclasses[i].mins; nent->maxs = entclasses[i].maxs; @@ -276,6 +268,39 @@ void(entedit_t *nent) editor_ents_updated = if (nent->isbsp) nent->ang = '0 0 0'; //bsp entities should not be displayed rotated, because that just messes everything up. + + + nent->bboxverts[0].xyz = (vec3)((__variant)nent->org); + nent->bboxverts[0].st = (vec2){0,0}; + nent->bboxverts[0].rgba = (vec4){nent->colourmod[0], nent->colourmod[1], nent->colourmod[2], nent->alpha?nent->alpha:1}; + nent->bboxverts[7] = nent->bboxverts[6] = nent->bboxverts[5] = nent->bboxverts[4] = nent->bboxverts[3] = nent->bboxverts[2] = nent->bboxverts[1] = nent->bboxverts[0]; + for (i = 0; i < 8; i++) + { + nent->bboxverts[i].xyz[0] = nent->org[0] + ((i&1i)?nent->maxs[0]:nent->mins[0]); + nent->bboxverts[i].xyz[1] = nent->org[1] + (((i&2i)?nent->maxs[1]:nent->mins[1])); + nent->bboxverts[i].xyz[2] = nent->org[2] + ((i&4)?nent->maxs[2]:nent->mins[2]); + } + /*void(string shadername, vector min, vector max, vector col) editor_ents_drawbbox = +{ + if (min == max) + return; + R_BeginPolygon(shadername); +#define line(x,y) R_PolygonVertex(x, '0 0', col, 1); R_PolygonVertex(y, '0 0', col, 1); R_EndPolygon() + line(min, ([max[0], min[1], min[2]])); + line(min, ([min[0], max[1], min[2]])); + line(min, ([min[0], min[1], max[2]])); + line(max, ([min[0], max[1], max[2]])); + line(max, ([max[0], min[1], max[2]])); + line(max, ([max[0], max[1], min[2]])); + + line(([max[0], min[1], min[2]]), ([max[0], max[1], min[2]])); + line(([max[0], min[1], min[2]]), ([max[0], min[1], max[2]])); + line(([min[0], max[1], min[2]]), ([min[0], max[1], max[2]])); + line(([min[0], max[1], min[2]]), ([max[0], max[1], min[2]])); + line(([min[0], min[1], max[2]]), ([min[0], max[1], max[2]])); + line(([min[0], min[1], max[2]]), ([max[0], min[1], max[2]])); +#undef line +};*/ }; entedit_t*(entedit_t *o) editor_ents_clone = @@ -314,7 +339,7 @@ void() editor_ents_reload = if (nent->fields) hash_destroytab(nent->fields); nent->fields = 0; - if (field == __NULL__) + if (field == __NULL__) //empty entity slot. continue; nent->fields = hash_createtab(12, EV_STRING); fcount = tokenize(field); @@ -353,28 +378,6 @@ void(int idx, string new) CSQC_MapEntityEdited = // editor_ents_edited(ent); }; -void(string shadername, vector min, vector max, vector col) editor_ents_drawbbox = -{ - if (min == max) - return; - R_BeginPolygon(shadername); -#define line(x,y) R_PolygonVertex(x, '0 0', col, 1); R_PolygonVertex(y, '0 0', col, 1); R_EndPolygon() - line(min, ([max[0], min[1], min[2]])); - line(min, ([min[0], max[1], min[2]])); - line(min, ([min[0], min[1], max[2]])); - line(max, ([min[0], max[1], max[2]])); - line(max, ([max[0], min[1], max[2]])); - line(max, ([max[0], max[1], min[2]])); - - line(([max[0], min[1], min[2]]), ([max[0], max[1], min[2]])); - line(([max[0], min[1], min[2]]), ([max[0], min[1], max[2]])); - line(([min[0], max[1], min[2]]), ([min[0], max[1], max[2]])); - line(([min[0], max[1], min[2]]), ([max[0], max[1], min[2]])); - line(([min[0], min[1], max[2]]), ([min[0], max[1], max[2]])); - line(([min[0], min[1], max[2]]), ([max[0], min[1], max[2]])); -#undef line -}; - void() editor_ents_addentities = { int e; @@ -437,7 +440,9 @@ void() editor_ents_addentities = addentity(tempent); } else - editor_ents_drawbbox("entbox", editents[e].org+editents[e].mins, editents[e].org+editents[e].maxs, editents[e].colourmod); + { + addtrisoup_simple("entbox", 0x800, &editents[e].bboxverts[0], bbox_line_idxs, bbox_line_idxs.length); + } } }; @@ -488,7 +493,7 @@ float(float key, float unic, vector mousepos) editor_ents_key = bestent = e; } } - tempent.modelindex = SOLID_NOT; + tempent.modelindex = 0; selectedent = bestent; } @@ -647,7 +652,4 @@ void(vector mousepos) editor_ents_overlay = drawrawstring(pos, sprintf("%s: ", editkey==""?"":editkey), '8 8 0', '0 0 1', 1); pos_y += 8; } - - if (entsdirty && cltime > entsapplytime) - updatemodelents(); }; diff --git a/specs/browser.txt b/specs/browser.txt index 48b663aa..6a1e5ecb 100644 --- a/specs/browser.txt +++ b/specs/browser.txt @@ -1,3 +1,89 @@ +FIXME: verify+clarify these docs... + + +There are multiple ways to embed a program into a browser. The 'web'/emscripten port, the nacl port, the npapi port, and the activex port. + +Quick start with browser-servers: +(this uses webrtc, which should give low latency but is incompatible with native servers at this time.) +On the server: +sv_port_rtc /magic //this assumes the manifest provides a default broker. Then start a game (remember to set deathmatch or coop first). +On the client: +connect /magic //connects to the default broker and attempts to start a webrtc connection with whichever browser-server that registered itself with that name. + +Quick start with dedicated servers: +(this uses websocket connections, which will have terrible latency on any network with notable packetloss, but can work on any correctly configured native fte server.) +On your server: +sv_port_tcp 27500 //listens for tcp connections, including websocket clients +In the browser client: +connect ws://serverip:27500 // ws:// is assumed if ommitted. it is best to always include the port. +Alternatively, if configured properly, just load http://serverip:27500 in your browser and be greated with the game! + + + +Cvars: +sv_port_tcp - This cvar controls which tcp port non-browser builds of fte should listen on. + This provides qizmo-tcp compat (including the tcpconnect in native clients). + It also provides websocket access for browser clients to connect to regular servers. It should allow quake connections from any site (as is supported for udp). + Additionally, you can use it to serve files, subject to allow_download_* cvars, the same as for vanilla quakeworld clients etc. + Further more, it also provides webrtc broker support. Browsers can use rtc://broker:port/NAME to connect games with potential clients (without them appearing on the server itself). + Brokering should be fairly low traffic, but will result in an extra tcp connection for each rtc client or server active at the time. +sv_port_rtc - This says the broker+resource used to register a webrtc server. Clients should connect using the same string, and by doing so will be able to relay webrtc connection info to the target browser-server. +cfg_save - This command saves your config to your browser's local storage. In combination with seta, you can save most settings this way. + Beware that browsers might still wipe it all eventually. + +Hosting: +To get fte running on a web page, you will need: +ftewebgl.html - An html file that embeds the javascript. You can probably modify fte's default if you want to integrate it better with your site, it doesn't change much. +ftewebgl.js - This is the meat of the engine. All in a single file. pre-gzip it if you can, to keep sizes down. +ftewebgl.js.mem - This is just the 'data', and annoyingly the 'bss' section too when emscripten is being buggy. Its version MUST match the js file. +ftewebgl.html.fmf - This is the manifest file that the browser port depends upon to figure out where all of its data files should come from. It will need to be modified for each mod. + Ideally, servers will share fmf files between them, one per mod. This should avoid downloading the same packages from multiple different servers. +*.pak / *.pk3 - These files are pulled in by the fmf. fte's web port doesn't include zlib, so pk3s should not use compression for individual files. Instead gzip them for solid archives with the browser doing all the decompression. + +Arguments: +You can pass arguments by eg browsing to the following url: +http://triptohell.info/moodles/web/ftewebgl.html?+connect%20ws://example.com:27500 +The browser port is set up to ignore most args when linked to from another site. This blocks other sites from screwing with any saved user configs. + +Built-in http server: +The http server provided by sv_port_tcp will provide a page (either directly or with a redirect) to a version of the webgl client. +Thanks to allow_download_* cvars, only certain things may be downloaded. +Additionally, there are also some files generated by the server. +index.html (and no resource) - attempts to provide a link to the webgl version of fte. +default.fmf - provides a redirect to the manifest's update url. +all other files match what you would be able to download over udp. + +Manifest files: +These are FTE's way of reconfiguring FTE for standalone mods. They offer basic rebranding features as well as content updates. +They contain a number of attributes, and frankly its easier to start with an example. Check http://triptohell.info/moodles/web/ for a few. +Note that the content you pull in will still be subject to various copyrights. If nquake is anything to go by, you can get away with pak0, but don't bother serving up the full registered version of quake. +Yes, what you can legally run is limited. + +Custom maps do not need to be named in manifests as they will just be downloaded from the game server automatically anyway. They will be redownloaded if the page is refreshed, yes, but this can also be good for people still working on their maps... + + + +The rest of this file is outdated. + + + + + + + + + + + + + + + + + +Embedding using npapi(no longer supported by any major browser) or ActiveX (deprecated and not supported in Edge): + +