/** * @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; }
/** * @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; }
/** * @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]; }
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; }
/** * @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(); }
/** * @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); }
/** * @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; }