diff --git a/src/blua/liolib.c b/src/blua/liolib.c index b43052194..a055aad3f 100644 --- a/src/blua/liolib.c +++ b/src/blua/liolib.c @@ -284,8 +284,16 @@ void Got_LuaFile(UINT8 **cp, INT32 playernum) // Push the first argument (file handle or nil) on the stack if (success) { + char mode[4]; + + // Ensure we are opening in binary mode + // (if it's a text file, newlines have been converted already) + strcpy(mode, luafiletransfers->mode); + if (!strchr(mode, 'b')) + strcat(mode, "b"); + pf = newfile(gL); // Create and push the file handle - *pf = fopen(luafiletransfers->realfilename, luafiletransfers->mode); // Open the file + *pf = fopen(luafiletransfers->realfilename, mode); // Open the file if (!*pf) I_Error("Can't open file \"%s\"\n", luafiletransfers->realfilename); // The file SHOULD exist } @@ -313,17 +321,14 @@ void Got_LuaFile(UINT8 **cp, INT32 playernum) RemoveLuaFileTransfer(); - if (server && luafiletransfers) + if (waitingforluafilecommand) { - if (FIL_FileOK(luafiletransfers->realfilename)) - SV_PrepareSendLuaFileToNextNode(); - else - { - // Send a net command with 0 as its first byte to indicate the file couldn't be opened - success = 0; - SendNetXCmd(XD_LUAFILE, &success, 1); - } + waitingforluafilecommand = false; + CL_PrepareDownloadLuaFile(); } + + if (server && luafiletransfers) + SV_PrepareSendLuaFile(); } diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 3a15a685c..95927710a 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1678,8 +1678,9 @@ static inline void CL_DrawConnectionStatus(void) // 15 pal entries total. const char *cltext; - for (i = 0; i < 16; ++i) - V_DrawFill((BASEVIDWIDTH/2-128) + (i * 16), BASEVIDHEIGHT-16, 16, 8, palstart + ((animtime - i) & 15)); + if (!(cl_mode == CL_DOWNLOADSAVEGAME && lastfilenum != -1)) + for (i = 0; i < 16; ++i) + V_DrawFill((BASEVIDWIDTH/2-128) + (i * 16), BASEVIDHEIGHT-16, 16, 8, palstart + ((animtime - i) & 15)); switch (cl_mode) { @@ -1687,10 +1688,22 @@ static inline void CL_DrawConnectionStatus(void) case CL_DOWNLOADSAVEGAME: if (lastfilenum != -1) { + UINT32 currentsize = fileneeded[lastfilenum].currentsize; + UINT32 totalsize = fileneeded[lastfilenum].totalsize; + INT32 dldlength; + 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",fileneeded[lastfilenum].currentsize>>10)); + 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)); } @@ -2098,7 +2111,7 @@ static void SV_SendSaveGame(INT32 node) WRITEUINT32(savebuffer, 0); } - SV_SendRam(node, buffertosend, length, SF_RAM, 0); + AddRamToSendQueue(node, buffertosend, length, SF_RAM, 0); save_p = NULL; // Remember when we started sending the savegame so we can handle timeouts @@ -2478,7 +2491,7 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent) return false; } // no problem if can't send packet, we will retry later - if (CL_SendRequestFile()) + if (CL_SendFileRequest()) { cl_mode = CL_DOWNLOADFILES; Snake_Initialise(); @@ -2622,10 +2635,13 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic else if (cl_mode == CL_DOWNLOADFILES && snake) Snake_Handle(); + if (client && (cl_mode == CL_DOWNLOADFILES || cl_mode == CL_DOWNLOADSAVEGAME)) + FileReceiveTicker(); + // why are these here? this is for servers, we're a client //if (key == 's' && server) // doomcom->numnodes = (INT16)pnumnodes; - //SV_FileSendTicker(); + //FileSendTicker(); *oldtic = I_GetTime(); #ifdef CLIENT_LOADINGSCREEN @@ -3764,6 +3780,7 @@ void D_QuitNetGame(void) CloseNetFile(); RemoveAllLuaFileTransfers(); waitingforluafiletransfer = false; + waitingforluafilecommand = false; if (server) { @@ -4437,13 +4454,23 @@ static void HandlePacketFromAwayNode(SINT8 node) break; } SERVERONLY - Got_Filetxpak(); + PT_FileFragment(); + break; + + case PT_FILEACK: + if (server) + PT_FileAck(); + break; + + case PT_FILERECEIVED: + if (server) + PT_FileReceived(); break; case PT_REQUESTFILE: if (server) { - if (!cv_downloading.value || !Got_RequestFilePak(node)) + if (!cv_downloading.value || !PT_RequestFile(node)) Net_CloseConnection(node); // close connection if one of the requested files could not be sent, or you disabled downloading anyway } else @@ -4739,11 +4766,7 @@ static void HandlePacketFromPlayer(SINT8 node) break; case PT_ASKLUAFILE: if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_ASKED) - { - char *name = va("%s" PATHSEP "%s", luafiledir, luafiletransfers->filename); - boolean textmode = !strchr(luafiletransfers->mode, 'b'); - SV_SendLuaFile(node, name, textmode); - } + AddLuaFileToSendQueue(node, luafiletransfers->realfilename); break; case PT_HASLUAFILE: if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_SENDING) @@ -4874,7 +4897,15 @@ static void HandlePacketFromPlayer(SINT8 node) break; } if (client) - Got_Filetxpak(); + PT_FileFragment(); + break; + case PT_FILEACK: + if (server) + PT_FileAck(); + break; + case PT_FILERECEIVED: + if (server) + PT_FileReceived(); break; case PT_SENDINGLUAFILE: if (client) @@ -5564,7 +5595,7 @@ void NetUpdate(void) CON_Ticker(); } - SV_FileSendTicker(); + FileSendTicker(); } /** Returns the number of players playing. diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 1135b043c..6b06764f9 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -76,6 +76,8 @@ typedef enum // In addition, this packet can't occupy all the available slots. PT_FILEFRAGMENT = PT_CANFAIL, // A part of a file. + PT_FILEACK, + PT_FILERECEIVED, PT_TEXTCMD, // Extra text commands from the client. PT_TEXTCMD2, // Splitscreen text commands. @@ -324,13 +326,30 @@ typedef struct UINT8 varlengthinputs[0]; // Playernames and netvars } ATTRPACK serverconfig_pak; -typedef struct { +typedef struct +{ UINT8 fileid; + UINT32 filesize; + UINT8 iteration; UINT32 position; UINT16 size; UINT8 data[0]; // Size is variable using hardware_MAXPACKETLENGTH } ATTRPACK filetx_pak; +typedef struct +{ + UINT32 start; + UINT32 acks; +} ATTRPACK fileacksegment_t; + +typedef struct +{ + UINT8 fileid; + UINT8 iteration; + UINT8 numsegments; + fileacksegment_t segments[0]; +} ATTRPACK fileack_pak; + #ifdef _MSC_VER #pragma warning(default : 4200) #endif @@ -446,6 +465,8 @@ typedef struct UINT8 resynchgot; // UINT8 textcmd[MAXTEXTCMD+1]; // 66049 bytes (wut??? 64k??? More like 257 bytes...) filetx_pak filetxpak; // 139 bytes + fileack_pak fileack; + UINT8 filereceived; clientconfig_pak clientcfg; // 136 bytes UINT8 md5sum[16]; serverinfo_pak serverinfo; // 1024 bytes diff --git a/src/d_net.c b/src/d_net.c index 8e62b8d25..2823ce219 100644 --- a/src/d_net.c +++ b/src/d_net.c @@ -806,6 +806,9 @@ static const char *packettypename[NUMPACKETTYPE] = "HASLUAFILE", "FILEFRAGMENT", + "FILEACK", + "FILERECEIVED", + "TEXTCMD", "TEXTCMD2", "CLIENTJOIN", diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 84070135a..8b75741ae 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -502,6 +502,8 @@ void D_RegisterServerCommands(void) COM_AddCommand("archivetest", Command_Archivetest_f); #endif + COM_AddCommand("downloads", Command_Downloads_f); + // for master server connection AddMServCommands(); diff --git a/src/d_netfil.c b/src/d_netfil.c index 6d3ac7f9d..7b99fddfb 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -56,7 +56,7 @@ #include // Prototypes -static boolean SV_SendFile(INT32 node, const char *filename, UINT8 fileid); +static boolean AddFileToSendQueue(INT32 node, const char *filename, UINT8 fileid); // Sender structure typedef struct filetx_s @@ -69,7 +69,6 @@ typedef struct filetx_s UINT32 size; // Size of the file UINT8 fileid; INT32 node; // Destination - boolean textmode; // For files requested by Lua without the "b" option struct filetx_s *next; // Next file in the list } filetx_t; @@ -77,8 +76,13 @@ typedef struct filetx_s typedef struct filetran_s { filetx_t *txlist; // Linked list of all files for the node + UINT8 iteration; + UINT8 ackediteration; UINT32 position; // The current position in the file + boolean *ackedfragments; + UINT32 ackedsize; FILE *currentfile; // The file currently being sent/received + tic_t dontsenduntil; } filetran_t; static filetran_t transfer[MAXNETNODES]; @@ -88,8 +92,20 @@ static filetran_t transfer[MAXNETNODES]; // Receiver structure INT32 fileneedednum; // Number of files needed to join the server fileneeded_t fileneeded[MAX_WADFILES]; // List of needed files +static tic_t lasttimeackpacketsent = 0; char downloaddir[512] = "DOWNLOAD"; +// For resuming failed downloads +typedef struct +{ + char filename[MAX_WADPATH]; + UINT8 md5sum[16]; + boolean *receivedfragments; + UINT32 fragmentsize; + UINT32 currentsize; +} pauseddownload_t; +static pauseddownload_t *pauseddownload = NULL; + #ifdef CLIENT_LOADINGSCREEN // for cl loading screen INT32 lastfilenum = -1; @@ -97,6 +113,7 @@ INT32 lastfilenum = -1; luafiletransfer_t *luafiletransfers = NULL; boolean waitingforluafiletransfer = false; +boolean waitingforluafilecommand = false; char luafiledir[256 + 16] = "luafiles"; @@ -159,25 +176,29 @@ void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr) for (i = 0; i < fileneedednum; i++) { fileneeded[i].status = FS_NOTFOUND; // We haven't even started looking for the file yet + fileneeded[i].justdownloaded = false; 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 - fileneeded[i].textmode = false; } } void CL_PrepareDownloadSaveGame(const char *tmpsave) { +#ifdef CLIENT_LOADINGSCREEN + lastfilenum = -1; +#endif + fileneedednum = 1; fileneeded[0].status = FS_REQUESTED; + fileneeded[0].justdownloaded = false; fileneeded[0].totalsize = UINT32_MAX; fileneeded[0].file = NULL; memset(fileneeded[0].md5sum, 0, 16); strcpy(fileneeded[0].filename, tmpsave); - fileneeded[0].textmode = false; } /** Checks the server to see if we CAN download all the files, @@ -246,6 +267,31 @@ boolean CL_CheckDownloadable(void) return false; } +/** Returns true if a needed file transfer can be resumed + * + * \param file The needed file to resume the transfer for + * \return True if the transfer can be resumed + * + */ +static boolean CL_CanResumeDownload(fileneeded_t *file) +{ + return pauseddownload + && !strcmp(pauseddownload->filename, file->filename) // Same name + && !memcmp(pauseddownload->md5sum, file->md5sum, 16) // Same checksum + && pauseddownload->fragmentsize == file->fragmentsize; // Same fragment size +} + +void CL_AbortDownloadResume(void) +{ + if (!pauseddownload) + return; + + free(pauseddownload->receivedfragments); + remove(pauseddownload->filename); + free(pauseddownload); + pauseddownload = NULL; +} + /** Sends requests for files in the ::fileneeded table with a status of * ::FS_NOTFOUND. * @@ -253,7 +299,7 @@ boolean CL_CheckDownloadable(void) * \note Sends a PT_REQUESTFILE packet * */ -boolean CL_SendRequestFile(void) +boolean CL_SendFileRequest(void) { char *p; INT32 i; @@ -298,7 +344,7 @@ boolean CL_SendRequestFile(void) // 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) +boolean PT_RequestFile(INT32 node) { char wad[MAX_WADPATH+1]; UINT8 *p = netbuffer->u.textcmd; @@ -309,7 +355,7 @@ boolean Got_RequestFilePak(INT32 node) if (id == 0xFF) break; READSTRINGN(p, wad, MAX_WADPATH); - if (!SV_SendFile(node, wad, id)) + if (!AddFileToSendQueue(node, wad, id)) { SV_AbortSendFiles(node); return false; // don't read the rest of the files @@ -462,8 +508,6 @@ void AddLuaFileTransfer(const char *filename, const char *mode) luafiletransfer_t *filetransfer; static INT32 id; - //CONS_Printf("AddLuaFileTransfer \"%s\"\n", filename); - // Find the last transfer in the list and set a pointer to its "next" field prevnext = &luafiletransfers; while (*prevnext) @@ -493,26 +537,11 @@ void AddLuaFileTransfer(const char *filename, const char *mode) strlcpy(filetransfer->mode, mode, sizeof(filetransfer->mode)); - if (server) - { - INT32 i; - - // Set status to "waiting" for everyone - for (i = 0; i < MAXNETNODES; i++) - filetransfer->nodestatus[i] = LFTNS_WAITING; - - if (!luafiletransfers->next) // Only if there is no transfer already going on - { - if (FIL_ReadFileOK(filetransfer->realfilename)) - SV_PrepareSendLuaFileToNextNode(); - else - { - // Send a net command with 0 as its first byte to indicate the file couldn't be opened - UINT8 success = 0; - SendNetXCmd(XD_LUAFILE, &success, 1); - } - } - } + // Only if there is no transfer already going on + if (server && filetransfer == luafiletransfers) + SV_PrepareSendLuaFile(); + else + filetransfer->ongoing = false; // Store the callback so it can be called once everyone has the file filetransfer->id = id; @@ -526,7 +555,7 @@ void AddLuaFileTransfer(const char *filename, const char *mode) } } -void SV_PrepareSendLuaFileToNextNode(void) +static void SV_PrepareSendLuaFileToNextNode(void) { INT32 i; UINT8 success = 1; @@ -550,6 +579,45 @@ void SV_PrepareSendLuaFileToNextNode(void) SendNetXCmd(XD_LUAFILE, &success, 1); } +void SV_PrepareSendLuaFile(void) +{ + char *binfilename; + INT32 i; + + luafiletransfers->ongoing = true; + + // Set status to "waiting" for everyone + for (i = 0; i < MAXNETNODES; i++) + luafiletransfers->nodestatus[i] = LFTNS_WAITING; + + if (FIL_ReadFileOK(luafiletransfers->realfilename)) + { + // If opening in text mode, convert all newlines to LF + if (!strchr(luafiletransfers->mode, 'b')) + { + binfilename = strdup(va("%s" PATHSEP "$$$%d%d.tmp", + luafiledir, rand(), rand())); + if (!binfilename) + I_Error("SV_PrepareSendLuaFile: Out of memory\n"); + + if (!FIL_ConvertTextFileToBinary(luafiletransfers->realfilename, binfilename)) + I_Error("SV_PrepareSendLuaFile: Failed to convert file newlines\n"); + + // Use the temporary file instead + free(luafiletransfers->realfilename); + luafiletransfers->realfilename = binfilename; + } + + SV_PrepareSendLuaFileToNextNode(); + } + else + { + // Send a net command with 0 as its first byte to indicate the file couldn't be opened + UINT8 success = 0; + SendNetXCmd(XD_LUAFILE, &success, 1); + } +} + void SV_HandleLuaFileSent(UINT8 node) { luafiletransfers->nodestatus[node] = LFTNS_SENT; @@ -560,6 +628,10 @@ void RemoveLuaFileTransfer(void) { luafiletransfer_t *filetransfer = luafiletransfers; + // If it was a temporary file, delete it + if (server && !strchr(filetransfer->mode, 'b')) + remove(filetransfer->realfilename); + RemoveLuaFileCallback(filetransfer->id); luafiletransfers = filetransfer->next; @@ -596,20 +668,28 @@ void CL_PrepareDownloadLuaFile(void) return; } + if (luafiletransfers->ongoing) + { + waitingforluafilecommand = true; + return; + } + // Tell the server we are ready to receive the file netbuffer->packettype = PT_ASKLUAFILE; HSendPacket(servernode, true, 0, 0); fileneedednum = 1; fileneeded[0].status = FS_REQUESTED; + fileneeded[0].justdownloaded = false; fileneeded[0].totalsize = UINT32_MAX; fileneeded[0].file = NULL; memset(fileneeded[0].md5sum, 0, 16); strcpy(fileneeded[0].filename, luafiletransfers->realfilename); - fileneeded[0].textmode = !strchr(luafiletransfers->mode, 'b'); // Make sure all directories in the file path exist MakePathDirs(fileneeded[0].filename); + + luafiletransfers->ongoing = true; } // Number of files to send @@ -620,12 +700,12 @@ static INT32 filestosend = 0; * * \param node The node to send the file to * \param filename The file to send - * \param fileid ??? - * \sa SV_SendRam - * \sa SV_SendLuaFile + * \param fileid The index of the file in the list of added files + * \sa AddRamToSendQueue + * \sa AddLuaFileToSendQueue * */ -static boolean SV_SendFile(INT32 node, const char *filename, UINT8 fileid) +static boolean AddFileToSendQueue(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 @@ -643,7 +723,7 @@ static boolean SV_SendFile(INT32 node, const char *filename, UINT8 fileid) // 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"); + I_Error("AddFileToSendQueue: No more memory\n"); // Initialise with zeros memset(p, 0, sizeof (filetx_t)); @@ -651,7 +731,7 @@ static boolean SV_SendFile(INT32 node, const char *filename, UINT8 fileid) // Allocate the file name p->id.filename = (char *)malloc(MAX_WADPATH); if (!p->id.filename) - I_Error("SV_SendFile: No more memory\n"); + I_Error("AddFileToSendQueue: No more memory\n"); // Set the file name and get rid of the path strlcpy(p->id.filename, filename, MAX_WADPATH); @@ -711,12 +791,12 @@ static boolean SV_SendFile(INT32 node, const char *filename, UINT8 fileid) * \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 - * \sa SV_SendLuaFile + * \param fileid The index of the file in the list of added files + * \sa AddFileToSendQueue + * \sa AddLuaFileToSendQueue * */ -void SV_SendRam(INT32 node, void *data, size_t size, freemethod_t freemethod, UINT8 fileid) +void AddRamToSendQueue(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 @@ -729,7 +809,7 @@ void SV_SendRam(INT32 node, void *data, size_t size, freemethod_t freemethod, UI // 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"); + I_Error("AddRamToSendQueue: No more memory\n"); // Initialise with zeros memset(p, 0, sizeof (filetx_t)); @@ -749,11 +829,11 @@ void SV_SendRam(INT32 node, void *data, size_t size, freemethod_t freemethod, UI * * \param node The node to send the file to * \param filename The file to send - * \sa SV_SendFile - * \sa SV_SendRam + * \sa AddFileToSendQueue + * \sa AddRamToSendQueue * */ -boolean SV_SendLuaFile(INT32 node, const char *filename, boolean textmode) +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 @@ -770,7 +850,7 @@ boolean SV_SendLuaFile(INT32 node, const char *filename, boolean textmode) // Allocate a file request and append it to the file list p = *q = (filetx_t *)malloc(sizeof (filetx_t)); if (!p) - I_Error("SV_SendLuaFile: No more memory\n"); + I_Error("AddLuaFileToSendQueue: No more memory\n"); // Initialise with zeros memset(p, 0, sizeof (filetx_t)); @@ -778,15 +858,12 @@ boolean SV_SendLuaFile(INT32 node, const char *filename, boolean textmode) // Allocate the file name p->id.filename = (char *)malloc(MAX_WADPATH); // !!! if (!p->id.filename) - I_Error("SV_SendLuaFile: No more memory\n"); + I_Error("AddLuaFileToSendQueue: 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); - // Open in text mode if required by the Lua script - p->textmode = textmode; - DEBFILE(va("Sending Lua file %s to %d\n", filename, 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->next = NULL; // End of list @@ -804,7 +881,8 @@ 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 + // Free the file request according to the freemethod + // parameter used with AddFileToSendQueue/AddRamToSendQueue switch (p->ram) { case SF_FILE: // It's a file, close it and free its filename @@ -829,43 +907,32 @@ static void SV_EndFileSend(INT32 node) // Indicate that the transmission is over transfer[node].currentfile = NULL; + if (transfer[node].ackedfragments) + free(transfer[node].ackedfragments); + transfer[node].ackedfragments = NULL; filestosend--; } #define PACKETPERTIC net_bandwidth/(TICRATE*software_MAXPACKETLENGTH) +#define FILEFRAGMENTSIZE (software_MAXPACKETLENGTH - (FILETXHEADER + BASEPACKETSIZE)) /** 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) +void FileSendTicker(void) { static INT32 currentnode = 0; filetx_pak *p; - size_t size; + size_t fragmentsize; 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 - { + if (cv_downloadspeed.value) // New 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; @@ -882,11 +949,12 @@ void SV_FileSendTicker(void) i = (i+1) % MAXNETNODES, j++) { if (transfer[i].txlist) - goto found; + break; } // no transfer to do - I_Error("filestosend=%d but no file to send found\n", filestosend); - found: + if (j >= MAXNETNODES) + I_Error("filestosend=%d but no file to send found\n", filestosend); + currentnode = (i+1) % MAXNETNODES; f = transfer[i].txlist; ram = f->ram; @@ -899,7 +967,7 @@ void SV_FileSendTicker(void) long filesize; transfer[i].currentfile = - fopen(f->id.filename, f->textmode ? "r" : "rb"); + fopen(f->id.filename, "rb"); if (!transfer[i].currentfile) I_Error("File %s does not exist", @@ -920,57 +988,232 @@ void SV_FileSendTicker(void) } else // Sending RAM transfer[i].currentfile = (FILE *)1; // Set currentfile to a non-null value to indicate that it is open + + transfer[i].iteration = 1; + transfer[i].ackediteration = 0; transfer[i].position = 0; + transfer[i].ackedsize = 0; + + transfer[i].ackedfragments = calloc(f->size / FILEFRAGMENTSIZE + 1, sizeof(*transfer[i].ackedfragments)); + if (!transfer[i].ackedfragments) + I_Error("FileSendTicker: No more memory\n"); + + transfer[i].dontsenduntil = 0; + } + + // If the client hasn't acknowledged any fragment from the previous iteration, + // it is most likely because their acks haven't had enough time to reach the server + // yet, due to latency. In that case, we wait a little to avoid useless resend. + if (I_GetTime() < transfer[i].dontsenduntil) + continue; + + // Find the first non-acknowledged fragment + while (transfer[i].ackedfragments[transfer[i].position / FILEFRAGMENTSIZE]) + { + transfer[i].position += FILEFRAGMENTSIZE; + if (transfer[i].position >= f->size) + { + if (transfer[i].ackediteration < transfer[i].iteration) + transfer[i].dontsenduntil = I_GetTime() + TICRATE / 2; + + transfer[i].position = 0; + transfer[i].iteration++; + } } // 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; + fragmentsize = FILEFRAGMENTSIZE; + if (f->size-transfer[i].position < fragmentsize) + fragmentsize = f->size-transfer[i].position; if (ram) - M_Memcpy(p->data, &f->id.ram[transfer[i].position], size); + M_Memcpy(p->data, &f->id.ram[transfer[i].position], fragmentsize); else { - size_t n = fread(p->data, 1, size, transfer[i].currentfile); - if (n != size) // Either an error or Windows turning CR-LF into LF - { - if (f->textmode && feof(transfer[i].currentfile)) - size = n; - 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)); - } + fseek(transfer[i].currentfile, transfer[i].position, SEEK_SET); + + if (fread(p->data, 1, fragmentsize, transfer[i].currentfile) != fragmentsize) + I_Error("FileSendTicker: can't read %s byte on %s at %d because %s", sizeu1(fragmentsize), f->id.filename, transfer[i].position, M_FileError(transfer[i].currentfile)); } + p->iteration = transfer[i].iteration; p->position = LONG(transfer[i].position); - // Put flag so receiver knows the total size - if (transfer[i].position + size == f->size || (f->textmode && feof(transfer[i].currentfile))) - p->position |= LONG(0x80000000); p->fileid = f->fileid; - p->size = SHORT((UINT16)size); + p->filesize = LONG(f->size); + p->size = SHORT((UINT16)FILEFRAGMENTSIZE); // Send the packet - if (HSendPacket(i, true, 0, FILETXHEADER + size)) // Reliable SEND + if (HSendPacket(i, false, 0, FILETXHEADER + fragmentsize)) // Don't use the default acknowledgement system { // Success - transfer[i].position = (UINT32)(transfer[i].position + size); - if (transfer[i].position == f->size || (f->textmode && feof(transfer[i].currentfile))) // Finish? - SV_EndFileSend(i); + transfer[i].position = (UINT32)(transfer[i].position + fragmentsize); + if (transfer[i].position >= f->size) + { + if (transfer[i].ackediteration < transfer[i].iteration) + transfer[i].dontsenduntil = I_GetTime() + TICRATE / 2; + + transfer[i].position = 0; + transfer[i].iteration++; + } } 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) +void PT_FileAck(void) +{ + fileack_pak *packet = &netbuffer->u.fileack; + INT32 node = doomcom->remotenode; + filetran_t *trans = &transfer[node]; + INT32 i, j; + + // Wrong file id? Ignore it, it's probably a late packet + if (!(trans->txlist && packet->fileid == trans->txlist->fileid)) + return; + + if (packet->numsegments * sizeof(*packet->segments) != doomcom->datalength - BASEPACKETSIZE - sizeof(*packet)) + { + Net_CloseConnection(node); + return; + } + + if (packet->iteration > trans->ackediteration) + { + trans->ackediteration = packet->iteration; + if (trans->ackediteration >= trans->iteration - 1) + trans->dontsenduntil = 0; + } + + for (i = 0; i < packet->numsegments; i++) + { + fileacksegment_t *segment = &packet->segments[i]; + + for (j = 0; j < 32; j++) + if (LONG(segment->acks) & (1 << j)) + { + if (LONG(segment->start) * FILEFRAGMENTSIZE >= trans->txlist->size) + { + Net_CloseConnection(node); + return; + } + + if (!trans->ackedfragments[LONG(segment->start) + j]) + { + trans->ackedfragments[LONG(segment->start) + j] = true; + trans->ackedsize += FILEFRAGMENTSIZE; + + // If the last missing fragment was acked, finish! + if (trans->ackedsize == trans->txlist->size) + { + SV_EndFileSend(node); + return; + } + } + } + } +} + +void PT_FileReceived(void) +{ + filetx_t *trans = transfer[doomcom->remotenode].txlist; + + if (trans && netbuffer->u.filereceived == trans->fileid) + SV_EndFileSend(doomcom->remotenode); +} + +static void SendAckPacket(fileack_pak *packet, UINT8 fileid) +{ + size_t packetsize; + INT32 i; + + packetsize = sizeof(*packet) + packet->numsegments * sizeof(*packet->segments); + + // Finalise the packet + packet->fileid = fileid; + for (i = 0; i < packet->numsegments; i++) + { + packet->segments[i].start = LONG(packet->segments[i].start); + packet->segments[i].acks = LONG(packet->segments[i].acks); + } + + // Send the packet + netbuffer->packettype = PT_FILEACK; + M_Memcpy(&netbuffer->u.fileack, packet, packetsize); + HSendPacket(servernode, false, 0, packetsize); + + // Clear the packet + memset(packet, 0, sizeof(*packet) + 512); +} + +static void AddFragmentToAckPacket(fileack_pak *packet, UINT8 iteration, UINT32 fragmentpos, UINT8 fileid) +{ + fileacksegment_t *segment = &packet->segments[packet->numsegments - 1]; + + packet->iteration = max(packet->iteration, iteration); + + if (packet->numsegments == 0 + || fragmentpos < segment->start + || fragmentpos - segment->start >= 32) + { + // If the packet becomes too big, send it + if ((packet->numsegments + 1) * sizeof(*segment) > 512) + SendAckPacket(packet, fileid); + + packet->numsegments++; + segment = &packet->segments[packet->numsegments - 1]; + segment->start = fragmentpos; + } + + // Set the bit that represents the fragment + segment->acks |= 1 << (fragmentpos - segment->start); +} + +void FileReceiveTicker(void) +{ + INT32 i; + + for (i = 0; i < fileneedednum; i++) + { + fileneeded_t *file = &fileneeded[i]; + + if (file->status == FS_DOWNLOADING) + { + if (lasttimeackpacketsent - I_GetTime() > TICRATE / 2) + SendAckPacket(file->ackpacket, i); + + // When resuming a tranfer, start with telling + // the server what parts we already received + if (file->ackresendposition != UINT32_MAX && file->status == FS_DOWNLOADING) + { + // Acknowledge ~70 MB/s, whichs means the client sends ~18 KB/s + INT32 j; + for (j = 0; j < 2048; j++) + { + if (file->receivedfragments[file->ackresendposition]) + AddFragmentToAckPacket(file->ackpacket, file->iteration, file->ackresendposition, i); + + file->ackresendposition++; + if (file->ackresendposition * file->fragmentsize >= file->totalsize) + { + file->ackresendposition = UINT32_MAX; + break; + } + } + } + } + } +} + +void PT_FileFragment(void) { INT32 filenum = netbuffer->u.filetxpak.fileid; fileneeded_t *file = &fileneeded[filenum]; + UINT32 fragmentpos = LONG(netbuffer->u.filetxpak.position); + UINT16 fragmentsize = SHORT(netbuffer->u.filetxpak.size); + UINT16 boundedfragmentsize = doomcom->datalength - BASEPACKETSIZE - sizeof(netbuffer->u.filetxpak); char *filename; - static INT32 filetime = 0; filename = va("%s", file->filename); nameonly(filename); @@ -995,49 +1238,105 @@ void Got_Filetxpak(void) if (file->status == FS_REQUESTED) { if (file->file) - I_Error("Got_Filetxpak: already open file\n"); - file->file = fopen(filename, file->textmode ? "w" : "wb"); - if (!file->file) - I_Error("Can't create file %s: %s", filename, strerror(errno)); - CONS_Printf("\r%s...\n",filename); - file->currentsize = 0; + I_Error("PT_FileFragment: already open file\n"); + file->status = FS_DOWNLOADING; + file->fragmentsize = fragmentsize; + file->iteration = 0; + + file->ackpacket = calloc(1, sizeof(*file->ackpacket) + 512); + if (!file->ackpacket) + I_Error("FileSendTicker: No more memory\n"); + + if (CL_CanResumeDownload(file)) + { + file->file = fopen(filename, "r+b"); + if (!file->file) + I_Error("Can't reopen file %s: %s", filename, strerror(errno)); + CONS_Printf("\r%s...\n", filename); + + CONS_Printf("Resuming download...\n"); + file->currentsize = pauseddownload->currentsize; + file->receivedfragments = pauseddownload->receivedfragments; + file->ackresendposition = 0; + + free(pauseddownload); + pauseddownload = NULL; + } + 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"); + } + + lasttimeackpacketsent = I_GetTime(); } 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 (size && 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; + if (fragmentpos >= file->totalsize) + I_Error("Invalid file fragment\n"); - // Finished? - if (file->currentsize == file->totalsize) + file->iteration = max(file->iteration, netbuffer->u.filetxpak.iteration); + + if (!file->receivedfragments[fragmentpos / fragmentsize]) // Not received yet { - fclose(file->file); - file->file = NULL; - file->status = FS_FOUND; - CONS_Printf(M_GetText("Downloading %s...(done)\n"), - filename); - if (luafiletransfers) + file->receivedfragments[fragmentpos / fragmentsize] = true; + + // 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)); + file->currentsize += boundedfragmentsize; + + AddFragmentToAckPacket(file->ackpacket, file->iteration, fragmentpos / fragmentsize, filenum); + + // Finished? + if (file->currentsize == file->totalsize) { + fclose(file->file); + file->file = NULL; + free(file->receivedfragments); + 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_HASLUAFILE; - HSendPacket(servernode, true, 0, 0); + netbuffer->packettype = PT_FILERECEIVED; + netbuffer->u.filereceived = filenum; + HSendPacket(servernode, true, 0, 1); + + if (luafiletransfers) + { + // Tell the server we have received the file + netbuffer->packettype = PT_HASLUAFILE; + HSendPacket(servernode, true, 0, 0); + } } } + else // Already received + { + // If they are sending us the fragment again, it's probably because + // they missed our previous ack, so we must re-acknowledge it + AddFragmentToAckPacket(file->ackpacket, file->iteration, fragmentpos / fragmentsize, filenum); + } } - else + else if (!file->justdownloaded) { const char *s; switch(file->status) @@ -1060,12 +1359,6 @@ void Got_Filetxpak(void) } 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; @@ -1078,7 +1371,7 @@ void Got_Filetxpak(void) * \return True if the node is downloading a file * */ -boolean SV_SendingFile(INT32 node) +boolean SendingFile(INT32 node) { return transfer[node].txlist != NULL; } @@ -1107,12 +1400,62 @@ void CloseNetFile(void) if (fileneeded[i].status == FS_DOWNLOADING && fileneeded[i].file) { fclose(fileneeded[i].file); - // File is not complete delete it - remove(fileneeded[i].filename); - } + free(fileneeded[i].ackpacket); - // Remove PT_FILEFRAGMENT from acknowledge list - Net_AbortPacketType(PT_FILEFRAGMENT); + if (!pauseddownload && i != 0) // 0 is either srb2.srb or the gamestate... + { + // Don't remove the file, save it for later in case we resume the download + pauseddownload = malloc(sizeof(*pauseddownload)); + if (!pauseddownload) + I_Error("CloseNetFile: No more memory\n"); + + strcpy(pauseddownload->filename, fileneeded[i].filename); + memcpy(pauseddownload->md5sum, fileneeded[i].md5sum, 16); + pauseddownload->currentsize = fileneeded[i].currentsize; + pauseddownload->receivedfragments = fileneeded[i].receivedfragments; + pauseddownload->fragmentsize = fileneeded[i].fragmentsize; + } + else + { + free(fileneeded[i].receivedfragments); + // File is not complete delete it + remove(fileneeded[i].filename); + } + } +} + +void Command_Downloads_f(void) +{ + INT32 node; + + for (node = 0; node < MAXNETNODES; node++) + if (transfer[node].txlist + && transfer[node].txlist->ram == SF_FILE) // Node is downloading a file? + { + const char *name = transfer[node].txlist->id.filename; + UINT32 position = transfer[node].ackedsize; + UINT32 size = transfer[node].txlist->size; + char ratecolor; + + // Avoid division by zero errors + if (!size) + size = 1; + + name = &name[strlen(name) - nameonlylength(name)]; + switch (4 * (position - 1) / size) + { + case 0: ratecolor = '\x85'; break; + case 1: ratecolor = '\x87'; break; + case 2: ratecolor = '\x82'; break; + case 3: ratecolor = '\x83'; break; + default: ratecolor = '\x80'; + } + + CONS_Printf("%2d %c%s ", node, ratecolor, name); // Node and file name + CONS_Printf("\x80%uK\x84/\x80%uK ", position / 1024, size / 1024); // Progress in kB + CONS_Printf("\x80(%c%u%%\x80) ", ratecolor, (UINT32)(100.0 * position / size)); // Progress in % + CONS_Printf("%s\n", I_GetNodeAddress(node)); // Address and newline + } } // Functions cut and pasted from Doomatic :) diff --git a/src/d_netfil.h b/src/d_netfil.h index 7d6efada0..2225157cb 100644 --- a/src/d_netfil.h +++ b/src/d_netfil.h @@ -14,6 +14,7 @@ #define __D_NETFIL__ #include "d_net.h" +#include "d_clisrv.h" #include "w_wad.h" typedef enum @@ -39,12 +40,18 @@ typedef struct UINT8 willsend; // Is the server willing to send it? char filename[MAX_WADPATH]; UINT8 md5sum[16]; + filestatus_t status; // The value returned by recsearch + boolean justdownloaded; // To prevent late fragments from causing an I_Error + // Used only for download FILE *file; + boolean *receivedfragments; + UINT32 fragmentsize; + UINT8 iteration; + fileack_pak *ackpacket; UINT32 currentsize; UINT32 totalsize; - filestatus_t status; // The value returned by recsearch - boolean textmode; // For files requested by Lua without the "b" option + UINT32 ackresendposition; // Used when resuming downloads } fileneeded_t; extern INT32 fileneedednum; @@ -61,16 +68,20 @@ void CL_PrepareDownloadSaveGame(const char *tmpsave); INT32 CL_CheckFiles(void); void CL_LoadServerFiles(void); -void SV_SendRam(INT32 node, void *data, size_t size, freemethod_t freemethod, +void AddRamToSendQueue(INT32 node, void *data, size_t size, freemethod_t freemethod, UINT8 fileid); -void SV_FileSendTicker(void); -void Got_Filetxpak(void); -boolean SV_SendingFile(INT32 node); +void FileSendTicker(void); +void PT_FileAck(void); +void PT_FileReceived(void); +boolean SendingFile(INT32 node); + +void FileReceiveTicker(void); +void PT_FileFragment(void); boolean CL_CheckDownloadable(void); -boolean CL_SendRequestFile(void); -boolean Got_RequestFilePak(INT32 node); +boolean CL_SendFileRequest(void); +boolean PT_RequestFile(INT32 node); typedef enum { @@ -86,18 +97,19 @@ typedef struct luafiletransfer_s char *realfilename; char mode[4]; // rb+/wb+/ab+ + null character INT32 id; // Callback ID + boolean ongoing; luafiletransfernodestatus_t nodestatus[MAXNETNODES]; struct luafiletransfer_s *next; } luafiletransfer_t; extern luafiletransfer_t *luafiletransfers; extern boolean waitingforluafiletransfer; +extern boolean waitingforluafilecommand; extern char luafiledir[256 + 16]; void AddLuaFileTransfer(const char *filename, const char *mode); -void SV_PrepareSendLuaFileToNextNode(void); -boolean SV_SendLuaFile(INT32 node, const char *filename, boolean textmode); -void SV_PrepareSendLuaFile(const char *filename); +void SV_PrepareSendLuaFile(void); +boolean AddLuaFileToSendQueue(INT32 node, const char *filename); void SV_HandleLuaFileSent(UINT8 node); void RemoveLuaFileTransfer(void); void RemoveAllLuaFileTransfers(void); @@ -110,6 +122,9 @@ void MakePathDirs(char *path); void SV_AbortSendFiles(INT32 node); void CloseNetFile(void); +void CL_AbortDownloadResume(void); + +void Command_Downloads_f(void); boolean fileexist(char *filename, time_t ptime); diff --git a/src/i_tcp.c b/src/i_tcp.c index 373ea1bd0..5180869a5 100644 --- a/src/i_tcp.c +++ b/src/i_tcp.c @@ -487,7 +487,7 @@ static void cleanupnodes(void) // Why can't I start at zero? for (j = 1; j < MAXNETNODES; j++) - if (!(nodeingame[j] || SV_SendingFile(j))) + if (!(nodeingame[j] || SendingFile(j))) nodeconnected[j] = false; } diff --git a/src/m_misc.c b/src/m_misc.c index 920a13198..c527d2296 100644 --- a/src/m_misc.c +++ b/src/m_misc.c @@ -297,6 +297,44 @@ size_t FIL_ReadFileTag(char const *name, UINT8 **buffer, INT32 tag) return length; } +/** Makes a copy of a text file with all newlines converted into LF newlines. + * + * \param textfilename The name of the source file + * \param binfilename The name of the destination file + */ +boolean FIL_ConvertTextFileToBinary(const char *textfilename, const char *binfilename) +{ + FILE *textfile; + FILE *binfile; + UINT8 buffer[1024]; + size_t count; + boolean success; + + textfile = fopen(textfilename, "r"); + if (!textfile) + return false; + + binfile = fopen(binfilename, "wb"); + if (!binfile) + { + fclose(textfile); + return false; + } + + do + { + count = fread(buffer, 1, sizeof(buffer), textfile); + fwrite(buffer, 1, count, binfile); + } while (count); + + success = !(ferror(textfile) || ferror(binfile)); + + fclose(textfile); + fclose(binfile); + + return success; +} + /** Check if the filename exists * * \param name Filename to check. diff --git a/src/m_misc.h b/src/m_misc.h index d64faea59..dbded37d0 100644 --- a/src/m_misc.h +++ b/src/m_misc.h @@ -48,6 +48,8 @@ boolean FIL_WriteFile(char const *name, const void *source, size_t length); size_t FIL_ReadFileTag(char const *name, UINT8 **buffer, INT32 tag); #define FIL_ReadFile(n, b) FIL_ReadFileTag(n, b, PU_STATIC) +boolean FIL_ConvertTextFileToBinary(const char *textfilename, const char *binfilename); + boolean FIL_FileExists(const char *name); boolean FIL_WriteFileOK(char const *name); boolean FIL_ReadFileOK(char const *name); diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index 4fdcec87e..e358308b4 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -295,6 +295,7 @@ static void I_ReportSignal(int num, int coredumped) FUNCNORETURN static ATTRNORETURN void signal_handler(INT32 num) { D_QuitNetGame(); // Fix server freezes + CL_AbortDownloadResume(); I_ReportSignal(num, 0); I_ShutdownSystem(); signal(num, SIG_DFL); //default signal action @@ -2295,6 +2296,7 @@ void I_Quit(void) G_StopMetalRecording(false); D_QuitNetGame(); + CL_AbortDownloadResume(); M_FreePlayerSetupColors(); I_ShutdownMusic(); I_ShutdownSound(); @@ -2412,6 +2414,7 @@ void I_Error(const char *error, ...) G_StopMetalRecording(false); D_QuitNetGame(); + CL_AbortDownloadResume(); M_FreePlayerSetupColors(); I_ShutdownMusic(); I_ShutdownSound(); diff --git a/src/win32/win_sys.c b/src/win32/win_sys.c index 6c4ff4484..a374a2587 100644 --- a/src/win32/win_sys.c +++ b/src/win32/win_sys.c @@ -467,6 +467,7 @@ static void signal_handler(int num) char sigdef[64]; D_QuitNetGame(); // Fix server freezes + CL_AbortDownloadResume(); I_ShutdownSystem(); switch (num) @@ -652,6 +653,7 @@ void I_Error(const char *error, ...) G_StopMetalRecording(false); D_QuitNetGame(); + CL_AbortDownloadResume(); M_FreePlayerSetupColors(); // shutdown everything that was started @@ -748,6 +750,7 @@ void I_Quit(void) // or something else that will be finished by I_ShutdownSystem(), // so do it before. D_QuitNetGame(); + CL_AbortDownloadResume(); M_FreePlayerSetupColors();