diff --git a/src/console.c b/src/console.c index 2c148bc6..79f2c8c4 100644 --- a/src/console.c +++ b/src/console.c @@ -33,6 +33,7 @@ #include "i_system.h" #include "d_main.h" #include "m_menu.h" +#include "filesrch.h" #ifdef _WINDOWS #include "win32/win_main.h" @@ -1261,12 +1262,15 @@ void CONS_Alert(alerttype_t level, const char *fmt, ...) switch (level) { case CONS_NOTICE: + // no notice for notices, hehe CONS_Printf("\x83" "%s" "\x80 ", M_GetText("NOTICE:")); break; case CONS_WARNING: + refreshdirmenu |= REFRESHDIR_WARNING; CONS_Printf("\x82" "%s" "\x80 ", M_GetText("WARNING:")); break; case CONS_ERROR: + refreshdirmenu |= REFRESHDIR_ERROR; CONS_Printf("\x85" "%s" "\x80 ", M_GetText("ERROR:")); break; } diff --git a/src/d_clisrv.h b/src/d_clisrv.h index fc15d056..5d086127 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -309,6 +309,7 @@ typedef struct } ATTRPACK clientconfig_pak; #define MAXSERVERNAME 32 +#define MAXFILENEEDED 915 // This packet is too large typedef struct { @@ -330,7 +331,7 @@ typedef struct unsigned char mapmd5[16]; UINT8 actnum; UINT8 iszone; - UINT8 fileneeded[915]; // is filled with writexxx (byteptr.h) + UINT8 fileneeded[MAXFILENEEDED]; // is filled with writexxx (byteptr.h) } ATTRPACK serverinfo_pak; typedef struct diff --git a/src/d_main.c b/src/d_main.c index 23835136..2ee0afae 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -74,6 +74,7 @@ int snprintf(char *str, size_t n, const char *fmt, ...); #include "m_cond.h" // condition initialization #include "fastcmp.h" #include "keys.h" +#include "filesrch.h" // refreshdirmenu, mainwadstally #ifdef CMAKECONFIG #include "config.h" @@ -586,6 +587,8 @@ void D_SRB2Loop(void) realtics = entertic - oldentertics; oldentertics = entertic; + refreshdirmenu = 0; // not sure where to put this, here as good as any? + #ifdef DEBUGFILE if (!realtics) if (debugload) @@ -855,12 +858,17 @@ static void IdentifyVersion(void) #else const char *musicfile = "music.dta"; #endif - const char *musicpath = va(pandf,srb2waddir,musicfile); - int ms = W_VerifyNMUSlumps(musicpath); // Don't forget the music! - if (ms == 1) - D_AddFile(musicpath); - else if (ms == 0) - I_Error("File %s has been modified with non-music lumps",musicfile); +#define MUSICTEST(str) \ + {\ + const char *musicpath = va(pandf,srb2waddir,str);\ + int ms = W_VerifyNMUSlumps(musicpath); \ + if (ms == 1) \ + D_AddFile(musicpath); \ + else if (ms == 0) \ + I_Error("File "str" has been modified with non-music/sound lumps"); \ + } + + MUSICTEST("music.dta") } #endif } @@ -1136,6 +1144,8 @@ void D_SRB2Main(void) #endif D_CleanFile(); + mainwads = 0; + #ifndef DEVELOP // md5s last updated 12/14/14 // Check MD5s of autoloaded files @@ -1146,15 +1156,20 @@ void D_SRB2Main(void) #ifdef USE_PATCH_DTA W_VerifyFileMD5(4, ASSET_HASH_PATCH_DTA); // patch.dta #endif - + mainwads++; // srb2.srb + mainwads++; // zones.dta + mainwads++; // player.dta + mainwads++; // rings.dta // don't check music.dta because people like to modify it, and it doesn't matter if they do // ...except it does if they slip maps in there, and that's what W_VerifyNMUSlumps is for. +#else +#ifdef USE_PATCH_DTA + mainwads++; // patch.dta +#endif + mainwads++; // music.dta #endif //ifndef DEVELOP - mainwads = 4; // there are 4 wads not to unload -#ifdef USE_PATCH_DTA - ++mainwads; // patch.dta adds one more -#endif + mainwadstally = packetsizetally; cht_Init(); diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 16788bae..a452e0b7 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -38,6 +38,7 @@ #include "d_main.h" #include "m_random.h" #include "f_finale.h" +#include "filesrch.h" #include "mserv.h" #include "md5.h" #include "z_zone.h" @@ -689,6 +690,14 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_firenaxis); CV_RegisterVar(&cv_firenaxis2); + // filesrch.c + CV_RegisterVar(&cv_addons_option); + CV_RegisterVar(&cv_addons_folder); + CV_RegisterVar(&cv_addons_md5); + CV_RegisterVar(&cv_addons_showall); + CV_RegisterVar(&cv_addons_search_type); + CV_RegisterVar(&cv_addons_search_case); + // WARNING: the order is important when initialising mouse2 // we need the mouse2port CV_RegisterVar(&cv_mouse2port); @@ -3018,25 +3027,12 @@ static void Command_Addfile(void) break; ++p; // check total packet size and no of files currently loaded + // See W_LoadWadFile in w_wad.c + if ((numwadfiles >= MAX_WADFILES) + || ((packetsizetally + nameonlylength(fn) + 22) > MAXFILENEEDED*sizeof(UINT8))) { - size_t packetsize = 0; - serverinfo_pak *dummycheck = NULL; - - // Shut the compiler up. - (void)dummycheck; - - // See W_LoadWadFile in w_wad.c - for (i = 0; i < numwadfiles; i++) - packetsize += nameonlylength(wadfiles[i]->filename) + 22; - - packetsize += nameonlylength(fn) + 22; - - if ((numwadfiles >= MAX_WADFILES) - || (packetsize > sizeof(dummycheck->fileneeded))) - { - CONS_Alert(CONS_ERROR, M_GetText("Too many files loaded to add %s\n"), fn); - return; - } + CONS_Alert(CONS_ERROR, M_GetText("Too many files loaded to add %s\n"), fn); + return; } WRITESTRINGN(buf_p,p,240); @@ -3153,13 +3149,8 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum) } // See W_LoadWadFile in w_wad.c - for (i = 0; i < numwadfiles; i++) - packetsize += nameonlylength(wadfiles[i]->filename) + 22; - - packetsize += nameonlylength(filename) + 22; - if ((numwadfiles >= MAX_WADFILES) - || (packetsize > sizeof(dummycheck->fileneeded))) + || ((packetsizetally + nameonlylength(filename) + 22) > MAXFILENEEDED*sizeof(UINT8))) toomany = true; else ncs = findfile(filename,md5sum,true); @@ -3342,6 +3333,9 @@ static void Command_Playintro_f(void) if (netgame) return; + if (dirmenu) + closefilemenu(true); + F_StartIntro(); } @@ -3899,6 +3893,9 @@ void Command_ExitGame_f(void) cv_debug = 0; emeralds = 0; + if (dirmenu) + closefilemenu(true); + if (!modeattacking) D_StartTitle(); } diff --git a/src/d_netfil.c b/src/d_netfil.c index 7c480658..1cc789ee 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -104,6 +104,7 @@ INT32 lastfilenum = -1; /** Fills a serverinfo packet with information about wad files loaded. * * \todo Give this function a better name since it is in global scope. + * Used to have size limiting built in - now handed via W_LoadWadFile in w_wad.c * */ UINT8 *PutFileNeeded(void) @@ -112,29 +113,22 @@ UINT8 *PutFileNeeded(void) UINT8 *p = netbuffer->u.serverinfo.fileneeded; char wadfilename[MAX_WADPATH] = ""; UINT8 filestatus; - size_t bytesused = 0; for (i = 0; i < numwadfiles; i++) { - // If it has only music/sound lumps, mark it as unimportant - if (W_VerifyNMUSlumps(wadfiles[i]->filename)) - filestatus = 0; - else - filestatus = 1; // Important + // If it has only music/sound lumps, don't put it in the list + if (!wadfiles[i]->important) + continue; + + filestatus = 1; // Importance - not really used any more, holds 1 by default for backwards compat with MS // Store in the upper four bits if (!cv_downloading.value) filestatus += (2 << 4); // Won't send - else if ((wadfiles[i]->filesize > (UINT32)cv_maxsend.value * 1024)) - filestatus += (0 << 4); // Won't send - else + else if ((wadfiles[i]->filesize <= (UINT32)cv_maxsend.value * 1024)) filestatus += (1 << 4); // Will send if requested - - bytesused += (nameonlylength(wadfilename) + 22); - - // Don't write too far... - if (bytesused > sizeof(netbuffer->u.serverinfo.fileneeded)) - I_Error("Too many wad files added to host a game. (%s, stopped on %s)\n", sizeu1(bytesused), wadfilename); + // else + // filestatus += (0 << 4); -- Won't send, too big WRITEUINT8(p, filestatus); @@ -167,7 +161,6 @@ void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr) { fileneeded[i].status = FS_NOTFOUND; // We haven't even started looking for the file yet filestatus = READUINT8(p); // The first byte is the file status - fileneeded[i].important = (UINT8)(filestatus & 3); fileneeded[i].willsend = (UINT8)(filestatus >> 4); fileneeded[i].totalsize = READUINT32(p); // The four next bytes are the file size fileneeded[i].file = NULL; // The file isn't open yet @@ -197,7 +190,7 @@ boolean CL_CheckDownloadable(void) UINT8 i,dlstatus = 0; for (i = 0; i < fileneedednum; i++) - if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN && fileneeded[i].important) + if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN) { if (fileneeded[i].willsend == 1) continue; @@ -218,7 +211,7 @@ boolean CL_CheckDownloadable(void) // not downloadable, put reason in console CONS_Alert(CONS_NOTICE, M_GetText("You need additional files to connect to this server:\n")); for (i = 0; i < fileneedednum; i++) - if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN && fileneeded[i].important) + if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN) { CONS_Printf(" * \"%s\" (%dK)", fileneeded[i].filename, fileneeded[i].totalsize >> 10); @@ -271,7 +264,7 @@ boolean CL_SendRequestFile(void) for (i = 0; i < fileneedednum; i++) if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN - && fileneeded[i].important && (fileneeded[i].willsend == 0 || fileneeded[i].willsend == 2)) + && (fileneeded[i].willsend == 0 || fileneeded[i].willsend == 2)) { I_Error("Attempted to download files that were not sendable"); } @@ -280,8 +273,7 @@ boolean CL_SendRequestFile(void) netbuffer->packettype = PT_REQUESTFILE; p = (char *)netbuffer->u.textcmd; for (i = 0; i < fileneedednum; i++) - if ((fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD) - && fileneeded[i].important) + if ((fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD)) { totalfreespaceneeded += fileneeded[i].totalsize; nameonly(fileneeded[i].filename); @@ -339,10 +331,6 @@ INT32 CL_CheckFiles(void) INT32 ret = 1; size_t packetsize = 0; size_t filestoget = 0; - serverinfo_pak *dummycheck = NULL; - - // Shut the compiler up. - (void)dummycheck; // if (M_CheckParm("-nofiles")) // return 1; @@ -360,13 +348,7 @@ INT32 CL_CheckFiles(void) CONS_Debug(DBG_NETPLAY, "game is modified; only doing basic checks\n"); for (i = 1, j = 1; i < fileneedednum || j < numwadfiles;) { - if (i < fileneedednum && !fileneeded[i].important) - { - // Eh whatever, don't care - ++i; - continue; - } - if (j < numwadfiles && W_VerifyNMUSlumps(wadfiles[j]->filename)) + if (j < numwadfiles && !wadfiles[j]->important) { // Unimportant on our side. still don't care. ++j; @@ -392,8 +374,7 @@ INT32 CL_CheckFiles(void) } // See W_LoadWadFile in w_wad.c - for (i = 0; i < numwadfiles; i++) - packetsize += nameonlylength(wadfiles[i]->filename) + 22; + packetsize = packetsizetally; for (i = 1; i < fileneedednum; i++) { @@ -411,13 +392,13 @@ INT32 CL_CheckFiles(void) break; } } - if (fileneeded[i].status != FS_NOTFOUND || !fileneeded[i].important) + if (fileneeded[i].status != FS_NOTFOUND) continue; packetsize += nameonlylength(fileneeded[i].filename) + 22; if ((numwadfiles+filestoget >= MAX_WADFILES) - || (packetsize > sizeof(dummycheck->fileneeded))) + || (packetsize > MAXFILENEEDED*sizeof(UINT8))) return 3; filestoget++; @@ -449,27 +430,8 @@ void CL_LoadServerFiles(void) fileneeded[i].status = FS_OPEN; } else if (fileneeded[i].status == FS_MD5SUMBAD) - { - // If the file is marked important, don't even bother proceeding. - if (fileneeded[i].important) - I_Error("Wrong version of important file %s", fileneeded[i].filename); - - // If it isn't, no need to worry the user with a console message, - // although it can't hurt to put something in the debug file. - - // ...but wait a second. What if the local version is "important"? - if (!W_VerifyNMUSlumps(fileneeded[i].filename)) - I_Error("File %s should only contain music and sound effects!", - fileneeded[i].filename); - - // Okay, NOW we know it's safe. Whew. - P_AddWadFile(fileneeded[i].filename, NULL); - if (fileneeded[i].important) - G_SetGameModified(true); - fileneeded[i].status = FS_OPEN; - DEBFILE(va("File %s found but with different md5sum\n", fileneeded[i].filename)); - } - else if (fileneeded[i].important) + I_Error("Wrong version of file %s", fileneeded[i].filename); + else { const char *s; switch(fileneeded[i].status) @@ -939,10 +901,11 @@ void nameonly(char *s) { ns = &(s[j+1]); len = strlen(ns); - if (false) +#if 0 M_Memcpy(s, ns, len+1); - else +#else memmove(s, ns, len+1); +#endif return; } } diff --git a/src/d_netfil.h b/src/d_netfil.h index a7b69293..3d7c2ed5 100644 --- a/src/d_netfil.h +++ b/src/d_netfil.h @@ -35,7 +35,6 @@ typedef enum typedef struct { - UINT8 important; UINT8 willsend; // Is the server willing to send it? char filename[MAX_WADPATH]; UINT8 md5sum[16]; diff --git a/src/filesrch.c b/src/filesrch.c index 2463e717..98be1f39 100644 --- a/src/filesrch.c +++ b/src/filesrch.c @@ -31,6 +31,8 @@ #include "filesrch.h" #include "d_netfil.h" #include "m_misc.h" +#include "z_zone.h" +#include "m_menu.h" // Addons_option_Onchange #if (defined (_WIN32) && !defined (_WIN32_WCE)) && defined (_MSC_VER) && !defined (_XBOX) @@ -255,6 +257,28 @@ readdir (DIR * dirp) return (struct dirent *) 0; } +/* + * rewinddir + * + * Makes the next readdir start from the beginning. + */ +int +rewinddir (DIR * dirp) +{ + errno = 0; + + /* Check for valid DIR struct. */ + if (!dirp) + { + errno = EFAULT; + return -1; + } + + dirp->dd_stat = 0; + + return 0; +} + /* * closedir * @@ -285,6 +309,41 @@ closedir (DIR * dirp) return rc; } #endif + +static CV_PossibleValue_t addons_cons_t[] = {{0, "Default"}, +#if 1 + {1, "HOME"}, {2, "SRB2"}, +#endif + {3, "CUSTOM"}, {0, NULL}}; + +consvar_t cv_addons_option = {"addons_option", "Default", CV_SAVE|CV_CALL, addons_cons_t, Addons_option_Onchange, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_addons_folder = {"addons_folder", "", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL}; + +static CV_PossibleValue_t addons_md5_cons_t[] = {{0, "Name"}, {1, "Contents"}, {0, NULL}}; +consvar_t cv_addons_md5 = {"addons_md5", "Name", CV_SAVE, addons_md5_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; + +consvar_t cv_addons_showall = {"addons_showall", "No", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL}; + +consvar_t cv_addons_search_case = {"addons_search_case", "No", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL}; + +static CV_PossibleValue_t addons_search_type_cons_t[] = {{0, "Start"}, {1, "Anywhere"}, {0, NULL}}; +consvar_t cv_addons_search_type = {"addons_search_type", "Anywhere", CV_SAVE, addons_search_type_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; + +char menupath[1024]; +size_t menupathindex[menudepth]; +size_t menudepthleft = menudepth; + +char menusearch[MAXSTRINGLENGTH+1]; + +char **dirmenu, **coredirmenu; // core only local for this file +size_t sizedirmenu, sizecoredirmenu; // ditto +size_t dir_on[menudepth]; +UINT8 refreshdirmenu = 0; +char *refreshdirname = NULL; + +size_t packetsizetally = 0; +size_t mainwadstally = 0; + #if defined (_XBOX) && defined (_MSC_VER) filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *wantedmd5sum, boolean completepath, int maxsearchdepth) @@ -296,6 +355,25 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want completepath = false; return FS_NOTFOUND; } + +void closefilemenu(boolean validsize) +{ + (void)validsize; + return; +} + +void searchfilemenu(char *tempname) +{ + (void)tempname; + return; +} + +boolean preparefilemenu(boolean samedepth) +{ + (void)samedepth; + return false; +} + #elif defined (_WIN32_WCE) filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *wantedmd5sum, boolean completepath, int maxsearchdepth) @@ -346,7 +424,27 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want #endif return FS_NOTFOUND; } + +void closefilemenu(boolean validsize) +{ + (void)validsize; + return; +} + +void searchfilemenu(char *tempname) +{ + (void)tempname; + return; +} + +boolean preparefilemenu(boolean samedepth) +{ + (void)samedepth; + return false; +} + #else + filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *wantedmd5sum, boolean completepath, int maxsearchdepth) { filestatus_t retval = FS_NOTFOUND; @@ -387,25 +485,29 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want { searchpath[searchpathindex[depthleft]]=0; dent = readdir(dirhandle[depthleft]); - if (dent) - strcpy(&searchpath[searchpathindex[depthleft]],dent->d_name); if (!dent) + { closedir(dirhandle[depthleft++]); - else if (dent->d_name[0]=='.' && + continue; + } + + if (dent->d_name[0]=='.' && (dent->d_name[1]=='\0' || (dent->d_name[1]=='.' && dent->d_name[2]=='\0'))) { // we don't want to scan uptree + continue; } - else if (stat(searchpath,&fsstat) < 0) // do we want to follow symlinks? if not: change it to lstat - { - // was the file (re)moved? can't stat it - } + + // okay, now we actually want searchpath to incorporate d_name + strcpy(&searchpath[searchpathindex[depthleft]],dent->d_name); + + if (stat(searchpath,&fsstat) < 0) // do we want to follow symlinks? if not: change it to lstat + ; // was the file (re)moved? can't stat it else if (S_ISDIR(fsstat.st_mode) && depthleft) { - strcpy(&searchpath[searchpathindex[depthleft]],dent->d_name); searchpathindex[--depthleft] = strlen(searchpath) + 1; dirhandle[depthleft] = opendir(searchpath); if (!dirhandle[depthleft]) @@ -444,6 +546,361 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want free(searchname); free(searchpathindex); free(dirhandle); + return retval; } + +char exttable[NUM_EXT_TABLE][7] = { // maximum extension length (currently 4) plus 3 (null terminator, stop, and length including previous two) + "\5.txt", "\5.cfg", // exec + "\5.wad", "\6.kart", /*"\5.pk3",*/ "\5.soc", "\5.lua"}; // addfile + +char filenamebuf[MAX_WADFILES][MAX_WADPATH]; + + +static boolean filemenucmp(char *haystack, char *needle) +{ + static char localhaystack[128]; + strlcpy(localhaystack, haystack, 128); + if (!cv_addons_search_case.value) + strupr(localhaystack); + if (cv_addons_search_type.value) + return (strstr(localhaystack, needle) != 0); + return (!strncmp(localhaystack, needle, menusearch[0])); +} + +void closefilemenu(boolean validsize) +{ + // search + if (dirmenu) + { + if (dirmenu != coredirmenu) + { + if (dirmenu[0] && ((UINT8)(dirmenu[0][DIR_TYPE]) == EXT_NORESULTS)) + { + Z_Free(dirmenu[0]); + dirmenu[0] = NULL; + } + Z_Free(dirmenu); + } + dirmenu = NULL; + sizedirmenu = 0; + } + + if (coredirmenu) + { + // core + if (validsize) + { + for (; sizecoredirmenu > 0; sizecoredirmenu--) + { + Z_Free(coredirmenu[sizecoredirmenu-1]); + coredirmenu[sizecoredirmenu-1] = NULL; + } + } + else + sizecoredirmenu = 0; + + Z_Free(coredirmenu); + coredirmenu = NULL; + } + + if (refreshdirname) + Z_Free(refreshdirname); + refreshdirname = NULL; +} + +void searchfilemenu(char *tempname) +{ + size_t i, first; + char localmenusearch[MAXSTRINGLENGTH] = ""; + + if (dirmenu) + { + if (dirmenu != coredirmenu) + { + if (dirmenu[0] && ((UINT8)(dirmenu[0][DIR_TYPE]) == EXT_NORESULTS)) + { + Z_Free(dirmenu[0]); + dirmenu[0] = NULL; + } + //Z_Free(dirmenu); -- Z_Realloc later tho... + } + else + dirmenu = NULL; + } + + first = (((UINT8)(coredirmenu[0][DIR_TYPE]) == EXT_UP) ? 1 : 0); // skip UP... + + if (!menusearch[0]) + { + if (dirmenu) + Z_Free(dirmenu); + dirmenu = coredirmenu; + sizedirmenu = sizecoredirmenu; + + if (tempname) + { + for (i = first; i < sizedirmenu; i++) + { + if (!strcmp(dirmenu[i]+DIR_STRING, tempname)) + { + dir_on[menudepthleft] = i; + break; + } + } + + if (i == sizedirmenu) + dir_on[menudepthleft] = first; + + Z_Free(tempname); + } + + return; + } + + strcpy(localmenusearch, menusearch+1); + if (!cv_addons_search_case.value) + strupr(localmenusearch); + + sizedirmenu = 0; + for (i = first; i < sizecoredirmenu; i++) + { + if (filemenucmp(coredirmenu[i]+DIR_STRING, localmenusearch)) + sizedirmenu++; + } + + if (!sizedirmenu) // no results... + { + if ((!(dirmenu = Z_Realloc(dirmenu, sizeof(char *), PU_STATIC, NULL))) + || !(dirmenu[0] = Z_StrDup(va("%c\13No results...", EXT_NORESULTS)))) + I_Error("searchfilemenu(): could not create \"No results...\"."); + sizedirmenu = 1; + dir_on[menudepthleft] = 0; + if (tempname) + Z_Free(tempname); + return; + } + + if (!(dirmenu = Z_Realloc(dirmenu, sizedirmenu*sizeof(char *), PU_STATIC, NULL))) + I_Error("searchfilemenu(): could not reallocate dirmenu."); + + sizedirmenu = 0; + for (i = first; i < sizecoredirmenu; i++) + { + if (filemenucmp(coredirmenu[i]+DIR_STRING, localmenusearch)) + { + if (tempname && !strcmp(coredirmenu[i]+DIR_STRING, tempname)) + { + dir_on[menudepthleft] = sizedirmenu; + Z_Free(tempname); + tempname = NULL; + } + dirmenu[sizedirmenu++] = coredirmenu[i]; // pointer reuse + } + } + + if (tempname) + { + dir_on[menudepthleft] = 0; //first; -- can't be first, causes problems + Z_Free(tempname); + } +} + +boolean preparefilemenu(boolean samedepth) +{ + DIR *dirhandle; + struct dirent *dent; + struct stat fsstat; + size_t pos = 0, folderpos = 0, numfolders = 0; + char *tempname = NULL; + + if (samedepth) + { + if (dirmenu && dirmenu[dir_on[menudepthleft]]) + tempname = Z_StrDup(dirmenu[dir_on[menudepthleft]]+DIR_STRING); // don't need to I_Error if can't make - not important, just QoL + } + else + menusearch[0] = menusearch[1] = 0; // clear search + + if (!(dirhandle = opendir(menupath))) // get directory + { + closefilemenu(true); + return false; + } + + for (; sizecoredirmenu > 0; sizecoredirmenu--) // clear out existing items + { + Z_Free(coredirmenu[sizecoredirmenu-1]); + coredirmenu[sizecoredirmenu-1] = NULL; + } + + while (true) + { + menupath[menupathindex[menudepthleft]] = 0; + dent = readdir(dirhandle); + + if (!dent) + break; + else if (dent->d_name[0]=='.' && + (dent->d_name[1]=='\0' || + (dent->d_name[1]=='.' && + dent->d_name[2]=='\0'))) + continue; // we don't want to scan uptree + + strcpy(&menupath[menupathindex[menudepthleft]],dent->d_name); + + if (stat(menupath,&fsstat) < 0) // do we want to follow symlinks? if not: change it to lstat + ; // was the file (re)moved? can't stat it + else // is a file or directory + { + if (!S_ISDIR(fsstat.st_mode)) // file + { + if (!cv_addons_showall.value) + { + size_t len = strlen(dent->d_name)+1; + UINT8 ext; + for (ext = 0; ext < NUM_EXT_TABLE; ext++) + if (!strcasecmp(exttable[ext]+1, dent->d_name+len-(exttable[ext][0]))) break; // extension comparison + if (ext == NUM_EXT_TABLE) continue; // not an addfile-able (or exec-able) file + } + } + else // directory + numfolders++; + + sizecoredirmenu++; + } + } + + if (!sizecoredirmenu) + { + closedir(dirhandle); + closefilemenu(false); + if (tempname) + Z_Free(tempname); + return false; + } + + if (menudepthleft != menudepth-1) // Make room for UP... + { + sizecoredirmenu++; + numfolders++; + folderpos++; + } + + if (dirmenu && dirmenu == coredirmenu) + dirmenu = NULL; + + if (!(coredirmenu = Z_Realloc(coredirmenu, sizecoredirmenu*sizeof(char *), PU_STATIC, NULL))) + { + closedir(dirhandle); // just in case + I_Error("preparefilemenu(): could not reallocate coredirmenu."); + } + + rewinddir(dirhandle); + + while ((pos+folderpos) < sizecoredirmenu) + { + menupath[menupathindex[menudepthleft]] = 0; + dent = readdir(dirhandle); + + if (!dent) + break; + else if (dent->d_name[0]=='.' && + (dent->d_name[1]=='\0' || + (dent->d_name[1]=='.' && + dent->d_name[2]=='\0'))) + continue; // we don't want to scan uptree + + strcpy(&menupath[menupathindex[menudepthleft]],dent->d_name); + + if (stat(menupath,&fsstat) < 0) // do we want to follow symlinks? if not: change it to lstat + ; // was the file (re)moved? can't stat it + else // is a file or directory + { + char *temp; + size_t len = strlen(dent->d_name)+1; + UINT8 ext = EXT_FOLDER; + UINT8 folder; + + if (!S_ISDIR(fsstat.st_mode)) // file + { + if (!((numfolders+pos) < sizecoredirmenu)) continue; // crash prevention + for (; ext < NUM_EXT_TABLE; ext++) + if (!strcasecmp(exttable[ext]+1, dent->d_name+len-(exttable[ext][0]))) break; // extension comparison + if (ext == NUM_EXT_TABLE && !cv_addons_showall.value) continue; // not an addfile-able (or exec-able) file + ext += EXT_START; // moving to be appropriate position + + if (ext >= EXT_LOADSTART) + { + size_t i; + for (i = 0; i < numwadfiles; i++) + { + if (!filenamebuf[i][0]) + { + strncpy(filenamebuf[i], wadfiles[i]->filename, MAX_WADPATH); + filenamebuf[i][MAX_WADPATH - 1] = '\0'; + nameonly(filenamebuf[i]); + } + + if (strcmp(dent->d_name, filenamebuf[i])) + continue; + if (cv_addons_md5.value && !checkfilemd5(menupath, wadfiles[i]->md5sum)) + continue; + + ext |= EXT_LOADED; + } + } + else if (ext == EXT_TXT) + { + if (!strcmp(dent->d_name, "log.txt") || !strcmp(dent->d_name, "errorlog.txt")) + ext |= EXT_LOADED; + } + + if (!strcmp(dent->d_name, configfile)) + ext |= EXT_LOADED; + + folder = 0; + } + else // directory + len += (folder = 1); + + if (len > 255) + len = 255; + + if (!(temp = Z_Malloc((len+DIR_STRING+folder) * sizeof (char), PU_STATIC, NULL))) + I_Error("preparefilemenu(): could not create file entry."); + temp[DIR_TYPE] = ext; + temp[DIR_LEN] = (UINT8)(len); + strlcpy(temp+DIR_STRING, dent->d_name, len); + if (folder) + { + strcpy(temp+len, PATHSEP); + coredirmenu[folderpos++] = temp; + } + else + coredirmenu[numfolders + pos++] = temp; + } + } + + closedir(dirhandle); + + if ((menudepthleft != menudepth-1) // now for UP... entry + && !(coredirmenu[0] = Z_StrDup(va("%c\5UP...", EXT_UP)))) + I_Error("preparefilemenu(): could not create \"UP...\"."); + + menupath[menupathindex[menudepthleft]] = 0; + sizecoredirmenu = (numfolders+pos); // just in case things shrink between opening and rewind + + if (!sizecoredirmenu) + { + dir_on[menudepthleft] = 0; + closefilemenu(false); + return false; + } + + searchfilemenu(tempname); + + return true; +} + #endif diff --git a/src/filesrch.h b/src/filesrch.h index 33b148d4..75fd70af 100644 --- a/src/filesrch.h +++ b/src/filesrch.h @@ -6,6 +6,9 @@ #include "doomdef.h" #include "d_netfil.h" +#include "m_menu.h" // MAXSTRINGLENGTH + +extern consvar_t cv_addons_option, cv_addons_folder, cv_addons_md5, cv_addons_showall, cv_addons_search_case, cv_addons_search_type; /** \brief The filesearch function @@ -25,4 +28,69 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *wantedmd5sum, boolean completepath, int maxsearchdepth); +#define menudepth 20 + +extern char menupath[1024]; +extern size_t menupathindex[menudepth]; +extern size_t menudepthleft; + +extern char menusearch[MAXSTRINGLENGTH+1]; + +extern char **dirmenu; +extern size_t sizedirmenu; +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, + EXT_UP, + EXT_NORESULTS, + EXT_START, + EXT_TXT = EXT_START, + EXT_CFG, + EXT_LOADSTART, + EXT_WAD = EXT_LOADSTART, + EXT_KART, + //EXT_PK3, + EXT_SOC, + EXT_LUA, // allowed even if not HAVE_BLUA so that we can yell on load attempt + NUM_EXT, + NUM_EXT_TABLE = NUM_EXT-EXT_START, + EXT_LOADED = 0x80 + /* + obviously there can only be 0x7F supported extensions in + addons menu because we're cramming this into a char out of + laziness/easy memory allocation (what's the difference?) + and have stolen a bit to show whether it's loaded or not + in practice the size of the data type is probably overkill + toast 02/05/17 + */ +} ext_enum; + +typedef enum +{ + DIR_TYPE = 0, + DIR_LEN, + DIR_STRING +} dirname_enum; + +typedef enum +{ + REFRESHDIR_NORMAL = 1, + REFRESHDIR_ADDFILE = 2, + REFRESHDIR_WARNING = 4, + REFRESHDIR_ERROR = 8, + REFRESHDIR_NOTLOADED = 16, + REFRESHDIR_MAX = 32 +} refreshdir_enum; + +void closefilemenu(boolean validsize); +void searchfilemenu(char *tempname); +boolean preparefilemenu(boolean samedepth); + #endif // __FILESRCH_H__ diff --git a/src/m_menu.c b/src/m_menu.c index 82c818c7..2ac2ba7f 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -33,6 +33,9 @@ #include "s_sound.h" #include "i_system.h" +// Addfile +#include "filesrch.h" + #include "v_video.h" #include "i_video.h" #include "keys.h" @@ -66,6 +69,10 @@ int snprintf(char *str, size_t n, const char *fmt, ...); //int vsnprintf(char *str, size_t n, const char *fmt, va_list ap); #endif +#if defined (__GNUC__) && (__GNUC__ >= 4) +#define FIXUPO0 +#endif + #define SKULLXOFF -32 #define LINEHEIGHT 16 #define STRINGHEIGHT 8 @@ -73,7 +80,6 @@ int snprintf(char *str, size_t n, const char *fmt, ...); #define SMALLLINEHEIGHT 8 #define SLIDER_RANGE 10 #define SLIDER_WIDTH (8*SLIDER_RANGE+6) -#define MAXSTRINGLENGTH 32 #define SERVERS_PER_PAGE 11 typedef enum @@ -203,6 +209,8 @@ menu_t MessageDef; menu_t SPauseDef; +#define lsheadingheight 16 + // Sky Room static void M_CustomLevelSelect(INT32 choice); static void M_CustomWarp(INT32 choice); @@ -296,9 +304,16 @@ menu_t OP_MonitorToggleDef; static void M_ScreenshotOptions(INT32 choice); static void M_EraseData(INT32 choice); +static void M_Addons(INT32 choice); +static void M_AddonsOptions(INT32 choice); +static patch_t *addonsp[NUM_EXT+5]; + +#define numaddonsshown 4 + // Drawing functions static void M_DrawGenericMenu(void); static void M_DrawCenteredMenu(void); +static void M_DrawAddons(void); static void M_DrawSkyRoom(void); static void M_DrawChecklist(void); static void M_DrawEmblemHints(void); @@ -333,6 +348,7 @@ static boolean M_CancelConnect(void); #endif static boolean M_ExitPandorasBox(void); static boolean M_QuitMultiPlayerMenu(void); +static void M_HandleAddons(INT32 choice); static void M_HandleSoundTest(INT32 choice); static void M_HandleImageDef(INT32 choice); static void M_HandleLoadSave(INT32 choice); @@ -440,10 +456,11 @@ static consvar_t cv_dummymares = {"dummymares", "Overall", CV_HIDEN|CV_CALL, dum // --------- static menuitem_t MainMenu[] = { - {IT_CALL |IT_STRING, NULL, "Secrets", M_SecretsMenu, 84}, - {IT_CALL |IT_STRING, NULL, "1 player", M_SinglePlayerMenu, 92}, - {IT_SUBMENU|IT_STRING, NULL, "multiplayer", &MP_MainDef, 100}, - {IT_CALL |IT_STRING, NULL, "options", M_Options, 108}, + {IT_CALL |IT_STRING, NULL, "Secrets", M_SecretsMenu, 76}, + {IT_CALL |IT_STRING, NULL, "1 player", M_SinglePlayerMenu, 84}, + {IT_SUBMENU|IT_STRING, NULL, "multiplayer", &MP_MainDef, 92}, + {IT_CALL |IT_STRING, NULL, "options", M_Options, 100}, + {IT_CALL |IT_STRING, NULL, "Addons", M_Addons, 108}, {IT_CALL |IT_STRING, NULL, "quit game", M_QuitSRB2, 116}, }; @@ -453,9 +470,15 @@ typedef enum singleplr, multiplr, options, + addons, quitdoom } main_e; +static menuitem_t MISC_AddonsMenu[] = +{ + {IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleAddons, 0}, // dummy menuitem for the control func +}; + // --------------------------------- // Pause Menu Mode Attacking Edition // --------------------------------- @@ -478,6 +501,7 @@ typedef enum // --------------------- static menuitem_t MPauseMenu[] = { + {IT_STRING | IT_CALL, NULL, "Add-ons...", M_Addons, 8}, {IT_STRING | IT_SUBMENU, NULL, "Scramble Teams...", &MISC_ScrambleTeamDef, 16}, {IT_STRING | IT_CALL, NULL, "Switch Map..." , M_MapChange, 24}, @@ -497,7 +521,8 @@ static menuitem_t MPauseMenu[] = typedef enum { - mpause_scramble = 0, + mpause_addons = 0, + mpause_scramble, mpause_switchmap, mpause_continue, @@ -983,6 +1008,7 @@ static menuitem_t OP_MainMenu[] = {IT_SUBMENU | IT_STRING, NULL, "Game Options...", &OP_GameOptionsDef, 70}, {IT_SUBMENU | IT_STRING, NULL, "Server Options...", &OP_ServerOptionsDef, 80}, + {IT_STRING | IT_CALL, NULL, "Add-on Options...", M_AddonsOptions, 90}, }; static menuitem_t OP_ControlsMenu[] = @@ -1277,6 +1303,24 @@ static menuitem_t OP_EraseDataMenu[] = {IT_STRING | IT_CALL, NULL, "\x85" "Erase ALL Data", M_EraseData, 40}, }; +static menuitem_t OP_AddonsOptionsMenu[] = +{ + {IT_HEADER, NULL, "Menu", NULL, 0}, + {IT_STRING|IT_CVAR, NULL, "Location", &cv_addons_option, 10}, + {IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder", &cv_addons_folder, 20}, + {IT_STRING|IT_CVAR, NULL, "Identify add-ons via", &cv_addons_md5, 48}, + {IT_STRING|IT_CVAR, NULL, "Show unsupported file types", &cv_addons_showall, 58}, + + {IT_HEADER, NULL, "Search", NULL, 76}, + {IT_STRING|IT_CVAR, NULL, "Matching", &cv_addons_search_type, 86}, + {IT_STRING|IT_CVAR, NULL, "Case-sensitive", &cv_addons_search_case, 96}, +}; + +enum +{ + op_addons_folder = 2, +}; + static menuitem_t OP_GameOptionsMenu[] = { #ifndef NONET @@ -1308,7 +1352,6 @@ static menuitem_t OP_ServerOptionsMenu[] = #ifndef NONET {IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "Server name", &cv_servername, 50}, -#endif {IT_STRING | IT_CVAR, NULL, "Intermission Timer", &cv_inttime, 80}, {IT_STRING | IT_CVAR, NULL, "Advance to next map", &cv_advancemap, 90}, @@ -1391,6 +1434,18 @@ static menuitem_t OP_MonitorToggleMenu[] = // Main Menu and related menu_t MainDef = CENTERMENUSTYLE(NULL, MainMenu, NULL, 72); +menu_t MISC_AddonsDef = +{ + NULL, + sizeof (MISC_AddonsMenu)/sizeof (menuitem_t), + &MainDef, + MISC_AddonsMenu, + M_DrawAddons, + 50, 28, + 0, + NULL +}; + menu_t MAPauseDef = PAUSEMENUSTYLE(MAPauseMenu, 40, 72); menu_t SPauseDef = PAUSEMENUSTYLE(SPauseMenu, 40, 72); menu_t MPauseDef = PAUSEMENUSTYLE(MPauseMenu, 40, 72); @@ -1736,6 +1791,7 @@ menu_t OP_OpenGLColorDef = #endif menu_t OP_DataOptionsDef = DEFAULTMENUSTYLE("M_DATA", OP_DataOptionsMenu, &OP_MainDef, 60, 30); menu_t OP_ScreenshotOptionsDef = DEFAULTMENUSTYLE("M_DATA", OP_ScreenshotOptionsMenu, &OP_DataOptionsDef, 30, 30); +menu_t OP_AddonsOptionsDef = DEFAULTMENUSTYLE("M_ADDONS", OP_AddonsOptionsMenu, &OP_MainDef, 30, 30); menu_t OP_EraseDataDef = DEFAULTMENUSTYLE("M_DATA", OP_EraseDataMenu, &OP_DataOptionsDef, 60, 30); // ========================================================================== @@ -1961,6 +2017,12 @@ void Moviemode_mode_Onchange(void) OP_ScreenshotOptionsMenu[i].status = IT_STRING|IT_CVAR; } +void Addons_option_Onchange(void) +{ + OP_AddonsOptionsMenu[op_addons_folder].status = + (cv_addons_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED); +} + // ========================================================================== // END ORGANIZATION STUFF. // ========================================================================== @@ -2565,6 +2627,7 @@ void M_StartControlPanel(void) else // multiplayer { MPauseMenu[mpause_switchmap].status = IT_DISABLED; + MPauseMenu[mpause_addons].status = IT_DISABLED; MPauseMenu[mpause_scramble].status = IT_DISABLED; MPauseMenu[mpause_psetupsplit].status = IT_DISABLED; MPauseMenu[mpause_psetupsplit2].status = IT_DISABLED; @@ -2576,6 +2639,7 @@ void M_StartControlPanel(void) if ((server || adminplayer == consoleplayer)) { MPauseMenu[mpause_switchmap].status = IT_STRING | IT_CALL; + MPauseMenu[mpause_addons].status = IT_STRING | IT_CALL; if (G_GametypeHasTeams()) MPauseMenu[mpause_scramble].status = IT_STRING | IT_SUBMENU; } @@ -3815,6 +3879,547 @@ static void M_HandleImageDef(INT32 choice) // MISC MAIN MENU OPTIONS // ====================== +static void M_AddonsOptions(INT32 choice) +{ + (void)choice; + Addons_option_Onchange(); + + M_SetupNextMenu(&OP_AddonsOptionsDef); +} + +#define LOCATIONSTRING1 "Visit \x83SRB2.ORG/MODS\x80 to get & make add-ons!" +#define LOCATIONSTRING2 "Visit \x88SRB2.ORG/MODS\x80 to get & make add-ons!" + +static void M_Addons(INT32 choice) +{ + const char *pathname = "."; + + (void)choice; + +#if 1 + if (cv_addons_option.value == 0) + pathname = usehome ? srb2home : srb2path; + else if (cv_addons_option.value == 1) + pathname = srb2home; + else if (cv_addons_option.value == 2) + pathname = srb2path; + else +#endif + if (cv_addons_option.value == 3 && *cv_addons_folder.string != '\0') + pathname = cv_addons_folder.string; + + strlcpy(menupath, pathname, 1024); + menupathindex[(menudepthleft = menudepth-1)] = strlen(menupath) + 1; + + if (menupath[menupathindex[menudepthleft]-2] != PATHSEP[0]) + { + menupath[menupathindex[menudepthleft]-1] = PATHSEP[0]; + menupath[menupathindex[menudepthleft]] = 0; + } + else + --menupathindex[menudepthleft]; + + if (!preparefilemenu(false)) + { + M_StartMessage(va("No files/folders found.\n\n%s\n\n(Press a key)\n", (recommendedflags == V_SKYMAP ? LOCATIONSTRING2 : LOCATIONSTRING1)),NULL,MM_NOTHING); + return; + } + else + dir_on[menudepthleft] = 0; + + if (addonsp[0]) // never going to have some provided but not all, saves individually checking + { + size_t i; + for (i = 0; i < NUM_EXT+5; i++) + W_UnlockCachedPatch(addonsp[i]); + } + + addonsp[EXT_FOLDER] = W_CachePatchName("M_FFLDR", PU_STATIC); + addonsp[EXT_UP] = W_CachePatchName("M_FBACK", PU_STATIC); + addonsp[EXT_NORESULTS] = W_CachePatchName("M_FNOPE", PU_STATIC); + addonsp[EXT_TXT] = W_CachePatchName("M_FTXT", PU_STATIC); + addonsp[EXT_CFG] = W_CachePatchName("M_FCFG", PU_STATIC); + addonsp[EXT_WAD] = W_CachePatchName("M_FWAD", PU_STATIC); + addonsp[EXT_KART] = W_CachePatchName("M_FKART", PU_STATIC); + //addonsp[EXT_PK3] = W_CachePatchName("M_FPK3", PU_STATIC); + addonsp[EXT_SOC] = W_CachePatchName("M_FSOC", PU_STATIC); + addonsp[EXT_LUA] = W_CachePatchName("M_FLUA", PU_STATIC); + addonsp[NUM_EXT] = W_CachePatchName("M_FUNKN", PU_STATIC); + addonsp[NUM_EXT+1] = W_CachePatchName("M_FSEL", PU_STATIC); + addonsp[NUM_EXT+2] = W_CachePatchName("M_FLOAD", PU_STATIC); + addonsp[NUM_EXT+3] = W_CachePatchName("M_FSRCH", PU_STATIC); + addonsp[NUM_EXT+4] = W_CachePatchName("M_FSAVE", PU_STATIC); + + MISC_AddonsDef.prevMenu = currentMenu; + M_SetupNextMenu(&MISC_AddonsDef); +} + +#define width 4 +#define vpadding 27 +#define h (BASEVIDHEIGHT-(2*vpadding)) +#define NUMCOLOURS 8 // when toast's coding it's british english hacker fucker +static void M_DrawTemperature(INT32 x, fixed_t t) +{ + INT32 y; + + // bounds check + if (t > FRACUNIT) + t = FRACUNIT; + /*else if (t < 0) -- not needed + t = 0;*/ + + // scale + if (t > 1) + t = (FixedMul(h<>FRACBITS); + + // border + V_DrawFill(x - 1, vpadding, 1, h, 120); + V_DrawFill(x + width, vpadding, 1, h, 120); + V_DrawFill(x - 1, vpadding-1, width+2, 1, 120); + V_DrawFill(x - 1, vpadding+h, width+2, 1, 120); + + // bar itself + y = h; + if (t) + for (t = h - t; y > 0; y--) + { + UINT8 colours[NUMCOLOURS] = {135, 133, 92, 77, 114, 178, 161, 162}; + UINT8 c; + if (y <= t) break; + if (y+vpadding >= BASEVIDHEIGHT/2) + c = 185; + else + c = colours[(NUMCOLOURS*(y-1))/(h/2)]; + V_DrawFill(x, y-1 + vpadding, width, 1, c); + } + + // fill the rest of the backing + if (y) + V_DrawFill(x, vpadding, width, y, 30); +} +#undef width +#undef vpadding +#undef h +#undef NUMCOLOURS + +static char *M_AddonsHeaderPath(void) +{ + UINT32 len; + static char header[1024]; + + strlcpy(header, va("%s folder%s", cv_addons_option.string, menupath+menupathindex[menudepth-1]-1), 1024); + len = strlen(header); + if (len > 34) + { + len = len-34; + header[len] = header[len+1] = header[len+2] = '.'; + } + else + len = 0; + + return header+len; +} + +#define UNEXIST S_StartSound(NULL, sfx_lose);\ + M_SetupNextMenu(MISC_AddonsDef.prevMenu);\ + M_StartMessage(va("\x82%s\x80\nThis folder no longer exists!\nAborting to main menu.\n\n(Press a key)\n", M_AddonsHeaderPath()),NULL,MM_NOTHING) + +#define CLEARNAME Z_Free(refreshdirname);\ + refreshdirname = NULL + +static void M_AddonsClearName(INT32 choice) +{ + CLEARNAME; + M_StopMessage(choice); +} + +// returns whether to do message draw +static boolean M_AddonsRefresh(void) +{ + if ((refreshdirmenu & REFRESHDIR_NORMAL) && !preparefilemenu(true)) + { + UNEXIST; + return true; + } + + if (refreshdirmenu & REFRESHDIR_ADDFILE) + { + char *message = NULL; + + if (refreshdirmenu & REFRESHDIR_NOTLOADED) + { + S_StartSound(NULL, sfx_lose); + if (refreshdirmenu & REFRESHDIR_MAX) + message = va("%c%s\x80\nMaximum number of add-ons reached.\nA file could not be loaded.\nIf you want to play with this add-on, restart the game to clear existing ones.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); + else + message = va("%c%s\x80\nA file was not loaded.\nCheck the console log for more information.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); + } + else if (refreshdirmenu & (REFRESHDIR_WARNING|REFRESHDIR_ERROR)) + { + S_StartSound(NULL, sfx_skid); + message = va("%c%s\x80\nA file was loaded with %s.\nCheck the console log for more information.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname, ((refreshdirmenu & REFRESHDIR_ERROR) ? "errors" : "warnings")); + } + + if (message) + { + M_StartMessage(message,M_AddonsClearName,MM_EVENTHANDLER); + return true; + } + + S_StartSound(NULL, sfx_strpst); + CLEARNAME; + } + + return false; +} + +#ifdef FIXUPO0 +#pragma GCC optimize ("0") +#endif + +static void M_DrawAddons(void) +{ + INT32 x, y; + ssize_t i, m; + const UINT8 *flashcol = NULL; + UINT8 hilicol; + + // hack - need to refresh at end of frame to handle addfile... + if (refreshdirmenu & M_AddonsRefresh()) + { + M_DrawMessageMenu(); + return; + } + + if (Playing()) + V_DrawCenteredString(BASEVIDWIDTH/2, 5, warningflags, "Adding files mid-game may cause problems."); + else + V_DrawCenteredString(BASEVIDWIDTH/2, 5, 0, (recommendedflags == V_SKYMAP ? LOCATIONSTRING2 : LOCATIONSTRING1)); + + if (numwadfiles <= mainwads+1) + y = 0; + else if (numwadfiles >= MAX_WADFILES) + y = FRACUNIT; + else + { + x = FixedDiv(((ssize_t)(numwadfiles) - (ssize_t)(mainwads+1))< y) + y = x; + if (y > FRACUNIT) // happens because of how we're shrinkin' it a little + y = FRACUNIT; + } + + M_DrawTemperature(BASEVIDWIDTH - 19 - 5, y); + + // DRAW MENU + x = currentMenu->x; + y = currentMenu->y + 1; + + hilicol = V_GetStringColormap(highlightflags)[120]; + + V_DrawString(x-21, (y - 16) + (lsheadingheight - 12), highlightflags|V_ALLOWLOWERCASE, M_AddonsHeaderPath()); + V_DrawFill(x-21, (y - 16) + (lsheadingheight - 3), MAXSTRINGLENGTH*8+6, 1, hilicol); + V_DrawFill(x-21, (y - 16) + (lsheadingheight - 2), MAXSTRINGLENGTH*8+6, 1, 30); + + m = (BASEVIDHEIGHT - currentMenu->y + 2) - (y - 1); + V_DrawFill(x - 21, y - 1, MAXSTRINGLENGTH*8+6, m, 239); + + // scrollbar! + if (sizedirmenu <= (2*numaddonsshown + 1)) + i = 0; + else + { + ssize_t q = m; + m = ((2*numaddonsshown + 1) * m)/sizedirmenu; + if (dir_on[menudepthleft] <= numaddonsshown) // all the way up + i = 0; + else if (sizedirmenu <= (dir_on[menudepthleft] + numaddonsshown + 1)) // all the way down + i = q-m; + else + i = ((dir_on[menudepthleft] - numaddonsshown) * (q-m))/(sizedirmenu - (2*numaddonsshown + 1)); + } + + V_DrawFill(x + MAXSTRINGLENGTH*8+5 - 21, (y - 1) + i, 1, m, hilicol); + + // get bottom... + m = dir_on[menudepthleft] + numaddonsshown + 1; + if (m > (ssize_t)sizedirmenu) + m = sizedirmenu; + + // then top... + i = m - (2*numaddonsshown + 1); + + // then adjust! + if (i < 0) + { + if ((m -= i) > (ssize_t)sizedirmenu) + m = sizedirmenu; + i = 0; + } + + if (i != 0) + V_DrawString(19, y+4 - (skullAnimCounter/5), highlightflags, "\x1A"); + + if (skullAnimCounter < 4) + flashcol = V_GetStringColormap(highlightflags); + + for (; i < m; i++) + { + UINT32 flags = V_ALLOWLOWERCASE; + if (y > BASEVIDHEIGHT) break; + if (dirmenu[i]) +#define type (UINT8)(dirmenu[i][DIR_TYPE]) + { + if (type & EXT_LOADED) + { + flags |= V_TRANSLUCENT; + V_DrawSmallScaledPatch(x-(16+4), y, V_TRANSLUCENT, addonsp[(type & ~EXT_LOADED)]); + V_DrawSmallScaledPatch(x-(16+4), y, 0, addonsp[NUM_EXT+2]); + } + else + V_DrawSmallScaledPatch(x-(16+4), y, 0, addonsp[(type & ~EXT_LOADED)]); + + if ((size_t)i == dir_on[menudepthleft]) + { + V_DrawFixedPatch((x-(16+4))< (charsonside*2 + 3)) + V_DrawString(x, y+4, flags, va("%.*s...%s", charsonside, dirmenu[i]+DIR_STRING, dirmenu[i]+DIR_STRING+dirmenu[i][DIR_LEN]-(charsonside+1))); +#undef charsonside + else + V_DrawString(x, y+4, flags, dirmenu[i]+DIR_STRING); + } +#undef type + y += 16; + } + + if (m != (ssize_t)sizedirmenu) + V_DrawString(19, y-12 + (skullAnimCounter/5), highlightflags, "\x1B"); + + y = BASEVIDHEIGHT - currentMenu->y + 1; + + M_DrawTextBox(x - (21 + 5), y, MAXSTRINGLENGTH, 1); + if (menusearch[0]) + V_DrawString(x - 18, y + 8, V_ALLOWLOWERCASE, menusearch+1); + else + V_DrawString(x - 18, y + 8, V_ALLOWLOWERCASE|V_TRANSLUCENT, "Type to search..."); + if (skullAnimCounter < 4) + V_DrawCharacter(x - 18 + V_StringWidth(menusearch+1, 0), y + 8, + '_' | 0x80, false); + + x -= (21 + 5 + 16); + V_DrawSmallScaledPatch(x, y + 4, (menusearch[0] ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+3]); + + x = BASEVIDWIDTH - x - 16; + V_DrawSmallScaledPatch(x, y + 4, ((!modifiedgame || savemoddata) ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+4]); + + if (modifiedgame) + V_DrawSmallScaledPatch(x, y + 4, 0, addonsp[NUM_EXT+2]); +} + +#ifdef FIXUPO0 +#pragma GCC reset_options +#endif + +static void M_AddonExec(INT32 ch) +{ + if (ch != 'y' && ch != KEY_ENTER) + return; + + S_StartSound(NULL, sfx_zoom); + COM_BufAddText(va("exec \"%s%s\"", menupath, dirmenu[dir_on[menudepthleft]]+DIR_STRING)); +} + +#define len menusearch[0] +static boolean M_ChangeStringAddons(INT32 choice) +{ + if (shiftdown && choice >= 32 && choice <= 127) + choice = shiftxform[choice]; + + switch (choice) + { + case KEY_DEL: + if (len) + { + len = menusearch[1] = 0; + return true; + } + break; + case KEY_BACKSPACE: + if (len) + { + menusearch[1+--len] = 0; + return true; + } + break; + default: + if (choice >= 32 && choice <= 127) + { + if (len < MAXSTRINGLENGTH - 1) + { + menusearch[1+len++] = (char)choice; + menusearch[1+len] = 0; + return true; + } + } + break; + } + return false; +} +#undef len + +static void M_HandleAddons(INT32 choice) +{ + boolean exitmenu = false; // exit to previous menu + + if (M_ChangeStringAddons(choice)) + { + char *tempname = NULL; + if (dirmenu && dirmenu[dir_on[menudepthleft]]) + tempname = Z_StrDup(dirmenu[dir_on[menudepthleft]]+DIR_STRING); // don't need to I_Error if can't make - not important, just QoL +#if 0 // much slower + if (!preparefilemenu(true)) + { + UNEXIST; + return; + } +#else // streamlined + searchfilemenu(tempname); +#endif + } + + switch (choice) + { + case KEY_DOWNARROW: + if (dir_on[menudepthleft] < sizedirmenu-1) + dir_on[menudepthleft]++; + S_StartSound(NULL, sfx_menu1); + break; + case KEY_UPARROW: + if (dir_on[menudepthleft]) + dir_on[menudepthleft]--; + S_StartSound(NULL, sfx_menu1); + break; + case KEY_PGDN: + { + UINT8 i; + for (i = numaddonsshown; i && (dir_on[menudepthleft] < sizedirmenu-1); i--) + dir_on[menudepthleft]++; + } + S_StartSound(NULL, sfx_menu1); + break; + case KEY_PGUP: + { + UINT8 i; + for (i = numaddonsshown; i && (dir_on[menudepthleft]); i--) + dir_on[menudepthleft]--; + } + S_StartSound(NULL, sfx_menu1); + break; + case KEY_ENTER: + { + boolean refresh = true; + if (!dirmenu[dir_on[menudepthleft]]) + S_StartSound(NULL, sfx_lose); + else + { + switch (dirmenu[dir_on[menudepthleft]][DIR_TYPE]) + { + case EXT_FOLDER: + strcpy(&menupath[menupathindex[menudepthleft]],dirmenu[dir_on[menudepthleft]]+DIR_STRING); + if (menudepthleft) + { + menupathindex[--menudepthleft] = strlen(menupath); + menupath[menupathindex[menudepthleft]] = 0; + + if (!preparefilemenu(false)) + { + S_StartSound(NULL, sfx_skid); + M_StartMessage(va("%c%s\x80\nThis folder is empty.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); + menupath[menupathindex[++menudepthleft]] = 0; + + if (!preparefilemenu(true)) + { + UNEXIST; + return; + } + } + else + { + S_StartSound(NULL, sfx_menu1); + dir_on[menudepthleft] = 1; + } + refresh = false; + } + else + { + S_StartSound(NULL, sfx_lose); + M_StartMessage(va("%c%s\x80\nThis folder is too deep to navigate to!\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); + menupath[menupathindex[menudepthleft]] = 0; + } + break; + case EXT_UP: + S_StartSound(NULL, sfx_menu1); + menupath[menupathindex[++menudepthleft]] = 0; + if (!preparefilemenu(false)) + { + UNEXIST; + return; + } + break; + case EXT_TXT: + M_StartMessage(va("%c%s\x80\nThis file may not be a console script.\nAttempt to run anyways? \n\n(Press 'Y' to confirm)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), dirmenu[dir_on[menudepthleft]]+DIR_STRING),M_AddonExec,MM_YESNO); + break; + case EXT_CFG: + M_AddonExec(KEY_ENTER); + break; + case EXT_LUA: +#ifndef HAVE_BLUA + S_StartSound(NULL, sfx_lose); + M_StartMessage(va("%c%s\x80\nThis copy of SRB2 was compiled\nwithout support for .lua files.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), dirmenu[dir_on[menudepthleft]]+DIR_STRING),NULL,MM_NOTHING); + break; +#endif + // else intentional fallthrough + case EXT_SOC: + case EXT_WAD: + case EXT_KART: + //case EXT_PK3: + COM_BufAddText(va("addfile \"%s%s\"", menupath, dirmenu[dir_on[menudepthleft]]+DIR_STRING)); + break; + default: + S_StartSound(NULL, sfx_lose); + } + } + if (refresh) + refreshdirmenu |= REFRESHDIR_NORMAL; + } + break; + + case KEY_ESCAPE: + exitmenu = true; + break; + + default: + break; + } + if (exitmenu) + { + closefilemenu(true); + + // Secret menu! + //MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED); + + if (currentMenu->prevMenu) + M_SetupNextMenu(currentMenu->prevMenu); + else + M_ClearMenus(true); + } +} + static void M_PandorasBox(INT32 choice) { (void)choice; diff --git a/src/m_menu.h b/src/m_menu.h index de76a271..eb770c19 100644 --- a/src/m_menu.h +++ b/src/m_menu.h @@ -124,6 +124,8 @@ boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt); #define IT_HEADER (IT_SPACE +IT_HEADERTEXT) #define IT_SECRET (IT_SPACE +IT_QUESTIONMARKS) +#define MAXSTRINGLENGTH 32 + typedef union { struct menu_s *submenu; // IT_SUBMENU @@ -223,6 +225,9 @@ void M_CheatActivationResponder(INT32 ch); void Moviemode_mode_Onchange(void); void Screenshot_option_Onchange(void); +// Addons menu updating +void Addons_option_Onchange(void); + // These defines make it a little easier to make menus #define DEFAULTMENUSTYLE(header, source, prev, x, y)\ {\ diff --git a/src/p_setup.c b/src/p_setup.c index 49b98e72..ca8f066e 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -54,6 +54,8 @@ #include "v_video.h" +#include "filesrch.h" // refreshdirmenu + // wipes #include "f_finale.h" @@ -3005,6 +3007,7 @@ boolean P_AddWadFile(const char *wadfilename, char **firstmapname) if ((numlumps = W_LoadWadFile(wadfilename)) == INT16_MAX) { + refreshdirmenu |= REFRESHDIR_NOTLOADED; CONS_Printf(M_GetText("Errors occurred while loading %s; not added.\n"), wadfilename); return false; } diff --git a/src/v_video.c b/src/v_video.c index 35b848f9..1f05585f 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -1014,6 +1014,7 @@ void V_DrawFadeConsBack(INT32 plines) // static const UINT8 *V_GetStringColormap(INT32 colorflags) { +#if 0 // perfect switch ((colorflags & V_CHARCOLORMASK) >> V_CHARCOLORSHIFT) { case 1: // 0x81, purple @@ -1033,6 +1034,12 @@ static const UINT8 *V_GetStringColormap(INT32 colorflags) default: // reset return NULL; } +#else // optimised + colorflags = ((colorflags & V_CHARCOLORMASK) >> V_CHARCOLORSHIFT); + if (!colorflags || colorflags > 15) // INT32 is signed, but V_CHARCOLORMASK is a very restrictive mask. + return NULL; + return (purplemap+((colorflags-1)<<8)); +#endif } // Writes a single character (draw WHITE if bit 7 set) diff --git a/src/w_wad.c b/src/w_wad.c index 4cedd3cf..92509ff7 100644 --- a/src/w_wad.c +++ b/src/w_wad.c @@ -34,6 +34,8 @@ #include "z_zone.h" #include "fastcmp.h" +#include "filesrch.h" + #include "i_video.h" // rendermode #include "d_netfil.h" #include "dehacked.h" @@ -299,12 +301,22 @@ UINT16 W_LoadWadFile(const char *filename) UINT32 numlumps; size_t i; INT32 compressed = 0; - size_t packetsize = 0; - serverinfo_pak *dummycheck = NULL; + size_t packetsize; UINT8 md5sum[16]; + boolean important; - // Shut the compiler up. - (void)dummycheck; + if (!(refreshdirmenu & REFRESHDIR_ADDFILE)) + refreshdirmenu = REFRESHDIR_NORMAL|REFRESHDIR_ADDFILE; // clean out cons_alerts that happened earlier + + if (refreshdirname) + Z_Free(refreshdirname); + if (dirmenu) + { + refreshdirname = Z_StrDup(filename); + nameonly(refreshdirname); + } + else + refreshdirname = NULL; //CONS_Debug(DBG_SETUP, "Loading %s\n", filename); // @@ -313,6 +325,7 @@ UINT16 W_LoadWadFile(const char *filename) if (numwadfiles >= MAX_WADFILES) { CONS_Alert(CONS_ERROR, M_GetText("Maximum wad files reached\n")); + refreshdirmenu |= REFRESHDIR_MAX; return INT16_MAX; } @@ -322,21 +335,21 @@ UINT16 W_LoadWadFile(const char *filename) // Check if wad files will overflow fileneededbuffer. Only the filename part // is send in the packet; cf. - for (i = 0; i < numwadfiles; i++) + // see PutFileNeeded in d_netfil.c + if ((important = !W_VerifyNMUSlumps(filename))) { - packetsize += nameonlylength(wadfiles[i]->filename); - packetsize += 22; // MD5, etc. - } + packetsize = packetsizetally + nameonlylength(filename) + 22; - packetsize += nameonlylength(filename); - packetsize += 22; + if (packetsize > MAXFILENEEDED*sizeof(UINT8)) + { + CONS_Alert(CONS_ERROR, M_GetText("Maximum wad files reached\n")); + refreshdirmenu |= REFRESHDIR_MAX; + if (handle) + fclose(handle); + return INT16_MAX; + } - if (packetsize > sizeof(dummycheck->fileneeded)) - { - CONS_Alert(CONS_ERROR, M_GetText("Maximum wad files reached\n")); - if (handle) - fclose(handle); - return INT16_MAX; + packetsizetally = packetsize; } // detect dehacked file with the "soc" extension @@ -483,6 +496,7 @@ UINT16 W_LoadWadFile(const char *filename) wadfile->handle = handle; wadfile->numlumps = (UINT16)numlumps; wadfile->lumpinfo = lumpinfo; + wadfile->important = important; fseek(handle, 0, SEEK_END); wadfile->filesize = (unsigned)ftell(handle); @@ -1230,19 +1244,27 @@ static int W_VerifyFile(const char *filename, lumpchecklist_t *checklist, */ int W_VerifyNMUSlumps(const char *filename) { - // MIDI, MOD/S3M/IT/XM/OGG/MP3/WAV, WAVE SFX - // ENDOOM text and palette lumps lumpchecklist_t NMUSlist[] = { - {"D_", 2}, - {"O_", 2}, - {"DS", 2}, - {"ENDOOM", 6}, - {"PLAYPAL", 7}, - {"COLORMAP", 8}, - {"PAL", 3}, - {"CLM", 3}, - {"TRANS", 5}, + {"D_", 2}, // MIDI music + {"O_", 2}, // Digital music + {"DS", 2}, // Sound effects + + {"ENDOOM", 6}, // ENDOOM text lump + {"PLAYPAL", 7}, // Palette + {"COLORMAP", 8}, // Colormap + {"PAL", 3}, // Palette changes + {"CLM", 3}, // Colormap changes + {"TRANS", 5}, // Translucency map + + {"LTFNT", 5}, // Level title font changes + {"STCFN", 5}, // Console font changes + {"TNYFN", 5}, // Tiny console font changes + {"MKFNT", 5}, // Kart font changes + + {"M_", 2}, // Menu changes + {"K_", 2}, // Kart graphic changes + {NULL, 0}, }; return W_VerifyFile(filename, NMUSlist, false); diff --git a/src/w_wad.h b/src/w_wad.h index 320bc322..62e41876 100644 --- a/src/w_wad.h +++ b/src/w_wad.h @@ -70,6 +70,7 @@ typedef struct wadfile_s FILE *handle; UINT32 filesize; // for network UINT8 md5sum[16]; + boolean important; } wadfile_t; #define WADFILENUM(lumpnum) (UINT16)((lumpnum)>>16) // wad flumpnum>>16) // wad file number in upper word