Warsow/Qrack Web Map/Model Download (Curl)

Post tutorials on how to do certain tasks within game or engine code here.
Post Reply
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Warsow/Qrack Web Map/Model Download (Curl)

Post by Baker »

Rook added some outstanding map/model download to Qrack which I have now added to ProQuake 3.99. The Qrack code was heavily derived from Warsow.

I am going to describe the very few changes I had to make to implement this in the Windows version of ProQuake 3.99, which should be nearly the same as doing so with GLQuake or for that matter WinQuake.
Notes wrote:Off hand I don't see anything operating system specific here either but haven't tried to compile a Mac OS X version. Curl is known to be multi-platform. When I do the OS X version, I'll write up whatever.
End Result

The end result is that connecting to a server, like ThreeWave CTF downloads the maps and model -- but not the sounds -- and does so very rapidly (seems instant even for 5 MB maps on my broadband) and reconnects.

Yes, DarkPlaces does it better but this is still very good but can definitely be improved upon.

But the nicest thing is how simple the modification is.

We begin ...

Requirements for this Tutorial

1. Windows operating system + MSVC6
2. Libcurl.lib (I have no clue which specific version Rook used off the CURL download page).
3. This version of the ProQuake 3.99 source but this should likely work with stock glquake source with Microsoft Visual C++ Express Edition but you'll be on your own but it shouldn't be too hard.

Questions and Miscellaneous Notes

1. The libcurl.lib gets compiled into the binary and the size of the binary is only increased negligibly (a few KB!). Excellent!
2. Does the binary work properly on old Windows 98 or ME? What about Windows 7? Or even Vista? I haven't tried any of those yet with the binary. I'm not going to make the assumption that, say, Windows 98 works with this because I just don't know.
3. The download works super-efficiently and is extremely fast. Far faster than really what you'd expect!

Tutorial

1. Add the following files to your project

You can borrow them from this (download) and be sure to take the libcurl.lib as well.
Files to add:

curl.h
curlver.h
easy.h
multi.h
webdownload.c
webdownload.h
2. client.h

Add the yellow ....
typedef enum {
ca_dedicated, // a dedicated server with no ability to start a client
ca_disconnected, // full screen console with no connection
ca_connected // valid netcon, talking to a server
} cactive_t;

typedef struct
{
qboolean web;
char *name;
double percent;
qboolean disconnect; // set when user tries to disconnect, to allow cleaning up webdownload
} download_t;
And still in client.h, add the yellow ...
// connection information
int signon; // 0 to SIGNONS
struct qsocket_s *netcon;
sizebuf_t message; // writing buffer to send to server
download_t download;
3. Add COM_GetFolder to common.c

Add the yellow.
/*
============
COM_GetFolder
============
*/
void COM_GetFolder (char *in, char *out)
{
char *last = NULL;

while (*in)
{
if (*in == '/')
last = out;
*out++ = *in++;
}
if (last)
*last = 0;
else
*out = 0;
}



/*
============
COM_FileBase
============
*/
void COM_FileBase (char *in, char *out)
{
char *s, *s2;

s = in + strlen(in) - 1;

while (s != in && *s != '.')
s--;

for (s2 = s ; *s2 && *s2 != '/' ; s2--)
;

if (s-s2 < 2)
strcpy (out,"?model?");
else
{
s--;
strncpy (out,s2+1, s-s2);
out[s-s2] = 0;
}
}
4. In common.h, add this anywhere really like at the end of the file
void COM_GetFolder (char *in, char *out);//R00k
5. In cl_parse.c

Add the yellow.

/*
=====================
CL_WebDownloadProgress
Callback function for webdownloads.
Since Web_Get only returns once it's done, we have to do various things here:
Update download percent, handle input, redraw UI and send net packets.
=====================
*/
static int CL_WebDownloadProgress( double percent )
{
static double time, oldtime, newtime;

cls.download.percent = percent;
CL_KeepaliveMessage();

newtime = Sys_DoubleTime ();
time = newtime - oldtime;

Host_Frame (time);

oldtime = newtime;

return cls.download.disconnect; // abort if disconnect received
}



/*
==================
CL_ParseServerInfo
==================
*/
void CL_ParseServerInfo (void)
{
char *str, tempname[MAX_QPATH];
int i, nummodels, numsounds;
char model_precache[MAX_MODELS][MAX_QPATH];
char sound_precache[MAX_SOUNDS][MAX_QPATH];

extern cvar_t cl_web_download;
extern cvar_t cl_web_download_url;
extern int Web_Get( const char *url, const char *referer, const char *name, int resume, int max_downloading_time, int timeout, int ( *_progress )(double) );
And further download in cl_parse.c make alike!
// now we try to load everything else until a cache allocation fails
for (i=1 ; i<nummodels ; i++)
{
cl.model_precache = Mod_ForName (model_precache, false);
if (cl.model_precache == NULL)
{

if (cl_web_download.value && cl_web_download_url.string)
{
char url[1024];
qboolean success = false;
char download_tempname[MAX_QPATH],download_finalname[MAX_QPATH];
char folder[MAX_QPATH];
char name[MAX_QPATH];
extern char server_name[MAX_QPATH];
extern int net_hostport;

//Create the FULL path where the file should be written
Q_snprintfz (download_tempname, MAX_OSPATH, "%s/%s.tmp", com_gamedir, model_precache);

//determine the proper folder and create it, the OS will ignore if already exsists
COM_GetFolder(model_precache,folder);// "progs/","maps/"
Q_snprintfz (name, sizeof(name), "%s/%s", com_gamedir, folder);
Sys_mkdir (name);

Con_Printf( "Web downloading: %s from %s%s\n", model_precache, cl_web_download_url.string, model_precache);

//assign the url + path + file + extension we want
Q_snprintfz( url, sizeof( url ), "%s%s", cl_web_download_url.string, model_precache);

cls.download.web = true;
cls.download.disconnect = false;
cls.download.percent = 0.0;

//let libCURL do it's magic!!
success = Web_Get(url, NULL, download_tempname, false, 600, 30, CL_WebDownloadProgress);

cls.download.web = false;

free(url);
free(name);
free(folder);

if (success)
{
Con_Printf("Web download succesfull: %s\n", download_tempname);
//Rename the .tmp file to the final precache filename
Q_snprintfz (download_finalname, MAX_OSPATH, "%s/%s", com_gamedir, model_precache);
rename (download_tempname, download_finalname);

free(download_tempname);
free(download_finalname);

Cbuf_AddText (va("connect %s:%u\n",server_name,net_hostport));//reconnect after each success
return;
}
else
{
remove (download_tempname);
Con_Printf( "Web download of %s failed\n", download_tempname );
return;
}

free(download_tempname);

if( cls.download.disconnect )//if the user type disconnect in the middle of the download
{
cls.download.disconnect = false;
CL_Disconnect_f();
return;
}
} else
#endif
{
Con_Printf("Model %s not found\n", model_precache);
return;
}
}
CL_KeepaliveMessage ();
}


S_BeginPrecaching ();
for (i=1 ; i<numsounds ; i++)
{
cl.sound_precache[i] = S_PrecacheSound (sound_precache[i]);
CL_KeepaliveMessage ();
}
S_EndPrecaching ();


6. Now in cl_main.c

*/
// cl_main.c -- client main loop

#include "quakedef.h"
#include "curl.h"


cvar_t cl_web_download = {"cl_web_download", "1", true};
cvar_t cl_web_download_url = {"cl_web_download_url", "http://www.mywebpage.com/maps/", true};


client_static_t cls;
client_state_t cl;


And then ...

void CL_Disconnect (void)
{
// stop sounds (especially looping!)
S_StopAllSounds (true);

// bring the console down and fade the colors back to normal
// SCR_BringDownConsole ();


// We have to shut down webdownloading first
if( cls.download.web )
{
cls.download.disconnect = true;
return;
}



// if running a local server, shut it down
if (cls.demoplayback)
{
CL_StopPlayback ();
}


And then near end of cl_main.c

Cmd_AddCommand ("timedemo", CL_TimeDemo_f);

Cvar_RegisterVariable (&cl_web_download, NULL);
Cvar_RegisterVariable (&cl_web_download_url, NULL);


7. In your project settings, include libcurl.lib to be compiled into the build. In MSVC6, you do this by clicking Project -> Settings and then I select GL Release with ProQuake 3.99 and click the Link Tab and in "Object/Library Modules" I added libcurl.lib to the end.

You are done.

Here is a before and after of the code more or less so someone with WinMerge or whatever can manually examine the differences.

Source code: Before (download) and after (download).
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Team Xlink
Posts: 368
Joined: Thu Jun 25, 2009 4:45 am
Location: Michigan

Post by Team Xlink »

Very nice Tutorial Baker!


This is very nicely written!
Downsider
Posts: 621
Joined: Tue Sep 16, 2008 1:35 am

Post by Downsider »

It's cool for people who like to come in and copypaste into their engine, but not understand anything that's going on. :cry:
Team Xlink
Posts: 368
Joined: Thu Jun 25, 2009 4:45 am
Location: Michigan

Post by Team Xlink »

Downsider wrote:It's cool for people who like to come in and copypaste into their engine, but not understand anything that's going on. :cry:

Well parts of it are self explanatory so it isn't that big of a deal.
If people want to understand it I think they can and will.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

One weakness of this tutorial is apparently a missing model during demo playback and this will cause the client to want to download it.

Needs a slight adjustment!
Teiman
Posts: 311
Joined: Sun Jun 03, 2007 9:39 am

Post by Teiman »

Baker wrote:One weakness of this tutorial is apparently a missing model during demo playback and this will cause the client to want to download it.

Needs a slight adjustment!
downloading could be deactivated while playing a demo.
playing a demo could be detected.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

...and here's a native Windows API version that doesn't need the libcurl DLLs in your Quake folder. This just replaces all of the libcurl stuff, and I've kept the interface the very same, so in theory it's just drop and go.

You should be familiar with the above code before even attempting to implement this, as I'm not otherwise documenting in detail what needs to be replaced. In brief however you don't need all of the extra .c files, .h files or .lib files, so you can remove them from your project. As mentioned above, if you're distributing a Windows version you can also remove the libcurl.dll and zlib1.dll files from your distribution, as you won't need those any more either. :D

Everything else in Baker's original tutorial should stay the same.

I should also mention that you don't need to add any .lib files to your project either, as I'm dynamically linking to DLLs in C:\Windows\System32 (or wherever). (Aside from perhaps winmm.lib, but I think Quake already links with that for it's CD audio.)

Note: the guts of it are in C++, but I've created a C interface so that it can be called from C programs, and also provide a sample implementation (in C).

So here's your cl_webdownload.cpp - this replaces all of the libcurl stuff:

Code: Select all

#include <windows.h>
#include <wininet.h>
#include <urlmon.h>


typedef int (*DOWNLOADPROGRESSPROC) (double);

class CDownloader : public IBindStatusCallback
{
public:
	CDownloader (DOWNLOADPROGRESSPROC progressproc, DWORD maxtime, DWORD timeout)
	{
		// progress updater (optional)
		this->DownloadProgressProc = progressproc;

		// store times in milliseconds for convenience
		this->DownloadMaxTime = maxtime * 1000;
		this->DownloadTimeOut = timeout * 1000;

		// init to sensible values
		this->DownloadBeginTime = timeGetTime ();
		this->DownloadCurrentTime = timeGetTime ();
		this->DownloadLastCurrentTime = timeGetTime ();
	}

	~CDownloader (void) {}

	// progress
	STDMETHOD (OnProgress) (ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText)
	{
		switch (ulStatusCode)
		{
		case BINDSTATUS_BEGINDOWNLOADDATA:
			// init timers
			this->DownloadBeginTime = timeGetTime ();
			this->DownloadCurrentTime = timeGetTime ();
			this->DownloadLastCurrentTime = timeGetTime ();
			return S_OK;

		case BINDSTATUS_ENDDOWNLOADDATA:
			// amount downloaded was not equal to the amount needed
			if (ulProgress != ulProgressMax) return E_ABORT;

			// alles gut
			return S_OK;

		case BINDSTATUS_DOWNLOADINGDATA:
			// update current time
			this->DownloadCurrentTime = timeGetTime ();

			// abort if the max download time is exceeded
			if (this->DownloadCurrentTime - this->DownloadBeginTime > this->DownloadMaxTime) return E_ABORT;

			// check for a hang and abort if required
			if (this->DownloadCurrentTime - this->DownloadLastCurrentTime > this->DownloadTimeOut) return E_ABORT;

			// update hang check
			this->DownloadLastCurrentTime = this->DownloadCurrentTime;

			// send through the standard proc
			if (ulProgressMax && this->DownloadProgressProc)
				return (this->DownloadProgressProc ((int) ((((double) ulProgress / (double) ulProgressMax) * 100) + 0.5)) ? S_OK : E_ABORT);
			else return S_OK;

		default:
			break;
		}

		return S_OK;
	}

	// unimplemented methods
	STDMETHOD (GetBindInfo) (DWORD *grfBINDF, BINDINFO *pbindinfo) {return E_NOTIMPL;}
	STDMETHOD (GetPriority) (LONG *pnPriority) {return E_NOTIMPL;}
	STDMETHOD (OnDataAvailable) (DWORD grfBSCF, DWORD dwSize, FORMATETC *pformatetc, STGMEDIUM *pstgmed ) {return E_NOTIMPL;}
	STDMETHOD (OnObjectAvailable) (REFIID riid, IUnknown *punk) {return E_NOTIMPL;}
	STDMETHOD (OnStartBinding) (DWORD dwReserved, IBinding *pib) {return E_NOTIMPL;}
	STDMETHOD (OnStopBinding) (HRESULT hresult, LPCWSTR szError){return E_NOTIMPL;}
	STDMETHOD (OnLowResource) (DWORD blah) {return E_NOTIMPL;}

	// IUnknown methods - URLDownloadToFile never calls these
	STDMETHOD_ (ULONG, AddRef) () {return 0;}
	STDMETHOD_ (ULONG, Release) () {return 0;}
	STDMETHOD (QueryInterface) (REFIID riid, void __RPC_FAR *__RPC_FAR *ppvObject) {return E_NOTIMPL;}

private:
	DOWNLOADPROGRESSPROC DownloadProgressProc;
	DWORD DownloadBeginTime;
	DWORD DownloadCurrentTime;
	DWORD DownloadLastCurrentTime;
	DWORD DownloadMaxTime;
	DWORD DownloadTimeOut;
};


typedef HRESULT (__stdcall *URLDOWNLOADTOFILEPROC) (LPUNKNOWN, LPCSTR, LPCSTR, DWORD, LPBINDSTATUSCALLBACK);
typedef BOOL (__stdcall *DELETEURLCACHEENTRYPROC) (__in LPCSTR);

extern "C" int Web_Get (const char *url, const char *referer, const char *name, int resume, int max_downloading_time, int timeout, int (*_progress) (double))
{
	// assume it's failed until we know it's succeeded
	int DownloadResult = 0;

	// this is to deal with kiddies who still think it's cool to remove all IE components from their computers,
	// and also to resolve potential issues when we might be linking to a different version than what's on the
	// target machine.  in theory we could just use standard URLDownloadToFile and DeleteUrlCacheEntry though.
	HINSTANCE hInstURLMON = LoadLibrary ("urlmon.dll");
	HINSTANCE hInstWININET = LoadLibrary ("wininet.dll");

	if (hInstURLMON && hInstWININET)
	{
		// grab the non-unicode versions of the functions
		URLDOWNLOADTOFILEPROC QURLDownloadToFile = (URLDOWNLOADTOFILEPROC) GetProcAddress (hInstURLMON, "URLDownloadToFileA");
		DELETEURLCACHEENTRYPROC QDeleteUrlCacheEntry = (DELETEURLCACHEENTRYPROC) GetProcAddress (hInstWININET, "DeleteUrlCacheEntryA");

		if (QURLDownloadToFile && QDeleteUrlCacheEntry)
		{
			// always take a fresh copy (fail silently if this fails)
			QDeleteUrlCacheEntry (url);

			// create the COM interface used for downloading
			CDownloader *DownloadClass = new CDownloader (_progress, max_downloading_time, timeout);

			// and download it
			HRESULT hrDownload = QURLDownloadToFile (NULL, url, name, 0, DownloadClass);

			// clean up
			delete DownloadClass;

			// check result
			if (FAILED (hrDownload))
				DownloadResult = 0;
			else DownloadResult = 1;
		}
		else DownloadResult = 0;
	}
	else DownloadResult = 0;

	if (hInstURLMON) FreeLibrary (hInstURLMON);
	if (hInstWININET) FreeLibrary (hInstWININET);

	return DownloadResult;
}
The only missing functionality from this is that it doesn't support resuming a download.

I wasn't entirely certain if the download callback provided by Baker uses a 0 to 1 or a 0 to 100 scale for it's percent complete; my code scales 0 to 100 but you can change that if it bothers you (it's in the "return this->DownloadProgressProc" line.)

And here's the sample implementation in C:

Code: Select all

#include <windows.h>
#include <stdio.h>
#include <conio.h>

#pragma comment (lib, "winmm.lib")

int Web_Get (const char *url, const char *referer, const char *name, int resume, int max_downloading_time, int timeout, int (*_progress) (double));
char *TargetURL = "http://download.microsoft.com/download/win2000platform/sp/sp2/nt5/en-us/w2ksp2.exe";
char *TargetFile = "C:\\Win2KSP2.exe";

int CL_WebDownloadProgress (double percent)
{
	static int oldpct = -1;

	if ((int) percent != oldpct)
	{
		printf ("...Downloading %i%%\n", (int) percent);
		oldpct = (int) percent;
	}

	return 1;
}


void main (void)
{
	timeBeginPeriod (1);

	printf ("Downloading %s\nto %s\n", TargetURL, TargetFile);

	if (Web_Get (TargetURL, NULL, TargetFile, 0, 600, 30, CL_WebDownloadProgress))
		printf ("Download succeeded\n");
	else printf ("Download failed\n");

	printf ("Press any key... ");

	while (1)
	{
		if (_kbhit ()) break;
		Sleep (5);
	}
}
For testing purposes I've just set it to download Windows 2000 SP2 - I'm on a gigabit internet connection here so I need something large enough to make download times and notification meaningful!

If you want you can compile the two of these into a console application and run the sample implementation to confirm that it works before you go hacking at your engine - I probably recommend doing that. ;)
Last edited by mh on Wed Mar 24, 2010 5:13 pm, edited 1 time in total.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Sajt
Posts: 1215
Joined: Sat Oct 16, 2004 3:39 am

Post by Sajt »

Shouldn't you delete DownloadClass afterwards?

Anyway, it's cool to have code like this lying around as reference. Sometimes it is nice to have an engine that can operate as a solitary exe instead of having to come with all sorts of dlls (on Windows).
F. A. Špork, an enlightened nobleman and a great patron of art, had a stately Baroque spa complex built on the banks of the River Labe.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Well spotted, you win the prize! :lol:

I also seem to have mixed up some return types while transitioning my original test code to a compatible interface. I'll clean these up tomorrow. :oops:

Update: done.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

A minor modification of the original R00k code that makes all the difference in the world:

The client keeps downloading all the models one after another and doesn't ever "reconnect" to the server, because you never leave it.

One of many improvements to be posted, I suspect.

Was:

Code: Select all

				if (success)
				{
					Con_Printf("Web download succesfull: %s\n", download_tempname);	
					//Rename the .tmp file to the final precache filename
					Q_snprintfz (download_finalname, MAX_OSPATH, "%s/%s", com_gamedir, model_precache[i]);					
					rename (download_tempname, download_finalname);
					
					free(download_tempname);
					free(download_finalname);
	
					Cbuf_AddText (va("connect %s:%u\n",server_name,net_hostport));//reconnect after each success					
					return;
				}
New:

Code: Select all

				if (success)
				{
					Con_Printf("Web download successful: %s\n", download_tempname);
					//Rename the .tmp file to the final precache filename
					snprintf (download_finalname, sizeof(download_finalname), "%s/%s", com_gamedirfull, model_precache[i]);
					rename (download_tempname, download_finalname);

					i--; // Subtract 1 so we try this model again in next iteration
					continue;  // Bail on loop and resume
				}
(To someone comparing, the removal of those "free" lines is just because they weren't supposed to be there to being with. They are never malloc'd.)
The night is young. How else can I annoy the world before sunsrise? 8) 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

Post by Baker »

Sound download ... the whole thing is a bit restructured ...

Not completely polished, downloads all files in a single pass including sounds. Does not inherently disconnect from the server, although a small amount of fussing with the KeepAlive messages needs to be done to ensure client connection isn't lost.

Code: Select all

	R_PreMapLoad (tempname);		// We haven't loaded the map yet!

// now we try to load everything else until a cache allocation fails
	for (i=1 ; i<nummodels ; i++)	// model 1 is the bsp
	{
		cl.model_precache[i] = Mod_ForName (model_precache[i], false);

		if (cl.model_precache[i] == NULL)
		{				
			qbool success = CL_WebDownload (model_precache[i]); 

			//if the user type disconnect in the middle of the download
			if (cls.download.disconnect) {cls.download.disconnect = false; CL_Disconnect_f(); return;} // disconnect and leave
			if (success) {CL_KeepaliveMessage (); i--; continue;} //	Model loaded, take another crack at it, restart this iteration

			// If we are here, we have failed, stop loading
			Con_Printf("Model %s not found\n", model_precache[i]);  ////Host_EndGame ("Server disconnected\n");  don't disconnect, let them sit in console and ask for help.
			return;
		}
		CL_KeepaliveMessage ();
	}

	S_BeginPrecaching ();
	for (i=1 ; i<numsounds ; i++)
	{
		extern qbool sound_missing;
		cl.sound_precache[i] = S_PrecacheSound (sound_precache[i]);
		
		if (sound_missing)
		{
			qbool success = CL_WebDownload (va("sound/%s", sound_precache[i])); 

			//if the user type disconnect in the middle of the download
			if (cls.download.disconnect) {cls.download.disconnect = false; CL_Disconnect_f(); return;} // disconnect and leave
			if (success) {CL_KeepaliveMessage (); i--; continue;} //	Model loaded, take another crack at it, restart this iteration

			// Failure to load a sound is just ignored
		}
		CL_KeepaliveMessage ();
	}
	S_EndPrecaching ();

Code: Select all

/*
==================
CL_WebDownload

If there is a missing file, try getting it from the web
Input: myGameFile is a relative game data file like
"progs\player.mdl" or "sound\something\noise.wav" 

Returns true if downloaded and installed in place

Attempts download from cl_web_download_url, like  "http://downloads.quake-1.com/"
==================
*/

qbool CL_WebDownload (const char *myGameFile)
{
	char	temp_filename_abs[MAX_OSPATH];		// Absolute path like c:\quake1\enginex\progs\ball_db.mdl.tmp
	char	final_filename_abs[MAX_OSPATH];	// Absolute path like c:\quake1\enginex\progs\ball_db.mdl 
	char	url_full[1024];					// http://downloads.quake-1.com/progs/ball_db.mdl
	extern	char com_gamedirfull[MAX_OSPATH]; // Like c:\quake1\enginex or c:\quake1\id1
	qbool	success;
	
	// Try to download if not single player or demo
	if (cls.demoplayback || sv.active || /* ignore map submodels --> */ myGameFile[0]== '*') 
		return false;

	if (!cl_web_download.value || !cl_web_download_url.string)
		return false;
	
	// Build tempname and final name
	snprintf (temp_filename_abs, sizeof(temp_filename_abs), "%s/%s.tmp", com_gamedirfull, myGameFile);
	snprintf (final_filename_abs, sizeof(final_filename_abs), "%s/%s", com_gamedirfull, myGameFile);

	// Build URL of file as it is named on internet ("http://downloads.quake-1.com/progs/ball_db.mdl")
	snprintf (url_full, sizeof(url_full), "%s%s", cl_web_download_url.string, myGameFile);

	// Make folder like c:\quake1\enginex\progs, we do NOT know if it exists 
	{
		char folder_relative[MAX_QPATH];//	"progs/", maps/"
		char folder_absolute[MAX_OSPATH]; //  c:\quake1\enginex\progs
		
		//determine the proper folder and create it, the OS will ignore if already exsists
		StringCopySD_GetFolder (myGameFile, folder_relative);// "progs/","maps/"

		if (!strncasecmp(folder_relative, "sound/", sizeof("sound/")-1))
		{
			// Due to sounds being one level deeper, we have to ensure that folder exists
			// Baker: since I do not even like the idea of mkdir, let's only do a recursive mkdir
			//        for this one single circumstance and hard code it
			char parentpath_absolute[MAX_OSPATH];
			snprintf (parentpath_absolute, sizeof(parentpath_absolute), "%s/%s", com_gamedirfull, "sound");
			
			Sys_Mkdir (parentpath_absolute);
		}

		snprintf (folder_absolute, sizeof(folder_absolute), "%s/%s", com_gamedirfull, folder_relative);
		Sys_Mkdir (folder_absolute);  // Like c:\quake1\enginex\progs ... absolute folder name

		// TODO:  recursive path building
	}
		
	Con_Printf( "Web downloading: %s\n", myGameFile);

	cls.download.web = true;
	cls.download.disconnect = false;
	cls.download.percent = 0.0;

	SCR_EndLoadingPlaque ();

	//let libCURL do it's magic!!
	success = Web_Get(url_full, NULL, temp_filename_abs, false, 600, 30, CL_WebDownloadProgress);

	cls.download.web = false;

	if (!success)  // Failed to download, delete temp file and return
	{
		remove (temp_filename_abs);
		Con_Printf ("Failed.  From: %s\n", url_full );
		return false;
	}

	// Success, rename the file to the correct file name and 
	rename (temp_filename_abs, final_filename_abs);
		
	Con_DPrintf("Done.");
	return true;
}
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
qbism
Posts: 1236
Joined: Thu Nov 04, 2004 5:51 am
Contact:

Re: Warsow/Qrack Web Map/Model Download (Curl)

Post by qbism »

Looking at this, and noticed "free" is being used on statically allocated memory. Normally it's just for dynamic memory. This came up after running cppcheck, a code checker with a plugin for codeblocks. OK, so the original post is 3 years old. I post again after I try it without the frees, 3 years hence.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Warsow/Qrack Web Map/Model Download (Curl)

Post by Baker »

qbism wrote:Looking at this, and noticed "free" is being used on statically allocated memory. Normally it's just for dynamic memory. This came up after running cppcheck, a code checker with a plugin for codeblocks. OK, so the original post is 3 years old. I post again after I try it without the frees, 3 years hence.
re: free of a non-dynamic array... That's a mistake in R00k's code (which is 4.5 years old now so ...) ... might have been an artifact or remnant of the Warsow code at the time (?).

If you want a cleaner implementation of this which is much more tidy, pull it out of ProQuake 4.93 cl_parse.c. The web download function is centralized so the insertion into the function is just a line here or there. The implementation in the first post works, but I feel that is a bit messy.

[But hey, it's probably downloaded several thousand maps or more so even where it is a bit untidy, even that old code sure works reliably ...]
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Post Reply