Save Deletion (SD) is a glitch in Source Engine games which allows the player to force the game to restart the map they are currently on, reverting it to a default state. This is done by making the game load a save it cannot access, either due to the file being deleted beforehand or through making them with special names.
Over the years, Save Deletion has been a subject of controversy for its interruption of gameplay and permitting runners to easily escape disadvantages incurred in previous maps. Its requirement of inputs in menus and the unorthodox use of save
comamnd have also put its legitimacy into question.
The original method for Save Deletion was first discovered by UnrealCanine in 2015. At the time, boatless was a challenge not only due to the amount of health and ammo management but also due to the difficulty in skipping the canals_11
cutscene that required the airboat to even start. So when this glitch solved both hurdles, lots of people within the community advocated for its acceptance in runs.
However, since it effectively reset the slate of the run, many members rejected it, especially when there hadn't been another glitch or trick that required the use of the menu. Its introduction would also necessitate the timing of the menu, something that SourceSplit had not yet supported.
Eventually the glitch and its many far-reaching benefits won over the community and was accepted into the standard route.
The special name methods came much later, starting with the long name method found by 2838 in 2020. The reserved name method was found around the same time.
In 2022, efforts were made to try and bring Save Deletion to native Linux builds, as the aforementioned methods did not work there. The empty string method was suggested by 2838, but it wasn't until 2024 when peng tried it was it found to be effective. Further testing and investigations revealed its usability on all game builds and platforms, although certain limitations were also found.
You must do a saveload before execution.
The save that is made to trigger the Save Delete must be the last very save made either by you or the game. This includes autosaves which are usually made within the first second of entering a new map, so either be quick when performing this glitch, or wait for the autosave to be made.
After entering the map on which to save delete and saveloading, open the save loading menu and delete the latest save, then force the game to reload by either killing yourself or using the reload
command.
This method does not work in Source 2004.
After entering the map on which to save delete and saveloading, create a save with a specific name then force the game to reload like in the original method.
Which name to use depends on the branch of the game and the platform being used. The table below shows which method will work for each:
Linux / Mac Wine | Windows | XBOX | Linux / Mac Native | |
---|---|---|---|---|
Source 2004 | Empty String | Empty String | - | - |
Pre-Steampipe | Long Name | Long Name | Reserved Name | - |
Post-Steampipe | Reserved Name | Reserved Name | - | Empty String |
The subsections below will go through each method, and provide a command that both makes the save and runs the reload
command.
The long name method requires the save's name to be some string that's extremely long, usually 200+ characters.
Example:
"save aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaretrhtgxfhtfgdxhtgfhrdtghfrtfgrhthrfgthrfawwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwgrtdfrgdftdddddddddddddddddddddddddddddddddeetret4rtrfhthfrthrfaaaaaaaaaaaaaaaaaaaaaaaaaaa;reload"
If the example doesn't work, keep on adding characters to the name, or move the game to a more deeply-nested folder on your comptuer.
The name needn't be crazy gibberish like in the example and can be any valid file name for the platform you're running on. Only its total length matters.
The reserved name method requires the save's name to be reserved or invalid on the platform that you are running.
For Windows, usable names are:
<
(less than)>
(greater than):
(colon) (only works if the save file is made on a drive that's formatted as NTFS)"
(double quote)/
(forward slash)\
(backslash)|
(vertical bar or pipe)?
(question mark)*
(asterisk)CON
(does not work on Windows 11)PRN
AUX
NUL
COM1
, COM2
, COM3
, COM4
, COM5
, COM6
, COM7
, COM8
, COM9
, COM0
LPT1
, LPT2
, LPT3
, LPT4
, LPT5
, LPT6
, LPT7
, LPT8
, LPT9
, LPT0
Example: "save :;reload"
, "save CON;reload"
The empty string method requires giving the save
command an empty string.
Example: save "";reload
.
Since the double quotes are required to represent an empty string as a parameter for the save
command, and double quotes cannot be nested in commands, the whole command above cannot be bound to a key. Instead, the command must be put into a config file, for example sd.cfg
, and placed in the cfg
folder of your game, the same location as autoexec.cfg
and config.cfg
, then it can be run by binding a key to exec sd.cfg
This method may not work, in which case restart the game and try again.
The glitch tries to convince the game that a save it has made cannot be loaded, causing it to execute crude fallback code that restarts the current map, reverting the map to a default state. The player being put at the start, sometimes with useful items and ammo critical to finish the map, is a result of that map restart.
When the game is told to reload the last loaded save (through the reload
command or by dying and respawing), if it can't be loaded, the game will load the "starting game map" instead. Below is a comment left by the developers in Source Engine's code.
// See if there is a most recently saved game
// Restart that game if there is
// Otherwise, restart the starting game map
The following is the code that handles this. pSaveName
is false
in this case, which instructs the game to run the code in the else
block.
pSaveName = saverestore->FindRecentSave( name, sizeof( name ) );
if ( pSaveName && saverestore->SaveFileExists( pSaveName ) )
{
HostState_LoadGame( pSaveName, remember_location );
}
else
{
HostState_NewGame( host_map.GetString(), remember_location, false );
}
HostState_NewGame
is called, a function which loads a map and reset the game state, as if a new game has just been started. This function is also used by the map
command and the restart
command.
It is unclear what the developers meant by the "starting game map", but host_map
seems to hold the name of that map, and it is only set to
HostState_NewGame
function, i.e. the last map on which a new game was started.Note that transitioning maps does not update this, which is why it is important to do a saveload before attempting a Save Delete, as only then is the host_map
variable set to the current map.
It is noteworthy that host_map
is a ConVar (i.e. a command like the save
command), which means you can choose what map to end up on after a Save Delete by doing host_map <map name>
immediately before doing one.
The long and reserved file name methods abuse the way files are handled both by the game and the platform you are running on to cause an inconsistency between what files the game think exists and what's actually saved to your computer.
Some names cannot be used for files in some platforms (those for Windows are listed above), and when the game tries to make a save with those names the file unknowingly gets dropped by the operating system, effectively making it "deleted" in the eyes of the game.
Peculiarly, pre-Steampipe builds have checks for this edge case and the save is correctly cancelled, while post-Steampipe builds along with more modern Source games have lost this ability. Where in code and logic this difference lies has not yet been pinpointed.
For the long name method, some platforms by default has a file length limit and different ways of dealing with violations of such. For Windows, the limit is 260 by default, and save files generated by the game which have a resulting file path longer than that will have their names unknowingly truncated.
For example:
Save's name in the game aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...(219 a's later)...aaaaaaaaaa Save's path in Windows D:/Half-Life 2/hl2/SAVE/aaaaaaa...(219 a's later)...aaaaaaaaaa Save's actual name aaaaaaa...(219 a's later)...aaaaaaaaaa ________________________
24 characters are missing from original save name. (Note that the .sav
extension saves usally have is omitted for clarity)
Since the game does not pick up on this, when it tries to load the save with the original name, it will never be able to find it, effectively making the file "deleted" and triggering the failsafe.
Similar to reserved names, modern Source games seem to account for this bug, while older ones do not. The precise reason for this is also not known.
Unlike the other name exploits which attack limits within file systems, this method abuses poor error handling by the game's code.
Inside the function with creates a save:
char name[256];
...
CalcSaveGameName( pSaveName, name, sizeof( name ) );
ConDMsg( "Saving game to %s...\n", name );
The char
array (basically, a string) name
is initialized with a maximum length of 256. It will contain the final file path of the save file, but for now its content is entirely random (thanks to standard C++ behavior) and will need to be calculated by the CalcSaveGameName
function, which is called with pSaveName
(the string given to the save
command) as its input. The final results (the initialized name
) will be printed to the console (only viewable with developer
on).
The definition and beginning of CalcSaveGameName
is as follows:
bool CSaveRestore::CalcSaveGameName( const char *pName, char *output, int outputStringLength )
{
if (!pName || !pName[0])
return false;
We can see here that this function returns a bool
(true or false indicating whether it was able to unable to "calculate" a file name), and the first thing it does is to check if pName
(in this case is the same as pSaveName
from before) is either null
(never the case here) or if it is empty. Since we did pass an empty string to save
, the function will immediately return false
indicating failure.
From here, the game should've handled that failed result, but instead barrels along with the save file creation using an uninitialized name
, leading to unforseeable consequences. Since the contents of name
right now is random, it may be a valid file name that the operating system is fine with storing, and the save
operation finishes without issue.
However, the game then saves pSaveName
as the name of the most recent save, so when the game is forced to reload from it, it will think it doesn't actually exist. The code that handles this also uses CalcSaveGameName
, but this time actually recognizes it indicating failure:
//-----------------------------------------------------------------------------
// Does this save file exist?
//-----------------------------------------------------------------------------
bool CSaveRestore::SaveFileExists( const char *pName )
{
...
char name[256];
if ( !CalcSaveGameName( pName, name, sizeof( name ) ) )
return false;
Seeing CalcSaveGameName
failing, it concludes that the save does not exist, leading into the same fallback we saw before.
However, as the value of name
(the file path of the save file) is unpredictable and random, it could represent an invalid path and cause the save creation to fail. Depending on the version of the Engine, it may recognize that this happened and cancel the save creation, foiling the Save Delete attempt. There is currently no proven way to manipulate memory allocation in such a way that name
's default value changes favorably, or methods to confidently refresh that value other than relaunching the whole game.
When developing and testing Source games, maps often have to be loaded fresh with the map
command as saves do not work after a recompile, so mappers sometimes make the default state of a map match how a casual player would be when entering it the first time. This is also needed to make selecting a chapter to play from possible, as that simply runs the equivalent of the map
comamnd. For those reasons, the default state usually has you spawning at the canonical beginning of a map, with ample weapons, ammo, and critical items such as the airboat for canals and the buggy for coast in Half-Life 2.
However, Source does not provide a prefab solution for this, so mappers have to place down entities and design their spawning logic manually, leading to some maps lacking them entirely, for example on Half-Life 2's d2_prison_05.