Easier LAN coop play of classic Quake
Moderator: InsideQC Admins
37 posts
• Page 1 of 3 • 1, 2, 3
Easier LAN coop play of classic Quake
I think wireless routers are pretty common, but networks not so common. I've noticed that Dropbox has "LAN" detection and will propogate file changes locally (nice!) which is very fast.
Any ideas of the best strategy to detect a possible Quake game (an open 26000) on a router?
To kill another annoyance, I plan to make a slightly altered protocol "670" (Fitz 666 plus 4), and if so, stick "game warp -quoth" or "game hipnotic -hipnotic" in a string and switch the gamedir. Clearly not literally like that. Probably serialize the gamedirs like "quoth;warp" and then another string with "-hipnotic" or "-rogue"
I don't know if I'll do this soon ... then again the above annoys me greatly on a LAN playing to play coop.
Any ideas of the best strategy to detect a possible Quake game (an open 26000) on a router?
The obvious thought that comes to mind is if the ip address is 192.168.x.y, start looking at 192.168.x.0 through x.20 and running a quick "test2" command on each of those. Or is there a better way?
To kill another annoyance, I plan to make a slightly altered protocol "670" (Fitz 666 plus 4), and if so, stick "game warp -quoth" or "game hipnotic -hipnotic" in a string and switch the gamedir. Clearly not literally like that. Probably serialize the gamedirs like "quoth;warp" and then another string with "-hipnotic" or "-rogue"
Sure, not a "master plan". Just an incremental one. A better plan would involve adding client/server ... map/model download too, but I'm more interested in the two modifications. A plus about the gamedir serialization is that it would make any demo recorded using "protocol 670" easier to play back as the client would have a better idea of what to expect for requirements.
I don't know if I'll do this soon ... then again the above annoys me greatly on a LAN playing to play coop.
The night is young. How else can I annoy the world before sunsrise?
Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
Re: Easier LAN coop play of classic Quake
Make your network socket broadcast capable, send a CCREQ_SERVER_INFO message to 255.255.255.255:26000, and see who replies. No need to send to every node individually (routers and isps will automatically restrict broadcasts to be site-local).
Do you really need a new distinct protocol? Can you not hide the difference within a stringcmd? Possibly one with a leading // ?
Do you really need a new distinct protocol? Can you not hide the difference within a stringcmd? Possibly one with a leading // ?
- Spike
- Posts: 2892
- Joined: Fri Nov 05, 2004 3:12 am
- Location: UK
Re: Easier LAN coop play of classic Quake
Spike wrote:Make your network socket broadcast capable, send a CCREQ_SERVER_INFO message to 255.255.255.255:26000, and see who replies. No need to send to every node individually (routers and isps will automatically restrict broadcasts to be site-local).
Do you really need a new distinct protocol? Can you not hide the difference within a stringcmd? Possibly one with a leading // ?
No, I dislike every little adjustment requiring a new protocol ---but I'm a bit lacking in imagination with your suggestion. Are you recommending tossing this in one of the signon replies or in SV_SendServerinfo? The significance of the // is so it gets treated as a comment and doesn't say "invalid command" to a non-supported client?
You recommended this before with a conversation with MH, but I believe that was regarding worldspawn keys in maps.
The night is young. How else can I annoy the world before sunsrise?
Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
Re: Easier LAN coop play of classic Quake
stuffcmd(self, "//gamedir \"MYMAGICGAMEDIR\"\n");
clients that understand '//gamedir' stuffcmds will change gamedir. clients that do not will silently discard it. its like a hidden svc.
so things won't break just because you added a hint.
The // is optional of course. omiting it means it'll work in existing clients that support a gamedir console command, but will be spammy. But hey, maybe that extra line of spam will give the motivation required for more engine authors to add support.
You'll of course need to ensure that its sent before the (nq) svc_serverinfo message, to ensure that its present before the precaches are seen.
If you want something more complex, you can try adding a handshake. You can mimic fte and do:
stuffcmd(self, "cmd pext\n");
and have the client translate that to:
localcmdf("cmd pext \"%#x\" \"%#x\" \"%#x\" \"%#x\"\n", e1_vendorid, e1_flags, e2_vendorid, e2_flags);
then have the serverdata mention the activated vendorid+flags in the serverdata just before the normal version (with the base protocol version acting as a terminator), so the client knows what's actually in use by the server too.
Which is how fte negotiates its protocol extensions when using NQ protocols.
If the client doesn't understand it, the server still sees a 'pext' clientcommand with no arguments, so no extensions, and if the client does support stuff, the server can read the argument pairs to see the vendor+flags info and pull out the things that it does know.
Yeah, okay, with this handshake demos will probably need a matching client for playback, but hey, you'd need that if the server was using some forced extension too.
worldspawn keys can be prefixed with a _ in order to silence them, a // prefix won't work there.
clients that understand '//gamedir' stuffcmds will change gamedir. clients that do not will silently discard it. its like a hidden svc.
so things won't break just because you added a hint.
The // is optional of course. omiting it means it'll work in existing clients that support a gamedir console command, but will be spammy. But hey, maybe that extra line of spam will give the motivation required for more engine authors to add support.
You'll of course need to ensure that its sent before the (nq) svc_serverinfo message, to ensure that its present before the precaches are seen.
If you want something more complex, you can try adding a handshake. You can mimic fte and do:
stuffcmd(self, "cmd pext\n");
and have the client translate that to:
localcmdf("cmd pext \"%#x\" \"%#x\" \"%#x\" \"%#x\"\n", e1_vendorid, e1_flags, e2_vendorid, e2_flags);
then have the serverdata mention the activated vendorid+flags in the serverdata just before the normal version (with the base protocol version acting as a terminator), so the client knows what's actually in use by the server too.
Which is how fte negotiates its protocol extensions when using NQ protocols.
If the client doesn't understand it, the server still sees a 'pext' clientcommand with no arguments, so no extensions, and if the client does support stuff, the server can read the argument pairs to see the vendor+flags info and pull out the things that it does know.
Yeah, okay, with this handshake demos will probably need a matching client for playback, but hey, you'd need that if the server was using some forced extension too.
worldspawn keys can be prefixed with a _ in order to silence them, a // prefix won't work there.
- Spike
- Posts: 2892
- Joined: Fri Nov 05, 2004 3:12 am
- Location: UK
Re: Easier LAN coop play of classic Quake
Tried out your trick.
Now of course you know it worked too, otherwise you wouldn't have suggested it.
Very nice trickery there. With the "//" it doesn't even print bad command but CL_Parse sure can look through it.
Very nice trickery there. With the "//" it doesn't even print bad command but CL_Parse sure can look through it.
The night is young. How else can I annoy the world before sunsrise?
Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
Re: Easier LAN coop play of classic Quake
Stockpiling for info purposes only (on LAN, I don't want clients to have to download stuff from internet, get from server).
But I'm not sure the "individual" file way that, say, Quakeworld does download is "right" for single player LAN games.
At least for single player coop, I have something else in mind, but what I have in mind doesn't even actually involve "gamedirs" as I'm sort of wondering if .zip files of a single player mod should ever even be unzipped at all.
I've seen Nintendo 64 emulators and such that don't seem to ever bother unzipping files. But I'm going to have to plan that out and that is likely months down the road ...
But I'm not sure the "individual" file way that, say, Quakeworld does download is "right" for single player LAN games.
At least for single player coop, I have something else in mind, but what I have in mind doesn't even actually involve "gamedirs" as I'm sort of wondering if .zip files of a single player mod should ever even be unzipped at all.
I've seen Nintendo 64 emulators and such that don't seem to ever bother unzipping files. But I'm going to have to plan that out and that is likely months down the road ...
- Code: Select all
//////////////////////////////////////////////////////
client.h / protocol.h
//////////////////////////////////////////////////////
#define svc_download 41 // [short] size [size bytes]
typedef enum {
dl_none,
dl_model,
dl_sound,
dl_skin,
dl_single
} dltype_t; // download type
typedef struct
{
...
FILE *download; // file transfer from server
char downloadtempname[MAX_OSPATH];
char downloadname[MAX_OSPATH];
int downloadnumber;
dltype_t downloadtype;
int downloadpercent;
...
} client_static_t;
extern client_static_t cls;
qboolean CL_CheckOrDownloadFile (char *filename);
qboolean CL_IsUploading(void);
- Code: Select all
//////////////////////////////////////////////////////
server.h
//////////////////////////////////////////////////////
typedef struct client_s
{
...
FILE *download; // file being downloaded
int downloadsize; // total bytes
int downloadcount; // bytes sent
...
} client_t;
- Code: Select all
//////////////////////////////////////////////////////
cl_demo.c
//////////////////////////////////////////////////////
CL_Record_f
// get the client to check and download skins
// when that is completed, a begin command will be issued
MSG_WriteByte (&buf, svc_stufftext);
MSG_WriteString (&buf, va("skins\n") );
- Code: Select all
//////////////////////////////////////////////////////
cl_main.c
//////////////////////////////////////////////////////
/*
=====================
CL_Disconnect
Sends a disconnect message to the server
This is also called on Host_Error, so it shouldn't cause any errors
=====================
*/
void CL_Disconnect (void)
{
byte final[10];
connect_time = -1;
#ifdef _WIN32
SetWindowText (mainwindow, "QuakeWorld: disconnected");
#endif
// stop sounds (especially looping!)
S_StopAllSounds (true);
// if running a local server, shut it down
if (cls.demoplayback)
CL_StopPlayback ();
else if (cls.state != ca_disconnected)
{
if (cls.demorecording)
CL_Stop_f ();
final[0] = clc_stringcmd;
strcpy (final+1, "drop");
Netchan_Transmit (&cls.netchan, 6, final);
Netchan_Transmit (&cls.netchan, 6, final);
Netchan_Transmit (&cls.netchan, 6, final);
cls.state = ca_disconnected;
cls.demoplayback = cls.demorecording = cls.timedemo = false;
}
Cam_Reset();
if (cls.download) {
fclose(cls.download);
cls.download = NULL;
}
CL_StopUpload();
}
/*
=================
CL_Changing_f
Just sent as a hint to the client that they should
drop to full console
=================
*/
void CL_Changing_f (void)
{
if (cls.download) // don't change when downloading
return;
S_StopAllSounds (true);
cl.intermission = 0;
cls.state = ca_connected; // not active anymore, but not disconnected
Con_Printf ("\nChanging map...\n");
}
/*
=================
CL_Reconnect_f
The server is changing levels
=================
*/
void CL_Reconnect_f (void)
{
if (cls.download) // don't change when downloading
return;
S_StopAllSounds (true);
if (cls.state == ca_connected) {
Con_Printf ("reconnecting...\n");
MSG_WriteChar (&cls.netchan.message, clc_stringcmd);
MSG_WriteString (&cls.netchan.message, "new");
return;
}
if (!*cls.servername) {
Con_Printf("No server to reconnect to...\n");
return;
}
CL_Disconnect();
CL_BeginServerConnect();
}
/*
=====================
CL_Download_f
=====================
*/
void CL_Download_f (void)
{
char *p, *q;
if (cls.state == ca_disconnected)
{
Con_Printf ("Must be connected.\n");
return;
}
if (Cmd_Argc() != 2)
{
Con_Printf ("Usage: download <datafile>\n");
return;
}
sprintf (cls.downloadname, "%s/%s", com_gamedir, Cmd_Argv(1));
p = cls.downloadname;
for (;;) {
if ((q = strchr(p, '/')) != NULL) {
*q = 0;
Sys_mkdir(cls.downloadname);
*q = '/';
p = q + 1;
} else
break;
}
strcpy(cls.downloadtempname, cls.downloadname);
cls.download = fopen (cls.downloadname, "wb");
cls.downloadtype = dl_single;
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
SZ_Print (&cls.netchan.message, va("download %s\n",Cmd_Argv(1)));
}
CL_Init ...
Cmd_AddCommand ("download", CL_Download_f);
- Code: Select all
//////////////////////////////////////////////////////
cl_parse.c
//////////////////////////////////////////////////////
"svc_download", // Baker: 41 I think ...
/*
===============
CL_CheckOrDownloadFile
Returns true if the file exists, otherwise it attempts
to start a download from the server.
===============
*/
qboolean CL_CheckOrDownloadFile (char *filename)
{
FILE *f;
if (strstr (filename, ".."))
{
Con_Printf ("Refusing to download a path with ..\n");
return true;
}
COM_FOpenFile (filename, &f);
if (f)
{ // it exists, no need to download
fclose (f);
return true;
}
//ZOID - can't download when recording
if (cls.demorecording) {
Con_Printf("Unable to download %s in record mode.\n", cls.downloadname);
return true;
}
//ZOID - can't download when playback
if (cls.demoplayback)
return true;
strcpy (cls.downloadname, filename);
Con_Printf ("Downloading %s...\n", cls.downloadname);
// download to a temp name, and only rename
// to the real name when done, so if interrupted
// a runt file wont be left
COM_StripExtension (cls.downloadname, cls.downloadtempname);
strcat (cls.downloadtempname, ".tmp");
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
MSG_WriteString (&cls.netchan.message, va("download %s", cls.downloadname));
cls.downloadnumber++;
return false;
}
/*
=================
Model_NextDownload
=================
*/
void Model_NextDownload (void)
{
char *s;
int i;
extern char gamedirfile[];
if (cls.downloadnumber == 0)
{
Con_Printf ("Checking models...\n");
cls.downloadnumber = 1;
}
cls.downloadtype = dl_model;
for (
; cl.model_name[cls.downloadnumber][0]
; cls.downloadnumber++)
{
s = cl.model_name[cls.downloadnumber];
if (s[0] == '*')
continue; // inline brush model
if (!CL_CheckOrDownloadFile(s))
return; // started a download
}
for (i=1 ; i<MAX_MODELS ; i++)
{
if (!cl.model_name[i][0])
break;
cl.model_precache[i] = Mod_ForName (cl.model_name[i], false);
if (!cl.model_precache[i])
{
Con_Printf ("\nThe required model file '%s' could not be found or downloaded.\n\n"
, cl.model_name[i]);
Con_Printf ("You may need to download or purchase a %s client "
"pack in order to play on this server.\n\n", gamedirfile);
CL_Disconnect ();
return;
}
}
// all done
cl.worldmodel = cl.model_precache[1];
R_NewMap ();
Hunk_Check (); // make sure nothing is hurt
// done with modellist, request first of static signon messages
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
// MSG_WriteString (&cls.netchan.message, va("prespawn %i 0 %i", cl.servercount, cl.worldmodel->checksum2));
MSG_WriteString (&cls.netchan.message, va(prespawn_name, cl.servercount, cl.worldmodel->checksum2));
}
/*
=================
Sound_NextDownload
=================
*/
void Sound_NextDownload (void)
{
char *s;
int i;
if (cls.downloadnumber == 0)
{
Con_Printf ("Checking sounds...\n");
cls.downloadnumber = 1;
}
cls.downloadtype = dl_sound;
for (
; cl.sound_name[cls.downloadnumber][0]
; cls.downloadnumber++)
{
s = cl.sound_name[cls.downloadnumber];
if (!CL_CheckOrDownloadFile(va("sound/%s",s)))
return; // started a download
}
for (i=1 ; i<MAX_SOUNDS ; i++)
{
if (!cl.sound_name[i][0])
break;
cl.sound_precache[i] = S_PrecacheSound (cl.sound_name[i]);
}
// done with sounds, request models now
memset (cl.model_precache, 0, sizeof(cl.model_precache));
cl_playerindex = -1;
cl_spikeindex = -1;
cl_flagindex = -1;
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
// MSG_WriteString (&cls.netchan.message, va("modellist %i 0", cl.servercount));
MSG_WriteString (&cls.netchan.message, va(modellist_name, cl.servercount, 0));
}
/*
======================
CL_RequestNextDownload
======================
*/
void CL_RequestNextDownload (void)
{
switch (cls.downloadtype)
{
case dl_single:
break;
case dl_skin:
Skin_NextDownload ();
break;
case dl_model:
Model_NextDownload ();
break;
case dl_sound:
Sound_NextDownload ();
break;
case dl_none:
default:
Con_DPrintf("Unknown download type.\n");
}
}
/*
=====================
CL_ParseDownload
A download message has been received from the server
=====================
*/
void CL_ParseDownload (void)
{
int size, percent;
byte name[1024];
int r;
// read the data
size = MSG_ReadShort ();
percent = MSG_ReadByte ();
if (cls.demoplayback) {
if (size > 0)
msg_readcount += size;
return; // not in demo playback
}
if (size == -1)
{
Con_Printf ("File not found.\n");
if (cls.download)
{
Con_Printf ("cls.download shouldn't have been set\n");
fclose (cls.download);
cls.download = NULL;
}
CL_RequestNextDownload ();
return;
}
// open the file if not opened yet
if (!cls.download)
{
if (strncmp(cls.downloadtempname,"skins/",6))
sprintf (name, "%s/%s", com_gamedir, cls.downloadtempname);
else
sprintf (name, "qw/%s", cls.downloadtempname);
COM_CreatePath (name);
cls.download = fopen (name, "wb");
if (!cls.download)
{
msg_readcount += size;
Con_Printf ("Failed to open %s\n", cls.downloadtempname);
CL_RequestNextDownload ();
return;
}
}
fwrite (net_message.data + msg_readcount, 1, size, cls.download);
msg_readcount += size;
if (percent != 100)
{
// change display routines by zoid
// request next block
#if 0
Con_Printf (".");
if (10*(percent/10) != cls.downloadpercent)
{
cls.downloadpercent = 10*(percent/10);
Con_Printf ("%i%%", cls.downloadpercent);
}
#endif
cls.downloadpercent = percent;
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
SZ_Print (&cls.netchan.message, "nextdl");
}
else
{
char oldn[MAX_OSPATH];
char newn[MAX_OSPATH];
#if 0
Con_Printf ("100%%\n");
#endif
fclose (cls.download);
// rename the temp file to it's final name
if (strcmp(cls.downloadtempname, cls.downloadname)) {
if (strncmp(cls.downloadtempname,"skins/",6)) {
sprintf (oldn, "%s/%s", com_gamedir, cls.downloadtempname);
sprintf (newn, "%s/%s", com_gamedir, cls.downloadname);
} else {
sprintf (oldn, "qw/%s", cls.downloadtempname);
sprintf (newn, "qw/%s", cls.downloadname);
}
r = rename (oldn, newn);
if (r)
Con_Printf ("failed to rename.\n");
}
cls.download = NULL;
cls.downloadpercent = 0;
// get another file if needed
CL_RequestNextDownload ();
}
}
CL_Parse_ServerData ...
// ask for the sound list next
memset(cl.sound_name, 0, sizeof(cl.sound_name));
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
// MSG_WriteString (&cls.netchan.message, va("soundlist %i 0", cl.servercount));
MSG_WriteString (&cls.netchan.message, va(soundlist_name, cl.servercount, 0));
// now waiting for downloads, etc
cls.state = ca_onserver;
}
/*
==================
CL_ParseSoundlist
==================
*/
void CL_ParseSoundlist (void)
{
int numsounds;
char *str;
int n;
// precache sounds
// memset (cl.sound_precache, 0, sizeof(cl.sound_precache));
numsounds = MSG_ReadByte();
for (;;) {
str = MSG_ReadString ();
if (!str[0])
break;
numsounds++;
if (numsounds == MAX_SOUNDS)
Host_EndGame ("Server sent too many sound_precache");
strcpy (cl.sound_name[numsounds], str);
}
n = MSG_ReadByte();
if (n) {
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
// MSG_WriteString (&cls.netchan.message, va("soundlist %i %i", cl.servercount, n));
MSG_WriteString (&cls.netchan.message, va(soundlist_name, cl.servercount, n));
return;
}
cls.downloadnumber = 0;
cls.downloadtype = dl_sound;
Sound_NextDownload ();
}
/*
==================
CL_ParseModellist
==================
*/
void CL_ParseModellist (void)
{
int nummodels;
char *str;
int n;
// precache models and note certain default indexes
nummodels = MSG_ReadByte();
for (;;)
{
str = MSG_ReadString ();
if (!str[0])
break;
nummodels++;
if (nummodels==MAX_MODELS)
Host_EndGame ("Server sent too many model_precache");
strcpy (cl.model_name[nummodels], str);
if (!strcmp(cl.model_name[nummodels],"progs/spike.mdl"))
cl_spikeindex = nummodels;
if (!strcmp(cl.model_name[nummodels],"progs/player.mdl"))
cl_playerindex = nummodels;
if (!strcmp(cl.model_name[nummodels],"progs/flag.mdl"))
cl_flagindex = nummodels;
}
n = MSG_ReadByte();
if (n) {
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
// MSG_WriteString (&cls.netchan.message, va("modellist %i %i", cl.servercount, n));
MSG_WriteString (&cls.netchan.message, va(modellist_name, cl.servercount, n));
return;
}
cls.downloadnumber = 0;
cls.downloadtype = dl_model;
Model_NextDownload ();
}
CL_Parse_ServerMessage
case svc_download:
CL_ParseDownload ();
break;
- Code: Select all
//////////////////////////////////////////////////////
common.c
//////////////////////////////////////////////////////
/*
============
COM_CreatePath
Only used for CopyFile and download
============
*/
void COM_CreatePath (char *path)
{
char *ofs;
for (ofs = path+1 ; *ofs ; ofs++)
{
if (*ofs == '/')
{ // create the directory
*ofs = 0;
Sys_mkdir (path);
*ofs = '/';
}
}
}
- Code: Select all
//////////////////////////////////////////////////////
console.c
//////////////////////////////////////////////////////
Draw Console ... (download progress)
// draw the download bar
// figure out width
if (cls.download) {
if ((text = strrchr(cls.downloadname, '/')) != NULL)
text++;
else
text = cls.downloadname;
x = con_linewidth - ((con_linewidth * 7) / 40);
y = x - strlen(text) - 8;
i = con_linewidth/3;
if (strlen(text) > i) {
y = x - i - 11;
strncpy(dlbar, text, i);
dlbar[i] = 0;
strcat(dlbar, "...");
} else
strcpy(dlbar, text);
strcat(dlbar, ": ");
i = strlen(dlbar);
dlbar[i++] = '\x80';
// where's the dot go?
if (cls.downloadpercent == 0)
n = 0;
else
n = y * cls.downloadpercent / 100;
for (j = 0; j < y; j++)
if (j == n)
dlbar[i++] = '\x83';
else
dlbar[i++] = '\x81';
dlbar[i++] = '\x82';
dlbar[i] = 0;
sprintf(dlbar + strlen(dlbar), " %02d%%", cls.downloadpercent);
// draw it
y = con_vislines-22 + 8;
for (i = 0; i < strlen(dlbar); i++)
Draw_Character ( (i+1)<<3, y, dlbar[i]);
}
// draw the input prompt, user text, and cursor if desired
Con_DrawInput ();
}
- Code: Select all
//////////////////////////////////////////////////////
draw.c / gl_draw.c
//////////////////////////////////////////////////////
if (cls.download) {
sprintf (ver, "%4.2f", VERSION);
dest = conback->data + 320 + 320*186 - 11 - 8*strlen(ver);
} else {
- Code: Select all
//////////////////////////////////////////////////////
sv_main.c
//////////////////////////////////////////////////////
cvar_t allow_download = {"allow_download", "1"};
cvar_t allow_download_skins = {"allow_download_skins", "1"};
cvar_t allow_download_models = {"allow_download_models", "1"};
cvar_t allow_download_sounds = {"allow_download_sounds", "1"};
cvar_t allow_download_maps = {"allow_download_maps", "1"};
Cvar_RegisterVariable (&allow_download);
Cvar_RegisterVariable (&allow_download_skins);
Cvar_RegisterVariable (&allow_download_models);
Cvar_RegisterVariable (&allow_download_sounds);
Cvar_RegisterVariable (&allow_download_maps);
SV_DropClient ...
if (drop->download)
{
fclose (drop->download);
drop->download = NULL;
}
- Code: Select all
//////////////////////////////////////////////////////
sv_user,c
//////////////////////////////////////////////////////
{"download", SV_BeginDownload_f},
{"nextdl", SV_NextDownload_f},
/*
==================
SV_NextDownload_f
==================
*/
void SV_NextDownload_f (void)
{
byte buffer[1024];
int r;
int percent;
int size;
if (!host_client->download)
return;
r = host_client->downloadsize - host_client->downloadcount;
if (r > 768)
r = 768;
r = fread (buffer, 1, r, host_client->download);
ClientReliableWrite_Begin (host_client, svc_download, 6+r);
ClientReliableWrite_Short (host_client, r);
host_client->downloadcount += r;
size = host_client->downloadsize;
if (!size)
size = 1;
percent = host_client->downloadcount*100/size;
ClientReliableWrite_Byte (host_client, percent);
ClientReliableWrite_SZ (host_client, buffer, r);
if (host_client->downloadcount != host_client->downloadsize)
return;
fclose (host_client->download);
host_client->download = NULL;
}
void OutofBandPrintf(netadr_t where, char *fmt, ...)
{
va_list argptr;
char send[1024];
send[0] = 0xff;
send[1] = 0xff;
send[2] = 0xff;
send[3] = 0xff;
send[4] = A2C_PRINT;
va_start (argptr, fmt);
vsprintf (send+5, fmt, argptr);
va_end (argptr);
NET_SendPacket (strlen(send)+1, send, where);
}
/*
==================
SV_NextUpload
==================
*/
void SV_NextUpload (void)
{
byte buffer[1024];
int r;
int percent;
int size;
client_t *client;
if (!*host_client->uploadfn) {
SV_ClientPrintf(host_client, PRINT_HIGH, "Upload denied\n");
ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
ClientReliableWrite_String (host_client, "stopul");
// suck out rest of packet
size = MSG_ReadShort (); MSG_ReadByte ();
msg_readcount += size;
return;
}
size = MSG_ReadShort ();
percent = MSG_ReadByte ();
if (!host_client->upload)
{
host_client->upload = fopen(host_client->uploadfn, "wb");
if (!host_client->upload) {
Sys_Printf("Can't create %s\n", host_client->uploadfn);
ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
ClientReliableWrite_String (host_client, "stopul");
*host_client->uploadfn = 0;
return;
}
Sys_Printf("Receiving %s from %d...\n", host_client->uploadfn, host_client->userid);
if (host_client->remote_snap)
OutofBandPrintf(host_client->snap_from, "Server receiving %s from %d...\n", host_client->uploadfn, host_client->userid);
}
fwrite (net_message.data + msg_readcount, 1, size, host_client->upload);
msg_readcount += size;
Con_DPrintf ("UPLOAD: %d received\n", size);
if (percent != 100) {
ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
ClientReliableWrite_String (host_client, "nextul\n");
} else {
fclose (host_client->upload);
host_client->upload = NULL;
Sys_Printf("%s upload completed.\n", host_client->uploadfn);
if (host_client->remote_snap) {
char *p;
if ((p = strchr(host_client->uploadfn, '/')) != NULL)
p++;
else
p = host_client->uploadfn;
OutofBandPrintf(host_client->snap_from, "%s upload completed.\nTo download, enter:\ndownload %s\n",
host_client->uploadfn, p);
}
}
}
/*
==================
SV_BeginDownload_f
==================
*/
void SV_BeginDownload_f(void)
{
char *name;
extern cvar_t allow_download;
extern cvar_t allow_download_skins;
extern cvar_t allow_download_models;
extern cvar_t allow_download_sounds;
extern cvar_t allow_download_maps;
extern int file_from_pak; // ZOID did file come from pak?
name = Cmd_Argv(1);
// hacked by zoid to allow more conrol over download
// first off, no .. or global allow check
if (strstr (name, "..") || !allow_download.value
// leading dot is no good
|| *name == '.'
// leading slash bad as well, must be in subdir
|| *name == '/'
// next up, skin check
|| (strncmp(name, "skins/", 6) == 0 && !allow_download_skins.value)
// now models
|| (strncmp(name, "progs/", 6) == 0 && !allow_download_models.value)
// now sounds
|| (strncmp(name, "sound/", 6) == 0 && !allow_download_sounds.value)
// now maps (note special case for maps, must not be in pak)
|| (strncmp(name, "maps/", 6) == 0 && !allow_download_maps.value)
// MUST be in a subdirectory
|| !strstr (name, "/") )
{ // don't allow anything with .. path
ClientReliableWrite_Begin (host_client, svc_download, 4);
ClientReliableWrite_Short (host_client, -1);
ClientReliableWrite_Byte (host_client, 0);
return;
}
if (host_client->download) {
fclose (host_client->download);
host_client->download = NULL;
}
// lowercase name (needed for casesen file systems)
{
char *p;
for (p = name; *p; p++)
*p = (char)tolower(*p);
}
host_client->downloadsize = COM_FOpenFile (name, &host_client->download);
host_client->downloadcount = 0;
if (!host_client->download
// special check for maps, if it came from a pak file, don't allow
// download ZOID
|| (strncmp(name, "maps/", 5) == 0 && file_from_pak))
{
if (host_client->download) {
fclose(host_client->download);
host_client->download = NULL;
}
Sys_Printf ("Couldn't download %s to %s\n", name, host_client->name);
ClientReliableWrite_Begin (host_client, svc_download, 4);
ClientReliableWrite_Short (host_client, -1);
ClientReliableWrite_Byte (host_client, 0);
return;
}
SV_NextDownload_f ();
Sys_Printf ("Downloading %s to %s\n", name, host_client->name);
}
The night is young. How else can I annoy the world before sunsrise?
Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
Re: Easier LAN coop play of classic Quake
copy-pastaing from quakeworld?
things to look out for:
nextdl is reliable and syncronous. this requires round-trips.
dltype_t is a little bit evil.
dp's download protocol might be slightly more supported amongst NQ clients, and is indeed detected using some stufftext hack.
package-at-a-time is generally prefered over file-at-a-time, yes. You probably don't want the engine to recognise .zip files, only .pak and .pk3 files.
note that if you connect to an fte qw server, the server will send a few package names and hashes. the client can then download/load the named packages. because the hashes are known to the client, the client can avoid infecting games when playing on other servers, plus has a mechanism to cope with serverside mod upgrades, but you do have to write code to cope with paks potentially being loaded/purged between maps.
of course, many mods simply don't provide paks, or engines already downloaded a file-at-a-time, so you typically won't really get a choice.
things to look out for:
nextdl is reliable and syncronous. this requires round-trips.
dltype_t is a little bit evil.
dp's download protocol might be slightly more supported amongst NQ clients, and is indeed detected using some stufftext hack.
package-at-a-time is generally prefered over file-at-a-time, yes. You probably don't want the engine to recognise .zip files, only .pak and .pk3 files.
note that if you connect to an fte qw server, the server will send a few package names and hashes. the client can then download/load the named packages. because the hashes are known to the client, the client can avoid infecting games when playing on other servers, plus has a mechanism to cope with serverside mod upgrades, but you do have to write code to cope with paks potentially being loaded/purged between maps.
of course, many mods simply don't provide paks, or engines already downloaded a file-at-a-time, so you typically won't really get a choice.
- Spike
- Posts: 2892
- Joined: Fri Nov 05, 2004 3:12 am
- Location: UK
Re: Easier LAN coop play of classic Quake
I'm posted in the code so I could mentally make a note of the different oddball conditions in the source like can't download while recording demo, clear downloads when X, Y or Z happen, etc.Spike wrote:copy-pastaing from quakeworld?
things to look out for: ..
Noticed that.Spike wrote:nextdl is reliable and syncronous. this requires round-trips.
And DarkPlaces download, you can play while the models/etc are downloading.Spike wrote:dp's download protocol might be slightly more supported amongst NQ clients, and is indeed detected using some stufftext hack.
I don't plan on doing that per se. Yet if I have 50 zip files of single player releases sitting around that get very occasional usage individually, I'm not sure they need to be eating up hard drive space.You probably don't want the engine to recognise .zip files
And it makes them less transportable. Plus if I don't unzip them, I don't need to zip them for LAN propagation. I'm somewhat thinking of a more orderly Quake folder.
Exactly. Actually the "hashes" angle is something I need to factor in here a little. (i.e. somehow a .zip file is infected by a creative virus and LAN propogation provides an "assist" to spreading it.).note that if you connect to an fte qw server, the server will send a few package names and hashes. the client can then download/load the named packages. because the hashes are known to the client, the client can avoid infecting games when playing on other servers
Fast LAN propogation is important to me. Some of these single player releases can require essentially 100 MB of zipped stuff (80 MB of Quoth files, then the 40MB mod itself) or Travail is about 100 MB. Over wireless, I'm not up to the latest top speed, but I think 1MB per second locally is about as fast as you can get.
A physical ethernet cable obviously increases the speed quite a bit (1 gigabit = 100 MB per second +/-)
The night is young. How else can I annoy the world before sunsrise?
Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
Re: Easier LAN coop play of classic Quake
if you want speed, the easiest way is to just make the server act as an http server too.
quakeworld's downloads are syncronous, so slow.
dp downloads are pretty fast, but if you get packetloss it'll reset to an earlier point with lots of wasted bandwidth (mitigated by conservative rate settings, this isn't a real issue, but those rate settings will slow things down).
fte's downloads also lack a slow-start bandwidth estimation (this is more a client-side omission rather than a protocol issue). It doesn't care too much about packetloss, but can easily ping you off the net if your (d)rate is too high.
Set up a basic http 1.0 server, get the client to download from that. No demo bloat, proper flow control, fork a thread on your data connection (once the file has been opened on the main thread) and it'll just fly over the (inter?)net with no framerate/quake limitations. But does unfortunatly require another port to be opened, and is thus more likely to be unusable than a udp-based download. Your choice really.
quakeworld's downloads are syncronous, so slow.
dp downloads are pretty fast, but if you get packetloss it'll reset to an earlier point with lots of wasted bandwidth (mitigated by conservative rate settings, this isn't a real issue, but those rate settings will slow things down).
fte's downloads also lack a slow-start bandwidth estimation (this is more a client-side omission rather than a protocol issue). It doesn't care too much about packetloss, but can easily ping you off the net if your (d)rate is too high.
Set up a basic http 1.0 server, get the client to download from that. No demo bloat, proper flow control, fork a thread on your data connection (once the file has been opened on the main thread) and it'll just fly over the (inter?)net with no framerate/quake limitations. But does unfortunatly require another port to be opened, and is thus more likely to be unusable than a udp-based download. Your choice really.
- Spike
- Posts: 2892
- Joined: Fri Nov 05, 2004 3:12 am
- Location: UK
Re: Easier LAN coop play of classic Quake
Spike wrote:if you want speed, the easiest way is to just make the server act as an http server too.
quakeworld's downloads are syncronous, so slow.
dp downloads are pretty fast, but if you get packetloss it'll reset to an earlier point with lots of wasted bandwidth (mitigated by conservative rate settings, this isn't a real issue, but those rate settings will slow things down).
fte's downloads also lack a slow-start bandwidth estimation (this is more a client-side omission rather than a protocol issue). It doesn't care too much about packetloss, but can easily ping you off the net if your (d)rate is too high.
Set up a basic http 1.0 server, get the client to download from that. No demo bloat, proper flow control, fork a thread on your data connection (once the file has been opened on the main thread) and it'll just fly over the (inter?)net with no framerate/quake limitations. But does unfortunatly require another port to be opened, and is thus more likely to be unusable than a udp-based download. Your choice really.
I'd rather work up the ladder in natural progression. I'm eyeing FTE's chunked downloads. I'm far better at working with the client/server than in the past, but I want to get more of a handle on this aspect so eventually I can upgrade "NetQuake" to use connectionless connections, ip6, sv_heartbeat and co.
Note to self: About ready to test the gamedir modification and already found a future "todo". Add capability for server to live switch gamedir with a map change somehow ("changelevel start game warp -quoth" or something?) for a seemless server-side gamedir switch both on server and client. NOTE: This would have to stop any client demo in progress. Or would it? No ... a multigame demo would be annoying as hell ... then again I guess a multimap demo might be missing a map.
Does FTE exclude file downloads from demos? Or does FTE prevent downloads during demo recording like original Quakeworld? Just wondering ...
The night is young. How else can I annoy the world before sunsrise?
Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
Re: Easier LAN coop play of classic Quake
Spike wrote:if you want speed, the easiest way is to just make the server act as an http server too.
OR, you will be more pragmatic and set a low footprint HTTP server to handle the downloads in a much nicer, efficient way.
Just because you can throw the kitchen sink into the engine, it doesn't mean you should.
I know FrikaC made a cgi-bin version of the quakec interpreter once and wrote part of his website in QuakeC
(LordHavoc)
-

frag.machine - Posts: 2090
- Joined: Sat Nov 25, 2006 1:49 pm
Re: Easier LAN coop play of classic Quake
frag.machine wrote:Spike wrote:if you want speed, the easiest way is to just make the server act as an http server too.
OR, you will be more pragmatic and set a low footprint HTTP server to handle the downloads in a much nicer, efficient way.
Just because you can throw the kitchen sink into the engine, it doesn't mean you should.
I'm listening and keeping an open mind ...
The night is young. How else can I annoy the world before sunsrise?
Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
Re: Easier LAN coop play of classic Quake
a basic http server is not fantastically hard.
create a listening socket. bind it to a port. call listen.
periodically call accept on it to see if anyone tried to connect. this gives you a new socket.
wait for the client to send 'GET /resource HTTP/1.X\n\n'. ignore any extra lines between the 1 and the new line.
open the file. create a new thread. send "HTTP/1.0 200 OK\nContent-Length: %i\nConnection: close\n\n$FILE" close the socket, close the file, terminate the thread.
connection: close is optional. without it the client is free to send a second request after the first, which while otherwise desirable, results in sync requirements with the main thread.
add Content-Encoding: gzip if the contents are gzipped, and the receiver is meant to un-gzip it before use.
new lines (in http's headers) should ALWAYS be \r\n. I'm lazy in my descriptions. Be prepared to accept just \n too, but don't generate it. an empty line demotes the end of the http 1 headers.
or just grab fte's httpserver.c file (with curl or fte's httpclient.c file for the clientside part).
by all means use some other server, or directly use a simple tcp connection with no http semantics (ie: origional http...) but I do recommend making sure the server parts are automagic, and have the same file/copyright access restrictions as other quake servers (ie: no access to pak*.pak or its (map?) contents, and no access to root directory or configs directory, etc). in-process is imho the easiest to configure.
if you really want to get creative, look out for websocket connections too, and allow people to actually play via them.
create a listening socket. bind it to a port. call listen.
periodically call accept on it to see if anyone tried to connect. this gives you a new socket.
wait for the client to send 'GET /resource HTTP/1.X\n\n'. ignore any extra lines between the 1 and the new line.
open the file. create a new thread. send "HTTP/1.0 200 OK\nContent-Length: %i\nConnection: close\n\n$FILE" close the socket, close the file, terminate the thread.
connection: close is optional. without it the client is free to send a second request after the first, which while otherwise desirable, results in sync requirements with the main thread.
add Content-Encoding: gzip if the contents are gzipped, and the receiver is meant to un-gzip it before use.
new lines (in http's headers) should ALWAYS be \r\n. I'm lazy in my descriptions. Be prepared to accept just \n too, but don't generate it. an empty line demotes the end of the http 1 headers.
or just grab fte's httpserver.c file (with curl or fte's httpclient.c file for the clientside part).
by all means use some other server, or directly use a simple tcp connection with no http semantics (ie: origional http...) but I do recommend making sure the server parts are automagic, and have the same file/copyright access restrictions as other quake servers (ie: no access to pak*.pak or its (map?) contents, and no access to root directory or configs directory, etc). in-process is imho the easiest to configure.
if you really want to get creative, look out for websocket connections too, and allow people to actually play via them.
- Spike
- Posts: 2892
- Joined: Fri Nov 05, 2004 3:12 am
- Location: UK
Re: Easier LAN coop play of classic Quake
Spike wrote:I'm lazy in my descriptions.
Usually a couple of vague descriptions [sometimes a bit of criticism seems to help too] and a hint or 2 like above sends me on my way.
Go figure ... I guess I get curious and start conducting obsessive experiments after resisting ideas initially ... we'll see what comes from this ...
The night is young. How else can I annoy the world before sunsrise?
Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
Re: Easier LAN coop play of classic Quake
Dissecting tiny httpd and friends (mini web)...
The night is young. How else can I annoy the world before sunsrise?
Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
37 posts
• Page 1 of 3 • 1, 2, 3
Who is online
Users browsing this forum: No registered users and 1 guest