Exemple #1
0
/**
 * @brief Called from the precache check to queue a download.
 * @sa CL_CheckOrDownloadFile
 */
bool CL_QueueHTTPDownload (const char *ufoPath)
{
	/* no http server (or we got booted) */
	if (!cls.downloadServer[0] || abortDownloads || !cl_http_downloads->integer)
		return false;

	dlqueue_t** anchor = &cls.downloadQueue;
	for (; *anchor; anchor = &(*anchor)->next) {
		/* avoid sending duplicate requests */
		if (Q_streq(ufoPath, (*anchor)->ufoPath))
			return true;
	}

	dlqueue_t* const q = Mem_AllocType(dlqueue_t);
	q->next = NULL;
	q->state = DLQ_STATE_NOT_STARTED;
	Q_strncpyz(q->ufoPath, ufoPath, sizeof(q->ufoPath));
	*anchor = q;

	/* special case for map file lists */
	if (cl_http_filelists->integer) {
		const char *extension = Com_GetExtension(ufoPath);
		if (extension != NULL && !Q_strcasecmp(extension, "bsp")) {
			char listPath[MAX_OSPATH];
			const size_t len = strlen(ufoPath);
			Com_sprintf(listPath, sizeof(listPath), BASEDIRNAME"/%.*s.filelist", (int)(len - 4), ufoPath);
			CL_QueueHTTPDownload(listPath);
		}
	}

	/* if a download entry has made it this far, CL_FinishHTTPDownload is guaranteed to be called. */
	pendingCount++;

	return true;
}
Exemple #2
0
/**
 * @brief Loads in a model for the given name
 * @param[in] filename Filename relative to base dir and with extension (models/model.md2)
 * @param[inout] mod Structure to initialize
 * @return True if the loading was succeed. True or false structure mod was edited.
 */
static bool R_LoadModel (model_t *mod, const char *filename)
{
	byte *buf;
	int modfilelen;
	char animname[MAX_QPATH];

	if (filename[0] == '\0')
		Com_Error(ERR_FATAL, "R_ModForName: NULL name");

	/* load the file */
	modfilelen = FS_LoadFile(filename, &buf);
	if (!buf)
		return false;

	OBJZERO(*mod);
	Q_strncpyz(mod->name, filename, sizeof(mod->name));

	/* call the appropriate loader */
	switch (LittleLong(*(unsigned *) buf)) {
	case IDALIASHEADER:
		/* MD2 header */
		R_ModLoadAliasMD2Model(mod, buf, modfilelen, true);
		break;

	case DPMHEADER:
		R_ModLoadAliasDPMModel(mod, buf, modfilelen);
		break;

	case IDMD3HEADER:
		/* MD3 header */
		R_ModLoadAliasMD3Model(mod, buf, modfilelen);
		break;

	case IDBSPHEADER:
		Com_Error(ERR_FATAL, "R_ModForName: don't load BSPs with this function");
		break;

	default:
	{
		const char* ext = Com_GetExtension(filename);
		if (ext != NULL && !Q_strcasecmp(ext, "obj"))
			R_LoadObjModel(mod, buf, modfilelen);
		else
			Com_Error(ERR_FATAL, "R_ModForName: unknown fileid for %s", mod->name);
	}
	}

	/* load the animations */
	Com_StripExtension(mod->name, animname, sizeof(animname));
	Com_DefaultExtension(animname, sizeof(animname), ".anm");

	/* try to load the animation file */
	if (FS_CheckFile("%s", animname) != -1) {
		R_ModLoadAnims(&mod->alias, animname);
	}

	FS_FreeFile(buf);

	return true;
}
Exemple #3
0
/**
 * @brief Tries to load a model
 * @param[in] name The model path or name (with or without extension) - see notes
 * this parameter is always relative to the game base dir - it can also be relative
 * to the models/ dir in the game folder
 * @note trying all supported model formats is only supported when you are using
 * a name without extension and relative to models/ dir
 * @note if first char of name is a '*' - this is an inline model
 * @note if there is not extension in the given filename the function will
 * try to load one of the supported model formats
 * @return NULL if no model could be found with the given name, model_t otherwise
 */
model_t *R_FindModel (const char *name)
{
	model_t *mod;
	model_t model;
	bool loaded = false;
	int i;

	if (!name || !name[0])
		return NULL;

	/* search for existing models */
	mod = R_GetModel(name);
	if (mod != NULL)
		return mod;

	/* no inline bsp models here */
	if (name[0] == '*')
		return NULL;

	/* load model */
	if (Com_GetExtension(name) == NULL) {
		char filename[MAX_QPATH];

		for (i = 0; mod_extensions[i] != NULL; i++) {
			Com_sprintf(filename, sizeof(filename), "models/%s.%s", name, mod_extensions[i]);
			loaded = R_LoadModel(&model, filename);
			if (loaded) {
				/* use short name */
				Q_strncpyz(model.name, name, sizeof(model.name));
				break;
			}
		}
	} else {
		/** @todo this case should be useless, do we ever use extension? */
		loaded = R_LoadModel(&model, name);
	}

	if (!loaded) {
		Com_Printf("R_FindModel: Could not find: '%s'\n", name);
		return NULL;
	}

	/* register the new model only after the loading is finished */

	/* find a free model slot spot */
	for (i = 0, mod = r_models; i < r_numModels; i++, mod++) {
		if (!mod->name[0])
			break;
	}
	if (i == r_numModels) {
		if (r_numModels == MAX_MOD_KNOWN)
			Com_Error(ERR_FATAL, "r_numModels == MAX_MOD_KNOWN");
		r_numModels++;
	}

	/* copy the model to the slot */
	r_models[i] = model;
	return &r_models[i];
}
Exemple #4
0
bool R_ModelExists (const char *name)
{
	if (Com_GetExtension(name) == NULL) {
		int i;
		for (i = 0; mod_extensions[i] != NULL; i++) {
			if (FS_CheckFile("models/%s.%s", name, mod_extensions[i]) != -1) {
				return true;
			}
		}
		return false;
	}

	return FS_CheckFile("models/%s", name) != -1;
}
Exemple #5
0
/**
 * @brief A download finished, find out what it was, whether there were any errors and
 * if so, how severe. If none, rename file and other such stuff.
 */
static void CL_FinishHTTPDownload (void)
{
	int messagesInQueue, i;
	CURLcode result;
	CURL *curl;
	long responseCode;
	double timeTaken, fileSize;
	char tempName[MAX_OSPATH];
	bool isFile;

	do {
		CURLMsg *msg = curl_multi_info_read(multi, &messagesInQueue);
		dlhandle_t *dl = NULL;

		if (!msg) {
			Com_Printf("CL_FinishHTTPDownload: Odd, no message for us...\n");
			return;
		}

		if (msg->msg != CURLMSG_DONE) {
			Com_Printf("CL_FinishHTTPDownload: Got some weird message...\n");
			continue;
		}

		curl = msg->easy_handle;

		/* curl doesn't provide reverse-lookup of the void * ptr, so search for it */
		for (i = 0; i < 4; i++) {
			if (cls.HTTPHandles[i].curl == curl) {
				dl = &cls.HTTPHandles[i];
				break;
			}
		}

		if (!dl)
			Com_Error(ERR_DROP, "CL_FinishHTTPDownload: Handle not found");

		/* we mark everything as done even if it errored to prevent multiple attempts. */
		dl->queueEntry->state = DLQ_STATE_DONE;

		/* filelist processing is done on read */
		if (dl->file)
			isFile = true;
		else
			isFile = false;

		if (isFile) {
			fclose(dl->file);
			dl->file = NULL;
		}

		/* might be aborted */
		if (pendingCount)
			pendingCount--;
		handleCount--;
		/* Com_Printf("finished dl: hc = %d\n", handleCount); */
		cls.downloadName[0] = 0;
		cls.downloadPosition = 0;

		result = msg->data.result;

		switch (result) {
		/* for some reason curl returns CURLE_OK for a 404... */
		case CURLE_HTTP_RETURNED_ERROR:
		case CURLE_OK:
			curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode);
			if (responseCode == 404) {
				const char *extension = Com_GetExtension(dl->queueEntry->ufoPath);
				if (extension != NULL && Q_streq(extension, "pk3"))
					downloadingPK3 = false;

				if (isFile)
					FS_RemoveFile(dl->filePath);
				Com_Printf("HTTP(%s): 404 File Not Found [%d remaining files]\n", dl->queueEntry->ufoPath, pendingCount);
				curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &fileSize);
				if (fileSize > 512) {
					/* ick */
					isFile = false;
					result = CURLE_FILESIZE_EXCEEDED;
					Com_Printf("Oversized 404 body received (%d bytes), aborting HTTP downloading.\n", (int)fileSize);
				} else {
					curl_multi_remove_handle(multi, dl->curl);
					continue;
				}
			} else if (responseCode == 200) {
				if (!isFile && !abortDownloads)
					CL_ParseFileList(dl);
				break;
			}

			/* every other code is treated as fatal, fallthrough here */

		/* fatal error, disable http */
		case CURLE_COULDNT_RESOLVE_HOST:
		case CURLE_COULDNT_CONNECT:
		case CURLE_COULDNT_RESOLVE_PROXY:
			if (isFile)
				FS_RemoveFile(dl->filePath);
			Com_Printf("Fatal HTTP error: %s\n", curl_easy_strerror(result));
			curl_multi_remove_handle(multi, dl->curl);
			if (abortDownloads)
				continue;
			CL_CancelHTTPDownloads(true);
			continue;
		default:
			i = strlen(dl->queueEntry->ufoPath);
			if (Q_streq(dl->queueEntry->ufoPath + i - 4, ".pk3"))
				downloadingPK3 = false;
			if (isFile)
				FS_RemoveFile(dl->filePath);
			Com_Printf("HTTP download failed: %s\n", curl_easy_strerror(result));
			curl_multi_remove_handle(multi, dl->curl);
			continue;
		}

		if (isFile) {
			/* rename the temp file */
			Com_sprintf(tempName, sizeof(tempName), "%s/%s", FS_Gamedir(), dl->queueEntry->ufoPath);

			if (!FS_RenameFile(dl->filePath, tempName, false))
				Com_Printf("Failed to rename %s for some odd reason...", dl->filePath);

			/* a pk3 file is very special... */
			i = strlen(tempName);
			if (Q_streq(tempName + i - 4, ".pk3")) {
				FS_RestartFilesystem(NULL);
				CL_ReVerifyHTTPQueue();
				downloadingPK3 = false;
			}
		}

		/* show some stats */
		curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &timeTaken);
		curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &fileSize);

		/** @todo
		 * technically i shouldn't need to do this as curl will auto reuse the
		 * existing handle when you change the URL. however, the handleCount goes
		 * all weird when reusing a download slot in this way. if you can figure
		 * out why, please let me know. */
		curl_multi_remove_handle(multi, dl->curl);

		Com_Printf("HTTP(%s): %.f bytes, %.2fkB/sec [%d remaining files]\n",
			dl->queueEntry->ufoPath, fileSize, (fileSize / 1024.0) / timeTaken, pendingCount);
	} while (messagesInQueue > 0);

	if (handleCount == 0) {
		if (abortDownloads == HTTPDL_ABORT_SOFT)
			abortDownloads = HTTPDL_ABORT_NONE;
		else if (abortDownloads == HTTPDL_ABORT_HARD)
			cls.downloadServer[0] = 0;
	}

	/* done current batch, see if we have more to dl - maybe a .bsp needs downloaded */
	if (cls.state == ca_connected && !CL_PendingHTTPDownloads())
		CL_RequestNextDownload();
}
Exemple #6
0
/**
 * @brief Validate a path supplied by a filelist.
 * @param[in,out] path Pointer to file (path) to download (high bits will be stripped).
 * @sa CL_QueueHTTPDownload
 * @sa CL_ParseFileList
 */
static void CL_CheckAndQueueDownload (char *path)
{
	size_t		length;
	const char	*ext;
	bool	pak;
	bool	gameLocal;

	StripHighBits(path);

	length = strlen(path);

	if (length >= MAX_QPATH)
		return;

	ext = Com_GetExtension(path);
	if (ext == NULL)
		return;

	if (Q_streq(ext, "pk3")) {
		Com_Printf("NOTICE: Filelist is requesting a .pk3 file (%s)\n", path);
		pak = true;
	} else
		pak = false;

	if (!pak &&
			!Q_streq(ext, "bsp") &&
			!Q_streq(ext, "wav") &&
			!Q_streq(ext, "md2") &&
			!Q_streq(ext, "ogg") &&
			!Q_streq(ext, "md3") &&
			!Q_streq(ext, "png") &&
			!Q_streq(ext, "jpg") &&
			!Q_streq(ext, "obj") &&
			!Q_streq(ext, "mat") &&
			!Q_streq(ext, "ump")) {
		Com_Printf("WARNING: Illegal file type '%s' in filelist.\n", path);
		return;
	}

	if (path[0] == '@') {
		if (pak) {
			Com_Printf("WARNING: @ prefix used on a pk3 file (%s) in filelist.\n", path);
			return;
		}
		gameLocal = true;
		path++;
		length--;
	} else
		gameLocal = false;

	if (strstr(path, "..") || !isvalidchar(path[0]) || !isvalidchar(path[length - 1]) || strstr(path, "//") ||
		strchr(path, '\\') || (!pak && !strchr(path, '/')) || (pak && strchr(path, '/'))) {
		Com_Printf("WARNING: Illegal path '%s' in filelist.\n", path);
		return;
	}

	/* by definition pk3s are game-local */
	if (gameLocal || pak) {
		bool exists;

		/* search the user homedir to find the pk3 file */
		if (pak) {
			char gamePath[MAX_OSPATH];
			FILE *f;
			Com_sprintf(gamePath, sizeof(gamePath), "%s/%s", FS_Gamedir(), path);
			f = fopen(gamePath, "rb");
			if (!f)
				exists = false;
			else {
				exists = true;
				fclose(f);
			}
		} else
			exists = FS_CheckFile("%s", path);

		if (!exists) {
			if (CL_QueueHTTPDownload(path)) {
				/* pk3s get bumped to the top and HTTP switches to single downloading.
				 * this prevents someone on 28k dialup trying to do both the main .pk3
				 * and referenced configstrings data at once. */
				if (pak) {
					dlqueue_t** anchor = &cls.downloadQueue;
					while ((*anchor)->next) anchor = &(*anchor)->next;
					/* Remove the last element from the end of the list ... */
					dlqueue_t* const d = *anchor;
					*anchor            = 0;
					/* ... and prepend it to the list. */
					d->next            = cls.downloadQueue;
					cls.downloadQueue  = d;
				}
			}
		}
	} else
		CL_CheckOrDownloadFile(path);
}
Exemple #7
0
/**
 * @brief Actually starts a download by adding it to the curl multi handle.
 */
static void CL_StartHTTPDownload (dlqueue_t *entry, dlhandle_t *dl)
{
	char tempFile[MAX_OSPATH];
	char escapedFilePath[MAX_QPATH * 4];
	const char *extension = Com_GetExtension(entry->ufoPath);

	/* yet another hack to accomodate filelists, how i wish i could push :(
	 * NULL file handle indicates filelist. */
	if (extension != NULL && Q_streq(extension, "filelist")) {
		dl->file = NULL;
		CL_EscapeHTTPPath(entry->ufoPath, escapedFilePath);
	} else {
		/** @todo use the FS_OpenFile function here */
		Com_sprintf(dl->filePath, sizeof(dl->filePath), "%s/%s", FS_Gamedir(), entry->ufoPath);

		Com_sprintf(tempFile, sizeof(tempFile), BASEDIRNAME"/%s", entry->ufoPath);
		CL_EscapeHTTPPath(tempFile, escapedFilePath);

		strcat(dl->filePath, ".tmp");

		FS_CreatePath(dl->filePath);

		/* don't bother with http resume... too annoying if server doesn't support it. */
		dl->file = fopen(dl->filePath, "wb");
		if (!dl->file) {
			Com_Printf("CL_StartHTTPDownload: Couldn't open %s for writing.\n", dl->filePath);
			entry->state = DLQ_STATE_DONE;
			/* CL_RemoveHTTPDownload(entry->ufoPath); */
			return;
		}
	}

	dl->tempBuffer = NULL;
	dl->speed = 0;
	dl->fileSize = 0;
	dl->position = 0;
	dl->queueEntry = entry;

	if (!dl->curl)
		dl->curl = curl_easy_init();

	Com_sprintf(dl->URL, sizeof(dl->URL), "%s%s", cls.downloadServer, escapedFilePath);

	curl_easy_setopt(dl->curl, CURLOPT_ENCODING, "");
#ifdef PARANOID
	curl_easy_setopt(dl->curl, CURLOPT_VERBOSE, 1);
#endif
	curl_easy_setopt(dl->curl, CURLOPT_NOPROGRESS, 0);
	if (dl->file) {
		curl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl->file);
		curl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, NULL);
	} else {
		curl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl);
		curl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, HTTP_Recv);
	}
	curl_easy_setopt(dl->curl, CURLOPT_CONNECTTIMEOUT, http_timeout->integer);
	curl_easy_setopt(dl->curl, CURLOPT_TIMEOUT, http_timeout->integer);
	curl_easy_setopt(dl->curl, CURLOPT_FAILONERROR, 1);
	curl_easy_setopt(dl->curl, CURLOPT_PROXY, http_proxy->string);
	curl_easy_setopt(dl->curl, CURLOPT_FOLLOWLOCATION, 1);
	curl_easy_setopt(dl->curl, CURLOPT_MAXREDIRS, 5);
	curl_easy_setopt(dl->curl, CURLOPT_WRITEHEADER, dl);
	curl_easy_setopt(dl->curl, CURLOPT_HEADERFUNCTION, HTTP_Header);
	curl_easy_setopt(dl->curl, CURLOPT_PROGRESSFUNCTION, CL_HTTP_Progress);
	curl_easy_setopt(dl->curl, CURLOPT_PROGRESSDATA, dl);
	curl_easy_setopt(dl->curl, CURLOPT_USERAGENT, GAME_TITLE " " UFO_VERSION);
	curl_easy_setopt(dl->curl, CURLOPT_REFERER, cls.downloadReferer);
	curl_easy_setopt(dl->curl, CURLOPT_URL, dl->URL);
	curl_easy_setopt(dl->curl, CURLOPT_NOSIGNAL, 1);

	if (curl_multi_add_handle(multi, dl->curl) != CURLM_OK) {
		Com_Printf("curl_multi_add_handle: error\n");
		dl->queueEntry->state = DLQ_STATE_DONE;
		return;
	}

	handleCount++;
	/*Com_Printf("started dl: hc = %d\n", handleCount); */
	Com_Printf("CL_StartHTTPDownload: Fetching %s...\n", dl->URL);
	dl->queueEntry->state = DLQ_STATE_RUNNING;
}