HTTP downloader port

This commit is contained in:
Lactozilla 2024-02-04 20:00:51 -03:00
parent fd92193f7e
commit b4beaa9f43
9 changed files with 872 additions and 271 deletions

View file

@ -1,7 +1,7 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2023 by Sonic Team Junior.
// Copyright (C) 1999-2024 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
@ -43,11 +43,45 @@ tic_t firstconnectattempttime = 0;
UINT8 mynode;
static void *snake = NULL;
static void CL_DrawConnectionStatusBox(void)
static boolean IsDownloadingFile(void)
{
if (cl_mode == CL_DOWNLOADFILES || cl_mode == CL_DOWNLOADHTTPFILES)
return filedownload.current != -1;
return false;
}
static void DrawConnectionStatusBox(void)
{
M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-16-8, 32, 1);
if (cl_mode != CL_CONFIRMCONNECT)
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-16, V_YELLOWMAP, "Press ESC to abort");
if (cl_mode == CL_CONFIRMCONNECT || IsDownloadingFile())
return;
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-16, V_YELLOWMAP, "Press ESC to abort");
}
static void DrawFileProgress(fileneeded_t *file, int y)
{
Net_GetNetStat();
INT32 dldlength = (INT32)((file->currentsize/(double)file->totalsize) * 256);
if (dldlength > 256)
dldlength = 256;
V_DrawFill(BASEVIDWIDTH/2-128, y, 256, 8, 111);
V_DrawFill(BASEVIDWIDTH/2-128, y, dldlength, 8, 96);
const char *progress_str;
if (file->totalsize >= 1024*1024)
progress_str = va(" %.2fMiB/%.2fMiB", (double)file->currentsize / (1024*1024), (double)file->totalsize / (1024*1024));
else if (file->totalsize < 1024)
progress_str = va(" %4uB/%4uB", file->currentsize, file->totalsize);
else
progress_str = va(" %.2fKiB/%.2fKiB", (double)file->currentsize / 1024, (double)file->totalsize / 1024);
V_DrawString(BASEVIDWIDTH/2-128, y, V_20TRANS|V_ALLOWLOWERCASE, progress_str);
V_DrawRightAlignedString(BASEVIDWIDTH/2+128, y, V_20TRANS|V_MONOSPACE, va("%3.1fK/s ", ((double)getbps)/1024));
}
//
@ -55,21 +89,21 @@ static void CL_DrawConnectionStatusBox(void)
//
// Keep the local client informed of our status.
//
static inline void CL_DrawConnectionStatus(void)
static void CL_DrawConnectionStatus(void)
{
INT32 ccstime = I_GetTime();
// Draw background fade
V_DrawFadeScreen(0xFF00, 16); // force default
if (cl_mode != CL_DOWNLOADFILES && cl_mode != CL_LOADFILES)
if (cl_mode != CL_DOWNLOADFILES && cl_mode != CL_DOWNLOADHTTPFILES && cl_mode != CL_LOADFILES)
{
INT32 animtime = ((ccstime / 4) & 15) + 16;
UINT8 palstart;
const char *cltext;
// Draw the bottom box.
CL_DrawConnectionStatusBox();
DrawConnectionStatusBox();
if (cl_mode == CL_SEARCHING)
palstart = 32; // Red
@ -78,33 +112,20 @@ static inline void CL_DrawConnectionStatus(void)
else
palstart = 96; // Green
if (!(cl_mode == CL_DOWNLOADSAVEGAME && lastfilenum != -1))
if (!(cl_mode == CL_DOWNLOADSAVEGAME && filedownload.current != -1))
for (INT32 i = 0; i < 16; ++i) // 15 pal entries total.
V_DrawFill((BASEVIDWIDTH/2-128) + (i * 16), BASEVIDHEIGHT-16, 16, 8, palstart + ((animtime - i) & 15));
switch (cl_mode)
{
case CL_DOWNLOADSAVEGAME:
if (fileneeded && lastfilenum != -1)
if (fileneeded && filedownload.current != -1)
{
UINT32 currentsize = fileneeded[lastfilenum].currentsize;
UINT32 totalsize = fileneeded[lastfilenum].totalsize;
INT32 dldlength;
fileneeded_t *file = &fileneeded[filedownload.current];
cltext = M_GetText("Downloading game state...");
Net_GetNetStat();
dldlength = (INT32)((currentsize/(double)totalsize) * 256);
if (dldlength > 256)
dldlength = 256;
V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, 256, 8, 111);
V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, dldlength, 8, 96);
V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE,
va(" %4uK/%4uK",currentsize>>10,totalsize>>10));
V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE,
va("%3.1fK/s ", ((double)getbps)/1024));
DrawFileProgress(file, BASEVIDHEIGHT-16);
}
else
cltext = M_GetText("Waiting to download game state...");
@ -156,12 +177,11 @@ static inline void CL_DrawConnectionStatus(void)
V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, 256, 8, 111);
V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, totalfileslength, 8, 96);
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE,
va(" %2u/%2u Files",loadcompletednum,fileneedednum));
va(" %2u/%2u files",loadcompletednum,fileneedednum));
}
else if (lastfilenum != -1)
else if (filedownload.current != -1)
{
INT32 dldlength;
static char tempname[28];
char tempname[28];
fileneeded_t *file;
char *filename;
@ -169,24 +189,16 @@ static inline void CL_DrawConnectionStatus(void)
Snake_Draw(snake);
// Draw the bottom box.
CL_DrawConnectionStatusBox();
DrawConnectionStatusBox();
if (fileneeded)
{
file = &fileneeded[lastfilenum];
file = &fileneeded[filedownload.current];
filename = file->filename;
}
else
return;
Net_GetNetStat();
dldlength = (INT32)((file->currentsize/(double)file->totalsize) * 256);
if (dldlength > 256)
dldlength = 256;
V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, 256, 8, 111);
V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, dldlength, 8, 96);
memset(tempname, 0, sizeof(tempname));
// offset filename to just the name only part
filename += strlen(filename) - nameonlylength(filename);
@ -199,22 +211,56 @@ static inline void CL_DrawConnectionStatus(void)
}
else // we can copy the whole thing in safely
{
strncpy(tempname, filename, sizeof(tempname)-1);
strlcpy(tempname, filename, sizeof(tempname));
}
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP,
va(M_GetText("Downloading \"%s\""), tempname));
V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE,
va(" %4uK/%4uK",fileneeded[lastfilenum].currentsize>>10,file->totalsize>>10));
V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE,
va("%3.1fK/s ", ((double)getbps)/1024));
// Lactozilla: Disabled, see below change
// (also it doesn't really fit on a typical SRB2 screen)
#if 0
const char *download_str = cl_mode == CL_DOWNLOADHTTPFILES
? M_GetText("HTTP downloading \"%s\"")
: M_GetText("Downloading \"%s\"");
#else
const char *download_str = M_GetText("Downloading \"%s\"");
#endif
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_ALLOWLOWERCASE|V_YELLOWMAP,
va(download_str, tempname));
// Rusty: actually lets do this instead
if (cl_mode == CL_DOWNLOADHTTPFILES)
{
const char *http_source = filedownload.http_source;
if (strlen(http_source) > sizeof(tempname)-1) // too long to display fully
{
size_t endhalfpos = strlen(http_source)-10;
// display as first 14 chars + ... + last 10 chars
// which should add up to 27 if our math(s) is correct
snprintf(tempname, sizeof(tempname), "%.14s...%.10s", http_source, http_source+endhalfpos);
}
else // we can copy the whole thing in safely
{
strlcpy(tempname, http_source, sizeof(tempname));
}
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-16, V_ALLOWLOWERCASE|V_YELLOWMAP,
va(M_GetText("from %s"), tempname));
}
else
{
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-16, V_ALLOWLOWERCASE|V_YELLOWMAP,
M_GetText("from the server"));
}
DrawFileProgress(file, BASEVIDHEIGHT-16);
}
else
{
if (snake)
Snake_Draw(snake);
CL_DrawConnectionStatusBox();
DrawConnectionStatusBox();
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP,
M_GetText("Waiting to download files..."));
}
@ -479,23 +525,98 @@ void CL_UpdateServerList(boolean internetsearch, INT32 room)
#endif // MASTERSERVER
}
static boolean IsFileDownloadable(fileneeded_t *file)
{
return file->status == FS_NOTFOUND || file->status == FS_MD5SUMBAD;
}
static boolean UseDirectDownloader(void)
{
return filedownload.http_source[0] == '\0' || filedownload.http_failed;
}
static void DoLoadFiles(void)
{
Snake_Free(&snake);
cl_mode = CL_LOADFILES;
}
static void AbortConnection(void)
{
Snake_Free(&snake);
D_QuitNetGame();
CL_Reset();
D_StartTitle();
// Will be reset by caller. Signals refusal.
cl_mode = CL_ABORTED;
}
static void BeginDownload(boolean direct)
{
filedownload.current = 0;
filedownload.remaining = 0;
for (int i = 0; i < fileneedednum; i++)
{
// Lactozilla: Rusty had fixed this SLIGHTLY incorrectly.
// Since [redacted] doesn't HAVE its own file transmission code - it spins an HTTP server - it wasn't
// instantly obvious to us where to do this, and we didn't want to check the original implementation.
if (fileneeded[i].status == FS_FALLBACK)
fileneeded[i].status = FS_NOTFOUND;
if (IsFileDownloadable(&fileneeded[i]))
filedownload.remaining++;
}
if (!filedownload.remaining)
{
DoLoadFiles();
return;
}
if (!direct)
{
cl_mode = CL_DOWNLOADHTTPFILES;
Snake_Allocate(&snake);
// Discard any paused downloads
CL_AbortDownloadResume();
}
else
{
// do old LEGACY request
if (CL_SendFileRequest())
{
cl_mode = CL_DOWNLOADFILES;
// don't alloc snake if already alloced
if (!snake)
Snake_Allocate(&snake);
}
else
{
AbortConnection();
// why was this its own cl_mode_t?
M_StartMessage(M_GetText(
"The direct downloader encountered an error.\n"
"See the logfile for more info.\n\n"
"Press ESC\n"
), NULL, MM_NOTHING);
}
}
}
static void M_ConfirmConnect(event_t *ev)
{
if (ev->type == ev_keydown)
{
if (ev->key == ' ' || ev->key == 'y' || ev->key == KEY_ENTER || ev->key == KEY_JOY1)
{
if (totalfilesrequestednum > 0)
{
if (CL_SendFileRequest())
{
cl_mode = CL_DOWNLOADFILES;
Snake_Allocate(&snake);
}
}
else
cl_mode = CL_LOADFILES;
BeginDownload(UseDirectDownloader());
M_ClearMenus(true);
}
else if (ev->key == 'n' || ev->key == KEY_ESCAPE || ev->key == KEY_JOY1 + 3)
@ -506,22 +627,122 @@ static void M_ConfirmConnect(event_t *ev)
}
}
static const char *GetPrintableFileSize(UINT64 filesize)
{
static char downloadsize[32];
if (filesize >= 1024*1024)
snprintf(downloadsize, sizeof(downloadsize), "%.2fMiB", (double)filesize / (1024*1024));
else if (filesize < 1024)
snprintf(downloadsize, sizeof(downloadsize), "%luB", filesize);
else
snprintf(downloadsize, sizeof(downloadsize), "%.2fKiB", (double)filesize / 1024);
return downloadsize;
}
static void ShowDownloadConsentMessage(void)
{
UINT64 totalsize = 0;
filedownload.completednum = 0;
filedownload.completedsize = 0;
if (fileneeded == NULL)
I_Error("CL_FinishedFileList: fileneeded == NULL");
for (int i = 0; i < fileneedednum; i++)
{
if (IsFileDownloadable(&fileneeded[i]))
totalsize += fileneeded[i].totalsize;
}
const char *downloadsize = GetPrintableFileSize(totalsize);
if (serverisfull)
M_StartMessage(va(M_GetText(
"This server is full!\n"
"Download of %s of additional\ncontent is required to join.\n"
"\n"
"You may download server addons,\nand wait for a slot.\n"
"\n"
"Press ENTER to continue\nor ESC to cancel.\n"
), downloadsize), M_ConfirmConnect, MM_EVENTHANDLER);
else
M_StartMessage(va(M_GetText(
"Download of %s of additional\ncontent is required to join.\n"
"\n"
"Press ENTER to continue\nor ESC to cancel.\n"
), downloadsize), M_ConfirmConnect, MM_EVENTHANDLER);
cl_mode = CL_CONFIRMCONNECT;
curfadevalue = 0;
}
static const char *GetDirectDownloadFailReason(UINT8 dlstatus)
{
switch (dlstatus)
{
case DLSTATUS_TOOLARGE:
return M_GetText("Some addons are larger than the server is willing to send.");
case DLSTATUS_WONTSEND:
return M_GetText("The server is not allowing download requests.");
case DLSTATUS_NODOWNLOAD:
return M_GetText("All addons downloadable, but you have chosen to disable addon downloading.");
case DLSTATUS_FOLDER:
return M_GetText("One or more addons were added as a folder, which the server cannot send.");
}
return "Unknown reason";
}
static void HandleDirectDownloadFail(UINT8 dlstatus)
{
// not downloadable, put reason in console
CONS_Alert(CONS_NOTICE, M_GetText("You need additional addons to connect to this server:\n"));
for (UINT8 i = 0; i < fileneedednum; i++)
{
if (fileneeded[i].folder || (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN))
{
CONS_Printf(" * \"%s\" ", fileneeded[i].filename);
if (fileneeded[i].folder)
{
CONS_Printf("(folder)");
}
else
{
CONS_Printf("(%s)", GetPrintableFileSize(fileneeded[i].totalsize));
if (fileneeded[i].status == FS_NOTFOUND)
CONS_Printf(M_GetText(" not found, md5: "));
else if (fileneeded[i].status == FS_MD5SUMBAD)
CONS_Printf(M_GetText(" wrong version, md5: "));
char md5tmp[33];
for (INT32 j = 0; j < 16; j++)
sprintf(&md5tmp[j*2], "%02x", fileneeded[i].md5sum[j]);
CONS_Printf("%s", md5tmp);
}
CONS_Printf("\n");
}
}
CONS_Printf("%s\n", GetDirectDownloadFailReason(dlstatus));
}
static boolean CL_FinishedFileList(void)
{
INT32 i;
char *downloadsize = NULL;
//CONS_Printf(M_GetText("Checking files...\n"));
i = CL_CheckFiles();
INT32 i = CL_CheckFiles();
if (i == 4) // still checking ...
{
return true;
}
else if (i == 3) // too many files
{
D_QuitNetGame();
CL_Reset();
D_StartTitle();
AbortConnection();
M_StartMessage(M_GetText(
"You have too many WAD files loaded\n"
"to add ones the server is using.\n"
@ -532,15 +753,15 @@ static boolean CL_FinishedFileList(void)
}
else if (i == 2) // cannot join for some reason
{
D_QuitNetGame();
CL_Reset();
D_StartTitle();
AbortConnection();
M_StartMessage(M_GetText(
"You have the wrong addons loaded.\n\n"
"You have the wrong addons loaded.\n"
"\n"
"To play on this server, restart\n"
"the game and don't load any addons.\n"
"SRB2 will automatically add\n"
"everything you need when you join.\n\n"
"everything you need when you join.\n"
"\n"
"Press ESC\n"
), NULL, MM_NOTHING);
return false;
@ -566,63 +787,38 @@ static boolean CL_FinishedFileList(void)
{
// must download something
// can we, though?
if (!CL_CheckDownloadable()) // nope!
// Rusty: always check downloadable
UINT8 status = CL_CheckDownloadable(UseDirectDownloader());
if (status != DLSTATUS_OK)
{
D_QuitNetGame();
CL_Reset();
D_StartTitle();
HandleDirectDownloadFail(status);
AbortConnection();
M_StartMessage(M_GetText(
"An error occurred when trying to\n"
"download missing addons.\n"
"(This is almost always a problem\n"
"with the server, not your game.)\n\n"
"with the server, not your game.)\n"
"\n"
"See the console or log file\n"
"for additional details.\n\n"
"for additional details.\n"
"\n"
"Press ESC\n"
), NULL, MM_NOTHING);
return false;
}
downloadcompletednum = 0;
downloadcompletedsize = 0;
totalfilesrequestednum = 0;
totalfilesrequestedsize = 0;
if (fileneeded == NULL)
I_Error("CL_FinishedFileList: fileneeded == NULL");
for (i = 0; i < fileneedednum; i++)
if (fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD)
{
totalfilesrequestednum++;
totalfilesrequestedsize += fileneeded[i].totalsize;
}
if (totalfilesrequestedsize>>20 >= 100)
downloadsize = Z_StrDup(va("%uM",totalfilesrequestedsize>>20));
if (!filedownload.http_failed)
{
// show download consent modal ONCE!
ShowDownloadConsentMessage();
}
else
downloadsize = Z_StrDup(va("%uK",totalfilesrequestedsize>>10));
if (serverisfull)
M_StartMessage(va(M_GetText(
"This server is full!\n"
"Download of %s additional content\nis required to join.\n"
"\n"
"You may download, load server addons,\nand wait for a slot.\n"
"\n"
"Press ENTER to continue\nor ESC to cancel.\n"
), downloadsize), M_ConfirmConnect, MM_EVENTHANDLER);
else
M_StartMessage(va(M_GetText(
"Download of %s additional content\nis required to join.\n"
"\n"
"Press ENTER to continue\nor ESC to cancel.\n"
), downloadsize), M_ConfirmConnect, MM_EVENTHANDLER);
Z_Free(downloadsize);
cl_mode = CL_CONFIRMCONNECT;
curfadevalue = 0;
{
// do a direct download
BeginDownload(true);
}
}
return true;
}
@ -734,15 +930,18 @@ static boolean CL_ServerConnectionSearchTicker(tic_t *asksent)
if (reason)
{
char *message = Z_StrDup(reason);
D_QuitNetGame();
CL_Reset();
D_StartTitle();
AbortConnection();
M_StartMessage(message, NULL, MM_NOTHING);
Z_Free(message);
return false;
}
}
if (serverlist[i].info.httpsource[0])
strlcpy(filedownload.http_source, serverlist[i].info.httpsource, MAX_MIRROR_LENGTH);
else
filedownload.http_source[0] = '\0';
D_ParseFileneeded(info->fileneedednum, info->fileneeded, 0);
if (info->flags & SV_LOTSOFADDONS)
@ -773,6 +972,42 @@ static boolean CL_ServerConnectionSearchTicker(tic_t *asksent)
return true;
}
static void HandleHTTPDownloadFail(void)
{
char filename[MAX_WADPATH];
CONS_Alert(CONS_WARNING, M_GetText("One or more addons failed to download:\n"));
for (int i = 0; i < fileneedednum; i++)
{
if (fileneeded[i].failed == FDOWNLOAD_FAIL_NONE)
continue;
strlcpy(filename, fileneeded[i].filename, sizeof filename);
nameonly(filename);
CONS_Printf(" * \"%s\" (%s)", filename, GetPrintableFileSize(fileneeded[i].totalsize));
if (fileneeded[i].failed == FDOWNLOAD_FAIL_NOTFOUND)
CONS_Printf(M_GetText(" not found, md5: "));
else if (fileneeded[i].failed == FDOWNLOAD_FAIL_MD5SUMBAD)
CONS_Printf(M_GetText(" wrong version, md5: "));
else
CONS_Printf(M_GetText(" other error, md5: "));
char md5tmp[33];
for (INT32 j = 0; j < 16; j++)
snprintf(&md5tmp[j*2], sizeof(md5tmp), "%02x", fileneeded[i].md5sum[j]);
CONS_Printf("%s\n", md5tmp);
fileneeded[i].failed = FDOWNLOAD_FAIL_NONE;
}
CONS_Printf(M_GetText("Falling back to direct downloader.\n"));
cl_mode = CL_CHECKFILES;
}
/** Called by CL_ConnectToServer
*
* \param tmpsave The name of the gamestate file???
@ -810,21 +1045,43 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic
if (!CL_FinishedFileList())
return false;
break;
case CL_DOWNLOADFILES:
case CL_DOWNLOADHTTPFILES:
waitmore = false;
for (INT32 i = 0; i < fileneedednum; i++)
if (fileneeded[i].status == FS_DOWNLOADING
|| fileneeded[i].status == FS_REQUESTED)
for (int i = filedownload.current; i < fileneedednum; i++)
{
if (IsFileDownloadable(&fileneeded[i]))
{
if (!filedownload.http_running)
{
if (!CURLPrepareFile(filedownload.http_source, i))
HandleHTTPDownloadFail();
}
waitmore = true;
break;
}
}
// Rusty TODO: multithread
if (filedownload.http_running)
CURLGetFile();
if (waitmore)
break; // exit the case
Snake_Free(&snake);
cl_mode = CL_LOADFILES;
// Done downloading files
if (!filedownload.remaining)
{
if (filedownload.http_failed)
HandleHTTPDownloadFail();
else
DoLoadFiles();
}
break;
case CL_DOWNLOADFILES:
// Done downloading files
if (!filedownload.remaining)
DoLoadFiles();
break;
case CL_LOADFILES:
if (CL_LoadServerFiles())
@ -839,10 +1096,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic
if (firstconnectattempttime + NEWTICRATE*300 < I_GetTime() && !server)
{
CONS_Printf(M_GetText("5 minute wait time exceeded.\n"));
CONS_Printf(M_GetText("Network game synchronization aborted.\n"));
D_QuitNetGame();
CL_Reset();
D_StartTitle();
AbortConnection();
M_StartMessage(M_GetText(
"5 minute wait time exceeded.\n"
"You may retry connection.\n"
@ -914,15 +1168,12 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic
CONS_Printf(M_GetText("Network game synchronization aborted.\n"));
M_StartMessage(M_GetText("Network game synchronization aborted.\n\nPress ESC\n"), NULL, MM_NOTHING);
Snake_Free(&snake);
AbortConnection();
D_QuitNetGame();
CL_Reset();
D_StartTitle();
memset(gamekeydown, 0, NUMKEYS);
return false;
}
else if (cl_mode == CL_DOWNLOADFILES && snake)
else if ((cl_mode == CL_DOWNLOADFILES || cl_mode == CL_DOWNLOADHTTPFILES) && snake)
Snake_Update(snake);
if (client && (cl_mode == CL_DOWNLOADFILES || cl_mode == CL_DOWNLOADSAVEGAME))
@ -973,7 +1224,7 @@ void CL_ConnectToServer(void)
sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home);
lastfilenum = -1;
filedownload.current = -1;
cl_mode = CL_SEARCHING;
@ -1121,14 +1372,9 @@ void PT_ServerRefuse(SINT8 node)
M_StartMessage(va(M_GetText("Server refuses connection\n\nReason:\n%s"),
reason), NULL, MM_NOTHING);
D_QuitNetGame();
CL_Reset();
D_StartTitle();
AbortConnection();
free(reason);
// Will be reset by caller. Signals refusal.
cl_mode = CL_ABORTED;
}
}

View file

@ -1,7 +1,7 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2023 by Sonic Team Junior.
// Copyright (C) 1999-2024 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
@ -36,7 +36,8 @@ typedef enum
CL_CONNECTED,
CL_ABORTED,
CL_ASKFULLFILELIST,
CL_CONFIRMCONNECT
CL_CONFIRMCONNECT,
CL_DOWNLOADHTTPFILES
} cl_mode_t;
extern serverelem_t serverlist[MAXSERVERLIST];

View file

@ -1,7 +1,7 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2023 by Sonic Team Junior.
// Copyright (C) 1999-2024 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
@ -116,6 +116,8 @@ consvar_t cv_playbackspeed = CVAR_INIT ("playbackspeed", "1", 0, playbackspeed_c
consvar_t cv_idletime = CVAR_INIT ("idletime", "0", CV_SAVE, CV_Unsigned, NULL);
consvar_t cv_dedicatedidletime = CVAR_INIT ("dedicatedidletime", "10", CV_SAVE, CV_Unsigned, NULL);
consvar_t cv_httpsource = CVAR_INIT ("http_source", "", CV_SAVE, NULL, NULL);
void ResetNode(INT32 node)
{
memset(&netnodes[node], 0, sizeof(*netnodes));
@ -153,12 +155,15 @@ void CL_Reset(void)
FreeFileNeeded();
fileneedednum = 0;
totalfilesrequestednum = 0;
totalfilesrequestedsize = 0;
firstconnectattempttime = 0;
serverisfull = false;
connectiontimeout = (tic_t)cv_nettimeout.value; //reset this temporary hack
filedownload.remaining = 0;
filedownload.http_failed = false;
filedownload.http_running = false;
filedownload.http_source[0] = '\0';
// D_StartTitle should get done now, but the calling function will handle it
}
@ -892,6 +897,74 @@ static void PT_Login(SINT8 node, INT32 netconsole)
#endif
}
/** Add a login for HTTP downloads. If the
* user/password is missing, remove it.
*
* \sa Command_list_http_logins
*/
static void Command_set_http_login (void)
{
HTTP_login *login;
HTTP_login **prev_next;
if (COM_Argc() < 2)
{
CONS_Printf(
"set_http_login <URL> [user:password]: Set or remove a login to "
"authenticate HTTP downloads.\n"
);
return;
}
login = CURLGetLogin(COM_Argv(1), &prev_next);
if (COM_Argc() == 2)
{
if (login)
{
(*prev_next) = login->next;
CONS_Printf("Login for '%s' removed.\n", login->url);
Z_Free(login);
}
}
else
{
if (login)
Z_Free(login->auth);
else
{
login = ZZ_Alloc(sizeof *login);
login->url = Z_StrDup(COM_Argv(1));
}
login->auth = Z_StrDup(COM_Argv(2));
login->next = curl_logins;
curl_logins = login;
}
}
/** List logins for HTTP downloads.
*
* \sa Command_set_http_login
*/
static void Command_list_http_logins (void)
{
HTTP_login *login;
for (
login = curl_logins;
login;
login = login->next
){
CONS_Printf(
"'%s' -> '%s'\n",
login->url,
login->auth
);
}
}
static void PT_AskLuaFile(SINT8 node)
{
if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_ASKED)
@ -1568,6 +1641,8 @@ void D_ClientServerInit(void)
COM_AddCommand("reloadbans", Command_ReloadBan, COM_LUA);
COM_AddCommand("connect", Command_connect, COM_LUA);
COM_AddCommand("nodes", Command_Nodes, COM_LUA);
COM_AddCommand("set_http_login", Command_set_http_login, 0);
COM_AddCommand("list_http_logins", Command_list_http_logins, 0);
COM_AddCommand("resendgamestate", Command_ResendGamestate, COM_LUA);
#ifdef PACKETDROP
COM_AddCommand("drop", Command_Drop, COM_LUA);

View file

@ -1,7 +1,7 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2023 by Sonic Team Junior.
// Copyright (C) 1999-2024 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
@ -74,6 +74,7 @@ extern UINT32 playerpingtable[MAXPLAYERS];
extern tic_t servermaxping;
extern consvar_t cv_netticbuffer, cv_resynchattempts, cv_blamecfail, cv_playbackspeed, cv_idletime, cv_dedicatedidletime;
extern consvar_t cv_httpsource;
// Used in d_net, the only dependence
void D_ClientServerInit(void);

View file

@ -1,7 +1,7 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2023 by Sonic Team Junior.
// Copyright (C) 1999-2024 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
@ -606,6 +606,7 @@ void D_RegisterServerCommands(void)
CV_RegisterVar(&cv_blamecfail);
CV_RegisterVar(&cv_dedicatedidletime);
CV_RegisterVar(&cv_idletime);
CV_RegisterVar(&cv_httpsource);
COM_AddCommand("ping", Command_Ping_f, COM_LUA);
CV_RegisterVar(&cv_nettimeout);

View file

@ -1,7 +1,7 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2023 by Sonic Team Junior.
// Copyright (C) 1999-2024 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
@ -53,6 +53,10 @@
#include <errno.h>
#ifdef HAVE_CURL
#include <curl/curl.h>
#endif
// Prototypes
static boolean AddFileToSendQueue(INT32 node, UINT8 fileid);
@ -104,12 +108,19 @@ typedef struct
} pauseddownload_t;
static pauseddownload_t *pauseddownload = NULL;
// for cl loading screen
INT32 lastfilenum = -1;
INT32 downloadcompletednum = 0;
UINT32 downloadcompletedsize = 0;
INT32 totalfilesrequestednum = 0;
UINT32 totalfilesrequestedsize = 0;
file_download_t filedownload;
static CURL *http_handle;
static CURLM *multi_handle;
static UINT32 curl_dlnow;
static UINT32 curl_dltotal;
static time_t curl_starttime;
static int curl_runninghandles = 0;
static UINT32 curl_origfilesize;
static UINT32 curl_origtotalfilesize;
static char *curl_realname = NULL;
static fileneeded_t *curl_curfile = NULL;
HTTP_login *curl_logins;
luafiletransfer_t *luafiletransfers = NULL;
boolean waitingforluafiletransfer = false;
@ -140,6 +151,13 @@ static UINT16 GetWadNumFromFileNeededId(UINT8 id)
return UINT16_MAX;
}
enum
{
WILLSEND_YES,
WILLSEND_NO,
WILLSEND_TOOLARGE
};
/** Fills a serverinfo packet with information about wad files loaded.
*
* \todo Give this function a better name since it is in global scope.
@ -186,9 +204,9 @@ UINT8 *PutFileNeeded(UINT16 firstfile)
{
// Store in the upper four bits
if (!cv_downloading.value)
filestatus += (2 << 4); // Won't send
filestatus += (WILLSEND_NO << 4); // Won't send
else if (wadfiles[i]->filesize <= (UINT32)cv_maxsend.value * 1024)
filestatus += (1 << 4); // Will send if requested
filestatus += (WILLSEND_YES << 4); // Will send if requested
// else
// filestatus += (0 << 4); -- Won't send, too big
}
@ -250,6 +268,7 @@ void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr, UINT16 fi
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
fileneeded[i].failed = FDOWNLOAD_FAIL_NONE;
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
}
@ -257,7 +276,7 @@ void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr, UINT16 fi
void CL_PrepareDownloadSaveGame(const char *tmpsave)
{
lastfilenum = -1;
filedownload.current = -1;
FreeFileNeeded();
AllocFileNeeded(1);
@ -275,86 +294,37 @@ void CL_PrepareDownloadSaveGame(const char *tmpsave)
/** Checks the server to see if we CAN download all the files,
* before starting to create them and requesting.
*
* \param direct Game will do a direct download
* \return True if we can download all the files
*
*/
boolean CL_CheckDownloadable(void)
UINT8 CL_CheckDownloadable(boolean direct)
{
UINT8 dlstatus = 0;
UINT8 dlstatus = DLSTATUS_OK;
for (UINT8 i = 0; i < fileneedednum; i++)
if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN)
{
if (fileneeded[i].folder)
{
dlstatus = 4;
dlstatus = DLSTATUS_FOLDER;
break;
}
if (fileneeded[i].willsend == 1)
if (!direct || fileneeded[i].willsend == WILLSEND_YES)
continue;
if (fileneeded[i].willsend == 0)
dlstatus = 1;
else //if (fileneeded[i].willsend == 2)
dlstatus = 2;
if (fileneeded[i].willsend == WILLSEND_TOOLARGE)
dlstatus = DLSTATUS_TOOLARGE;
else //if (fileneeded[i].willsend == WILLSEND_NO)
dlstatus = DLSTATUS_WONTSEND;
}
// Downloading locally disabled
if (!dlstatus && M_CheckParm("-nodownload"))
dlstatus = 3;
if (direct && !dlstatus && M_CheckParm("-nodownload"))
dlstatus = DLSTATUS_NODOWNLOAD;
if (!dlstatus)
return true;
// not downloadable, put reason in console
CONS_Alert(CONS_NOTICE, M_GetText("You need additional addons to connect to this server:\n"));
for (UINT8 i = 0; i < fileneedednum; i++)
{
if (fileneeded[i].folder || (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN))
{
CONS_Printf(" * \"%s\" ", fileneeded[i].filename);
if (fileneeded[i].folder)
{
CONS_Printf("(folder)");
}
else
{
CONS_Printf("(%dK)", fileneeded[i].totalsize >> 10);
if (fileneeded[i].status == FS_NOTFOUND)
CONS_Printf(M_GetText(" not found, md5: "));
else if (fileneeded[i].status == FS_MD5SUMBAD)
CONS_Printf(M_GetText(" wrong version, md5: "));
char md5tmp[33];
for (INT32 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 addons 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 addons downloadable, but you have chosen to disable addon downloading.\n"));
break;
case 4:
CONS_Printf(M_GetText("One or more addons were added as a folder, which the server cannot send.\n"));
break;
}
return false;
return dlstatus;
}
/** Returns true if a needed file transfer can be resumed
@ -396,23 +366,42 @@ boolean CL_SendFileRequest(void)
#ifdef PARANOIA
if (M_CheckParm("-nodownload"))
I_Error("Attempted to download files in -nodownload mode");
{
CONS_Printf("Attempted to download files in -nodownload mode");
return false;
}
#endif
for (INT32 i = 0; i < fileneedednum; i++)
{
if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN
&& (fileneeded[i].willsend == 0 || fileneeded[i].willsend == 2))
&& (fileneeded[i].willsend == WILLSEND_TOOLARGE || fileneeded[i].willsend == WILLSEND_NO || fileneeded[i].folder))
{
I_Error("Attempted to download files that were not sendable");
CONS_Printf("Attempted to download files that were not sendable");
return false;
}
#endif
if (fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD)
{
// Error check for the first time around.
totalfreespaceneeded += fileneeded[i].totalsize;
}
}
I_GetDiskFreeSpace(&availablefreespace);
if (totalfreespaceneeded > availablefreespace)
{
CONS_Printf("To play on this server you must download %s KB,\n"
"but you have only %s KB free space on your device\n",
sizeu1((size_t)(totalfreespaceneeded>>10)), sizeu2((size_t)(availablefreespace>>10)));
return false;
}
netbuffer->packettype = PT_REQUESTFILE;
p = (char *)netbuffer->u.textcmd;
for (INT32 i = 0; i < fileneedednum; i++)
if (fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD)
{
totalfreespaceneeded += fileneeded[i].totalsize;
WRITEUINT8(p, i); // fileid
// put it in download dir
@ -424,15 +413,16 @@ boolean CL_SendFileRequest(void)
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)));
if (!HSendPacket(servernode, true, 0, p - (char *)netbuffer->u.textcmd))
{
CONS_Printf("Could not send download request packet to server\n");
return false;
}
// prepare to download
I_mkdir(downloaddir, 0755);
return HSendPacket(servernode, true, 0, p - (char *)netbuffer->u.textcmd);
return true;
}
// get request filepak and put it on the send queue
@ -514,7 +504,7 @@ INT32 CL_CheckFiles(void)
for (i = 0; i < fileneedednum; i++)
{
if (fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD)
if (fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD || fileneeded[i].status == FS_FALLBACK)
downloadrequired = true;
if (fileneeded[i].status != FS_OPEN)
@ -932,8 +922,6 @@ boolean AddLuaFileToSendQueue(INT32 node, const char *filename)
{
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];
luafiletransfers->nodestatus[node] = LFTNS_SENDING;
@ -1306,6 +1294,21 @@ void FileReceiveTicker(void)
}
}
static void OpenNewFileForDownload(fileneeded_t *file, const char *filename)
{
file->file = fopen(filename, "wb");
if (!file->file)
I_Error("Can't create file %s: %s", filename, strerror(errno));
file->currentsize = 0;
file->totalsize = LONG(netbuffer->u.filetxpak.filesize);
file->ackresendposition = UINT32_MAX; // Only used for resumed downloads
file->receivedfragments = calloc(file->totalsize / file->fragmentsize + 1, sizeof(*file->receivedfragments));
if (!file->receivedfragments)
I_Error("FileSendTicker: No more memory\n");
}
void PT_FileFragment(SINT8 node, INT32 netconsole)
{
if (netnodes[node].ingame)
@ -1348,8 +1351,6 @@ void PT_FileFragment(SINT8 node, INT32 netconsole)
))
I_Error("Tried to download \"%s\"", filename);
filename = file->filename;
if (filenum >= fileneedednum)
{
DEBFILE(va("fileframent not needed %d>%d\n", filenum, fileneedednum));
@ -1372,15 +1373,24 @@ void PT_FileFragment(SINT8 node, INT32 netconsole)
if (CL_CanResumeDownload(file))
{
file->file = fopen(filename, "r+b");
file->file = fopen(file->filename, "r+b");
if (!file->file)
I_Error("Can't reopen file %s: %s", filename, strerror(errno));
CONS_Printf("\r%s...\n", filename);
{
CONS_Alert(CONS_ERROR, "Couldn't reopen file %s: %s\n", file->filename, strerror(errno));
CONS_Printf("Resuming download...\n");
file->currentsize = pauseddownload->currentsize;
file->receivedfragments = pauseddownload->receivedfragments;
file->ackresendposition = 0;
free(pauseddownload->receivedfragments);
CONS_Printf("Restarting download of addon \"%s\"...\n", filename);
OpenNewFileForDownload(file, file->filename);
}
else
{
CONS_Printf("Resuming download of addon \"%s\"...\n", filename);
file->currentsize = pauseddownload->currentsize;
file->receivedfragments = pauseddownload->receivedfragments;
file->ackresendposition = 0;
}
free(pauseddownload);
pauseddownload = NULL;
@ -1388,20 +1398,8 @@ void PT_FileFragment(SINT8 node, INT32 netconsole)
else
{
CL_AbortDownloadResume();
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->totalsize = LONG(netbuffer->u.filetxpak.filesize);
file->ackresendposition = UINT32_MAX; // Only used for resumed downloads
file->receivedfragments = calloc(file->totalsize / fragmentsize + 1, sizeof(*file->receivedfragments));
if (!file->receivedfragments)
I_Error("FileSendTicker: No more memory\n");
OpenNewFileForDownload(file, file->filename);
CONS_Printf("Downloading addon \"%s\" from the server...\n", filename);
}
lasttimeackpacketsent = I_GetTime();
@ -1421,7 +1419,7 @@ void PT_FileFragment(SINT8 node, INT32 netconsole)
// We can receive packets in the wrong order, anyway all OSes support gaped files
fseek(file->file, fragmentpos, SEEK_SET);
if (fragmentsize && fwrite(netbuffer->u.filetxpak.data, boundedfragmentsize, 1, file->file) != 1)
I_Error("Can't write to %s: %s\n",filename, M_FileError(file->file));
I_Error("Can't write to %s: %s\n",file->filename, M_FileError(file->file));
file->currentsize += boundedfragmentsize;
AddFragmentToAckPacket(file->ackpacket, file->iteration, fragmentpos / fragmentsize, filenum);
@ -1435,8 +1433,6 @@ void PT_FileFragment(SINT8 node, INT32 netconsole)
free(file->ackpacket);
file->status = FS_FOUND;
file->justdownloaded = true;
CONS_Printf(M_GetText("Downloading %s...(done)\n"),
filename);
// Tell the server we have received the file
netbuffer->packettype = PT_FILERECEIVED;
@ -1449,6 +1445,16 @@ void PT_FileFragment(SINT8 node, INT32 netconsole)
netbuffer->packettype = PT_HASLUAFILE;
HSendPacket(servernode, true, 0, 0);
FreeFileNeeded();
CONS_Printf(M_GetText("Downloaded \"%s\"\n"), filename);
}
else
{
filedownload.completednum++;
filedownload.completedsize += file->totalsize;
filedownload.remaining--;
CONS_Printf(M_GetText("Finished download of addon \"%s\"\n"), filename);
}
}
}
@ -1483,7 +1489,7 @@ void PT_FileFragment(SINT8 node, INT32 netconsole)
I_Error("Received a file not requested (file id: %d, file status: %s)\n", filenum, s);
}
lastfilenum = filenum;
filedownload.current = filenum;
}
/** \brief Checks if a node is downloading a file
@ -1581,6 +1587,220 @@ void Command_Downloads_f(void)
}
}
static size_t curlwrite_data(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
return fwrite(ptr, size, nmemb, stream);
}
static int curlprogress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
(void)clientp;
(void)ultotal;
(void)ulnow; // Function prototype requires these but we won't use, so just discard
curl_dlnow = (UINT32)dlnow;
curl_dltotal = (UINT32)dltotal;
getbytes = ((double)dlnow) / (time(NULL) - curl_starttime); // To-do: Make this more accurate???
return 0;
}
boolean 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;
char md5tmp[33];
for (INT32 j = 0; j < 16; j++)
sprintf(&md5tmp[j*2], "%02x", curl_curfile->md5sum[j]);
curl_easy_setopt(http_handle, CURLOPT_URL, va("%s/%s?md5=%s", url, curl_realname, md5tmp));
// Only allow HTTP and HTTPS
curl_easy_setopt(http_handle, CURLOPT_PROTOCOLS_STR, "http,https");
// Set user agent, as some servers won't accept invalid user agents.
curl_easy_setopt(http_handle, CURLOPT_USERAGENT, va("Sonic Robo Blast 2/v%d.%d", VERSION, SUBVERSION));
// 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 addon \"%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_XFERINFOFUNCTION, curlprogress_callback);
curl_curfile->status = FS_DOWNLOADING;
curl_multi_add_handle(multi_handle, http_handle);
curl_multi_perform(multi_handle, &curl_runninghandles);
curl_starttime = time(NULL);
filedownload.current = dfilenum;
filedownload.http_running = true;
return true;
}
filedownload.http_running = false;
return false;
}
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;
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;
char *filename = Z_StrDup(curl_realname);
nameonly(filename);
if (easyres != CURLE_OK)
{
long response_code = 0;
if (easyres == CURLE_HTTP_RETURNED_ERROR)
curl_easy_getinfo(e, CURLINFO_RESPONSE_CODE, &response_code);
if (response_code == 404)
curl_curfile->failed = FDOWNLOAD_FAIL_NOTFOUND;
else
curl_curfile->failed = FDOWNLOAD_FAIL_OTHER;
easy_handle_error = (response_code) ? va("HTTP response code %ld", response_code) : curl_easy_strerror(easyres);
curl_curfile->status = FS_FALLBACK;
curl_curfile->currentsize = curl_origfilesize;
curl_curfile->totalsize = curl_origtotalfilesize;
filedownload.http_failed = true;
fclose(curl_curfile->file);
remove(curl_curfile->filename);
CONS_Alert(CONS_ERROR, M_GetText("Failed to download addon \"%s\" (%s)\n"), filename, easy_handle_error);
}
else
{
fclose(curl_curfile->file);
CONS_Printf(M_GetText("Finished download of addon \"%s\"\n"), filename);
if (checkfilemd5(curl_curfile->filename, curl_curfile->md5sum) == FS_MD5SUMBAD)
{
CONS_Alert(CONS_WARNING, M_GetText("File \"%s\" does not match the version used by the server\n"), filename);
curl_curfile->status = FS_FALLBACK;
curl_curfile->failed = FDOWNLOAD_FAIL_MD5SUMBAD;
filedownload.http_failed = true;
}
else
{
filedownload.completednum++;
filedownload.completedsize += curl_curfile->totalsize;
curl_curfile->status = FS_FOUND;
}
}
Z_Free(filename);
curl_curfile->file = NULL;
filedownload.http_running = false;
filedownload.remaining--;
curl_multi_remove_handle(multi_handle, e);
curl_easy_cleanup(e);
if (!filedownload.remaining)
break;
}
}
if (!filedownload.remaining)
{
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;
}
// Functions cut and pasted from Doomatic :)
void nameonly(char *s)

View file

@ -1,7 +1,7 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2023 by Sonic Team Junior.
// Copyright (C) 1999-2024 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
@ -25,6 +25,23 @@ typedef enum
SF_NOFREERAM
} freemethod_t;
typedef enum
{
DLSTATUS_OK,
DLSTATUS_TOOLARGE,
DLSTATUS_WONTSEND,
DLSTATUS_NODOWNLOAD,
DLSTATUS_FOLDER
} dlstatus_t;
typedef enum
{
FDOWNLOAD_FAIL_NONE,
FDOWNLOAD_FAIL_NOTFOUND,
FDOWNLOAD_FAIL_MD5SUMBAD,
FDOWNLOAD_FAIL_OTHER
} filedownloadfail_t;
typedef enum
{
FS_NOTCHECKED,
@ -33,7 +50,8 @@ typedef enum
FS_REQUESTED,
FS_DOWNLOADING,
FS_OPEN, // Is opened and used in w_wad
FS_MD5SUMBAD
FS_MD5SUMBAD,
FS_FALLBACK
} filestatus_t;
typedef enum
@ -51,6 +69,7 @@ typedef struct
UINT8 willsend; // Is the server willing to send it?
UINT8 folder; // File is a folder
fileneededtype_t type;
filedownloadfail_t failed;
boolean justdownloaded; // To prevent late fragments from causing an I_Error
// Used only for download
@ -70,11 +89,30 @@ extern INT32 fileneedednum;
extern fileneeded_t *fileneeded;
extern char downloaddir[512];
extern INT32 lastfilenum;
extern INT32 downloadcompletednum;
extern UINT32 downloadcompletedsize;
extern INT32 totalfilesrequestednum;
extern UINT32 totalfilesrequestedsize;
typedef struct
{
INT32 current;
INT32 remaining;
INT32 completednum;
UINT32 completedsize;
boolean http_failed;
boolean http_running;
char http_source[MAX_MIRROR_LENGTH];
} file_download_t;
extern file_download_t filedownload;
typedef struct HTTP_login HTTP_login;
extern struct HTTP_login
{
char * url;
char * auth;
HTTP_login * next;
}
*curl_logins;
extern consvar_t cv_maxsend, cv_noticedownload, cv_downloadspeed;
@ -97,10 +135,14 @@ boolean SendingFile(INT32 node);
void FileReceiveTicker(void);
void PT_FileFragment(SINT8 node, INT32 netconsole);
boolean CL_CheckDownloadable(void);
UINT8 CL_CheckDownloadable(boolean direct);
boolean CL_SendFileRequest(void);
void PT_RequestFile(SINT8 node);
boolean CURLPrepareFile(const char* url, int dfilenum);
void CURLGetFile(void);
HTTP_login * CURLGetLogin (const char *url, HTTP_login ***return_prev_next);
typedef enum
{
LFTNS_NONE, // This node is not connected

View file

@ -1,7 +1,7 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2023 by Sonic Team Junior.
// Copyright (C) 1999-2024 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
@ -26,7 +26,7 @@ packet versions.
If you change the struct or the meaning of a field
therein, increment this number.
*/
#define PACKETVERSION 4
#define PACKETVERSION 5
// Network play related stuff.
// There is a data struct that stores network
@ -200,6 +200,7 @@ enum {
#define MAXSERVERNAME 32
#define MAXFILENEEDED 915
#define MAX_MIRROR_LENGTH 256
// This packet is too large
typedef struct
@ -230,6 +231,7 @@ typedef struct
unsigned char mapmd5[16];
UINT8 actnum;
UINT8 iszone;
char httpsource[MAX_MIRROR_LENGTH];
UINT8 fileneeded[MAXFILENEEDED]; // is filled with writexxx (byteptr.h)
} ATTRPACK serverinfo_pak;

View file

@ -1,7 +1,7 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2023 by Sonic Team Junior.
// Copyright (C) 1999-2024 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
@ -127,6 +127,8 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
memset(netbuffer->u.serverinfo.maptitle, 0, sizeof netbuffer->u.serverinfo.maptitle);
memset(netbuffer->u.serverinfo.httpsource, 0, MAX_MIRROR_LENGTH);
if (mapheaderinfo[gamemap-1] && *mapheaderinfo[gamemap-1]->lvlttl)
{
char *read = mapheaderinfo[gamemap-1]->lvlttl, *writ = netbuffer->u.serverinfo.maptitle;
@ -153,6 +155,17 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
if (mapheaderinfo[gamemap-1])
netbuffer->u.serverinfo.actnum = mapheaderinfo[gamemap-1]->actnum;
const char *httpurl = cv_httpsource.string;
size_t mirror_length = strlen(httpurl);
if (mirror_length > MAX_MIRROR_LENGTH)
mirror_length = MAX_MIRROR_LENGTH;
if (snprintf(netbuffer->u.serverinfo.httpsource, mirror_length+1, "%s", httpurl) < 0)
// If there's an encoding error, send nothing, we accept that the above may be truncated
strncpy(netbuffer->u.serverinfo.httpsource, "", mirror_length);
netbuffer->u.serverinfo.httpsource[MAX_MIRROR_LENGTH-1] = '\0';
p = PutFileNeeded(0);
HSendPacket(node, false, 0, p - ((UINT8 *)&netbuffer->u));