mirror of
https://git.do.srb2.org/KartKrew/Kart-Public.git
synced 2025-01-13 13:21:31 +00:00
8f354ad9c1
Co-Authored-By: Sally Coolatta <tehrealsalt@gmail.com> Co-Authored-By: James R <justsomejames2@gmail.com> Co-Authored-By: Monster Iestyn <iestynjealous@ntlworld.com> Co-Authored-By: katsy <katmint@live.com> Place Frame Interpolation in "Experimental" video options header This seems like an appropriate way to describe the feature for now. Add smooth level platter under interpolation, `renderdeltatics` `renderdeltatics` can be used as a standard delta time in any place, allowing for smooth menus. It will always be equal to `realtics` when frame interpolation is turned off, producing consistent framerate behavior everywhere it is used. Add smooth rendering to save select screen Add smooth rendering to Record/NiGHTS Attack, F_SkyScroll Ensure viewsector is accurate to viewx/viewy This fixes a potential crash in OpenGL when changing between levels. Ensure + commands get executed before map start Always have precise_t defined Fix misc dropshadow issues Reset view interpolation on level load Remove unnecessary precipmobj thinker hack Add reset interpolation state functions Reset precip interpolation on snap to ceil Reset mobj interp state on TeleportMove Only swap view interp state if a tick is run Run anti-lag chasecam at tic frequency Fixes jittery and unstable chasecam in high latency netgames Homogenize mobj interpolations Add sector plane level interpolations Add SectorScroll interpolator Add SideScroll interpolator Add Polyobj interpolator Intialize interpolator list at a better time Delete interpolators associated with thinkers Interpolate mobj angles and player drawangle Interpolate HWR_DrawModel Add functions to handle interpolation Much less code duplication P_InitAngle, to fix angle interpolation on spawning objects Fully fix drop shadows It used the thing's floorz / ceilingz directly -- that wouldn't account for interpolated coordinates. Do not speed up underwater/heatwave effect in OpenGL Closer OpenGL underwater/heatwave effect to Software Interpolate from time of previous tic Previously interpolated from last 35th of a second, which may be offset from game time due to connection lag. Consider this the proper fix to 54148a0dd0 too. Calculate FPS stuff even if frame is skipped I decided ultimately to actually keep the frame skip optimization disabled, because I think it is actually a little bit helpful that you can still get accurate rendering perfstats while paused, however if we decide otherwise then we can have this optimization back without making the game act like it's lagging. Keep rect in memory Feel better about this than creating one all da time Lots of FPS stuff - Disabled VSync, due to the numerous problems it has. - Instead, added an FPS cap. - Frame interpolation is now tied to fpscap != 35. - By default, the FPS cap is set to the monitor's refresh rate. - Rewrote the FPS counter. (This also consolidates several more commits ahead of this fixing various issues. -eid) Misc changes after Kart cherry-picks Fix renderdeltatics with new timing data Update mobj oldstates before all thinkers Allow FPS cap values Adjust how FPS cap is checked to improve FPS stability Fix precip crash from missing vars Improve the framerate limiter's timing for extreme stable FPS Handle the sleep at the end of D_SRB2Loop instead of the start Simplifies logic in the other parts of the loop, and fixes problems with it frequently waiting too long. Reset mobj interp state on add Add mobj interpolator on load netgame Move mobj interpolators to r_fps Dynamic slope interpolators I_GetFrameTime to try and improve frame pace (It doesn't feel that much better though.) Move I_FinishUpdate to D_SRB2Loop to sync screen updates with FPS cap, use timestamps in I_FrameCapSleep to simplify the code Fix plane interpolation light level flickering Fix flickering plane interpolation for OpenGL in the exact same way Funny OpenGL renderer being at least 50% copy-pasted Software code :) P_SetOrigin & P_MoveOrigin to replace P_TeleportMove Convert P_TeleportMove use to origin funcs Revert "P_InitAngle, to fix angle interpolation on spawning objects" This reverts commit a80c98bd164a2748cbbfad9027b34601185d93f5. Waypoint polyobjects interpolate z & children Add interpolation to more moving plane types Adds interpolation to the following: - Crumbling platforms - Mario blocks - Floatbob platforms (this one works really strangely due to two thinkers, maybe double-check this one?) Reset overlays interp states each TryRunTics Interpolate model interpolation (lol) Use interp tracer pos for GL linkdraw Papersprite angle interpolation Makes the ending signpost smooth Move intermission emerald bounce to ticker Bring back shadows on polyobjects Also optimizes the method used so rings can show their shadows too. Using just the subsector is a tad bit imprecise admittedly but any more precise methods get really laggy. Fix a bunch of ticking in hu_ drawing functions Revert "Reset overlays interp states each TryRunTics" This reverts commit a71a216faa20e8751b3bd0157354e8d748940c92. Move intro ticking out of the drawer Adjust 1up monitor icon z offsets Fixes interpolation issues with 1up monitors. Delta time choose player menu animations Add drawerlib deltaTime function Interpolate afterimages further back Use old sleep in dedicated mode Clamp cechotimer to 0 Fixes issues with cechos staying on-screen and glitching out (NiGHTS items for example). Revert "Remove unnecessary precipmobj thinker hack" This reverts commit 0e38208620d19ec2ab690740438ac2fc7862a49e. Fix frame pacing when game lags behind The frame timestamp should've been made at the start of the frame, not the end. Fix I_FrameCapSleep not respecting cpusleep Jonathan Joestar bruh Allow dedicated to use precise sleep timing again Instead of only using one old sleep, just enforce framerate cap to match TICRATE. Make Lua TeleportMove call MoveOrigin Reset Metal fume interp state on appear Add interpdebug Put interpdebug stuff in perfstats instead Add timescale cvar Slow the game down to debug animations / interpolation problems! Speed it up if you need to get somewhere quickly while mapping! Enable timescale outside of DEVELOP builds It has NETVAR, so it should be fine -- put an end to useful debugging features excluded in multiplayer! Force interpolation when timescale != 1.0 Reset old_z in MT_LOCKON think Fixes interpolation artifacting due to spawn pos. Fix cutscenes in interp Fix boss1 laser in interp Interpolate mobj scale Precalculate refresh rate Slower PCs can have issue querying mode over and over. This might kinda suck for windowed mode if you have different refresh rate displays but oh well Fix interp scaling crashing software Reset interp scale when Lua sets .scale Disable angle interp on fresh mobjs Fix interp scale crash for hires sprites Interp shadow scales Copy interp state in P_SpawnMobjFromMobj Fix multiplayer character select Don't interpolate mobj state if frac = 1.0 Fix Mario block item placement Interpolate spritescale/offset x/y Fix offset copies for SpawnMobjFromMobj THANKS SAL Add Lua HUD drawlists Buffers draw calls between tics to ensure hooks run at the originally intended rate. Rename drawerlib deltaTime to getDeltaTime Make renderisnewtic is false between tics I know what I'm doing! I swear Completely refactor timing system Time is now tracked internally in the game using I_GetPreciseTime and I_UpdateTime. I_Time now pulls from this internal timer. The system code no longer needs to keep track of time itself. This significantly improves frame and tic timing in interp mode, resulting in a much smoother image with essentially no judder at any framerate. Ensure mobj interpolators reset on level load Ensure view is not interpolated on first frame Disable sprite offset interpolation (for now) Refactor timing code even more System layer is greatly simplified and framecap logic has been moved internally. I_Sleep now takes a sleep duration and I_SleepDuration generically implements a precise sleep with spin loop.
1249 lines
34 KiB
C
1249 lines
34 KiB
C
// SONIC ROBO BLAST 2
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 1998-2000 by DooM Legacy Team.
|
|
// Copyright (C) 1999-2018 by Sonic Team Junior.
|
|
//
|
|
// This program is free software distributed under the
|
|
// terms of the GNU General Public License, version 2.
|
|
// See the 'LICENSE' file for more details.
|
|
//-----------------------------------------------------------------------------
|
|
/// \file d_netfil.c
|
|
/// \brief Transfer a file using HSendPacket.
|
|
|
|
#include <stdio.h>
|
|
#ifndef _WIN32_WCE
|
|
#ifdef __OS2__
|
|
#include <sys/types.h>
|
|
#endif // __OS2__
|
|
#include <sys/stat.h>
|
|
#endif
|
|
|
|
#if !defined (UNDER_CE)
|
|
#include <time.h>
|
|
#endif
|
|
|
|
#if ((defined (_WIN32) && !defined (_WIN32_WCE)) || defined (__DJGPP__)) && !defined (_XBOX)
|
|
#include <io.h>
|
|
#include <direct.h>
|
|
#elif !defined (_WIN32_WCE) && !(defined (_XBOX) && !defined (__GNUC__))
|
|
#include <sys/types.h>
|
|
#include <dirent.h>
|
|
#include <utime.h>
|
|
#endif
|
|
|
|
#ifdef __GNUC__
|
|
#include <unistd.h>
|
|
#include <limits.h>
|
|
#elif defined (_WIN32) && !defined (_WIN32_WCE)
|
|
#include <sys/utime.h>
|
|
#endif
|
|
#ifdef __DJGPP__
|
|
#include <dir.h>
|
|
#include <utime.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_CURL
|
|
#include "curl/curl.h"
|
|
#endif
|
|
|
|
#include "doomdef.h"
|
|
#include "doomstat.h"
|
|
#include "d_main.h"
|
|
#include "g_game.h"
|
|
#include "i_time.h"
|
|
#include "i_net.h"
|
|
#include "i_system.h"
|
|
#include "m_argv.h"
|
|
#include "d_net.h"
|
|
#include "w_wad.h"
|
|
#include "d_netfil.h"
|
|
#include "z_zone.h"
|
|
#include "byteptr.h"
|
|
#include "p_setup.h"
|
|
#include "m_misc.h"
|
|
#include "m_menu.h"
|
|
#include "md5.h"
|
|
#include "filesrch.h"
|
|
|
|
#include <errno.h>
|
|
|
|
// Prototypes
|
|
static boolean SV_SendFile(INT32 node, const char *filename, UINT8 fileid);
|
|
|
|
#ifdef HAVE_CURL
|
|
size_t curlwrite_data(void *ptr, size_t size, size_t nmemb, FILE *stream);
|
|
int curlprogress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow);
|
|
#endif
|
|
|
|
// Sender structure
|
|
typedef struct filetx_s
|
|
{
|
|
INT32 ram;
|
|
union {
|
|
char *filename; // Name of the file
|
|
char *ram; // Pointer to the data in RAM
|
|
} id;
|
|
UINT32 size; // Size of the file
|
|
UINT8 fileid;
|
|
INT32 node; // Destination
|
|
struct filetx_s *next; // Next file in the list
|
|
} filetx_t;
|
|
|
|
// Current transfers (one for each node)
|
|
typedef struct filetran_s
|
|
{
|
|
filetx_t *txlist; // Linked list of all files for the node
|
|
UINT32 position; // The current position in the file
|
|
FILE *currentfile; // The file currently being sent/received
|
|
} filetran_t;
|
|
static filetran_t transfer[MAXNETNODES];
|
|
|
|
// Read time of file: stat _stmtime
|
|
// Write time of file: utime
|
|
|
|
// Receiver structure
|
|
INT32 fileneedednum; // Number of files needed to join the server
|
|
fileneeded_t fileneeded[MAX_WADFILES]; // List of needed files
|
|
char downloaddir[512] = "DOWNLOAD";
|
|
|
|
#ifdef CLIENT_LOADINGSCREEN
|
|
// for cl loading screen
|
|
INT32 lastfilenum = -1;
|
|
INT32 downloadcompletednum = 0;
|
|
UINT32 downloadcompletedsize = 0;
|
|
INT32 totalfilesrequestednum = 0;
|
|
UINT32 totalfilesrequestedsize = 0;
|
|
#endif
|
|
|
|
#ifdef HAVE_CURL
|
|
static CURL *http_handle;
|
|
static CURLM *multi_handle;
|
|
boolean curl_running = false;
|
|
boolean curl_failedwebdownload = false;
|
|
static double curl_dlnow;
|
|
static double curl_dltotal;
|
|
static time_t curl_starttime;
|
|
INT32 curl_transfers = 0;
|
|
static int curl_runninghandles = 0;
|
|
static UINT32 curl_origfilesize;
|
|
static UINT32 curl_origtotalfilesize;
|
|
static char *curl_realname = NULL;
|
|
fileneeded_t *curl_curfile = NULL;
|
|
HTTP_login *curl_logins;
|
|
#endif
|
|
|
|
/** Fills a serverinfo packet with information about wad files loaded.
|
|
*
|
|
* \todo Give this function a better name since it is in global scope.
|
|
* Used to have size limiting built in - now handed via W_LoadWadFile in w_wad.c
|
|
*
|
|
*/
|
|
UINT8 *PutFileNeeded(UINT16 firstfile)
|
|
{
|
|
size_t i;
|
|
UINT8 count = 0;
|
|
UINT8 *p_start = netbuffer->packettype == PT_MOREFILESNEEDED ? netbuffer->u.filesneededcfg.files : netbuffer->u.serverinfo.fileneeded;
|
|
UINT8 *p = p_start;
|
|
char wadfilename[MAX_WADPATH] = "";
|
|
UINT8 filestatus;
|
|
|
|
for (i = mainwads+1; i < numwadfiles; i++) //mainwads+1, otherwise we start on the first mainwad
|
|
{
|
|
// If it has only music/sound lumps, don't put it in the list
|
|
if (!wadfiles[i]->important)
|
|
continue;
|
|
|
|
if (firstfile)
|
|
{ // Skip files until we reach the first file.
|
|
firstfile--;
|
|
continue;
|
|
}
|
|
|
|
nameonly(strcpy(wadfilename, wadfiles[i]->filename));
|
|
|
|
// Look below at the WRITE macros to understand what these numbers mean.
|
|
if (p + 1 + 4 + min(strlen(wadfilename) + 1, MAX_WADPATH) + 16 > p_start + MAXFILENEEDED)
|
|
{
|
|
// Too many files to send all at once
|
|
if (netbuffer->packettype == PT_MOREFILESNEEDED)
|
|
netbuffer->u.filesneededcfg.more = 1;
|
|
else
|
|
netbuffer->u.serverinfo.kartvars |= SV_LOTSOFADDONS;
|
|
break;
|
|
}
|
|
|
|
filestatus = 1; // Importance - not really used any more, holds 1 by default for backwards compat with MS
|
|
|
|
// Store in the upper four bits
|
|
if (!cv_downloading.value)
|
|
filestatus += (2 << 4); // Won't send
|
|
else if ((wadfiles[i]->filesize <= (UINT32)cv_maxsend.value * 1024))
|
|
filestatus += (1 << 4); // Will send if requested
|
|
// else
|
|
// filestatus += (0 << 4); -- Won't send, too big
|
|
|
|
WRITEUINT8(p, filestatus);
|
|
|
|
count++;
|
|
WRITEUINT32(p, wadfiles[i]->filesize);
|
|
WRITESTRINGN(p, wadfilename, MAX_WADPATH);
|
|
WRITEMEM(p, wadfiles[i]->md5sum, 16);
|
|
}
|
|
if (netbuffer->packettype == PT_MOREFILESNEEDED)
|
|
netbuffer->u.filesneededcfg.num = count;
|
|
else
|
|
netbuffer->u.serverinfo.fileneedednum = count;
|
|
|
|
return p;
|
|
}
|
|
|
|
/** Parses the serverinfo packet and fills the fileneeded table on client
|
|
*
|
|
* \param fileneedednum_parm The number of files (sent in this page) needed to join the server
|
|
* \param fileneededstr The memory block containing the list of needed files
|
|
* \param firstfile The first file index to read from
|
|
*/
|
|
void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr, UINT16 firstfile)
|
|
{
|
|
INT32 i;
|
|
UINT8 *p;
|
|
UINT8 filestatus;
|
|
|
|
fileneedednum = firstfile + fileneedednum_parm;
|
|
p = (UINT8 *)fileneededstr;
|
|
for (i = firstfile; i < fileneedednum; i++)
|
|
{
|
|
fileneeded[i].status = FS_NOTCHECKED; // We haven't even started looking for the file yet
|
|
filestatus = READUINT8(p); // The first byte is the file status
|
|
fileneeded[i].willsend = (UINT8)(filestatus >> 4);
|
|
fileneeded[i].totalsize = READUINT32(p); // The four next bytes are the file size
|
|
fileneeded[i].file = NULL; // The file isn't open yet
|
|
READSTRINGN(p, fileneeded[i].filename, MAX_WADPATH); // The next bytes are the file name
|
|
READMEM(p, fileneeded[i].md5sum, 16); // The last 16 bytes are the file checksum
|
|
}
|
|
}
|
|
|
|
void CL_PrepareDownloadSaveGame(const char *tmpsave)
|
|
{
|
|
fileneedednum = 1;
|
|
fileneeded[0].status = FS_REQUESTED;
|
|
fileneeded[0].totalsize = UINT32_MAX;
|
|
fileneeded[0].file = NULL;
|
|
memset(fileneeded[0].md5sum, 0, 16);
|
|
strcpy(fileneeded[0].filename, tmpsave);
|
|
}
|
|
|
|
/** Checks the server to see if we CAN download all the files,
|
|
* before starting to create them and requesting.
|
|
*
|
|
* \return True if we can download all the files
|
|
*
|
|
*/
|
|
boolean CL_CheckDownloadable(void)
|
|
{
|
|
UINT8 i,dlstatus = 0;
|
|
|
|
for (i = 0; i < fileneedednum; i++)
|
|
if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN)
|
|
{
|
|
if (fileneeded[i].willsend == 1)
|
|
continue;
|
|
|
|
if (fileneeded[i].willsend == 0)
|
|
dlstatus = 1;
|
|
else //if (fileneeded[i].willsend == 2)
|
|
dlstatus = 2;
|
|
}
|
|
|
|
// Downloading locally disabled
|
|
if (!dlstatus && M_CheckParm("-nodownload"))
|
|
dlstatus = 3;
|
|
|
|
if (!dlstatus)
|
|
return true;
|
|
|
|
// not downloadable, put reason in console
|
|
CONS_Alert(CONS_NOTICE, M_GetText("You need additional files to connect to this server:\n"));
|
|
for (i = 0; i < fileneedednum; i++)
|
|
if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN)
|
|
{
|
|
CONS_Printf(" * \"%s\" (%dK)", fileneeded[i].filename, fileneeded[i].totalsize >> 10);
|
|
|
|
if (fileneeded[i].status == FS_MD5SUMBAD)
|
|
CONS_Printf(M_GetText(" wrong version, md5: "));
|
|
else
|
|
CONS_Printf(M_GetText(" not found, md5: "));
|
|
|
|
{
|
|
INT32 j;
|
|
char md5tmp[33];
|
|
for (j = 0; j < 16; j++)
|
|
sprintf(&md5tmp[j*2], "%02x", fileneeded[i].md5sum[j]);
|
|
CONS_Printf("%s", md5tmp);
|
|
}
|
|
CONS_Printf("\n");
|
|
}
|
|
|
|
switch (dlstatus)
|
|
{
|
|
case 1:
|
|
CONS_Printf(M_GetText("Some files are larger than the server is willing to send.\n"));
|
|
break;
|
|
case 2:
|
|
CONS_Printf(M_GetText("The server is not allowing download requests.\n"));
|
|
break;
|
|
case 3:
|
|
CONS_Printf(M_GetText("All files downloadable, but you have chosen to disable downloading locally.\n"));
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Sends requests for files in the ::fileneeded table with a status of
|
|
* ::FS_NOTFOUND.
|
|
*
|
|
* \return True if the packet was successfully sent
|
|
* \note Sends a PT_REQUESTFILE packet
|
|
*
|
|
*/
|
|
boolean CL_SendRequestFile(void)
|
|
{
|
|
char *p;
|
|
INT32 i;
|
|
INT64 totalfreespaceneeded = 0, availablefreespace;
|
|
|
|
#ifdef PARANOIA
|
|
if (M_CheckParm("-nodownload"))
|
|
I_Error("Attempted to download files in -nodownload mode");
|
|
|
|
for (i = 0; i < fileneedednum; i++)
|
|
if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN
|
|
&& (fileneeded[i].willsend == 0 || fileneeded[i].willsend == 2))
|
|
{
|
|
I_Error("Attempted to download files that were not sendable");
|
|
}
|
|
#endif
|
|
|
|
netbuffer->packettype = PT_REQUESTFILE;
|
|
p = (char *)netbuffer->u.textcmd;
|
|
for (i = 0; i < fileneedednum; i++)
|
|
if ((fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD || fileneeded[i].status == FS_FALLBACK))
|
|
{
|
|
totalfreespaceneeded += fileneeded[i].totalsize;
|
|
nameonly(fileneeded[i].filename);
|
|
WRITEUINT8(p, i); // fileid
|
|
WRITESTRINGN(p, fileneeded[i].filename, MAX_WADPATH);
|
|
// put it in download dir
|
|
strcatbf(fileneeded[i].filename, downloaddir, "/");
|
|
fileneeded[i].status = FS_REQUESTED;
|
|
}
|
|
WRITEUINT8(p, 0xFF);
|
|
I_GetDiskFreeSpace(&availablefreespace);
|
|
if (totalfreespaceneeded > availablefreespace)
|
|
I_Error("To play on this server you must download %s KB,\n"
|
|
"but you have only %s KB free space on this drive\n",
|
|
sizeu1((size_t)(totalfreespaceneeded>>10)), sizeu2((size_t)(availablefreespace>>10)));
|
|
|
|
// prepare to download
|
|
I_mkdir(downloaddir, 0755);
|
|
return HSendPacket(servernode, true, 0, p - (char *)netbuffer->u.textcmd);
|
|
}
|
|
|
|
// get request filepak and put it on the send queue
|
|
// returns false if a requested file was not found or cannot be sent
|
|
boolean Got_RequestFilePak(INT32 node)
|
|
{
|
|
char wad[MAX_WADPATH+1];
|
|
UINT8 *p = netbuffer->u.textcmd;
|
|
UINT8 id;
|
|
while (p < netbuffer->u.textcmd + MAXTEXTCMD-1) // Don't allow hacked client to overflow
|
|
{
|
|
id = READUINT8(p);
|
|
if (id == 0xFF)
|
|
break;
|
|
READSTRINGN(p, wad, MAX_WADPATH);
|
|
if (!SV_SendFile(node, wad, id))
|
|
{
|
|
SV_AbortSendFiles(node);
|
|
return false; // don't read the rest of the files
|
|
}
|
|
}
|
|
return true; // no problems with any files
|
|
}
|
|
|
|
/** Checks if the files needed aren't already loaded or on the disk
|
|
*
|
|
* \return 0 if some files are missing
|
|
* 1 if all files exist
|
|
* 2 if some already loaded files are not requested or are in a different order
|
|
* 3 too many files, over WADLIMIT
|
|
* 4 still checking, continuing next tic
|
|
*
|
|
*/
|
|
INT32 CL_CheckFiles(void)
|
|
{
|
|
INT32 i, j;
|
|
char wadfilename[MAX_WADPATH];
|
|
size_t packetsize = 0;
|
|
size_t filestoload = 0;
|
|
boolean downloadrequired = false;
|
|
|
|
// if (M_CheckParm("-nofiles"))
|
|
// return 1;
|
|
|
|
// the first is the iwad (the main wad file)
|
|
// we don't care if it's called srb2.srb or srb2.wad.
|
|
// Never download the IWAD, just assume it's there and identical
|
|
// ...No! Why were we sending the base wads to begin with??
|
|
//fileneeded[0].status = FS_OPEN;
|
|
|
|
// Modified game handling -- check for an identical file list
|
|
// must be identical in files loaded AND in order
|
|
// Return 2 on failure -- disconnect from server
|
|
if (modifiedgame)
|
|
{
|
|
CONS_Debug(DBG_NETPLAY, "game is modified; only doing basic checks\n");
|
|
for (i = 0, j = mainwads+1; i < fileneedednum || j < numwadfiles;)
|
|
{
|
|
if (j < numwadfiles && !wadfiles[j]->important)
|
|
{
|
|
// Unimportant on our side. still don't care.
|
|
++j;
|
|
continue;
|
|
}
|
|
|
|
// If this test is true, we've reached the end of one file list
|
|
// and the other still has a file that's important
|
|
if (i >= fileneedednum || j >= numwadfiles)
|
|
return 2;
|
|
|
|
// For the sake of speed, only bother with a md5 check
|
|
if (memcmp(wadfiles[j]->md5sum, fileneeded[i].md5sum, 16))
|
|
return 2;
|
|
|
|
// It's accounted for! let's keep going.
|
|
CONS_Debug(DBG_NETPLAY, "'%s' accounted for\n", fileneeded[i].filename);
|
|
fileneeded[i].status = FS_OPEN;
|
|
++i;
|
|
++j;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
for (i = 0; i < fileneedednum; i++)
|
|
{
|
|
if (fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD || fileneeded[i].status == FS_FALLBACK)
|
|
downloadrequired = true;
|
|
|
|
if (fileneeded[i].status != FS_OPEN)
|
|
filestoload++;
|
|
|
|
if (fileneeded[i].status != FS_NOTCHECKED) //since we're running this over multiple tics now, its possible for us to come across files checked in previous tics
|
|
continue;
|
|
|
|
CONS_Debug(DBG_NETPLAY, "searching for '%s' ", fileneeded[i].filename);
|
|
|
|
// Check in already loaded files
|
|
for (j = mainwads+1; wadfiles[j]; j++)
|
|
{
|
|
nameonly(strcpy(wadfilename, wadfiles[j]->filename));
|
|
if (!stricmp(wadfilename, fileneeded[i].filename) &&
|
|
!memcmp(wadfiles[j]->md5sum, fileneeded[i].md5sum, 16))
|
|
{
|
|
CONS_Debug(DBG_NETPLAY, "already loaded\n");
|
|
fileneeded[i].status = FS_OPEN;
|
|
return 4;
|
|
}
|
|
}
|
|
|
|
packetsize += nameonlylength(fileneeded[i].filename) + 22;
|
|
|
|
fileneeded[i].status = findfile(fileneeded[i].filename, fileneeded[i].md5sum, true);
|
|
CONS_Debug(DBG_NETPLAY, "found %d\n", fileneeded[i].status);
|
|
return 4;
|
|
}
|
|
|
|
//now making it here means we've checked the entire list and no FS_NOTCHECKED files remain
|
|
if (numwadfiles+filestoload > MAX_WADFILES)
|
|
return 3;
|
|
else if (downloadrequired)
|
|
return 0; //some stuff is FS_NOTFOUND, needs download
|
|
else
|
|
return 1; //everything is FS_OPEN or FS_FOUND, proceed to loading
|
|
}
|
|
|
|
// Load it now
|
|
boolean CL_LoadServerFiles(void)
|
|
{
|
|
INT32 i;
|
|
|
|
// if (M_CheckParm("-nofiles"))
|
|
// return;
|
|
|
|
for (i = 0; i < fileneedednum; i++)
|
|
{
|
|
if (fileneeded[i].status == FS_OPEN)
|
|
continue; // Already loaded
|
|
else if (fileneeded[i].status == FS_FOUND)
|
|
{
|
|
P_AddWadFile(fileneeded[i].filename);
|
|
G_SetGameModified(true, false);
|
|
fileneeded[i].status = FS_OPEN;
|
|
return false;
|
|
}
|
|
else if (fileneeded[i].status == FS_MD5SUMBAD)
|
|
I_Error("Wrong version of file %s", fileneeded[i].filename);
|
|
else
|
|
{
|
|
const char *s;
|
|
switch(fileneeded[i].status)
|
|
{
|
|
case FS_NOTFOUND:
|
|
s = "FS_NOTFOUND";
|
|
break;
|
|
case FS_REQUESTED:
|
|
s = "FS_REQUESTED";
|
|
break;
|
|
case FS_DOWNLOADING:
|
|
s = "FS_DOWNLOADING";
|
|
break;
|
|
default:
|
|
s = "unknown";
|
|
break;
|
|
}
|
|
I_Error("Try to load file \"%s\" with status of %d (%s)\n", fileneeded[i].filename,
|
|
fileneeded[i].status, s);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Number of files to send
|
|
// Little optimization to quickly test if there is a file in the queue
|
|
static INT32 filestosend = 0;
|
|
|
|
/** Adds a file to the file list for a node
|
|
*
|
|
* \param node The node to send the file to
|
|
* \param filename The file to send
|
|
* \param fileid ???
|
|
* \sa SV_SendRam
|
|
*
|
|
*/
|
|
static boolean SV_SendFile(INT32 node, const char *filename, UINT8 fileid)
|
|
{
|
|
filetx_t **q; // A pointer to the "next" field of the last file in the list
|
|
filetx_t *p; // The new file request
|
|
INT32 i;
|
|
char wadfilename[MAX_WADPATH];
|
|
|
|
if (cv_noticedownload.value)
|
|
CONS_Printf("Sending file \"%s\" to node %d (%s)\n", filename, node, I_GetNodeAddress(node));
|
|
|
|
// Find the last file in the list and set a pointer to its "next" field
|
|
q = &transfer[node].txlist;
|
|
while (*q)
|
|
q = &((*q)->next);
|
|
|
|
// Allocate a file request and append it to the file list
|
|
p = *q = (filetx_t *)malloc(sizeof (filetx_t));
|
|
if (!p)
|
|
I_Error("SV_SendFile: No more memory\n");
|
|
|
|
// Initialise with zeros
|
|
memset(p, 0, sizeof (filetx_t));
|
|
|
|
// Allocate the file name
|
|
p->id.filename = (char *)malloc(MAX_WADPATH);
|
|
if (!p->id.filename)
|
|
I_Error("SV_SendFile: No more memory\n");
|
|
|
|
// Set the file name and get rid of the path
|
|
strlcpy(p->id.filename, filename, MAX_WADPATH);
|
|
nameonly(p->id.filename);
|
|
|
|
// Look for the requested file through all loaded files
|
|
for (i = 0; wadfiles[i]; i++)
|
|
{
|
|
strlcpy(wadfilename, wadfiles[i]->filename, MAX_WADPATH);
|
|
nameonly(wadfilename);
|
|
if (!stricmp(wadfilename, p->id.filename))
|
|
{
|
|
// Copy file name with full path
|
|
strlcpy(p->id.filename, wadfiles[i]->filename, MAX_WADPATH);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Handle non-loaded file requests
|
|
if (!wadfiles[i])
|
|
{
|
|
DEBFILE(va("%s not found in wadfiles\n", filename));
|
|
// This formerly checked if (!findfile(p->id.filename, NULL, true))
|
|
|
|
// Not found
|
|
// Don't inform client (probably someone who thought they could leak 2.2 ACZ)
|
|
DEBFILE(va("Client %d request %s: not found\n", node, filename));
|
|
free(p->id.filename);
|
|
free(p);
|
|
*q = NULL;
|
|
return false; // cancel the rest of the requests
|
|
}
|
|
|
|
// Handle huge file requests (i.e. bigger than cv_maxsend.value KB)
|
|
if (wadfiles[i]->filesize > (UINT32)cv_maxsend.value * 1024)
|
|
{
|
|
// Too big
|
|
// Don't inform client (client sucks, man)
|
|
DEBFILE(va("Client %d request %s: file too big, not sending\n", node, filename));
|
|
free(p->id.filename);
|
|
free(p);
|
|
*q = NULL;
|
|
return false; // cancel the rest of the requests
|
|
}
|
|
|
|
DEBFILE(va("Sending file %s (id=%d) to %d\n", filename, fileid, node));
|
|
p->ram = SF_FILE; // It's a file, we need to close it and free its name once we're done sending it
|
|
p->fileid = fileid;
|
|
p->next = NULL; // End of list
|
|
filestosend++;
|
|
return true;
|
|
}
|
|
|
|
/** Adds a memory block to the file list for a node
|
|
*
|
|
* \param node The node to send the memory block to
|
|
* \param data The memory block to send
|
|
* \param size The size of the block in bytes
|
|
* \param freemethod How to free the block after it has been sent
|
|
* \param fileid ???
|
|
* \sa SV_SendFile
|
|
*
|
|
*/
|
|
void SV_SendRam(INT32 node, void *data, size_t size, freemethod_t freemethod, UINT8 fileid)
|
|
{
|
|
filetx_t **q; // A pointer to the "next" field of the last file in the list
|
|
filetx_t *p; // The new file request
|
|
|
|
// Find the last file in the list and set a pointer to its "next" field
|
|
q = &transfer[node].txlist;
|
|
while (*q)
|
|
q = &((*q)->next);
|
|
|
|
// Allocate a file request and append it to the file list
|
|
p = *q = (filetx_t *)malloc(sizeof (filetx_t));
|
|
if (!p)
|
|
I_Error("SV_SendRam: No more memory\n");
|
|
|
|
// Initialise with zeros
|
|
memset(p, 0, sizeof (filetx_t));
|
|
|
|
p->ram = freemethod; // Remember how to free the memory block for when we're done sending it
|
|
p->id.ram = data;
|
|
p->size = (UINT32)size;
|
|
p->fileid = fileid;
|
|
p->next = NULL; // End of list
|
|
|
|
DEBFILE(va("Sending ram %p(size:%u) to %d (id=%u)\n",p->id.ram,p->size,node,fileid));
|
|
|
|
filestosend++;
|
|
}
|
|
|
|
/** Stops sending a file for a node, and removes the file request from the list,
|
|
* either because the file has been fully sent or because the node was disconnected
|
|
*
|
|
* \param node The destination
|
|
*
|
|
*/
|
|
static void SV_EndFileSend(INT32 node)
|
|
{
|
|
filetx_t *p = transfer[node].txlist;
|
|
|
|
// Free the file request according to the freemethod parameter used with SV_SendFile/Ram
|
|
switch (p->ram)
|
|
{
|
|
case SF_FILE: // It's a file, close it and free its filename
|
|
if (cv_noticedownload.value)
|
|
CONS_Printf("Ending file transfer for node %d\n", node);
|
|
if (transfer[node].currentfile)
|
|
fclose(transfer[node].currentfile);
|
|
free(p->id.filename);
|
|
break;
|
|
case SF_Z_RAM: // It's a memory block allocated with Z_Alloc or the likes, use Z_Free
|
|
Z_Free(p->id.ram);
|
|
break;
|
|
case SF_RAM: // It's a memory block allocated with malloc, use free
|
|
free(p->id.ram);
|
|
case SF_NOFREERAM: // Nothing to free
|
|
break;
|
|
}
|
|
|
|
// Remove the file request from the list
|
|
transfer[node].txlist = p->next;
|
|
free(p);
|
|
|
|
// Indicate that the transmission is over
|
|
transfer[node].currentfile = NULL;
|
|
|
|
filestosend--;
|
|
}
|
|
|
|
#define PACKETPERTIC net_bandwidth/(TICRATE*software_MAXPACKETLENGTH)
|
|
|
|
/** Handles file transmission
|
|
*
|
|
* \todo Use an acknowledging method more adapted to file transmission
|
|
* The current download speed suffers from lack of ack packets,
|
|
* especially when the one downloading has high latency
|
|
*
|
|
*/
|
|
void SV_FileSendTicker(void)
|
|
{
|
|
static INT32 currentnode = 0;
|
|
filetx_pak *p;
|
|
size_t size;
|
|
filetx_t *f;
|
|
INT32 packetsent, ram, i, j;
|
|
INT32 maxpacketsent;
|
|
|
|
if (!filestosend) // No file to send
|
|
return;
|
|
|
|
if (cv_downloadspeed.value) // New (and experimental) behavior
|
|
{
|
|
packetsent = cv_downloadspeed.value;
|
|
// Don't send more packets than we have free acks
|
|
#ifndef NONET
|
|
maxpacketsent = Net_GetFreeAcks(false) - 5; // Let 5 extra acks just in case
|
|
#else
|
|
maxpacketsent = 1;
|
|
#endif
|
|
if (packetsent > maxpacketsent && maxpacketsent > 0) // Send at least one packet
|
|
packetsent = maxpacketsent;
|
|
}
|
|
else // Old behavior
|
|
{
|
|
packetsent = PACKETPERTIC;
|
|
if (!packetsent)
|
|
packetsent = 1;
|
|
}
|
|
|
|
netbuffer->packettype = PT_FILEFRAGMENT;
|
|
|
|
// (((sendbytes-nowsentbyte)*TICRATE)/(I_GetTime()-starttime)<(UINT32)net_bandwidth)
|
|
while (packetsent-- && filestosend != 0)
|
|
{
|
|
for (i = currentnode, j = 0; j < MAXNETNODES;
|
|
i = (i+1) % MAXNETNODES, j++)
|
|
{
|
|
if (transfer[i].txlist)
|
|
goto found;
|
|
}
|
|
// no transfer to do
|
|
I_Error("filestosend=%d but no file to send found\n", filestosend);
|
|
found:
|
|
currentnode = (i+1) % MAXNETNODES;
|
|
f = transfer[i].txlist;
|
|
ram = f->ram;
|
|
|
|
// Open the file if it isn't open yet, or
|
|
if (!transfer[i].currentfile)
|
|
{
|
|
if (!ram) // Sending a file
|
|
{
|
|
long filesize;
|
|
|
|
transfer[i].currentfile =
|
|
fopen(f->id.filename, "rb");
|
|
|
|
if (!transfer[i].currentfile)
|
|
I_Error("File %s does not exist",
|
|
f->id.filename);
|
|
|
|
fseek(transfer[i].currentfile, 0, SEEK_END);
|
|
filesize = ftell(transfer[i].currentfile);
|
|
|
|
// Nobody wants to transfer a file bigger
|
|
// than 4GB!
|
|
if (filesize >= LONG_MAX)
|
|
I_Error("filesize of %s is too large", f->id.filename);
|
|
if (filesize == -1)
|
|
I_Error("Error getting filesize of %s", f->id.filename);
|
|
|
|
f->size = (UINT32)filesize;
|
|
fseek(transfer[i].currentfile, 0, SEEK_SET);
|
|
}
|
|
else // Sending RAM
|
|
transfer[i].currentfile = (FILE *)1; // Set currentfile to a non-null value to indicate that it is open
|
|
transfer[i].position = 0;
|
|
}
|
|
|
|
// Build a packet containing a file fragment
|
|
p = &netbuffer->u.filetxpak;
|
|
size = software_MAXPACKETLENGTH - (FILETXHEADER + BASEPACKETSIZE);
|
|
if (f->size-transfer[i].position < size)
|
|
size = f->size-transfer[i].position;
|
|
if (ram)
|
|
M_Memcpy(p->data, &f->id.ram[transfer[i].position], size);
|
|
else if (fread(p->data, 1, size, transfer[i].currentfile) != size)
|
|
I_Error("SV_FileSendTicker: can't read %s byte on %s at %d because %s", sizeu1(size), f->id.filename, transfer[i].position, M_FileError(transfer[i].currentfile));
|
|
p->position = LONG(transfer[i].position);
|
|
// Put flag so receiver knows the total size
|
|
if (transfer[i].position + size == f->size)
|
|
p->position |= LONG(0x80000000);
|
|
p->fileid = f->fileid;
|
|
p->size = SHORT((UINT16)size);
|
|
|
|
// Send the packet
|
|
if (HSendPacket(i, true, 0, FILETXHEADER + size)) // Reliable SEND
|
|
{ // Success
|
|
transfer[i].position = (UINT32)(transfer[i].position + size);
|
|
if (transfer[i].position == f->size) // Finish?
|
|
SV_EndFileSend(i);
|
|
}
|
|
else
|
|
{ // Not sent for some odd reason, retry at next call
|
|
if (!ram)
|
|
fseek(transfer[i].currentfile,transfer[i].position, SEEK_SET);
|
|
// Exit the while (can't send this one so why should i send the next?)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Got_Filetxpak(void)
|
|
{
|
|
INT32 filenum = netbuffer->u.filetxpak.fileid;
|
|
fileneeded_t *file = &fileneeded[filenum];
|
|
char *filename = file->filename;
|
|
static INT32 filetime = 0;
|
|
|
|
if (!(strcmp(filename, "srb2.srb")
|
|
&& strcmp(filename, "srb2.wad")
|
|
&& strcmp(filename, "patch.dta")
|
|
//&& strcmp(filename, "music.dta")
|
|
&& strcmp(filename, "gfx.kart")
|
|
&& strcmp(filename, "textures.kart")
|
|
&& strcmp(filename, "chars.kart")
|
|
&& strcmp(filename, "maps.kart")
|
|
&& strcmp(filename, "sounds.kart")
|
|
&& strcmp(filename, "music.kart")
|
|
&& strcmp(filename, "patch.kart")
|
|
))
|
|
I_Error("Tried to download \"%s\"", filename);
|
|
|
|
if (filenum >= fileneedednum)
|
|
{
|
|
DEBFILE(va("fileframent not needed %d>%d\n", filenum, fileneedednum));
|
|
//I_Error("Received an unneeded file fragment (file id received: %d, file id needed: %d)\n", filenum, fileneedednum);
|
|
return;
|
|
}
|
|
|
|
if (file->status == FS_REQUESTED)
|
|
{
|
|
if (file->file)
|
|
I_Error("Got_Filetxpak: already open file\n");
|
|
file->file = fopen(filename, "wb");
|
|
if (!file->file)
|
|
I_Error("Can't create file %s: %s", filename, strerror(errno));
|
|
CONS_Printf("\r%s...\n",filename);
|
|
file->currentsize = 0;
|
|
file->status = FS_DOWNLOADING;
|
|
}
|
|
|
|
if (file->status == FS_DOWNLOADING)
|
|
{
|
|
UINT32 pos = LONG(netbuffer->u.filetxpak.position);
|
|
UINT16 size = SHORT(netbuffer->u.filetxpak.size);
|
|
// Use a special trick to know when the file is complete (not always used)
|
|
// WARNING: file fragments can arrive out of order so don't stop yet!
|
|
if (pos & 0x80000000)
|
|
{
|
|
pos &= ~0x80000000;
|
|
file->totalsize = pos + size;
|
|
}
|
|
// We can receive packet in the wrong order, anyway all os support gaped file
|
|
fseek(file->file, pos, SEEK_SET);
|
|
if (fwrite(netbuffer->u.filetxpak.data,size,1,file->file) != 1)
|
|
I_Error("Can't write to %s: %s\n",filename, M_FileError(file->file));
|
|
file->currentsize += size;
|
|
|
|
// Finished?
|
|
if (file->currentsize == file->totalsize)
|
|
{
|
|
fclose(file->file);
|
|
file->file = NULL;
|
|
file->status = FS_FOUND;
|
|
CONS_Printf(M_GetText("Downloading %s...(done)\n"),
|
|
filename);
|
|
#ifndef NONET
|
|
downloadcompletednum++;
|
|
downloadcompletedsize += file->totalsize;
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const char *s;
|
|
switch(file->status)
|
|
{
|
|
case FS_NOTFOUND:
|
|
s = "FS_NOTFOUND";
|
|
break;
|
|
case FS_FOUND:
|
|
s = "FS_FOUND";
|
|
break;
|
|
case FS_OPEN:
|
|
s = "FS_OPEN";
|
|
break;
|
|
case FS_MD5SUMBAD:
|
|
s = "FS_MD5SUMBAD";
|
|
break;
|
|
default:
|
|
s = "unknown";
|
|
break;
|
|
}
|
|
I_Error("Received a file not requested (file id: %d, file status: %s)\n", filenum, s);
|
|
}
|
|
// Send ack back quickly
|
|
if (++filetime == 3)
|
|
{
|
|
Net_SendAcks(servernode);
|
|
filetime = 0;
|
|
}
|
|
|
|
#ifdef CLIENT_LOADINGSCREEN
|
|
lastfilenum = filenum;
|
|
#endif
|
|
}
|
|
|
|
/** \brief Checks if a node is downloading a file
|
|
*
|
|
* \param node The node to check for
|
|
* \return True if the node is downloading a file
|
|
*
|
|
*/
|
|
boolean SV_SendingFile(INT32 node)
|
|
{
|
|
return transfer[node].txlist != NULL;
|
|
}
|
|
|
|
/** Cancels all file requests for a node
|
|
*
|
|
* \param node The destination
|
|
* \sa SV_EndFileSend
|
|
*
|
|
*/
|
|
void SV_AbortSendFiles(INT32 node)
|
|
{
|
|
while (transfer[node].txlist)
|
|
SV_EndFileSend(node);
|
|
}
|
|
|
|
void CloseNetFile(void)
|
|
{
|
|
INT32 i;
|
|
// Is sending?
|
|
for (i = 0; i < MAXNETNODES; i++)
|
|
SV_AbortSendFiles(i);
|
|
|
|
// Receiving a file?
|
|
for (i = 0; i < MAX_WADFILES; i++)
|
|
if (fileneeded[i].status == FS_DOWNLOADING && fileneeded[i].file)
|
|
{
|
|
fclose(fileneeded[i].file);
|
|
// File is not complete delete it
|
|
remove(fileneeded[i].filename);
|
|
}
|
|
|
|
// Remove PT_FILEFRAGMENT from acknowledge list
|
|
Net_AbortPacketType(PT_FILEFRAGMENT);
|
|
}
|
|
|
|
// Functions cut and pasted from Doomatic :)
|
|
|
|
void nameonly(char *s)
|
|
{
|
|
size_t j, len;
|
|
void *ns;
|
|
|
|
for (j = strlen(s); j != (size_t)-1; j--)
|
|
if ((s[j] == '\\') || (s[j] == ':') || (s[j] == '/'))
|
|
{
|
|
ns = &(s[j+1]);
|
|
len = strlen(ns);
|
|
#if 0
|
|
M_Memcpy(s, ns, len+1);
|
|
#else
|
|
memmove(s, ns, len+1);
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Returns the length in characters of the last element of a path.
|
|
size_t nameonlylength(const char *s)
|
|
{
|
|
size_t j, len = strlen(s);
|
|
|
|
for (j = len; j != (size_t)-1; j--)
|
|
if ((s[j] == '\\') || (s[j] == ':') || (s[j] == '/'))
|
|
return len - j - 1;
|
|
|
|
return len;
|
|
}
|
|
|
|
#ifndef O_BINARY
|
|
#define O_BINARY 0
|
|
#endif
|
|
|
|
filestatus_t checkfilemd5(char *filename, const UINT8 *wantedmd5sum)
|
|
{
|
|
#if defined (NOMD5) || defined (_arch_dreamcast)
|
|
(void)wantedmd5sum;
|
|
(void)filename;
|
|
#else
|
|
FILE *fhandle;
|
|
UINT8 md5sum[16];
|
|
|
|
if (!wantedmd5sum)
|
|
return FS_FOUND;
|
|
|
|
fhandle = fopen(filename, "rb");
|
|
if (fhandle)
|
|
{
|
|
md5_stream(fhandle,md5sum);
|
|
fclose(fhandle);
|
|
if (!memcmp(wantedmd5sum, md5sum, 16))
|
|
return FS_FOUND;
|
|
return FS_MD5SUMBAD;
|
|
}
|
|
|
|
I_Error("Couldn't open %s for md5 check", filename);
|
|
#endif
|
|
return FS_FOUND; // will never happen, but makes the compiler shut up
|
|
}
|
|
|
|
// Rewritten by Monster Iestyn to be less stupid
|
|
// Note: if completepath is true, "filename" is modified, but only if FS_FOUND is going to be returned
|
|
// (Don't worry about WinCE's version of filesearch, nobody cares about that OS anymore)
|
|
filestatus_t findfile(char *filename, const UINT8 *wantedmd5sum, boolean completepath)
|
|
{
|
|
filestatus_t homecheck; // store result of last file search
|
|
boolean badmd5 = false; // store whether md5 was bad from either of the first two searches (if nothing was found in the third)
|
|
|
|
// first, check SRB2's "home" directory
|
|
homecheck = filesearch(filename, srb2home, wantedmd5sum, completepath, 10);
|
|
|
|
if (homecheck == FS_FOUND) // we found the file, so return that we have :)
|
|
return FS_FOUND;
|
|
else if (homecheck == FS_MD5SUMBAD) // file has a bad md5; move on and look for a file with the right md5
|
|
badmd5 = true;
|
|
// if not found at all, just move on without doing anything
|
|
|
|
// next, check SRB2's "path" directory
|
|
homecheck = filesearch(filename, srb2path, wantedmd5sum, completepath, 10);
|
|
|
|
if (homecheck == FS_FOUND) // we found the file, so return that we have :)
|
|
return FS_FOUND;
|
|
else if (homecheck == FS_MD5SUMBAD) // file has a bad md5; move on and look for a file with the right md5
|
|
badmd5 = true;
|
|
// if not found at all, just move on without doing anything
|
|
|
|
// finally check "." directory
|
|
#ifdef _arch_dreamcast
|
|
homecheck = filesearch(filename, "/cd", wantedmd5sum, completepath, 10);
|
|
#else
|
|
homecheck = filesearch(filename, ".", wantedmd5sum, completepath, 10);
|
|
#endif
|
|
|
|
if (homecheck != FS_NOTFOUND) // if not found this time, fall back on the below return statement
|
|
return homecheck; // otherwise return the result we got
|
|
|
|
return (badmd5 ? FS_MD5SUMBAD : FS_NOTFOUND); // md5 sum bad or file not found
|
|
}
|
|
|
|
#ifdef HAVE_CURL
|
|
size_t curlwrite_data(void *ptr, size_t size, size_t nmemb, FILE *stream)
|
|
{
|
|
size_t written;
|
|
written = fwrite(ptr, size, nmemb, stream);
|
|
return written;
|
|
}
|
|
|
|
int curlprogress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)
|
|
{
|
|
(void)clientp;
|
|
(void)ultotal;
|
|
(void)ulnow; // Function prototype requires these but we won't use, so just discard
|
|
curl_dlnow = dlnow;
|
|
curl_dltotal = dltotal;
|
|
getbytes = curl_dlnow / (time(NULL) - curl_starttime); // To-do: Make this more accurate???
|
|
return 0;
|
|
}
|
|
|
|
void CURLPrepareFile(const char* url, int dfilenum)
|
|
{
|
|
HTTP_login *login;
|
|
|
|
#ifdef PARANOIA
|
|
if (M_CheckParm("-nodownload"))
|
|
I_Error("Attempted to download files in -nodownload mode");
|
|
#endif
|
|
|
|
curl_global_init(CURL_GLOBAL_ALL);
|
|
|
|
http_handle = curl_easy_init();
|
|
multi_handle = curl_multi_init();
|
|
|
|
if (http_handle && multi_handle)
|
|
{
|
|
I_mkdir(downloaddir, 0755);
|
|
|
|
curl_curfile = &fileneeded[dfilenum];
|
|
curl_realname = curl_curfile->filename;
|
|
nameonly(curl_realname);
|
|
|
|
curl_origfilesize = curl_curfile->currentsize;
|
|
curl_origtotalfilesize = curl_curfile->totalsize;
|
|
|
|
curl_easy_setopt(http_handle, CURLOPT_URL, va("%s/%s", url, curl_realname));
|
|
|
|
// Only allow HTTP and HTTPS
|
|
curl_easy_setopt(http_handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS);
|
|
|
|
curl_easy_setopt(http_handle, CURLOPT_USERAGENT, va("SRB2Kart/v%d.%d", VERSION, SUBVERSION)); // Set user agent as some servers won't accept invalid user agents.
|
|
|
|
// Authenticate if the user so wishes
|
|
login = CURLGetLogin(url, NULL);
|
|
|
|
if (login)
|
|
{
|
|
curl_easy_setopt(http_handle, CURLOPT_USERPWD, login->auth);
|
|
}
|
|
|
|
// Follow a redirect request, if sent by the server.
|
|
curl_easy_setopt(http_handle, CURLOPT_FOLLOWLOCATION, 1L);
|
|
|
|
curl_easy_setopt(http_handle, CURLOPT_FAILONERROR, 1L);
|
|
|
|
CONS_Printf("Downloading %s from %s\n", curl_realname, url);
|
|
|
|
strcatbf(curl_curfile->filename, downloaddir, "/");
|
|
curl_curfile->file = fopen(curl_curfile->filename, "wb");
|
|
curl_easy_setopt(http_handle, CURLOPT_WRITEDATA, curl_curfile->file);
|
|
curl_easy_setopt(http_handle, CURLOPT_WRITEFUNCTION, curlwrite_data);
|
|
curl_easy_setopt(http_handle, CURLOPT_NOPROGRESS, 0L);
|
|
curl_easy_setopt(http_handle, CURLOPT_PROGRESSFUNCTION, curlprogress_callback);
|
|
|
|
curl_curfile->status = FS_DOWNLOADING;
|
|
lastfilenum = dfilenum;
|
|
curl_multi_add_handle(multi_handle, http_handle);
|
|
|
|
curl_multi_perform(multi_handle, &curl_runninghandles);
|
|
curl_starttime = time(NULL);
|
|
curl_running = true;
|
|
}
|
|
}
|
|
|
|
void CURLGetFile(void)
|
|
{
|
|
CURLMcode mc; /* return code used by curl_multi_wait() */
|
|
CURLcode easyres; /* Return from easy interface */
|
|
int numfds;
|
|
CURLMsg *m; /* for picking up messages with the transfer status */
|
|
CURL *e;
|
|
int msgs_left; /* how many messages are left */
|
|
const char *easy_handle_error;
|
|
long response_code = 0;
|
|
|
|
if (curl_runninghandles)
|
|
{
|
|
curl_multi_perform(multi_handle, &curl_runninghandles);
|
|
|
|
/* wait for activity, timeout or "nothing" */
|
|
mc = curl_multi_wait(multi_handle, NULL, 0, 1000, &numfds);
|
|
|
|
if (mc != CURLM_OK)
|
|
{
|
|
CONS_Alert(CONS_WARNING, "curl_multi_wait() failed, code %d.\n", mc);
|
|
return;
|
|
}
|
|
curl_curfile->currentsize = curl_dlnow;
|
|
curl_curfile->totalsize = curl_dltotal;
|
|
}
|
|
|
|
/* See how the transfers went */
|
|
while ((m = curl_multi_info_read(multi_handle, &msgs_left)))
|
|
{
|
|
if (m && (m->msg == CURLMSG_DONE))
|
|
{
|
|
e = m->easy_handle;
|
|
easyres = m->data.result;
|
|
if (easyres != CURLE_OK)
|
|
{
|
|
if (easyres == CURLE_HTTP_RETURNED_ERROR)
|
|
curl_easy_getinfo(e, CURLINFO_RESPONSE_CODE, &response_code);
|
|
|
|
easy_handle_error = (response_code) ? va("HTTP reponse code %ld", response_code) : curl_easy_strerror(easyres);
|
|
curl_curfile->status = FS_FALLBACK;
|
|
curl_curfile->currentsize = curl_origfilesize;
|
|
curl_curfile->totalsize = curl_origtotalfilesize;
|
|
curl_failedwebdownload = true;
|
|
fclose(curl_curfile->file);
|
|
remove(curl_curfile->filename);
|
|
curl_curfile->file = NULL;
|
|
//nameonly(curl_curfile->filename);
|
|
nameonly(curl_realname);
|
|
CONS_Printf(M_GetText("Failed to download %s (%s)\n"), curl_realname, easy_handle_error);
|
|
}
|
|
else
|
|
{
|
|
nameonly(curl_realname);
|
|
CONS_Printf(M_GetText("Finished downloading %s\n"), curl_realname);
|
|
downloadcompletednum++;
|
|
downloadcompletedsize += curl_curfile->totalsize;
|
|
curl_curfile->status = FS_FOUND;
|
|
fclose(curl_curfile->file);
|
|
}
|
|
|
|
curl_running = false;
|
|
curl_transfers--;
|
|
curl_multi_remove_handle(multi_handle, e);
|
|
curl_easy_cleanup(e);
|
|
|
|
if (!curl_transfers)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!curl_transfers)
|
|
{
|
|
curl_multi_cleanup(multi_handle);
|
|
curl_global_cleanup();
|
|
}
|
|
}
|
|
|
|
HTTP_login *
|
|
CURLGetLogin (const char *url, HTTP_login ***return_prev_next)
|
|
{
|
|
HTTP_login * login;
|
|
HTTP_login ** prev_next;
|
|
|
|
for (
|
|
prev_next = &curl_logins;
|
|
( login = (*prev_next));
|
|
prev_next = &login->next
|
|
){
|
|
if (strcmp(login->url, url) == 0)
|
|
{
|
|
if (return_prev_next)
|
|
(*return_prev_next) = prev_next;
|
|
|
|
return login;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
#endif
|