diff --git a/src/common/menu/savegamemanager.cpp b/src/common/menu/savegamemanager.cpp index efb5a02af..f436ba8a3 100644 --- a/src/common/menu/savegamemanager.cpp +++ b/src/common/menu/savegamemanager.cpp @@ -53,6 +53,8 @@ CVAR(String, save_dir, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_SYSTEM_ONLY); FString SavegameFolder; CVAR(Int, save_sort_order, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +extern bool netgame; + //============================================================================= // // Save data maintenance @@ -264,12 +266,47 @@ void FSavegameManagerBase::DoSave(int Selected, const char *savegamestring) FString filename; int i; - for (i = 0;; ++i) + if (netgame) { - filename = BuildSaveName("save", i); - if (!FileExists(filename)) + // For netgames it's usually a bad idea to use the default savexx names, so instead + // sanitize the description to use as a name. + filename = savegamestring; + FixPathSeperator(filename); + bool failed = false; + if (filename[0] == '/') { - break; + Printf("saving to an absolute path is not allowed\n"); + failed = true; + } + else if (filename.IndexOf("..") >= 0) + { + Printf("'..' not allowed in file names\n"); + failed = true; + } +#ifdef _WIN32 + // block all invalid characters for Windows file names + else if (filename.IndexOfAny(":?*<>|") >= 0) + { + Printf("file name contains invalid characters\n"); + failed = true; + } +#endif + if (failed) + { + M_ClearMenus(); + return; + } + filename = G_BuildSaveName(filename.GetChars()); + } + else + { + for (i = 0;; ++i) + { + filename = BuildSaveName("save", i); + if (!FileExists(filename)) + { + break; + } } } PerformSaveGame(filename.GetChars(), savegamestring); @@ -561,7 +598,15 @@ FString G_GetSavegamesFolder() FString name; bool usefilter; - if (const char* const dir = Args->CheckValue("-savedir")) + // Always use the netgame folder for multiplayer games to prevent any singleplayer saves + // from being overridden by someone else. Also makes it easier for everyone to load from + // it. + if (netgame) + { + name = M_GetSavegamesPath(); + usefilter = true; + } + else if (const char* const dir = Args->CheckValue("-savedir")) { name = dir; usefilter = false; //-savedir specifies an absolute save directory path. diff --git a/src/common/platform/posix/unix/i_specialpaths.cpp b/src/common/platform/posix/unix/i_specialpaths.cpp index 112e92ee7..c06e27b67 100644 --- a/src/common/platform/posix/unix/i_specialpaths.cpp +++ b/src/common/platform/posix/unix/i_specialpaths.cpp @@ -42,6 +42,8 @@ #include "version.h" // for GAMENAME +extern bool netgame; + FString GetUserFile (const char *file) { @@ -191,7 +193,10 @@ FString M_GetScreenshotsPath() FString M_GetSavegamesPath() { - return NicePath("$HOME/" GAME_DIR "/savegames/"); + FString pName = "$HOME/" GAME_DIR "/savegames/"; + if (netgame) + pName << "netgame/"; + return NicePath(pName.GetChars()); } //=========================================================================== diff --git a/src/common/platform/win32/i_specialpaths.cpp b/src/common/platform/win32/i_specialpaths.cpp index 8862024b7..c7f53c24a 100644 --- a/src/common/platform/win32/i_specialpaths.cpp +++ b/src/common/platform/win32/i_specialpaths.cpp @@ -51,6 +51,8 @@ static int isportable = -1; +extern bool netgame; + //=========================================================================== // // IsProgramDirectoryWritable @@ -377,6 +379,8 @@ FString M_GetSavegamesPath() path = GetKnownFolder(-1, FOLDERID_SavedGames, true); path << "/" GAMENAME "/"; } + if (netgame) + path << "NetGame/"; return path; } diff --git a/src/console/c_cmds.cpp b/src/console/c_cmds.cpp index 9b5e8f612..1d45a1130 100644 --- a/src/console/c_cmds.cpp +++ b/src/console/c_cmds.cpp @@ -713,7 +713,7 @@ UNSAFE_CCMD(save) Printf("saving to an absolute path is not allowed\n"); return; } - if (fname.IndexOf("..") > 0) + if (fname.IndexOf("..") >= 0) { Printf("'..' not allowed in file names\n"); return; diff --git a/src/d_net.cpp b/src/d_net.cpp index 7d7f08587..90dbd83b2 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -159,6 +159,7 @@ CVAR(Bool, vid_lowerinbackground, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR(Bool, net_ticbalance, false, CVAR_SERVERINFO | CVAR_NOSAVE) // Currently deprecated, but may be brought back later. CVAR(Bool, net_extratic, false, CVAR_SERVERINFO | CVAR_NOSAVE) +CVAR(Bool, net_limitsaves, true, CVAR_SERVERINFO | CVAR_NOSAVE) CVAR(Bool, net_repeatableactioncooldown, true, CVAR_SERVERINFO | CVAR_NOSAVE) CVAR(Bool, net_limitconversations, false, CVAR_SERVERINFO | CVAR_NOSAVE) CUSTOM_CVAR(Int, net_disablepause, 0, CVAR_SERVERINFO | CVAR_NOSAVE) @@ -1818,7 +1819,21 @@ size_t Net_SetGameInfo(uint8_t*& stream) WriteString(startmap.GetChars(), &stream); WriteInt32(rngseed, &stream); C_WriteCVars(&stream, CVAR_SERVERINFO, true); - return stream - start; + + auto load = Args->CheckValue("-loadgame"); + if (load != nullptr) + { + stream[0] = true; + const size_t len = strlen(load) + 1; + memcpy(&stream[1], load, len); + stream += len; + } + else + { + stream[0] = false; + } + + return (stream + 1) - start; } size_t Net_ReadGameInfo(uint8_t*& stream) @@ -1827,6 +1842,25 @@ size_t Net_ReadGameInfo(uint8_t*& stream) startmap = ReadStringConst(&stream); rngseed = ReadInt32(&stream); C_ReadCVars(&stream); + + if (stream[0]) + { + ++stream; + const size_t len = strlen((char*)stream) + 1; + // Don't override the existing argument in case they need to use + // a custom savefile name. + if (!Args->CheckParm("-loadgame")) + { + Args->AppendArg("-loadgame"); + Args->AppendArg((char*)stream); + } + stream += len; + } + else + { + ++stream; + } + return stream - start; } diff --git a/src/g_game.cpp b/src/g_game.cpp index 808d8ba15..558a592e1 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -128,6 +128,7 @@ CVAR (Bool, enablescriptscreenshot, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR (Bool, cl_restartondeath, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); EXTERN_CVAR (Float, con_midtime); EXTERN_CVAR(Int, net_disablepause) +EXTERN_CVAR(Bool, net_limitsaves) //========================================================================== // @@ -2161,6 +2162,10 @@ void G_SaveGame (const char *filename, const char *description) { Printf ("%s\n", GStrings.GetString("TXT_SPPLAYERDEAD")); } + else if (netgame && net_limitsaves && !players[consoleplayer].settings_controller) + { + Printf("Only settings controllers can save the game\n"); + } else { savegamefile = filename; @@ -2196,6 +2201,10 @@ CUSTOM_CVAR (Int, quicksaverotationcount, 4, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) void G_DoAutoSave () { + // Never autosave in netgames since you can't load this properly anyway. + if (netgame) + return; + FString description; FString file; // Keep up to four autosaves at a time @@ -2231,6 +2240,10 @@ void G_DoAutoSave () void G_DoQuickSave () { + // Never quicksave in netgames since you can't load this properly anyway. + if (netgame) + return; + FString description; FString file; // Keeps a rotating set of quicksaves diff --git a/src/menu/doommenu.cpp b/src/menu/doommenu.cpp index 5718bd6b0..e21f977db 100644 --- a/src/menu/doommenu.cpp +++ b/src/menu/doommenu.cpp @@ -477,7 +477,7 @@ CCMD (quicksave) return; // If the quick save rotation is enabled, it handles the save slot. - if (quicksaverotation) + if (!netgame && quicksaverotation) { G_DoQuickSave(); return;