Raise addon limit

This commit is contained in:
LZA 2021-12-02 22:50:44 +00:00 committed by LJ Sonic
parent 491ffc7447
commit a3353be0dc
14 changed files with 768 additions and 376 deletions

View file

@ -128,10 +128,14 @@ static UINT8 localtextcmd[MAXTEXTCMD];
static UINT8 localtextcmd2[MAXTEXTCMD]; // splitscreen
static tic_t neededtic;
SINT8 servernode = 0; // the number of the server node
/// \brief do we accept new players?
/// \todo WORK!
boolean acceptnewnode = true;
static boolean serverisfull = false; //lets us be aware if the server was full after we check files, but before downloading, so we can ask if the user still wants to download or not
static tic_t firstconnectattempttime = 0;
// engine
// Must be a power of two
@ -511,18 +515,24 @@ static INT16 Consistancy(void);
typedef enum
{
CL_SEARCHING,
CL_CHECKFILES,
CL_DOWNLOADFILES,
CL_ASKJOIN,
CL_LOADFILES,
CL_WAITJOINRESPONSE,
CL_DOWNLOADSAVEGAME,
CL_CONNECTED,
CL_ABORTED
CL_ABORTED,
CL_ASKFULLFILELIST,
CL_CONFIRMCONNECT
} cl_mode_t;
static void GetPackets(void);
static cl_mode_t cl_mode = CL_SEARCHING;
static UINT16 cl_lastcheckedfilecount = 0; // used for full file list
#ifndef NONET
#define SNAKE_SPEED 5
@ -920,6 +930,8 @@ static void Snake_Draw(void)
INT16 i;
// Background
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
V_DrawFlatFill(
SNAKE_LEFT_X + SNAKE_BORDER_SIZE,
SNAKE_TOP_Y + SNAKE_BORDER_SIZE,
@ -1021,6 +1033,13 @@ static void Snake_Draw(void)
);
}
static void CL_DrawConnectionStatusBox(void)
{
M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-16-8, 32, 1);
if (cl_mode != CL_CONFIRMCONNECT)
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-16, V_YELLOWMAP, "Press ESC to abort");
}
//
// CL_DrawConnectionStatus
//
@ -1031,28 +1050,32 @@ static inline void CL_DrawConnectionStatus(void)
INT32 ccstime = I_GetTime();
// Draw background fade
if (!menuactive) // menu already draws its own fade
V_DrawFadeScreen(0xFF00, 16); // force default
V_DrawFadeScreen(0xFF00, 16); // force default
// Draw the bottom box.
M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-16-8, 32, 1);
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-16, V_YELLOWMAP, "Press ESC to abort");
if (cl_mode != CL_DOWNLOADFILES)
if (cl_mode != CL_DOWNLOADFILES && cl_mode != CL_LOADFILES)
{
INT32 i, animtime = ((ccstime / 4) & 15) + 16;
UINT8 palstart = (cl_mode == CL_SEARCHING) ? 32 : 96;
// 15 pal entries total.
UINT8 palstart;
const char *cltext;
// Draw the bottom box.
CL_DrawConnectionStatusBox();
if (cl_mode == CL_SEARCHING)
palstart = 32; // Red
else if (cl_mode == CL_CONFIRMCONNECT)
palstart = 48; // Orange
else
palstart = 96; // Green
if (!(cl_mode == CL_DOWNLOADSAVEGAME && lastfilenum != -1))
for (i = 0; i < 16; ++i)
for (i = 0; i < 16; ++i) // 15 pal entries total.
V_DrawFill((BASEVIDWIDTH/2-128) + (i * 16), BASEVIDHEIGHT-16, 16, 8, palstart + ((animtime - i) & 15));
switch (cl_mode)
{
case CL_DOWNLOADSAVEGAME:
if (lastfilenum != -1)
if (fileneeded && lastfilenum != -1)
{
UINT32 currentsize = fileneeded[lastfilenum].currentsize;
UINT32 totalsize = fileneeded[lastfilenum].totalsize;
@ -1076,9 +1099,22 @@ static inline void CL_DrawConnectionStatus(void)
else
cltext = M_GetText("Waiting to download game state...");
break;
case CL_ASKFULLFILELIST:
case CL_CHECKFILES:
cltext = M_GetText("Checking server addon list...");
break;
case CL_CONFIRMCONNECT:
cltext = "";
break;
case CL_LOADFILES:
cltext = M_GetText("Loading server addons...");
break;
case CL_ASKJOIN:
case CL_WAITJOINRESPONSE:
cltext = M_GetText("Requesting to join...");
if (serverisfull)
cltext = M_GetText("Server full, waiting for a slot...");
else
cltext = M_GetText("Requesting to join...");
break;
default:
cltext = M_GetText("Connecting to server...");
@ -1088,14 +1124,51 @@ static inline void CL_DrawConnectionStatus(void)
}
else
{
if (lastfilenum != -1)
if (cl_mode == CL_LOADFILES)
{
INT32 totalfileslength;
INT32 loadcompletednum = 0;
INT32 i;
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-16, V_YELLOWMAP, "Press ESC to abort");
//ima just count files here
if (fileneeded)
{
for (i = 0; i < fileneedednum; i++)
if (fileneeded[i].status == FS_OPEN)
loadcompletednum++;
}
// Loading progress
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP, "Loading server addons...");
totalfileslength = (INT32)((loadcompletednum/(double)(fileneedednum)) * 256);
M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-16-8, 32, 1);
V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, 256, 8, 111);
V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, totalfileslength, 8, 96);
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE,
va(" %2u/%2u Files",loadcompletednum,fileneedednum));
}
else if (lastfilenum != -1)
{
INT32 dldlength;
static char tempname[28];
fileneeded_t *file = &fileneeded[lastfilenum];
char *filename = file->filename;
fileneeded_t *file;
char *filename;
Snake_Draw();
if (snake)
Snake_Draw();
// Draw the bottom box.
CL_DrawConnectionStatusBox();
if (fileneeded)
{
file = &fileneeded[lastfilenum];
filename = file->filename;
}
else
return;
Net_GetNetStat();
dldlength = (INT32)((file->currentsize/(double)file->totalsize) * 256);
@ -1128,20 +1201,32 @@ static inline void CL_DrawConnectionStatus(void)
va("%3.1fK/s ", ((double)getbps)/1024));
}
else
{
if (snake)
Snake_Draw();
CL_DrawConnectionStatusBox();
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP,
M_GetText("Waiting to download files..."));
}
}
}
#endif
static boolean CL_AskFileList(INT32 firstfile)
{
netbuffer->packettype = PT_TELLFILESNEEDED;
netbuffer->u.filesneedednum = firstfile;
return HSendPacket(servernode, false, 0, sizeof (INT32));
}
/** Sends a special packet to declare how many players in local
* Used only in arbitratrenetstart()
* Sends a PT_CLIENTJOIN packet to the server
*
* \return True if the packet was successfully sent
* \todo Improve the description...
* Because to be honest, I have no idea what arbitratrenetstart is...
* Is it even used...?
*
*/
static boolean CL_SendJoin(void)
@ -1240,7 +1325,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
sizeof netbuffer->u.serverinfo.gametypename);
netbuffer->u.serverinfo.modifiedgame = (UINT8)modifiedgame;
netbuffer->u.serverinfo.cheatsenabled = CV_CheatsEnabled();
netbuffer->u.serverinfo.isdedicated = (UINT8)dedicated;
netbuffer->u.serverinfo.flags = (dedicated ? SV_DEDICATED : 0);
strncpy(netbuffer->u.serverinfo.servername, cv_servername.string,
MAXSERVERNAME);
strncpy(netbuffer->u.serverinfo.mapname, G_BuildMapName(gamemap), 7);
@ -1275,7 +1360,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
if (mapheaderinfo[gamemap-1])
netbuffer->u.serverinfo.actnum = mapheaderinfo[gamemap-1]->actnum;
p = PutFileNeeded();
p = PutFileNeeded(0);
HSendPacket(node, false, 0, p - ((UINT8 *)&netbuffer->u));
}
@ -1526,6 +1611,8 @@ static void CL_LoadReceivedSavegame(boolean reloading)
size_t length, decompressedlen;
char tmpsave[256];
FreeFileNeeded();
sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home);
length = FIL_ReadFile(tmpsave, &savebuffer);
@ -1836,12 +1923,164 @@ void CL_UpdateServerList(boolean internetsearch, INT32 room)
#endif/*MASTERSERVER*/
}
static const char * InvalidServerReason (INT32 i)
#endif // ifndef NONET
static void M_ConfirmConnect(event_t *ev)
{
#ifndef NONET
if (ev->type == ev_keydown)
{
if (ev->key == ' ' || ev->key == 'y' || ev->key == KEY_ENTER)
{
if (totalfilesrequestednum > 0)
{
if (CL_SendFileRequest())
{
cl_mode = CL_DOWNLOADFILES;
Snake_Initialise();
}
}
else
cl_mode = CL_LOADFILES;
M_ClearMenus(true);
}
else if (ev->key == 'n' || ev->key == KEY_ESCAPE)
{
cl_mode = CL_ABORTED;
M_ClearMenus(true);
}
}
#else
(void)ev;
#endif
}
static boolean CL_FinishedFileList(void)
{
INT32 i;
char *downloadsize = NULL;
//CONS_Printf(M_GetText("Checking files...\n"));
i = CL_CheckFiles();
if (i == 4) // still checking ...
{
return true;
}
else if (i == 3) // too many files
{
D_QuitNetGame();
CL_Reset();
D_StartTitle();
M_StartMessage(M_GetText(
"You have too many WAD files loaded\n"
"to add ones the server is using.\n"
"Please restart SRB2 before connecting.\n\n"
"Press ESC\n"
), NULL, MM_NOTHING);
return false;
}
else if (i == 2) // cannot join for some reason
{
D_QuitNetGame();
CL_Reset();
D_StartTitle();
M_StartMessage(M_GetText(
"You have the wrong addons loaded.\n\n"
"To play on this server, restart\n"
"the game and don't load any addons.\n"
"SRB2 will automatically add\n"
"everything you need when you join.\n\n"
"Press ESC\n"
), NULL, MM_NOTHING);
return false;
}
else if (i == 1)
{
if (serverisfull)
{
M_StartMessage(M_GetText(
"This server is full!\n"
"\n"
"You may load server addons (if any), and wait for a slot.\n"
"\n"
"Press ENTER to continue\nor ESC to cancel.\n\n"
), M_ConfirmConnect, MM_EVENTHANDLER);
cl_mode = CL_CONFIRMCONNECT;
curfadevalue = 0;
}
else
cl_mode = CL_LOADFILES;
}
else
{
// must download something
// can we, though?
if (!CL_CheckDownloadable()) // nope!
{
D_QuitNetGame();
CL_Reset();
D_StartTitle();
M_StartMessage(M_GetText(
"An error occured when trying to\n"
"download missing addons.\n"
"(This is almost always a problem\n"
"with the server, not your game.)\n\n"
"See the console or log file\n"
"for additional details.\n\n"
"Press ESC\n"
), NULL, MM_NOTHING);
return false;
}
#ifndef NONET
downloadcompletednum = 0;
downloadcompletedsize = 0;
totalfilesrequestednum = 0;
totalfilesrequestedsize = 0;
if (fileneeded == NULL)
I_Error("CL_FinishedFileList: fileneeded == NULL");
for (i = 0; i < fileneedednum; i++)
if (fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD)
{
totalfilesrequestednum++;
totalfilesrequestedsize += fileneeded[i].totalsize;
}
if (totalfilesrequestedsize>>20 >= 100)
downloadsize = Z_StrDup(va("%uM",totalfilesrequestedsize>>20));
else
downloadsize = Z_StrDup(va("%uK",totalfilesrequestedsize>>10));
#endif
if (serverisfull)
M_StartMessage(va(M_GetText(
"This server is full!\n"
"Download of %s additional content\nis required to join.\n"
"\n"
"You may download, load server addons,\nand wait for a slot.\n"
"\n"
"Press ENTER to continue\nor ESC to cancel.\n"
), downloadsize), M_ConfirmConnect, MM_EVENTHANDLER);
else
M_StartMessage(va(M_GetText(
"Download of %s additional content\nis required to join.\n"
"\n"
"Press ENTER to continue\nor ESC to cancel.\n"
), downloadsize), M_ConfirmConnect, MM_EVENTHANDLER);
Z_Free(downloadsize);
cl_mode = CL_CONFIRMCONNECT;
curfadevalue = 0;
}
return true;
}
static const char * InvalidServerReason (serverinfo_pak *info)
{
#define EOT "\nPress ESC\n"
serverinfo_pak *info = &serverlist[i].info;
/* magic number for new packet format */
if (info->_255 != 255)
{
@ -1902,8 +2141,6 @@ static const char * InvalidServerReason (INT32 i)
#undef EOT
}
#endif // ifndef NONET
/** Called by CL_ServerConnectionTicker
*
* \param asksent The last time we asked the server to join. We re-ask every second in case our request got lost in transmit.
@ -1936,86 +2173,44 @@ static boolean CL_ServerConnectionSearchTicker(tic_t *asksent)
if (client)
{
const char *reason = InvalidServerReason(i);
serverinfo_pak *info = &serverlist[i].info;
// Quit here rather than downloading files
// and being refused later.
if (reason)
{
char *message = Z_StrDup(reason);
D_QuitNetGame();
CL_Reset();
D_StartTitle();
M_StartMessage(message, NULL, MM_NOTHING);
Z_Free(message);
return false;
}
D_ParseFileneeded(serverlist[i].info.fileneedednum,
serverlist[i].info.fileneeded);
CONS_Printf(M_GetText("Checking files...\n"));
i = CL_CheckFiles();
if (i == 3) // too many files
{
D_QuitNetGame();
CL_Reset();
D_StartTitle();
M_StartMessage(M_GetText(
"You have too many WAD files loaded\n"
"to add ones the server is using.\n"
"Please restart SRB2 before connecting.\n\n"
"Press ESC\n"
), NULL, MM_NOTHING);
return false;
}
else if (i == 2) // cannot join for some reason
{
D_QuitNetGame();
CL_Reset();
D_StartTitle();
M_StartMessage(M_GetText(
"You have the wrong addons loaded.\n\n"
"To play on this server, restart\n"
"the game and don't load any addons.\n"
"SRB2 will automatically add\n"
"everything you need when you join.\n\n"
"Press ESC\n"
), NULL, MM_NOTHING);
return false;
}
else if (i == 1)
cl_mode = CL_ASKJOIN;
if (info->refusereason == REFUSE_SLOTS_FULL)
serverisfull = true;
else
{
// must download something
// can we, though?
if (!CL_CheckDownloadable()) // nope!
const char *reason = InvalidServerReason(info);
// Quit here rather than downloading files
// and being refused later.
if (reason)
{
char *message = Z_StrDup(reason);
D_QuitNetGame();
CL_Reset();
D_StartTitle();
M_StartMessage(M_GetText(
"You cannot connect to this server\n"
"because you cannot download the files\n"
"that you are missing from the server.\n\n"
"See the console or log file for\n"
"more details.\n\n"
"Press ESC\n"
), NULL, MM_NOTHING);
M_StartMessage(message, NULL, MM_NOTHING);
Z_Free(message);
return false;
}
// no problem if can't send packet, we will retry later
if (CL_SendFileRequest())
{
cl_mode = CL_DOWNLOADFILES;
#ifndef NONET
Snake_Initialise();
#endif
}
}
D_ParseFileneeded(info->fileneedednum, info->fileneeded, 0);
if (info->flags & SV_LOTSOFADDONS)
{
cl_mode = CL_ASKFULLFILELIST;
cl_lastcheckedfilecount = 0;
return true;
}
cl_mode = CL_CHECKFILES;
}
else
{
cl_mode = CL_ASKJOIN; // files need not be checked for the server.
*asksent = 0;
}
return true;
}
@ -2061,6 +2256,22 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic
return false;
break;
case CL_ASKFULLFILELIST:
if (cl_lastcheckedfilecount == UINT16_MAX) // All files retrieved
cl_mode = CL_CHECKFILES;
else if (fileneedednum != cl_lastcheckedfilecount || I_GetTime() >= *asksent)
{
if (CL_AskFileList(fileneedednum))
{
cl_lastcheckedfilecount = fileneedednum;
*asksent = I_GetTime() + NEWTICRATE;
}
}
break;
case CL_CHECKFILES:
if (!CL_FinishedFileList())
return false;
break;
case CL_DOWNLOADFILES:
waitmore = false;
for (i = 0; i < fileneedednum; i++)
@ -2081,21 +2292,51 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic
}
#endif
cl_mode = CL_ASKJOIN; // don't break case continue to cljoin request now
/* FALLTHRU */
cl_mode = CL_LOADFILES;
break;
case CL_LOADFILES:
if (CL_LoadServerFiles())
{
FreeFileNeeded();
*asksent = 0; //This ensure the first join ask is right away
firstconnectattempttime = I_GetTime();
cl_mode = CL_ASKJOIN;
}
break;
case CL_ASKJOIN:
CL_LoadServerFiles();
if (firstconnectattempttime + NEWTICRATE*300 < I_GetTime() && !server)
{
CONS_Printf(M_GetText("5 minute wait time exceeded.\n"));
CONS_Printf(M_GetText("Network game synchronization aborted.\n"));
D_QuitNetGame();
CL_Reset();
D_StartTitle();
M_StartMessage(M_GetText(
"5 minute wait time exceeded.\n"
"You may retry connection.\n"
"\n"
"Press ESC\n"
), NULL, MM_NOTHING);
return false;
}
#ifndef NONET
// prepare structures to save the file
// WARNING: this can be useless in case of server not in GS_LEVEL
// but since the network layer doesn't provide ordered packets...
CL_PrepareDownloadSaveGame(tmpsave);
#endif
if (CL_SendJoin())
if (I_GetTime() >= *asksent && CL_SendJoin())
{
*asksent = I_GetTime() + NEWTICRATE*3;
cl_mode = CL_WAITJOINRESPONSE;
}
break;
case CL_WAITJOINRESPONSE:
if (I_GetTime() >= *asksent)
{
cl_mode = CL_ASKJOIN;
}
break;
#ifndef NONET
case CL_DOWNLOADSAVEGAME:
// At this state, the first (and only) needed file is the gamestate
@ -2109,8 +2350,8 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic
break;
#endif
case CL_WAITJOINRESPONSE:
case CL_CONNECTED:
case CL_CONFIRMCONNECT: //logic is handled by M_ConfirmConnect
default:
break;
@ -2118,7 +2359,6 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic
case CL_ABORTED:
cl_mode = CL_SEARCHING;
return false;
}
GetPackets();
@ -2128,13 +2368,19 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic
if (*oldtic != I_GetTime())
{
I_OsPolling();
for (; eventtail != eventhead; eventtail = (eventtail+1) & (MAXEVENTS-1))
G_MapEventsToControls(&events[eventtail]);
if (gamekeydown[KEY_ESCAPE] || gamekeydown[KEY_JOY1+1])
if (cl_mode == CL_CONFIRMCONNECT)
D_ProcessEvents(); //needed for menu system to receive inputs
else
{
for (; eventtail != eventhead; eventtail = (eventtail+1) & (MAXEVENTS-1))
G_MapEventsToControls(&events[eventtail]);
}
if (gamekeydown[KEY_ESCAPE] || gamekeydown[KEY_JOY1+1] || cl_mode == CL_ABORTED)
{
CONS_Printf(M_GetText("Network game synchronization aborted.\n"));
// M_StartMessage(M_GetText("Network game synchronization aborted.\n\nPress ESC\n"), NULL, MM_NOTHING);
M_StartMessage(M_GetText("Network game synchronization aborted.\n\nPress ESC\n"), NULL, MM_NOTHING);
#ifndef NONET
if (snake)
@ -2167,13 +2413,20 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic
#ifndef NONET
if (client && cl_mode != CL_CONNECTED && cl_mode != CL_ABORTED)
{
if (cl_mode != CL_DOWNLOADFILES && cl_mode != CL_DOWNLOADSAVEGAME)
if (!snake)
{
F_MenuPresTicker(true); // title sky
F_TitleScreenTicker(true);
F_TitleScreenDrawer();
}
CL_DrawConnectionStatus();
#ifdef HAVE_THREADS
I_lock_mutex(&m_menu_mutex);
#endif
M_Drawer(); //Needed for drawing messageboxes on the connection screen
#ifdef HAVE_THREADS
I_unlock_mutex(m_menu_mutex);
#endif
I_UpdateNoVsync(); // page flip or blit buffer
if (moviemode)
M_SaveFrame();
@ -2235,8 +2488,10 @@ static void CL_ConnectToServer(void)
ClearAdminPlayers();
pnumnodes = 1;
oldtic = I_GetTime() - 1;
#ifndef NONET
asksent = (tic_t) - TICRATE;
firstconnectattempttime = I_GetTime();
i = SL_SearchServer(servernode);
@ -2676,8 +2931,16 @@ void CL_Reset(void)
SV_ResetServer();
// make sure we don't leave any fileneeded gunk over from a failed join
FreeFileNeeded();
fileneedednum = 0;
memset(fileneeded, 0, sizeof(fileneeded));
#ifndef NONET
totalfilesrequestednum = 0;
totalfilesrequestedsize = 0;
#endif
firstconnectattempttime = 0;
serverisfull = false;
connectiontimeout = (tic_t)cv_nettimeout.value; //reset this temporary hack
// D_StartTitle should get done now, but the calling function will handle it
}
@ -3968,31 +4231,40 @@ static void HandlePacketFromAwayNode(SINT8 node)
switch (netbuffer->packettype)
{
case PT_ASKINFOVIAMS:
#if 0
Net_CloseConnection(node);
break;
case PT_TELLFILESNEEDED:
if (server && serverrunning)
{
INT32 clientnode;
if (ms_RoomId < 0) // ignore if we're not actually on the MS right now
{
Net_CloseConnection(node); // and yes, close connection
return;
}
clientnode = I_NetMakeNode(netbuffer->u.msaskinfo.clientaddr);
if (clientnode != -1)
{
SV_SendServerInfo(clientnode, (tic_t)LONG(netbuffer->u.msaskinfo.time));
SV_SendPlayerInfo(clientnode); // Send extra info
Net_CloseConnection(clientnode);
// Don't close connection to MS...
}
else
Net_CloseConnection(node); // ...unless the IP address is not valid
UINT8 *p;
INT32 firstfile = netbuffer->u.filesneedednum;
netbuffer->packettype = PT_MOREFILESNEEDED;
netbuffer->u.filesneededcfg.first = firstfile;
netbuffer->u.filesneededcfg.more = 0;
p = PutFileNeeded(firstfile);
HSendPacket(node, false, 0, p - ((UINT8 *)&netbuffer->u));
}
else // Shouldn't get this if you aren't the server...?
Net_CloseConnection(node);
break;
case PT_MOREFILESNEEDED:
if (server && serverrunning)
{ // But wait I thought I'm the server?
Net_CloseConnection(node);
break;
}
SERVERONLY
if (cl_mode == CL_ASKFULLFILELIST && netbuffer->u.filesneededcfg.first == fileneedednum)
{
D_ParseFileneeded(netbuffer->u.filesneededcfg.num, netbuffer->u.filesneededcfg.files, netbuffer->u.filesneededcfg.first);
if (!netbuffer->u.filesneededcfg.more)
cl_lastcheckedfilecount = UINT16_MAX; // Got the whole file list
}
else
Net_CloseConnection(node); // you're not supposed to get it, so ignore it
#else
Net_CloseConnection(node);
#endif
break;
case PT_ASKINFO:
@ -4018,13 +4290,24 @@ static void HandlePacketFromAwayNode(SINT8 node)
if (!reason)
I_Error("Out of memory!\n");
D_QuitNetGame();
CL_Reset();
D_StartTitle();
if (strstr(reason, "Maximum players reached"))
{
serverisfull = true;
//Special timeout for when refusing due to player cap. The client will wait 3 seconds between join requests when waiting for a slot, so we need this to be much longer
//We set it back to the value of cv_nettimeout.value in CL_Reset
connectiontimeout = NEWTICRATE*7;
cl_mode = CL_ASKJOIN;
free(reason);
break;
}
M_StartMessage(va(M_GetText("Server refuses connection\n\nReason:\n%s"),
reason), NULL, MM_NOTHING);
D_QuitNetGame();
CL_Reset();
D_StartTitle();
free(reason);
// Will be reset by caller. Signals refusal.

View file

@ -94,6 +94,9 @@ typedef enum
PT_LOGIN, // Login attempt from the client.
PT_TELLFILESNEEDED, // Client, to server: "what other files do I need starting from this number?"
PT_MOREFILESNEEDED, // Server, to client: "you need these (+ more on top of those)"
PT_PING, // Packet sent to tell clients the other client's latency to server.
NUMPACKETTYPE
} packettype_t;
@ -198,6 +201,9 @@ typedef struct
char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME];
} ATTRPACK clientconfig_pak;
#define SV_DEDICATED 0x40 // server is dedicated
#define SV_LOTSOFADDONS 0x20 // flag used to ask for full file list in d_netfil
enum {
REFUSE_JOINS_DISABLED = 1,
REFUSE_SLOTS_FULL,
@ -225,7 +231,7 @@ typedef struct
char gametypename[24];
UINT8 modifiedgame;
UINT8 cheatsenabled;
UINT8 isdedicated;
UINT8 flags;
UINT8 fileneedednum;
tic_t time;
tic_t leveltime;
@ -279,6 +285,14 @@ typedef struct
UINT8 ctfteam;
} ATTRPACK plrconfig;
typedef struct
{
INT32 first;
UINT8 num;
UINT8 more;
UINT8 files[MAXFILENEEDED]; // is filled with writexxx (byteptr.h)
} ATTRPACK filesneededconfig_pak;
//
// Network packet data
//
@ -308,6 +322,8 @@ typedef struct
msaskinfo_pak msaskinfo; // 22 bytes
plrinfo playerinfo[MAXPLAYERS]; // 576 bytes(?)
plrconfig playerconfig[MAXPLAYERS]; // (up to) 528 bytes(?)
INT32 filesneedednum; // 4 bytes
filesneededconfig_pak filesneededcfg; // ??? bytes
UINT32 pingtable[MAXPLAYERS+1]; // 68 bytes
} u; // This is needed to pack diff packet types data together
} ATTRPACK doomdata_t;

View file

@ -65,7 +65,7 @@
#include "m_cond.h" // condition initialization
#include "fastcmp.h"
#include "keys.h"
#include "filesrch.h" // refreshdirmenu, mainwadstally
#include "filesrch.h" // refreshdirmenu
#include "g_input.h" // tutorial mode control scheming
#include "m_perfstats.h"
@ -96,11 +96,8 @@ int SUBVERSION;
// platform independant focus loss
UINT8 window_notinfocus = false;
//
// DEMO LOOP
//
static char *startupwadfiles[MAX_WADFILES];
static char *startuppwads[MAX_WADFILES];
static addfilelist_t startupwadfiles;
static addfilelist_t startuppwads;
boolean devparm = false; // started game with -devparm
@ -119,6 +116,9 @@ boolean midi_disabled = false;
boolean sound_disabled = false;
boolean digital_disabled = false;
//
// DEMO LOOP
//
boolean advancedemo;
#ifdef DEBUGFILE
INT32 debugload = 0;
@ -923,51 +923,68 @@ void D_StartTitle(void)
tutorialmode = false;
}
//
// D_AddFile
//
static void D_AddFile(char **list, const char *file)
{
size_t pnumwadfiles;
char *newfile;
#define REALLOC_FILE_LIST \
if (list->files == NULL) \
{ \
list->files = calloc(sizeof(list->files), 2); \
list->numfiles = 1; \
} \
else \
{ \
index = list->numfiles; \
list->files = realloc(list->files, sizeof(list->files) * ((++list->numfiles) + 1)); \
if (list->files == NULL) \
I_Error("%s: No more free memory to add file %s", __FUNCTION__, file); \
}
for (pnumwadfiles = 0; list[pnumwadfiles]; pnumwadfiles++)
;
static void D_AddFile(addfilelist_t *list, const char *file)
{
char *newfile;
size_t index = 0;
REALLOC_FILE_LIST
newfile = malloc(strlen(file) + 1);
if (!newfile)
I_Error("No more free memory to AddFile %s",file);
I_Error("D_AddFile: No more free memory to add file %s", file);
strcpy(newfile, file);
list[pnumwadfiles] = newfile;
list->files[index] = newfile;
}
static void D_AddFolder(char **list, const char *file)
static void D_AddFolder(addfilelist_t *list, const char *file)
{
size_t pnumwadfiles;
char *newfile;
size_t index = 0;
for (pnumwadfiles = 0; list[pnumwadfiles]; pnumwadfiles++)
;
REALLOC_FILE_LIST
newfile = malloc(strlen(file) + 2); // Path delimiter + NULL terminator
if (!newfile)
I_Error("No more free memory to AddFolder %s",file);
I_Error("D_AddFolder: No more free memory to add folder %s", file);
strcpy(newfile, file);
strcat(newfile, PATHSEP);
list[pnumwadfiles] = newfile;
list->files[index] = newfile;
}
static inline void D_CleanFile(char **list)
#undef REALLOC_FILE_LIST
static inline void D_CleanFile(addfilelist_t *list)
{
size_t pnumwadfiles;
for (pnumwadfiles = 0; list[pnumwadfiles]; pnumwadfiles++)
if (list->files)
{
free(list[pnumwadfiles]);
list[pnumwadfiles] = NULL;
size_t pnumwadfiles = 0;
for (; pnumwadfiles < list->numfiles; pnumwadfiles++)
free(list->files[pnumwadfiles]);
free(list->files);
list->files = NULL;
}
list->numfiles = 0;
}
///\brief Checks if a netgame URL is being handled, and changes working directory to the EXE's if so.
@ -1051,7 +1068,7 @@ static void IdentifyVersion(void)
// Load the IWAD
if (srb2wad != NULL && FIL_ReadFileOK(srb2wad))
D_AddFile(startupwadfiles, srb2wad);
D_AddFile(&startupwadfiles, srb2wad);
else
I_Error("srb2.pk3 not found! Expected in %s, ss file: %s\n", srb2waddir, srb2wad);
@ -1062,14 +1079,14 @@ static void IdentifyVersion(void)
// checking in D_SRB2Main
// Add the maps
D_AddFile(startupwadfiles, va(pandf,srb2waddir,"zones.pk3"));
D_AddFile(&startupwadfiles, va(pandf,srb2waddir, "zones.pk3"));
// Add the players
D_AddFile(startupwadfiles, va(pandf,srb2waddir, "player.dta"));
D_AddFile(&startupwadfiles, va(pandf,srb2waddir, "player.dta"));
#ifdef USE_PATCH_DTA
// Add our crappy patches to fix our bugs
D_AddFile(startupwadfiles, va(pandf,srb2waddir,"patch.pk3"));
D_AddFile(&startupwadfiles, va(pandf,srb2waddir, "patch.pk3"));
#endif
#if !defined (HAVE_SDL) || defined (HAVE_MIXER)
@ -1079,7 +1096,7 @@ static void IdentifyVersion(void)
const char *musicpath = va(pandf,srb2waddir,str);\
int ms = W_VerifyNMUSlumps(musicpath, false); \
if (ms == 1) \
D_AddFile(startupwadfiles, musicpath); \
D_AddFile(&startupwadfiles, musicpath); \
else if (ms == 0) \
I_Error("File "str" has been modified with non-music/sound lumps"); \
}
@ -1269,9 +1286,9 @@ void D_SRB2Main(void)
else if (myargv[i][0] == '-' || myargv[i][0] == '+')
addontype = 0;
else if (addontype == 1)
D_AddFile(startuppwads, myargv[i]);
D_AddFile(&startuppwads, myargv[i]);
else if (addontype == 2)
D_AddFolder(startuppwads, myargv[i]);
D_AddFolder(&startuppwads, myargv[i]);
}
}
@ -1310,8 +1327,8 @@ void D_SRB2Main(void)
// load wad, including the main wad file
CONS_Printf("W_InitMultipleFiles(): Adding IWAD and main PWADs.\n");
W_InitMultipleFiles(startupwadfiles);
D_CleanFile(startupwadfiles);
W_InitMultipleFiles(&startupwadfiles);
D_CleanFile(&startupwadfiles);
#ifndef DEVELOP // md5s last updated 22/02/20 (ddmmyy)
@ -1326,8 +1343,6 @@ void D_SRB2Main(void)
// ...except it does if they slip maps in there, and that's what W_VerifyNMUSlumps is for.
#endif //ifndef DEVELOP
mainwadstally = packetsizetally; // technically not accurate atm, remember to port the two-stage -file process from kart in 2.2.x
cht_Init();
//---------------------------------------------------- READY SCREEN
@ -1360,9 +1375,12 @@ void D_SRB2Main(void)
CON_StopRefresh(); // Temporarily stop refreshing the screen for wad loading
CONS_Printf("W_InitMultipleFiles(): Adding extra PWADs.\n");
W_InitMultipleFiles(startuppwads);
D_CleanFile(startuppwads);
if (startuppwads.numfiles)
{
CONS_Printf("W_InitMultipleFiles(): Adding extra PWADs.\n");
W_InitMultipleFiles(&startuppwads);
D_CleanFile(&startuppwads);
}
CON_StartRefresh(); // Restart the refresh!

View file

@ -815,6 +815,8 @@ static const char *packettypename[NUMPACKETTYPE] =
"CLIENTJOIN",
"NODETIMEOUT",
"LOGIN",
"TELLFILESNEEDED",
"MOREFILESNEEDED",
"PING"
};

View file

@ -3223,7 +3223,7 @@ static void Command_RunSOC(void)
static void Got_RunSOCcmd(UINT8 **cp, INT32 playernum)
{
char filename[256];
filestatus_t ncs = FS_NOTFOUND;
filestatus_t ncs = FS_NOTCHECKED;
if (playernum != serverplayer && !IsPlayerAdmin(playernum))
{
@ -3347,10 +3347,9 @@ static void Command_Addfile(void)
break;
++p;
// check total packet size and no of files currently loaded
// See W_InitFile in w_wad.c
if ((numwadfiles >= MAX_WADFILES)
|| ((packetsizetally + nameonlylength(fn) + FILENEEDEDSIZE) > MAXFILENEEDED*sizeof(UINT8)))
// check no of files currently loaded
// See W_LoadWadFile in w_wad.c
if (numwadfiles >= MAX_WADFILES)
{
CONS_Alert(CONS_ERROR, M_GetText("Too many files loaded to add %s\n"), fn);
return;
@ -3379,6 +3378,9 @@ static void Command_Addfile(void)
for (i = 0; i < numwadfiles; i++)
{
if (wadfiles[i]->type == RET_FOLDER)
continue;
if (!memcmp(wadfiles[i]->md5sum, md5sum, 16))
{
CONS_Alert(CONS_ERROR, M_GetText("%s is already loaded\n"), fn);
@ -3469,10 +3471,9 @@ static void Command_Addfolder(void)
continue;
}
// check total packet size and no of files currently loaded
// See W_InitFile in w_wad.c
if ((numwadfiles >= MAX_WADFILES)
|| ((packetsizetally + strlen(fn) + FILENEEDEDSIZE) > MAXFILENEEDED*sizeof(UINT8)))
// check no of files currently loaded
// See W_LoadWadFile in w_wad.c
if (numwadfiles >= MAX_WADFILES)
{
CONS_Alert(CONS_ERROR, M_GetText("Too many files loaded to add %s\n"), fn);
return;
@ -3534,7 +3535,7 @@ static void Command_Addfolder(void)
static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
{
char filename[241];
filestatus_t ncs = FS_NOTFOUND;
filestatus_t ncs = FS_NOTCHECKED;
UINT8 md5sum[16];
boolean kick = false;
boolean toomany = false;
@ -3559,9 +3560,7 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
return;
}
// See W_InitFile in w_wad.c
if ((numwadfiles >= MAX_WADFILES)
|| ((packetsizetally + nameonlylength(filename) + FILENEEDEDSIZE) > MAXFILENEEDED*sizeof(UINT8)))
if (numwadfiles >= MAX_WADFILES)
toomany = true;
else
ncs = findfile(filename,md5sum,true);
@ -3594,7 +3593,7 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
static void Got_RequestAddfoldercmd(UINT8 **cp, INT32 playernum)
{
char path[241];
filestatus_t ncs = FS_NOTFOUND;
filestatus_t ncs = FS_NOTCHECKED;
boolean kick = false;
boolean toomany = false;
INT32 i,j;
@ -3619,9 +3618,7 @@ static void Got_RequestAddfoldercmd(UINT8 **cp, INT32 playernum)
return;
}
// See W_InitFile in w_wad.c
if ((numwadfiles >= MAX_WADFILES)
|| ((packetsizetally + strlen(path) + FILENEEDEDSIZE) > MAXFILENEEDED*sizeof(UINT8)))
if (numwadfiles >= MAX_WADFILES)
toomany = true;
else
ncs = findfolder(path);
@ -3652,7 +3649,7 @@ static void Got_RequestAddfoldercmd(UINT8 **cp, INT32 playernum)
static void Got_Addfilecmd(UINT8 **cp, INT32 playernum)
{
char filename[241];
filestatus_t ncs = FS_NOTFOUND;
filestatus_t ncs = FS_NOTCHECKED;
UINT8 md5sum[16];
READSTRINGN(*cp, filename, 240);
@ -3700,7 +3697,7 @@ static void Got_Addfilecmd(UINT8 **cp, INT32 playernum)
static void Got_Addfoldercmd(UINT8 **cp, INT32 playernum)
{
char path[241];
filestatus_t ncs = FS_NOTFOUND;
filestatus_t ncs = FS_NOTCHECKED;
READSTRINGN(*cp, path, 240);
@ -3744,7 +3741,13 @@ static void Command_ListWADS_f(void)
{
INT32 i = numwadfiles;
char *tempname;
CONS_Printf(M_GetText("There are %d wads loaded:\n"),numwadfiles);
#ifdef ENFORCE_WAD_LIMIT
CONS_Printf(M_GetText("There are %d/%d files loaded:\n"),numwadfiles,MAX_WADFILES);
#else
CONS_Printf(M_GetText("There are %d files loaded:\n"),numwadfiles);
#endif
for (i--; i >= 0; i--)
{
nameonly(tempname = va("%s", wadfiles[i]->filename));

View file

@ -73,6 +73,7 @@ extern consvar_t cv_teamscramble;
extern consvar_t cv_scrambleonchange;
extern consvar_t cv_netstat;
extern consvar_t cv_nettimeout;
extern consvar_t cv_countdowntime;
extern consvar_t cv_runscripts;

View file

@ -52,7 +52,7 @@
#include <errno.h>
// Prototypes
static boolean AddFileToSendQueue(INT32 node, const char *filename, UINT8 fileid);
static boolean AddFileToSendQueue(INT32 node, UINT8 fileid);
// Sender structure
typedef struct filetx_s
@ -87,7 +87,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
fileneeded_t *fileneeded; // List of needed files
static tic_t lasttimeackpacketsent = 0;
char downloaddir[512] = "DOWNLOAD";
@ -105,6 +105,10 @@ static pauseddownload_t *pauseddownload = NULL;
#ifndef NONET
// for cl loading screen
INT32 lastfilenum = -1;
INT32 downloadcompletednum = 0;
UINT32 downloadcompletedsize = 0;
INT32 totalfilesrequestednum = 0;
UINT32 totalfilesrequestedsize = 0;
#endif
luafiletransfer_t *luafiletransfers = NULL;
@ -113,25 +117,62 @@ boolean waitingforluafilecommand = false;
char luafiledir[256 + 16] = "luafiles";
static UINT16 GetWadNumFromFileNeededId(UINT8 id)
{
UINT16 wadnum;
for (wadnum = mainwads; wadnum < numwadfiles; wadnum++)
{
if (!wadfiles[wadnum]->important)
continue;
if (id == 0)
return wadnum;
id--;
}
return UINT16_MAX;
}
/** Fills a serverinfo packet with information about wad files loaded.
*
* \todo Give this function a better name since it is in global scope.
* Used to have size limiting built in - now handled via W_InitFile in w_wad.c
*
*/
UINT8 *PutFileNeeded(void)
UINT8 *PutFileNeeded(UINT16 firstfile)
{
size_t i, count = 0;
UINT8 *p = netbuffer->u.serverinfo.fileneeded;
size_t i;
UINT8 count = 0;
UINT8 *p_start = netbuffer->packettype == PT_MOREFILESNEEDED ? netbuffer->u.filesneededcfg.files : netbuffer->u.serverinfo.fileneeded;
UINT8 *p = p_start;
char wadfilename[MAX_WADPATH] = "";
UINT8 filestatus, folder;
for (i = 0; i < numwadfiles; i++)
for (i = mainwads; i < numwadfiles; i++) //mainwads, otherwise we start on the first mainwad
{
// If it has only music/sound lumps, don't put it in the list
if (!wadfiles[i]->important)
continue;
if (firstfile)
{ // Skip files until we reach the first file.
firstfile--;
continue;
}
nameonly(strcpy(wadfilename, wadfiles[i]->filename));
// Look below at the WRITE macros to understand what these numbers mean.
if (p + 1 + 4 + min(strlen(wadfilename) + 1, MAX_WADPATH) + 16 > p_start + MAXFILENEEDED)
{
// Too many files to send all at once
if (netbuffer->packettype == PT_MOREFILESNEEDED)
netbuffer->u.filesneededcfg.more = 1;
else
netbuffer->u.serverinfo.flags |= SV_LOTSOFADDONS;
break;
}
filestatus = 1; // Importance - not really used any more, holds 1 by default for backwards compat with MS
folder = (wadfiles[i]->type == RET_FOLDER);
@ -148,32 +189,53 @@ UINT8 *PutFileNeeded(void)
count++;
WRITEUINT32(p, wadfiles[i]->filesize);
nameonly(strcpy(wadfilename, wadfiles[i]->filename));
WRITESTRINGN(p, wadfilename, MAX_WADPATH);
WRITEMEM(p, wadfiles[i]->md5sum, 16);
}
netbuffer->u.serverinfo.fileneedednum = (UINT8)count;
if (netbuffer->packettype == PT_MOREFILESNEEDED)
netbuffer->u.filesneededcfg.num = count;
else
netbuffer->u.serverinfo.fileneedednum = count;
return p;
}
void AllocFileNeeded(INT32 size)
{
if (fileneeded == NULL)
fileneeded = Z_Calloc(sizeof(fileneeded_t) * size, PU_STATIC, NULL);
else
fileneeded = Z_Realloc(fileneeded, sizeof(fileneeded_t) * size, PU_STATIC, NULL);
}
void FreeFileNeeded(void)
{
Z_Free(fileneeded);
fileneeded = NULL;
}
/** Parses the serverinfo packet and fills the fileneeded table on client
*
* \param fileneedednum_parm The number of files needed to join the server
* \param fileneededstr The memory block containing the list of needed files
*
*/
void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr)
void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr, UINT16 firstfile)
{
INT32 i;
UINT8 *p;
UINT8 filestatus;
fileneedednum = fileneedednum_parm;
fileneedednum = firstfile + fileneedednum_parm;
p = (UINT8 *)fileneededstr;
for (i = 0; i < fileneedednum; i++)
AllocFileNeeded(fileneedednum);
for (i = firstfile; i < fileneedednum; i++)
{
fileneeded[i].status = FS_NOTFOUND; // We haven't even started looking for the file yet
fileneeded[i].type = FILENEEDED_WAD;
fileneeded[i].status = FS_NOTCHECKED; // 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].folder = READUINT8(p); // The second byte is the folder flag
@ -191,7 +253,11 @@ void CL_PrepareDownloadSaveGame(const char *tmpsave)
lastfilenum = -1;
#endif
FreeFileNeeded();
AllocFileNeeded(1);
fileneedednum = 1;
fileneeded[0].type = FILENEEDED_SAVEGAME;
fileneeded[0].status = FS_REQUESTED;
fileneeded[0].justdownloaded = false;
fileneeded[0].totalsize = UINT32_MAX;
@ -322,14 +388,18 @@ boolean CL_SendFileRequest(void)
if ((fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD))
{
totalfreespaceneeded += fileneeded[i].totalsize;
nameonly(fileneeded[i].filename);
WRITEUINT8(p, i); // fileid
WRITESTRINGN(p, fileneeded[i].filename, MAX_WADPATH);
// put it in download dir
nameonly(fileneeded[i].filename);
strcatbf(fileneeded[i].filename, downloaddir, "/");
fileneeded[i].status = FS_REQUESTED;
}
WRITEUINT8(p, 0xFF);
I_GetDiskFreeSpace(&availablefreespace);
if (totalfreespaceneeded > availablefreespace)
I_Error("To play on this server you must download %s KB,\n"
@ -345,21 +415,22 @@ boolean CL_SendFileRequest(void)
// returns false if a requested file was not found or cannot be sent
boolean PT_RequestFile(INT32 node)
{
char wad[MAX_WADPATH+1];
UINT8 *p = netbuffer->u.textcmd;
UINT8 id;
while (p < netbuffer->u.textcmd + MAXTEXTCMD-1) // Don't allow hacked client to overflow
{
id = READUINT8(p);
if (id == 0xFF)
break;
READSTRINGN(p, wad, MAX_WADPATH);
if (!AddFileToSendQueue(node, wad, id))
if (!AddFileToSendQueue(node, id))
{
SV_AbortSendFiles(node);
return false; // don't read the rest of the files
}
}
return true; // no problems with any files
}
@ -368,23 +439,16 @@ boolean PT_RequestFile(INT32 node)
* \return 0 if some files are missing
* 1 if all files exist
* 2 if some already loaded files are not requested or are in a different order
* 3 too many files, over WADLIMIT
* 4 still checking, continuing next tic
*
*/
INT32 CL_CheckFiles(void)
{
INT32 i, j;
char wadfilename[MAX_WADPATH];
INT32 ret = 1;
size_t packetsize = 0;
size_t filestoget = 0;
// if (M_CheckParm("-nofiles"))
// return 1;
// the first is the iwad (the main wad file)
// we don't care if it's called srb2.pk3 or not.
// Never download the IWAD, just assume it's there and identical
fileneeded[0].status = FS_OPEN;
size_t filestoload = 0;
boolean downloadrequired = false;
// Modified game handling -- check for an identical file list
// must be identical in files loaded AND in order
@ -392,7 +456,7 @@ INT32 CL_CheckFiles(void)
if (modifiedgame)
{
CONS_Debug(DBG_NETPLAY, "game is modified; only doing basic checks\n");
for (i = 1, j = 1; i < fileneedednum || j < numwadfiles;)
for (i = 0, j = mainwads; i < fileneedednum || j < numwadfiles;)
{
if (j < numwadfiles && !wadfiles[j]->important)
{
@ -419,15 +483,21 @@ INT32 CL_CheckFiles(void)
return 1;
}
// See W_InitFile in w_wad.c
packetsize = packetsizetally;
for (i = 1; i < fileneedednum; i++)
for (i = 0; i < fileneedednum; i++)
{
if (fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD)
downloadrequired = true;
if (fileneeded[i].status != FS_OPEN)
filestoload++;
if (fileneeded[i].status != FS_NOTCHECKED) //since we're running this over multiple tics now, its possible for us to come across files checked in previous tics
continue;
CONS_Debug(DBG_NETPLAY, "searching for '%s' ", fileneeded[i].filename);
// Check in already loaded files
for (j = 1; wadfiles[j]; j++)
for (j = mainwads; wadfiles[j]; j++)
{
nameonly(strcpy(wadfilename, wadfiles[j]->filename));
if (!stricmp(wadfilename, fileneeded[i].filename) &&
@ -435,43 +505,34 @@ INT32 CL_CheckFiles(void)
{
CONS_Debug(DBG_NETPLAY, "already loaded\n");
fileneeded[i].status = FS_OPEN;
break;
return 4;
}
}
if (fileneeded[i].status != FS_NOTFOUND)
continue;
if (fileneeded[i].folder)
packetsize += strlen(fileneeded[i].filename) + FILENEEDEDSIZE;
else
packetsize += nameonlylength(fileneeded[i].filename) + FILENEEDEDSIZE;
if ((numwadfiles+filestoget >= MAX_WADFILES)
|| (packetsize > MAXFILENEEDED*sizeof(UINT8)))
return 3;
filestoget++;
if (fileneeded[i].folder)
fileneeded[i].status = findfolder(fileneeded[i].filename);
else
fileneeded[i].status = findfile(fileneeded[i].filename, fileneeded[i].md5sum, true);
CONS_Debug(DBG_NETPLAY, "found %d\n", fileneeded[i].status);
if (fileneeded[i].status != FS_FOUND)
ret = 0;
return 4;
}
return ret;
//now making it here means we've checked the entire list and no FS_NOTCHECKED files remain
if (numwadfiles+filestoload > MAX_WADFILES)
return 3;
else if (downloadrequired)
return 0; //some stuff is FS_NOTFOUND, needs download
else
return 1; //everything is FS_OPEN or FS_FOUND, proceed to loading
}
// Load it now
void CL_LoadServerFiles(void)
boolean CL_LoadServerFiles(void)
{
INT32 i;
// if (M_CheckParm("-nofiles"))
// return;
for (i = 1; i < fileneedednum; i++)
for (i = 0; i < fileneedednum; i++)
{
if (fileneeded[i].status == FS_OPEN)
continue; // Already loaded
@ -483,6 +544,7 @@ void CL_LoadServerFiles(void)
P_AddWadFile(fileneeded[i].filename);
G_SetGameModified(true);
fileneeded[i].status = FS_OPEN;
return false;
}
else if (fileneeded[i].status == FS_MD5SUMBAD)
I_Error("Wrong version of file %s", fileneeded[i].filename);
@ -508,6 +570,7 @@ void CL_LoadServerFiles(void)
fileneeded[i].status, s);
}
}
return true;
}
void AddLuaFileTransfer(const char *filename, const char *mode)
@ -689,7 +752,11 @@ void CL_PrepareDownloadLuaFile(void)
netbuffer->packettype = PT_ASKLUAFILE;
HSendPacket(servernode, true, 0, 0);
FreeFileNeeded();
AllocFileNeeded(1);
fileneedednum = 1;
fileneeded[0].type = FILENEEDED_LUAFILE;
fileneeded[0].status = FS_REQUESTED;
fileneeded[0].justdownloaded = false;
fileneeded[0].totalsize = UINT32_MAX;
@ -716,15 +783,11 @@ static INT32 filestosend = 0;
* \sa AddLuaFileToSendQueue
*
*/
static boolean AddFileToSendQueue(INT32 node, const char *filename, UINT8 fileid)
static boolean AddFileToSendQueue(INT32 node, UINT8 fileid)
{
filetx_t **q; // A pointer to the "next" field of the last file in the list
filetx_t *p; // The new file request
INT32 i;
char wadfilename[MAX_WADPATH];
if (cv_noticedownload.value)
CONS_Printf("Sending file \"%s\" to node %d (%s)\n", filename, node, I_GetNodeAddress(node));
UINT16 wadnum;
// Find the last file in the list and set a pointer to its "next" field
q = &transfer[node].txlist;
@ -744,51 +807,43 @@ static boolean AddFileToSendQueue(INT32 node, const char *filename, UINT8 fileid
if (!p->id.filename)
I_Error("AddFileToSendQueue: No more memory\n");
// Set the file name and get rid of the path
strlcpy(p->id.filename, filename, MAX_WADPATH);
nameonly(p->id.filename);
// Look for the requested file through all loaded files
for (i = 0; wadfiles[i]; i++)
{
strlcpy(wadfilename, wadfiles[i]->filename, MAX_WADPATH);
nameonly(wadfilename);
if (!stricmp(wadfilename, p->id.filename))
{
// Copy file name with full path
strlcpy(p->id.filename, wadfiles[i]->filename, MAX_WADPATH);
break;
}
}
// Find the wad the ID refers to
wadnum = GetWadNumFromFileNeededId(fileid);
// Handle non-loaded file requests
if (!wadfiles[i])
if (wadnum == UINT16_MAX)
{
DEBFILE(va("%s not found in wadfiles\n", filename));
DEBFILE(va("fileneeded %d not found in wadfiles\n", fileid));
// This formerly checked if (!findfile(p->id.filename, NULL, true))
// Not found
// Don't inform client
DEBFILE(va("Client %d request %s: not found\n", node, filename));
DEBFILE(va("Client %d request fileneeded %d: not found\n", node, fileid));
free(p->id.filename);
free(p);
*q = NULL;
return false; // cancel the rest of the requests
}
// Set the file name and get rid of the path
strlcpy(p->id.filename, wadfiles[wadnum]->filename, MAX_WADPATH);
// Handle huge file requests (i.e. bigger than cv_maxsend.value KB)
if (wadfiles[i]->filesize > (UINT32)cv_maxsend.value * 1024)
if (wadfiles[wadnum]->filesize > (UINT32)cv_maxsend.value * 1024)
{
// Too big
// Don't inform client (client sucks, man)
DEBFILE(va("Client %d request %s: file too big, not sending\n", node, filename));
DEBFILE(va("Client %d request %s: file too big, not sending\n", node, p->id.filename));
free(p->id.filename);
free(p);
*q = NULL;
return false; // cancel the rest of the requests
}
DEBFILE(va("Sending file %s (id=%d) to %d\n", filename, fileid, node));
if (cv_noticedownload.value)
CONS_Printf("Sending file \"%s\" to node %d (%s)\n", p->id.filename, node, I_GetNodeAddress(node));
DEBFILE(va("Sending file %s (id=%d) to %d\n", p->id.filename, fileid, node));
p->ram = SF_FILE; // It's a file, we need to close it and free its name once we're done sending it
p->fileid = fileid;
p->next = NULL; // End of list
@ -1242,6 +1297,9 @@ void PT_FileFragment(void)
UINT16 boundedfragmentsize = doomcom->datalength - BASEPACKETSIZE - sizeof(netbuffer->u.filetxpak);
char *filename;
if (!file)
return;
filename = va("%s", file->filename);
nameonly(filename);
@ -1353,6 +1411,7 @@ void PT_FileFragment(void)
// Tell the server we have received the file
netbuffer->packettype = PT_HASLUAFILE;
HSendPacket(servernode, true, 0, 0);
FreeFileNeeded();
}
}
}
@ -1423,32 +1482,37 @@ void CloseNetFile(void)
SV_AbortSendFiles(i);
// Receiving a file?
for (i = 0; i < MAX_WADFILES; i++)
if (fileneeded[i].status == FS_DOWNLOADING && fileneeded[i].file)
{
fclose(fileneeded[i].file);
free(fileneeded[i].ackpacket);
if (!pauseddownload && i != 0) // 0 is either srb2.srb or the gamestate...
if (fileneeded)
{
for (i = 0; i < fileneedednum; i++)
if (fileneeded[i].status == FS_DOWNLOADING && fileneeded[i].file)
{
// 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");
fclose(fileneeded[i].file);
free(fileneeded[i].ackpacket);
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;
if (!pauseddownload && (fileneeded[i].type == FILENEEDED_WAD || i != 0)) // 0 is 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
{
// File is not complete, delete it.
free(fileneeded[i].receivedfragments);
remove(fileneeded[i].filename);
}
}
else
{
free(fileneeded[i].receivedfragments);
// File is not complete delete it
remove(fileneeded[i].filename);
}
}
}
FreeFileNeeded();
}
void Command_Downloads_f(void)

View file

@ -27,6 +27,7 @@ typedef enum
typedef enum
{
FS_NOTCHECKED,
FS_NOTFOUND,
FS_FOUND,
FS_REQUESTED,
@ -35,13 +36,21 @@ typedef enum
FS_MD5SUMBAD
} filestatus_t;
typedef enum
{
FILENEEDED_WAD,
FILENEEDED_SAVEGAME,
FILENEEDED_LUAFILE
} fileneededtype_t;
typedef struct
{
UINT8 willsend; // Is the server willing to send it?
UINT8 folder; // File is a folder
char filename[MAX_WADPATH];
UINT8 md5sum[16];
filestatus_t status; // The value returned by recsearch
UINT8 willsend; // Is the server willing to send it?
UINT8 folder; // File is a folder
fileneededtype_t type;
boolean justdownloaded; // To prevent late fragments from causing an I_Error
// Used only for download
@ -58,19 +67,25 @@ typedef struct
#define FILENEEDEDSIZE 23
extern INT32 fileneedednum;
extern fileneeded_t fileneeded[MAX_WADFILES];
extern fileneeded_t *fileneeded;
extern char downloaddir[512];
#ifndef NONET
extern INT32 lastfilenum;
extern INT32 downloadcompletednum;
extern UINT32 downloadcompletedsize;
extern INT32 totalfilesrequestednum;
extern UINT32 totalfilesrequestedsize;
#endif
UINT8 *PutFileNeeded(void);
void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr);
void AllocFileNeeded(INT32 size);
void FreeFileNeeded(void);
UINT8 *PutFileNeeded(UINT16 firstfile);
void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr, UINT16 firstfile);
void CL_PrepareDownloadSaveGame(const char *tmpsave);
INT32 CL_CheckFiles(void);
void CL_LoadServerFiles(void);
boolean CL_LoadServerFiles(void);
void AddRamToSendQueue(INT32 node, void *data, size_t size, freemethod_t freemethod,
UINT8 fileid);

View file

@ -152,6 +152,9 @@ extern char logfilename[1024];
// Comment or uncomment this as necessary.
#define USE_PATCH_DTA
// Enforce a limit of loaded WAD files.
//#define ENFORCE_WAD_LIMIT
// Use .kart extension addons
//#define USE_KART

View file

@ -338,9 +338,6 @@ size_t dir_on[menudepth];
UINT8 refreshdirmenu = 0;
char *refreshdirname = NULL;
size_t packetsizetally = 0;
size_t mainwadstally = 0;
#define dirpathlen 1024
#define maxdirdepth 48
@ -830,7 +827,7 @@ char exttable[NUM_EXT_TABLE][7] = { // maximum extension length (currently 4) pl
#endif
"\5.pk3", "\5.soc", "\5.lua"}; // addfile
char filenamebuf[MAX_WADFILES][MAX_WADPATH];
static char (*filenamebuf)[MAX_WADPATH];
static boolean filemenucmp(char *haystack, char *needle)
{
@ -1102,6 +1099,10 @@ boolean preparefilemenu(boolean samedepth)
if (ext >= EXT_LOADSTART)
{
size_t i;
if (filenamebuf == NULL)
filenamebuf = calloc(sizeof(char) * MAX_WADPATH, numwadfiles);
for (i = 0; i < numwadfiles; i++)
{
if (!filenamebuf[i][0])
@ -1151,6 +1152,12 @@ boolean preparefilemenu(boolean samedepth)
}
}
if (filenamebuf)
{
free(filenamebuf);
filenamebuf = NULL;
}
closedir(dirhandle);
if ((menudepthleft != menudepth-1) // now for UP... entry

View file

@ -53,9 +53,6 @@ extern size_t dir_on[menudepth];
extern UINT8 refreshdirmenu;
extern char *refreshdirname;
extern size_t packetsizetally;
extern size_t mainwadstally;
typedef enum
{
EXT_FOLDER = 0,

View file

@ -3215,7 +3215,7 @@ boolean M_Responder(event_t *ev)
if (gamestate == GS_TITLESCREEN && finalecount < TICRATE)
return false;
if (CON_Ready())
if (CON_Ready() && gamestate != GS_WAITINGPLAYERS)
return false;
if (noFurtherInput)
@ -6410,6 +6410,7 @@ static void M_Addons(INT32 choice)
M_SetupNextMenu(&MISC_AddonsDef);
}
#ifdef ENFORCE_WAD_LIMIT
#define width 4
#define vpadding 27
#define h (BASEVIDHEIGHT-(2*vpadding))
@ -6457,6 +6458,7 @@ static void M_DrawTemperature(INT32 x, fixed_t t)
#undef vpadding
#undef h
#undef NUMCOLOURS
#endif
static char *M_AddonsHeaderPath(void)
{
@ -6550,21 +6552,20 @@ static void M_DrawAddons(void)
V_DrawCenteredString(BASEVIDWIDTH/2, 5, 0, LOCATIONSTRING1);
// (recommendedflags == V_SKYMAP ? LOCATIONSTRING2 : LOCATIONSTRING1)
#ifdef ENFORCE_WAD_LIMIT
if (numwadfiles <= mainwads+1)
y = 0;
else if (numwadfiles >= MAX_WADFILES)
y = FRACUNIT;
else
{
x = FixedDiv(((ssize_t)(numwadfiles) - (ssize_t)(mainwads+1))<<FRACBITS, ((ssize_t)MAX_WADFILES - (ssize_t)(mainwads+1))<<FRACBITS);
y = FixedDiv((((ssize_t)packetsizetally-(ssize_t)mainwadstally)<<FRACBITS), ((((ssize_t)MAXFILENEEDED*sizeof(UINT8)-(ssize_t)mainwadstally)-(5+22))<<FRACBITS)); // 5+22 = (a.ext + checksum length) is minimum addition to packet size tally
if (x > y)
y = x;
y = FixedDiv(((ssize_t)(numwadfiles) - (ssize_t)(mainwads+1))<<FRACBITS, ((ssize_t)MAX_WADFILES - (ssize_t)(mainwads+1))<<FRACBITS);
if (y > FRACUNIT) // happens because of how we're shrinkin' it a little
y = FRACUNIT;
}
M_DrawTemperature(BASEVIDWIDTH - 19 - 5, y);
#endif
// DRAW MENU
x = currentMenu->x;

View file

@ -105,7 +105,7 @@ static UINT16 lumpnumcacheindex = 0;
// GLOBALS
//===========================================================================
UINT16 numwadfiles; // number of active wadfiles
wadfile_t *wadfiles[MAX_WADFILES]; // 0 to numwadfiles-1 are valid
wadfile_t **wadfiles; // 0 to numwadfiles-1 are valid
// W_Shutdown
// Closes all of the WAD files before quitting
@ -134,6 +134,8 @@ void W_Shutdown(void)
Z_Free(wad->lumpinfo);
Z_Free(wad);
}
Z_Free(wadfiles);
}
//===========================================================================
@ -844,7 +846,6 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
#ifndef NOMD5
size_t i;
#endif
size_t packetsize;
UINT8 md5sum[16];
int important;
@ -862,9 +863,8 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
refreshdirname = NULL;
//CONS_Debug(DBG_SETUP, "Loading %s\n", filename);
//
// check if limit of active wadfiles
//
// Check if the game reached the limit of active wadfiles.
if (numwadfiles >= MAX_WADFILES)
{
CONS_Alert(CONS_ERROR, M_GetText("Maximum wad files reached\n"));
@ -884,24 +884,7 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
return INT16_MAX;
}
// Check if wad files will overflow fileneededbuffer. Only the filename part
// is send in the packet; cf.
// see PutFileNeeded in d_netfil.c
if ((important = !important))
{
packetsize = packetsizetally + nameonlylength(filename) + FILENEEDEDSIZE;
if (packetsize > MAXFILENEEDED*sizeof(UINT8))
{
CONS_Alert(CONS_ERROR, M_GetText("Maximum wad files reached\n"));
refreshdirmenu |= REFRESHDIR_MAX;
if (handle)
fclose(handle);
return W_InitFileError(filename, startup);
}
packetsizetally = packetsize;
}
important = !important;
#ifndef NOMD5
//
@ -913,11 +896,12 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
for (i = 0; i < numwadfiles; i++)
{
if (wadfiles[i]->type == RET_FOLDER)
continue;
if (!memcmp(wadfiles[i]->md5sum, md5sum, 16))
{
CONS_Alert(CONS_ERROR, M_GetText("%s is already loaded\n"), filename);
if (important)
packetsizetally -= nameonlylength(filename) + FILENEEDEDSIZE;
if (handle)
fclose(handle);
return W_InitFileError(filename, false);
@ -984,6 +968,7 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
// add the wadfile
//
CONS_Printf(M_GetText("Added file %s (%u lumps)\n"), filename, numlumps);
wadfiles = Z_Realloc(wadfiles, sizeof(wadfile_t) * (numwadfiles + 1), PU_STATIC, NULL);
wadfiles[numwadfiles] = wadfile;
numwadfiles++; // must come BEFORE W_LoadDehackedLumps, so any addfile called by COM_BufInsertText called by Lua doesn't overwrite what we just loaded
@ -1046,22 +1031,7 @@ UINT16 W_InitFolder(const char *path, boolean mainfile, boolean startup)
return W_InitFileError(path, startup);
}
important = 0; // ???
/// \todo Implement a W_VerifyFolder.
if ((important = !important))
{
size_t packetsize = packetsizetally + strlen(path) + FILENEEDEDSIZE;
if (packetsize > MAXFILENEEDED*sizeof(UINT8))
{
CONS_Alert(CONS_ERROR, M_GetText("Maximum wad files reached\n"));
refreshdirmenu |= REFRESHDIR_MAX;
return W_InitFileError(path, startup);
}
packetsizetally = packetsize;
}
important = 0; /// \todo Implement a W_VerifyFolder.
// Remove path delimiters.
p = path + (strlen(path) - 1);
@ -1132,8 +1102,6 @@ UINT16 W_InitFolder(const char *path, boolean mainfile, boolean startup)
if (samepaths(wadfiles[i]->path, fullpath) > 0)
{
CONS_Alert(CONS_ERROR, M_GetText("%s is already loaded\n"), path);
if (important)
packetsizetally -= strlen(path) + FILENEEDEDSIZE;
Z_Free(fn);
Z_Free(fullpath);
return W_InitFileError(path, false);
@ -1197,11 +1165,13 @@ UINT16 W_InitFolder(const char *path, boolean mainfile, boolean startup)
*
* \param filenames A null-terminated list of files to use.
*/
void W_InitMultipleFiles(char **filenames)
void W_InitMultipleFiles(addfilelist_t *list)
{
for (; *filenames; filenames++)
size_t i = 0;
for (; i < list->numfiles; i++)
{
const char *fn = (*filenames);
const char *fn = list->files[i];
char pathsep = fn[strlen(fn) - 1];
boolean mainfile = (numwadfiles < mainwads);
@ -1219,7 +1189,7 @@ void W_InitMultipleFiles(char **filenames)
*/
static boolean TestValidLump(UINT16 wad, UINT16 lump)
{
I_Assert(wad < MAX_WADFILES);
I_Assert(wad < numwadfiles);
if (!wadfiles[wad]) // make sure the wad file exists
return false;
@ -1636,7 +1606,7 @@ size_t W_LumpLength(lumpnum_t lumpnum)
//
// W_IsLumpWad
// Is the lump a WAD? (presumably in a PK3)
// Is the lump a WAD? (presumably not in a WAD)
//
boolean W_IsLumpWad(lumpnum_t lumpnum)
{
@ -1649,12 +1619,12 @@ boolean W_IsLumpWad(lumpnum_t lumpnum)
return !strnicmp(lumpfullName + strlen(lumpfullName) - 4, ".wad", 4);
}
return false; // WADs should never be inside non-PK3s as far as SRB2 is concerned
return false; // WADs should never be inside WADs as far as SRB2 is concerned
}
//
// W_IsLumpFolder
// Is the lump a folder? (in a PK3 obviously)
// Is the lump a folder? (not in a WAD obviously)
//
boolean W_IsLumpFolder(UINT16 wad, UINT16 lump)
{
@ -1665,7 +1635,7 @@ boolean W_IsLumpFolder(UINT16 wad, UINT16 lump)
return (name[strlen(name)-1] == '/'); // folders end in '/'
}
return false; // non-PK3s don't have folders
return false; // WADs don't have folders
}
#ifdef HAVE_ZLIB
@ -2217,7 +2187,7 @@ void W_VerifyFileMD5(UINT16 wadfilenum, const char *matchmd5)
#else
I_Error
#endif
(M_GetText("File is old, is corrupt or has been modified: %s (found md5: %s, wanted: %s)\n"), wadfiles[wadfilenum]->filename, actualmd5text, matchmd5);
(M_GetText("File is old, is corrupt or has been modified:\n%s\nFound MD5: %s\nWanted MD5: %s\n"), wadfiles[wadfilenum]->filename, actualmd5text, matchmd5);
}
#endif
}

View file

@ -97,9 +97,15 @@ virtlump_t* vres_Find(const virtres_t*, const char*);
// DYNAMIC WAD LOADING
// =========================================================================
// Maximum of files that can be loaded
// (there is a max of simultaneous open files anyway)
#ifdef ENFORCE_WAD_LIMIT
#define MAX_WADFILES 2048 // This cannot be any higher than UINT16_MAX.
#else
#define MAX_WADFILES UINT16_MAX
#endif
#define MAX_WADPATH 512
#define MAX_WADFILES 48 // maximum of wad files used at the same time
// (there is a max of simultaneous open files anyway, and this should be plenty)
#define lumpcache_t void *
@ -134,7 +140,13 @@ typedef struct wadfile_s
#define LUMPNUM(lumpnum) (UINT16)((lumpnum)&0xFFFF) // lump number for this pwad
extern UINT16 numwadfiles;
extern wadfile_t *wadfiles[MAX_WADFILES];
extern wadfile_t **wadfiles;
typedef struct
{
char **files;
size_t numfiles;
} addfilelist_t;
// =========================================================================
@ -148,7 +160,7 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup);
UINT16 W_InitFolder(const char *path, boolean mainfile, boolean startup);
// W_InitMultipleFiles exits if a file was not found, but not if all is okay.
void W_InitMultipleFiles(char **filenames);
void W_InitMultipleFiles(addfilelist_t *list);
#define W_FileHasFolders(wadfile) ((wadfile)->type == RET_PK3 || (wadfile)->type == RET_FOLDER)