From 061fd4761a0c752c56163ee039c31c646b811c7a Mon Sep 17 00:00:00 2001 From: Louis-Antoine Date: Tue, 12 May 2020 19:06:40 +0200 Subject: [PATCH 01/12] Rename some file transfer functions --- src/d_clisrv.c | 16 ++++++++-------- src/d_netfil.c | 49 +++++++++++++++++++++++++------------------------ src/d_netfil.h | 14 +++++++------- src/i_tcp.c | 2 +- 4 files changed, 41 insertions(+), 40 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 43321d92d..a0a554609 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1598,7 +1598,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 @@ -1978,7 +1978,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; } } @@ -2106,7 +2106,7 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic // 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 @@ -3911,13 +3911,13 @@ static void HandlePacketFromAwayNode(SINT8 node) break; } SERVERONLY - Got_Filetxpak(); + PT_FileFragment(); 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 @@ -4216,7 +4216,7 @@ static void HandlePacketFromPlayer(SINT8 node) { char *name = va("%s" PATHSEP "%s", luafiledir, luafiletransfers->filename); boolean textmode = !strchr(luafiletransfers->mode, 'b'); - SV_SendLuaFile(node, name, textmode); + AddLuaFileToSendQueue(node, name, textmode); } break; case PT_HASLUAFILE: @@ -4348,7 +4348,7 @@ static void HandlePacketFromPlayer(SINT8 node) break; } if (client) - Got_Filetxpak(); + PT_FileFragment(); break; case PT_SENDINGLUAFILE: if (client) @@ -5073,7 +5073,7 @@ void NetUpdate(void) CON_Ticker(); } - SV_FileSendTicker(); + FileSendTicker(); } /** Returns the number of players playing. diff --git a/src/d_netfil.c b/src/d_netfil.c index 6d3ac7f9d..ec0a9b66c 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 @@ -253,7 +253,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 +298,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 +309,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 @@ -621,11 +621,11 @@ 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 + * \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 +643,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 +651,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); @@ -712,11 +712,11 @@ static boolean SV_SendFile(INT32 node, const char *filename, UINT8 fileid) * \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 + * \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 +729,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 +749,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, boolean textmode) { 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 +770,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,7 +778,7 @@ 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); // !!! @@ -804,7 +804,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 @@ -842,7 +843,7 @@ static void SV_EndFileSend(INT32 node) * especially when the one downloading has high latency * */ -void SV_FileSendTicker(void) +void FileSendTicker(void) { static INT32 currentnode = 0; filetx_pak *p; @@ -938,7 +939,7 @@ void SV_FileSendTicker(void) 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)); + I_Error("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); @@ -965,7 +966,7 @@ void SV_FileSendTicker(void) } } -void Got_Filetxpak(void) +void PT_FileFragment(void) { INT32 filenum = netbuffer->u.filetxpak.fileid; fileneeded_t *file = &fileneeded[filenum]; @@ -995,7 +996,7 @@ void Got_Filetxpak(void) if (file->status == FS_REQUESTED) { if (file->file) - I_Error("Got_Filetxpak: already open file\n"); + I_Error("PT_FileFragment: 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)); @@ -1078,7 +1079,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; } diff --git a/src/d_netfil.h b/src/d_netfil.h index 7d6efada0..f212e16f8 100644 --- a/src/d_netfil.h +++ b/src/d_netfil.h @@ -61,16 +61,16 @@ 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_FileFragment(void); +boolean SendingFile(INT32 node); boolean CL_CheckDownloadable(void); -boolean CL_SendRequestFile(void); -boolean Got_RequestFilePak(INT32 node); +boolean CL_SendFileRequest(void); +boolean PT_RequestFile(INT32 node); typedef enum { @@ -96,7 +96,7 @@ 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); +boolean AddLuaFileToSendQueue(INT32 node, const char *filename, boolean textmode); void SV_PrepareSendLuaFile(const char *filename); void SV_HandleLuaFileSent(UINT8 node); void RemoveLuaFileTransfer(void); 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; } From bf660dd35af7fe9e17e2641d3b82be9722d869c5 Mon Sep 17 00:00:00 2001 From: Louis-Antoine Date: Tue, 12 May 2020 19:58:16 +0200 Subject: [PATCH 02/12] Rename local variables --- src/d_netfil.c | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/d_netfil.c b/src/d_netfil.c index ec0a9b66c..f9d049be5 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -847,7 +847,7 @@ 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; @@ -926,33 +926,33 @@ void FileSendTicker(void) // 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 = software_MAXPACKETLENGTH - (FILETXHEADER + BASEPACKETSIZE); + 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 + size_t n = fread(p->data, 1, fragmentsize, transfer[i].currentfile); + if (n != fragmentsize) // 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("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)); + fragmentsize = n; + else 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->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))) + if (transfer[i].position + fragmentsize == f->size || (f->textmode && feof(transfer[i].currentfile))) p->position |= LONG(0x80000000); p->fileid = f->fileid; - p->size = SHORT((UINT16)size); + p->size = SHORT((UINT16)fragmentsize); // Send the packet - if (HSendPacket(i, true, 0, FILETXHEADER + size)) // Reliable SEND + if (HSendPacket(i, true, 0, FILETXHEADER + fragmentsize)) // Reliable SEND { // Success - transfer[i].position = (UINT32)(transfer[i].position + size); + transfer[i].position = (UINT32)(transfer[i].position + fragmentsize); if (transfer[i].position == f->size || (f->textmode && feof(transfer[i].currentfile))) // Finish? SV_EndFileSend(i); } @@ -1007,20 +1007,20 @@ void PT_FileFragment(void) if (file->status == FS_DOWNLOADING) { - UINT32 pos = LONG(netbuffer->u.filetxpak.position); - UINT16 size = SHORT(netbuffer->u.filetxpak.size); + UINT32 fragmentpos = LONG(netbuffer->u.filetxpak.position); + UINT16 fragmentsize = 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) + if (fragmentpos & 0x80000000) { - pos &= ~0x80000000; - file->totalsize = pos + size; + fragmentpos &= ~0x80000000; + file->totalsize = fragmentpos + fragmentsize; } // 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) + fseek(file->file, fragmentpos, SEEK_SET); + if (fragmentsize && fwrite(netbuffer->u.filetxpak.data,fragmentsize,1,file->file) != 1) I_Error("Can't write to %s: %s\n",filename, M_FileError(file->file)); - file->currentsize += size; + file->currentsize += fragmentsize; // Finished? if (file->currentsize == file->totalsize) From 3c7c758d176f6f3fb1f92da6e15799f839825080 Mon Sep 17 00:00:00 2001 From: Louis-Antoine Date: Sat, 16 May 2020 22:09:00 +0200 Subject: [PATCH 03/12] Rewrite file transfer code This code uses a custom packet acknowledgement system, which is more suited for file transfer and does not suffer from the small sender window used by the default acknowledgement system --- src/d_clisrv.c | 21 ++++ src/d_clisrv.h | 21 +++- src/d_net.c | 3 + src/d_netfil.c | 270 +++++++++++++++++++++++++++++++++++++------------ src/d_netfil.h | 11 +- 5 files changed, 262 insertions(+), 64 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index a0a554609..a83d3033e 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -2103,6 +2103,9 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic return false; } + 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; @@ -3914,6 +3917,16 @@ static void HandlePacketFromAwayNode(SINT8 node) PT_FileFragment(); break; + case PT_FILEACK: + if (server) + PT_FileAck(); + break; + + case PT_FILERECEIVED: + if (server) + PT_FileReceived(); + break; + case PT_REQUESTFILE: if (server) { @@ -4350,6 +4363,14 @@ static void HandlePacketFromPlayer(SINT8 node) if (client) PT_FileFragment(); break; + case PT_FILEACK: + if (server) + PT_FileAck(); + break; + case PT_FILERECEIVED: + if (server) + PT_FileReceived(); + break; case PT_SENDINGLUAFILE: if (client) CL_PrepareDownloadLuaFile(); diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 463240a2a..cbae55017 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -75,6 +75,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. @@ -320,13 +322,28 @@ typedef struct UINT8 varlengthinputs[0]; // Playernames and netvars } ATTRPACK serverconfig_pak; -typedef struct { +typedef struct +{ UINT8 fileid; + UINT32 filesize; 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 numsegments; + fileacksegment_t segments[0]; +} ATTRPACK fileack_pak; + #ifdef _MSC_VER #pragma warning(default : 4200) #endif @@ -442,6 +459,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 1db75f3da..c0f97ca87 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_netfil.c b/src/d_netfil.c index f9d049be5..9dc09a15f 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -78,6 +78,8 @@ typedef struct filetran_s { filetx_t *txlist; // Linked list of all files for the node UINT32 position; // The current position in the file + boolean *ackedfragments; + UINT32 ackedsize; FILE *currentfile; // The file currently being sent/received } filetran_t; static filetran_t transfer[MAXNETNODES]; @@ -88,6 +90,7 @@ 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"; #ifdef CLIENT_LOADINGSCREEN @@ -159,6 +162,7 @@ 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 @@ -173,6 +177,7 @@ void CL_PrepareDownloadSaveGame(const char *tmpsave) { 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); @@ -602,6 +607,7 @@ void CL_PrepareDownloadLuaFile(void) 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); @@ -830,17 +836,17 @@ 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 FileSendTicker(void) @@ -850,23 +856,12 @@ void FileSendTicker(void) 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; @@ -883,11 +878,12 @@ void 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; @@ -921,19 +917,37 @@ void 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].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"); + } + + // Find the first non-acknowledged fragment + while (transfer[i].ackedfragments[transfer[i].position / FILEFRAGMENTSIZE]) + { + transfer[i].position += FILEFRAGMENTSIZE; + if (transfer[i].position >= f->size) + transfer[i].position = 0; } // Build a packet containing a file fragment p = &netbuffer->u.filetxpak; - fragmentsize = software_MAXPACKETLENGTH - (FILETXHEADER + BASEPACKETSIZE); + 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], fragmentsize); else { - size_t n = fread(p->data, 1, fragmentsize, transfer[i].currentfile); + size_t n; + + fseek(transfer[i].currentfile, transfer[i].position, SEEK_SET); + + n = fread(p->data, 1, fragmentsize, transfer[i].currentfile); if (n != fragmentsize) // Either an error or Windows turning CR-LF into LF { if (f->textmode && feof(transfer[i].currentfile)) @@ -943,35 +957,145 @@ void FileSendTicker(void) } } p->position = LONG(transfer[i].position); - // Put flag so receiver knows the total size - if (transfer[i].position + fragmentsize == f->size || (f->textmode && feof(transfer[i].currentfile))) - p->position |= LONG(0x80000000); p->fileid = f->fileid; - p->size = SHORT((UINT16)fragmentsize); + p->filesize = LONG(f->size); + p->size = SHORT((UINT16)FILEFRAGMENTSIZE); // Send the packet - if (HSendPacket(i, true, 0, FILETXHEADER + fragmentsize)) // Reliable SEND + if (HSendPacket(i, false, 0, FILETXHEADER + fragmentsize)) // Don't use the default acknowledgement system { // Success transfer[i].position = (UINT32)(transfer[i].position + fragmentsize); - if (transfer[i].position == f->size || (f->textmode && feof(transfer[i].currentfile))) // Finish? - SV_EndFileSend(i); + if (transfer[i].position >= f->size) + transfer[i].position = 0; } 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 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; + } + + 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, UINT32 fragmentpos, UINT8 fileid) +{ + fileacksegment_t *segment = &packet->segments[packet->numsegments - 1]; + + 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; + + if (lasttimeackpacketsent - I_GetTime() > TICRATE / 2) + for (i = 0; i < fileneedednum; i++) + if (fileneeded[i].status == FS_DOWNLOADING) + { + SendAckPacket(fileneeded[i].ackpacket, i); + 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); @@ -997,48 +1121,74 @@ void PT_FileFragment(void) { if (file->file) I_Error("PT_FileFragment: 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; file->status = FS_DOWNLOADING; + + file->totalsize = LONG(netbuffer->u.filetxpak.filesize); + + file->receivedfragments = calloc(file->totalsize / fragmentsize + 1, sizeof(*file->receivedfragments)); + file->ackpacket = calloc(1, sizeof(*file->ackpacket) + 512); + if (!(file->receivedfragments && file->ackpacket)) + I_Error("FileSendTicker: No more memory\n"); + lasttimeackpacketsent = I_GetTime(); } if (file->status == FS_DOWNLOADING) { - UINT32 fragmentpos = LONG(netbuffer->u.filetxpak.position); - UINT16 fragmentsize = 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 (fragmentpos & 0x80000000) - { - fragmentpos &= ~0x80000000; - file->totalsize = fragmentpos + fragmentsize; - } - // We can receive packet in the wrong order, anyway all os support gaped file - fseek(file->file, fragmentpos, SEEK_SET); - if (fragmentsize && fwrite(netbuffer->u.filetxpak.data,fragmentsize,1,file->file) != 1) - I_Error("Can't write to %s: %s\n",filename, M_FileError(file->file)); - file->currentsize += fragmentsize; + if (fragmentpos >= file->totalsize) + I_Error("Invalid file fragment\n"); - // Finished? - if (file->currentsize == file->totalsize) + 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, 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, fragmentpos / fragmentsize, filenum); + } } - else + else if (!file->justdownloaded) { const char *s; switch(file->status) @@ -1061,12 +1211,6 @@ void PT_FileFragment(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; @@ -1108,6 +1252,8 @@ void CloseNetFile(void) if (fileneeded[i].status == FS_DOWNLOADING && fileneeded[i].file) { fclose(fileneeded[i].file); + free(fileneeded[i].receivedfragments); + free(fileneeded[i].ackpacket); // File is not complete delete it remove(fileneeded[i].filename); } diff --git a/src/d_netfil.h b/src/d_netfil.h index f212e16f8..c87ff6f8c 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 @@ -41,9 +42,13 @@ typedef struct UINT8 md5sum[16]; // Used only for download FILE *file; + boolean *receivedfragments; + fileack_pak *ackpacket; + tic_t lasttimeackpacketsent; UINT32 currentsize; UINT32 totalsize; filestatus_t status; // The value returned by recsearch + boolean justdownloaded; // To prevent late fragments from causing an I_Error boolean textmode; // For files requested by Lua without the "b" option } fileneeded_t; @@ -65,9 +70,13 @@ void AddRamToSendQueue(INT32 node, void *data, size_t size, freemethod_t freemet UINT8 fileid); void FileSendTicker(void); -void PT_FileFragment(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_SendFileRequest(void); boolean PT_RequestFile(INT32 node); From 66ecfb741a34179662b5a536baf75f91b91331d1 Mon Sep 17 00:00:00 2001 From: Louis-Antoine Date: Sat, 16 May 2020 22:49:20 +0200 Subject: [PATCH 04/12] Show total size when downloading gamestate --- src/d_clisrv.c | 19 ++++++++++++++++--- src/d_netfil.c | 4 ++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index a83d3033e..089d967af 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1180,8 +1180,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-24, 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-24, 16, 8, palstart + ((animtime - i) & 15)); switch (cl_mode) { @@ -1189,10 +1190,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-24, 256, 8, 111); + V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, dldlength, 8, 96); + V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE, - va(" %4uK",fileneeded[lastfilenum].currentsize>>10)); + va(" %4uK/%4uK",currentsize>>10,totalsize>>10)); + V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE, va("%3.1fK/s ", ((double)getbps)/1024)); } diff --git a/src/d_netfil.c b/src/d_netfil.c index 9dc09a15f..690a5e162 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -175,6 +175,10 @@ void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr) void CL_PrepareDownloadSaveGame(const char *tmpsave) { +#ifdef CLIENT_LOADINGSCREEN + lastfilenum = -1; +#endif + fileneedednum = 1; fileneeded[0].status = FS_REQUESTED; fileneeded[0].justdownloaded = false; From db85c62c6fb2a5066a3ce37a6f99a4df93518520 Mon Sep 17 00:00:00 2001 From: Louis-Antoine Date: Tue, 19 May 2020 11:28:24 +0200 Subject: [PATCH 05/12] Allow resuming the most recent file transfer --- src/d_netfil.c | 146 +++++++++++++++++++++++++++++++++++++------- src/d_netfil.h | 4 +- src/sdl/i_system.c | 3 + src/win32/win_sys.c | 3 + 4 files changed, 132 insertions(+), 24 deletions(-) diff --git a/src/d_netfil.c b/src/d_netfil.c index 690a5e162..b5647ebe2 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -93,6 +93,17 @@ 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; @@ -255,6 +266,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. * @@ -630,7 +666,7 @@ static INT32 filestosend = 0; * * \param node The node to send the file to * \param filename The file to send - * \param fileid ??? + * \param fileid The index of the file in the list of added files * \sa AddRamToSendQueue * \sa AddLuaFileToSendQueue * @@ -721,7 +757,7 @@ static boolean AddFileToSendQueue(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 ??? + * \param fileid The index of the file in the list of added files * \sa AddFileToSendQueue * \sa AddLuaFileToSendQueue * @@ -1083,13 +1119,36 @@ void FileReceiveTicker(void) { INT32 i; - if (lasttimeackpacketsent - I_GetTime() > TICRATE / 2) - for (i = 0; i < fileneedednum; i++) - if (fileneeded[i].status == FS_DOWNLOADING) + 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) { - SendAckPacket(fileneeded[i].ackpacket, i); - break; + // 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->ackresendposition, i); + + file->ackresendposition++; + if (file->ackresendposition * file->fragmentsize >= file->totalsize) + { + file->ackresendposition = UINT32_MAX; + break; + } + } } + } + } } void PT_FileFragment(void) @@ -1126,20 +1185,47 @@ void PT_FileFragment(void) if (file->file) I_Error("PT_FileFragment: 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; file->status = FS_DOWNLOADING; + file->fragmentsize = fragmentsize; - file->totalsize = LONG(netbuffer->u.filetxpak.filesize); - - file->receivedfragments = calloc(file->totalsize / fragmentsize + 1, sizeof(*file->receivedfragments)); file->ackpacket = calloc(1, sizeof(*file->ackpacket) + 512); - if (!(file->receivedfragments && file->ackpacket)) + if (!file->ackpacket) I_Error("FileSendTicker: No more memory\n"); + + if (CL_CanResumeDownload(file)) + { + file->file = fopen(filename, file->textmode ? "r+" : "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, 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; + 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(); } @@ -1256,14 +1342,28 @@ void CloseNetFile(void) if (fileneeded[i].status == FS_DOWNLOADING && fileneeded[i].file) { fclose(fileneeded[i].file); - free(fileneeded[i].receivedfragments); free(fileneeded[i].ackpacket); - // File is not complete delete it - remove(fileneeded[i].filename); - } - // 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); + } + } } // Functions cut and pasted from Doomatic :) diff --git a/src/d_netfil.h b/src/d_netfil.h index c87ff6f8c..171b3920f 100644 --- a/src/d_netfil.h +++ b/src/d_netfil.h @@ -43,10 +43,11 @@ typedef struct // Used only for download FILE *file; boolean *receivedfragments; + UINT32 fragmentsize; fileack_pak *ackpacket; - tic_t lasttimeackpacketsent; UINT32 currentsize; UINT32 totalsize; + UINT32 ackresendposition; // Used when resuming downloads filestatus_t status; // The value returned by recsearch boolean justdownloaded; // To prevent late fragments from causing an I_Error boolean textmode; // For files requested by Lua without the "b" option @@ -119,6 +120,7 @@ void MakePathDirs(char *path); void SV_AbortSendFiles(INT32 node); void CloseNetFile(void); +void CL_AbortDownloadResume(void); boolean fileexist(char *filename, time_t ptime); diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index d2ed62516..e5cdb3206 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -293,6 +293,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 @@ -2293,6 +2294,7 @@ void I_Quit(void) G_StopMetalRecording(false); D_QuitNetGame(); + CL_AbortDownloadResume(); I_ShutdownMusic(); I_ShutdownSound(); I_ShutdownCD(); @@ -2409,6 +2411,7 @@ void I_Error(const char *error, ...) G_StopMetalRecording(false); D_QuitNetGame(); + CL_AbortDownloadResume(); I_ShutdownMusic(); I_ShutdownSound(); I_ShutdownCD(); diff --git a/src/win32/win_sys.c b/src/win32/win_sys.c index f9d66be7f..fb0742c5a 100644 --- a/src/win32/win_sys.c +++ b/src/win32/win_sys.c @@ -465,6 +465,7 @@ static void signal_handler(int num) char sigdef[64]; D_QuitNetGame(); // Fix server freezes + CL_AbortDownloadResume(); I_ShutdownSystem(); switch (num) @@ -650,6 +651,7 @@ void I_Error(const char *error, ...) G_StopMetalRecording(false); D_QuitNetGame(); + CL_AbortDownloadResume(); // shutdown everything that was started I_ShutdownSystem(); @@ -745,6 +747,7 @@ void I_Quit(void) // or something else that will be finished by I_ShutdownSystem(), // so do it before. D_QuitNetGame(); + CL_AbortDownloadResume(); // shutdown everything that was started I_ShutdownSystem(); From 06d3af671634779a640c15a3fe9f9c953635aaa6 Mon Sep 17 00:00:00 2001 From: Louis-Antoine Date: Tue, 19 May 2020 15:16:51 +0200 Subject: [PATCH 06/12] Refactor Lua file transfer code --- src/blua/liolib.c | 11 +---------- src/d_netfil.c | 43 ++++++++++++++++++++++--------------------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/src/blua/liolib.c b/src/blua/liolib.c index b43052194..ce630a20b 100644 --- a/src/blua/liolib.c +++ b/src/blua/liolib.c @@ -314,16 +314,7 @@ void Got_LuaFile(UINT8 **cp, INT32 playernum) RemoveLuaFileTransfer(); if (server && luafiletransfers) - { - 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); - } - } + SV_PrepareSendLuaFile(); } diff --git a/src/d_netfil.c b/src/d_netfil.c index b5647ebe2..2262a1f93 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -538,26 +538,9 @@ 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(); // Store the callback so it can be called once everyone has the file filetransfer->id = id; @@ -571,7 +554,7 @@ void AddLuaFileTransfer(const char *filename, const char *mode) } } -void SV_PrepareSendLuaFileToNextNode(void) +static void SV_PrepareSendLuaFileToNextNode(void) { INT32 i; UINT8 success = 1; @@ -595,6 +578,24 @@ void SV_PrepareSendLuaFileToNextNode(void) SendNetXCmd(XD_LUAFILE, &success, 1); } +void SV_PrepareSendLuaFile(void) +{ + INT32 i; + + // Set status to "waiting" for everyone + for (i = 0; i < MAXNETNODES; i++) + luafiletransfers->nodestatus[i] = LFTNS_WAITING; + + if (FIL_ReadFileOK(luafiletransfers->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); + } +} + void SV_HandleLuaFileSent(UINT8 node) { luafiletransfers->nodestatus[node] = LFTNS_SENT; From 34c5da39e25a4ecffea860d08086861c31b93d8b Mon Sep 17 00:00:00 2001 From: Louis-Antoine Date: Tue, 19 May 2020 20:00:58 +0200 Subject: [PATCH 07/12] Create FIL_ConvertTextFileToBinary --- src/m_misc.c | 38 ++++++++++++++++++++++++++++++++++++++ src/m_misc.h | 2 ++ 2 files changed, 40 insertions(+) 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); From f620b52672870e82684e0d87ba0c73e1889315c5 Mon Sep 17 00:00:00 2001 From: Louis-Antoine Date: Tue, 19 May 2020 21:36:21 +0200 Subject: [PATCH 08/12] Fix sending Lua files in text mode --- src/blua/liolib.c | 10 ++++++++- src/d_clisrv.c | 6 +----- src/d_netfil.c | 52 ++++++++++++++++++++++++++--------------------- src/d_netfil.h | 6 ++---- 4 files changed, 41 insertions(+), 33 deletions(-) diff --git a/src/blua/liolib.c b/src/blua/liolib.c index ce630a20b..2ccfa70e8 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 } diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 089d967af..275eb790e 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -4239,11 +4239,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'); - AddLuaFileToSendQueue(node, name, textmode); - } + AddLuaFileToSendQueue(node, luafiletransfers->realfilename); break; case PT_HASLUAFILE: if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_SENDING) diff --git a/src/d_netfil.c b/src/d_netfil.c index 2262a1f93..da190bc5c 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -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; @@ -180,7 +179,6 @@ void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr) 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; } } @@ -197,7 +195,6 @@ void CL_PrepareDownloadSaveGame(const char *tmpsave) 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, @@ -507,8 +504,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) @@ -580,6 +575,7 @@ static void SV_PrepareSendLuaFileToNextNode(void) void SV_PrepareSendLuaFile(void) { + char *binfilename; INT32 i; // Set status to "waiting" for everyone @@ -587,7 +583,25 @@ void SV_PrepareSendLuaFile(void) 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 @@ -606,6 +620,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; @@ -653,7 +671,6 @@ void CL_PrepareDownloadLuaFile(void) 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); @@ -800,7 +817,7 @@ void AddRamToSendQueue(INT32 node, void *data, size_t size, freemethod_t freemet * \sa AddRamToSendQueue * */ -boolean AddLuaFileToSendQueue(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 @@ -831,9 +848,6 @@ boolean AddLuaFileToSendQueue(INT32 node, const char *filename, boolean textmode 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 @@ -937,7 +951,7 @@ void 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", @@ -984,18 +998,10 @@ void FileSendTicker(void) M_Memcpy(p->data, &f->id.ram[transfer[i].position], fragmentsize); else { - size_t n; - fseek(transfer[i].currentfile, transfer[i].position, SEEK_SET); - n = fread(p->data, 1, fragmentsize, transfer[i].currentfile); - if (n != fragmentsize) // Either an error or Windows turning CR-LF into LF - { - if (f->textmode && feof(transfer[i].currentfile)) - fragmentsize = n; - else 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)); - } + 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->position = LONG(transfer[i].position); p->fileid = f->fileid; @@ -1195,7 +1201,7 @@ void PT_FileFragment(void) if (CL_CanResumeDownload(file)) { - file->file = fopen(filename, file->textmode ? "r+" : "r+b"); + 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); @@ -1212,7 +1218,7 @@ void PT_FileFragment(void) { CL_AbortDownloadResume(); - file->file = fopen(filename, file->textmode ? "w" : "wb"); + file->file = fopen(filename, "wb"); if (!file->file) I_Error("Can't create file %s: %s", filename, strerror(errno)); diff --git a/src/d_netfil.h b/src/d_netfil.h index 171b3920f..dce2c929f 100644 --- a/src/d_netfil.h +++ b/src/d_netfil.h @@ -50,7 +50,6 @@ typedef struct UINT32 ackresendposition; // Used when resuming downloads filestatus_t status; // The value returned by recsearch boolean justdownloaded; // To prevent late fragments from causing an I_Error - boolean textmode; // For files requested by Lua without the "b" option } fileneeded_t; extern INT32 fileneedednum; @@ -105,9 +104,8 @@ extern boolean waitingforluafiletransfer; extern char luafiledir[256 + 16]; void AddLuaFileTransfer(const char *filename, const char *mode); -void SV_PrepareSendLuaFileToNextNode(void); -boolean AddLuaFileToSendQueue(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); From c8948909d37e1421d4faf7959ab9be29ec4277a4 Mon Sep 17 00:00:00 2001 From: Louis-Antoine Date: Tue, 19 May 2020 23:50:37 +0200 Subject: [PATCH 09/12] Fix I_Error when queuing multiple Lua files --- src/blua/liolib.c | 6 ++++++ src/d_clisrv.c | 1 + src/d_netfil.c | 13 +++++++++++++ src/d_netfil.h | 2 ++ 4 files changed, 22 insertions(+) diff --git a/src/blua/liolib.c b/src/blua/liolib.c index 2ccfa70e8..a055aad3f 100644 --- a/src/blua/liolib.c +++ b/src/blua/liolib.c @@ -321,6 +321,12 @@ void Got_LuaFile(UINT8 **cp, INT32 playernum) RemoveLuaFileTransfer(); + if (waitingforluafilecommand) + { + waitingforluafilecommand = false; + CL_PrepareDownloadLuaFile(); + } + if (server && luafiletransfers) SV_PrepareSendLuaFile(); } diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 275eb790e..dbf10d58e 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -3256,6 +3256,7 @@ void D_QuitNetGame(void) CloseNetFile(); RemoveAllLuaFileTransfers(); waitingforluafiletransfer = false; + waitingforluafilecommand = false; if (server) { diff --git a/src/d_netfil.c b/src/d_netfil.c index da190bc5c..c5f3d8658 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -110,6 +110,7 @@ INT32 lastfilenum = -1; luafiletransfer_t *luafiletransfers = NULL; boolean waitingforluafiletransfer = false; +boolean waitingforluafilecommand = false; char luafiledir[256 + 16] = "luafiles"; @@ -536,6 +537,8 @@ void AddLuaFileTransfer(const char *filename, const char *mode) // 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; @@ -578,6 +581,8 @@ 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; @@ -660,6 +665,12 @@ 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); @@ -674,6 +685,8 @@ void CL_PrepareDownloadLuaFile(void) // Make sure all directories in the file path exist MakePathDirs(fileneeded[0].filename); + + luafiletransfers->ongoing = true; } // Number of files to send diff --git a/src/d_netfil.h b/src/d_netfil.h index dce2c929f..be58f807f 100644 --- a/src/d_netfil.h +++ b/src/d_netfil.h @@ -95,12 +95,14 @@ 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); From 06060c02d38d174031e2d0fc1488f44920323611 Mon Sep 17 00:00:00 2001 From: Louis-Antoine Date: Wed, 20 May 2020 00:24:53 +0200 Subject: [PATCH 10/12] Add a command to list current file transfers --- src/d_netcmd.c | 2 ++ src/d_netfil.c | 34 ++++++++++++++++++++++++++++++++++ src/d_netfil.h | 2 ++ 3 files changed, 38 insertions(+) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index dfc7351f5..042b99cc7 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -501,6 +501,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 c5f3d8658..9e43da791 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -1386,6 +1386,40 @@ void CloseNetFile(void) } } +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].position; + 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 :) void nameonly(char *s) diff --git a/src/d_netfil.h b/src/d_netfil.h index be58f807f..a653d5a4b 100644 --- a/src/d_netfil.h +++ b/src/d_netfil.h @@ -122,6 +122,8 @@ void SV_AbortSendFiles(INT32 node); void CloseNetFile(void); void CL_AbortDownloadResume(void); +void Command_Downloads_f(void); + boolean fileexist(char *filename, time_t ptime); // Search a file in the wadpath, return FS_FOUND when found From bf00955786cf0628afb2639915ba1a1c8bf69eba Mon Sep 17 00:00:00 2001 From: Louis-Antoine Date: Wed, 20 May 2020 16:21:18 +0200 Subject: [PATCH 11/12] Wait for acks before resending file fragments --- src/d_clisrv.h | 2 ++ src/d_netfil.c | 46 ++++++++++++++++++++++++++++++++++++++++++---- src/d_netfil.h | 6 ++++-- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/d_clisrv.h b/src/d_clisrv.h index cbae55017..c8241fede 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -326,6 +326,7 @@ typedef struct { UINT8 fileid; UINT32 filesize; + UINT8 iteration; UINT32 position; UINT16 size; UINT8 data[0]; // Size is variable using hardware_MAXPACKETLENGTH @@ -340,6 +341,7 @@ typedef struct typedef struct { UINT8 fileid; + UINT8 iteration; UINT8 numsegments; fileacksegment_t segments[0]; } ATTRPACK fileack_pak; diff --git a/src/d_netfil.c b/src/d_netfil.c index 9e43da791..560e4d334 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -76,10 +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]; @@ -986,20 +989,36 @@ void 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 @@ -1016,6 +1035,7 @@ void FileSendTicker(void) 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); p->fileid = f->fileid; p->filesize = LONG(f->size); @@ -1026,7 +1046,13 @@ void FileSendTicker(void) { // Success 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 @@ -1053,6 +1079,13 @@ void PT_FileAck(void) 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]; @@ -1114,10 +1147,12 @@ static void SendAckPacket(fileack_pak *packet, UINT8 fileid) memset(packet, 0, sizeof(*packet) + 512); } -static void AddFragmentToAckPacket(fileack_pak *packet, UINT32 fragmentpos, UINT8 fileid) +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) @@ -1157,7 +1192,7 @@ void FileReceiveTicker(void) for (j = 0; j < 2048; j++) { if (file->receivedfragments[file->ackresendposition]) - AddFragmentToAckPacket(file->ackpacket, file->ackresendposition, i); + AddFragmentToAckPacket(file->ackpacket, file->iteration, file->ackresendposition, i); file->ackresendposition++; if (file->ackresendposition * file->fragmentsize >= file->totalsize) @@ -1207,6 +1242,7 @@ void PT_FileFragment(void) file->status = FS_DOWNLOADING; file->fragmentsize = fragmentsize; + file->iteration = 0; file->ackpacket = calloc(1, sizeof(*file->ackpacket) + 512); if (!file->ackpacket) @@ -1254,6 +1290,8 @@ void PT_FileFragment(void) if (fragmentpos >= file->totalsize) I_Error("Invalid file fragment\n"); + file->iteration = max(file->iteration, netbuffer->u.filetxpak.iteration); + if (!file->receivedfragments[fragmentpos / fragmentsize]) // Not received yet { file->receivedfragments[fragmentpos / fragmentsize] = true; @@ -1264,7 +1302,7 @@ void PT_FileFragment(void) I_Error("Can't write to %s: %s\n",filename, M_FileError(file->file)); file->currentsize += boundedfragmentsize; - AddFragmentToAckPacket(file->ackpacket, fragmentpos / fragmentsize, filenum); + AddFragmentToAckPacket(file->ackpacket, file->iteration, fragmentpos / fragmentsize, filenum); // Finished? if (file->currentsize == file->totalsize) @@ -1295,7 +1333,7 @@ void PT_FileFragment(void) { // 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, fragmentpos / fragmentsize, filenum); + AddFragmentToAckPacket(file->ackpacket, file->iteration, fragmentpos / fragmentsize, filenum); } } else if (!file->justdownloaded) diff --git a/src/d_netfil.h b/src/d_netfil.h index a653d5a4b..2225157cb 100644 --- a/src/d_netfil.h +++ b/src/d_netfil.h @@ -40,16 +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; UINT32 ackresendposition; // Used when resuming downloads - filestatus_t status; // The value returned by recsearch - boolean justdownloaded; // To prevent late fragments from causing an I_Error } fileneeded_t; extern INT32 fileneedednum; From 3c3a60d189981920f9b0837a4db24a3488ac9f53 Mon Sep 17 00:00:00 2001 From: Louis-Antoine Date: Mon, 1 Jun 2020 10:37:05 +0200 Subject: [PATCH 12/12] Fix "downloads" command reporting incorrect progress --- src/d_netfil.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d_netfil.c b/src/d_netfil.c index 560e4d334..7b99fddfb 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -1433,7 +1433,7 @@ void Command_Downloads_f(void) && transfer[node].txlist->ram == SF_FILE) // Node is downloading a file? { const char *name = transfer[node].txlist->id.filename; - UINT32 position = transfer[node].position; + UINT32 position = transfer[node].ackedsize; UINT32 size = transfer[node].txlist->size; char ratecolor;