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 ...
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);
}