Jump to content

[Plugin] Auto Create Persistence File Backup - Prevent File Loss From Missing Parts


DMagic

Recommended Posts

CurseForge Link

Mediafire link:http://www./download/98wkt28ke25gsxn/DMagic_AutoSave_V3.1.zip

Version 3.1 is compiled against .net 3.5 and won't cause ModStatistics to throw debug log errors

-----

So after looking at one of the threads over in the suggestion forum I realized that a simple plugin should be able to create a backup persistence file to prevent the old "Missing Part-now a dozen vessels are deleted" thing from happening.

Yes, the best way to prevent this is to make your own backups and immediately quit after getting the "Missing Parts" message, but that doesn't always happen.

The plugin generates a new backup file every time you restart KSP, it only does so once per game, but resets when you back out to the main menu. The included settings.cfg file can be used to specify the number of backups to generate before the plugin begins overwriting old files.

And here's the code if anyone wants to check for some boneheaded mistake, or any potentially catastrophic errors waiting to happen:


using System;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

namespace AutoSave
{
[KSPAddonImproved(KSPAddonImproved.Startup.SpaceCenter | KSPAddonImproved.Startup.Flight | KSPAddonImproved.Startup.EditorAny | KSPAddonImproved.Startup.TrackingStation | KSPAddonImproved.Startup.MainMenu, false)]
public class AutoSave : MonoBehaviour
{
private int max = 3;
private ConfigNode node = null;
private string path = null;
private static bool Saved;

public void Start()
{
if (HighLogic.LoadedScene == GameScenes.MAINMENU)
Saved = false;
else if (Saved == false)
saveBackup();
}

public void saveBackup()
{
DateTime oldestFile = new DateTime(2050, 1, 1);
string replaceBackup = null;
string activeDirectory = Path.Combine(Path.Combine(new DirectoryInfo(KSPUtil.ApplicationRootPath).FullName, "saves"), HighLogic.fetch.GameSaveFolder);
path = Path.Combine(new DirectoryInfo(KSPUtil.ApplicationRootPath).FullName, "GameData/AutoSave/Settings.cfg").Replace("\\", "/");

if (File.Exists(path)) //Load Settings.cfg to check for change in max number of saves
{
max = getMaxSave("MaxSaves");
print("Changing max saves value to " + max.ToString());
}

for (int i = 0; i < max; i++)
{
string filepath = Path.Combine(activeDirectory, "persistent Backup " + i.ToString() + ".sfs");
if (!File.Exists(filepath))
{
replaceBackup = "persistent Backup " + i.ToString() + ".sfs";
break;
}
else //If all backups have been written, check for the oldest file and rewrite that one
{
DateTime modified = File.GetLastWriteTime(filepath);
if (modified < oldestFile)
{
replaceBackup = "persistent Backup " + i.ToString() + ".sfs";
oldestFile = modified;
}
}
}
File.Copy(Path.Combine(activeDirectory, "persistent.sfs"), Path.Combine(activeDirectory, replaceBackup), true);
print("Backup saved as " + replaceBackup);
Saved = true;
}

public int getMaxSave(string entry) //Make sure that no amount of screwing up the Settings file will break the plugin
{
int number = 3;
node = ConfigNode.Load(path);
if (node != null)
{
string value = node.GetValue(entry);
if (value == null) return number;
else if (Int32.TryParse(value, out number))
return number;
else return 3;
}
else return number;
}
}
}

If anyone spots any issues, has any suggestions, or thinks this is a dumb idea, let me know.

License for plugin software: BSD License

Edited by DMagic
Update to version 3.1
Link to comment
Share on other sites

Yeah, I usually catch it too, but I really hate it when I'm not thinking click through to some other scene.

This might also be useful for development. I have run into issues a number of times where some error while loading a plugin has nuked all of my parts (not just the mod parts, everything), so not only do you lose the crafts, but you have to repurchase everything in the R&D center. A backup would be nice then.

If you're using some kind of quickload plugin to skip the spacecenter scene this plugin might not work, but it can probably be easily modified to fix that. The key part is creating the save file while still in the MainMenu scene, that way nothing can get corrupted by errors after loading.

Edit: Just realized the time stamp was a bad idea. You'd get an ever-growing list of backups, better to create one and overwrite it. I updated the link and source.

Edited by DMagic
Link to comment
Share on other sites

just have it make say 3 backups each wtih just a name of backup1-3 wouldnt that be better or if u cn have it limit the number of back ups then have a name based on time and date.

Link to comment
Share on other sites

Edit: Just realized the time stamp was a bad idea. You'd get an ever-growing list of backups, better to create one and overwrite it. I updated the link and source.

Good catch. Would've gotten big, soon!

Link to comment
Share on other sites

I threw out the bool, so now the plugin runs every time you go back to the main menu. This way you can load several save games and it will make a backup each time, but only when you back out to the main menu. This seems like the best way to do it, if your save file was working the last time you quit, then the backup should also work (as long as you fix whatever broke your main persistent file in the first place).

I changed the code in the preview above, though I haven't actually uploaded a new version. I think what's shown above is about as simple as it's going to get.

just have it make say 3 backups each wtih just a name of backup1-3 wouldnt that be better or if u cn have it limit the number of back ups then have a name based on time and date.

This is probably a good idea, I'll try to find a way to do this.

Thinking of possibility to auto-commit the persistent file to repository (svn/git/...else).

This might also be a good idea, but it seems like it might run afoul of the rule about not altering or creating files outside of the KSP directory.

Link to comment
Share on other sites

Ever get the feeling that your parents dropped you on your head when you were little? like, repeatedly? I knew that checking existing backups would require knowing the current savefile directory. Getting the main KSP directory is easy enough, then just append 'saves' to the end. But it took me forever to figure out how to get the name of the directory for the current savefile, only to realize that it was staring right at me, in the code I had already written and posted, just above this post; HighLogic.fetch.GameSaveFolder.

So anyway, this code makes two backups. It checks for existing backups, then takes the 'recent' backup and copies it over the old backup before creating a new 'recent' file.

Unfortunately, it's not scalable. I need a different way to make three, or preferably one that makes three by default but allows an easy way to specify however many you want. I think I just need to make a loop that checks for the LastModifiedTime and returns the oldest file. I'll see what I can come up with.

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using System.IO;


namespace AutoSave
{
[KSPAddon(KSPAddon.Startup.MainMenu, false)]
public class AutoSave : MonoBehaviour
{
public void Start()
{
GameEvents.onGameSceneLoadRequested.Add(saveBackup);
}

public void saveBackup(GameScenes scene)
{
scene = HighLogic.LoadedScene;
if (scene == GameScenes.MAINMENU)
{
//This doesn't seem to like combining three strings into one path for some reason, so I combine two strings twice, I'm guessing the "saves" string needs some kind of / \.
string activeDirectory = Path.Combine(Path.Combine(new System.IO.DirectoryInfo(KSPUtil.ApplicationRootPath).FullName, "saves"), HighLogic.fetch.GameSaveFolder);
System.IO.FileInfo oldBackup = new System.IO.FileInfo(Path.Combine(activeDirectory, "Persistent Backup.sfs"));
if (oldBackup.Exists)
{
System.IO.FileInfo newBackup = new System.IO.FileInfo(Path.Combine(activeDirectory, "Persistent Backup Most Recent.sfs"));
if (newBackup.Exists) newBackup.Replace(Path.Combine(activeDirectory, "Persistent Backup.sfs"), Path.Combine(activeDirectory, "Persistent Backup Most Recent.sfs"));
var save = GamePersistence.SaveGame("Persistent Backup Most Recent", HighLogic.fetch.GameSaveFolder, 0);
GameEvents.onGameSceneLoadRequested.Remove(saveBackup);
}
else
{
var save = GamePersistence.SaveGame("Persistent Backup", HighLogic.fetch.GameSaveFolder, 0);
GameEvents.onGameSceneLoadRequested.Remove(saveBackup);
}
}
}

}
}

Link to comment
Share on other sites

Stick a fork in it, I think it's done.

This sets a default value of three backups and it can be changed using the included Settings.cfg file. I should probably test it a little more to make sure it works and doesn't break if you screw up the .cfg file, but otherwise I think it's good. I'll upload the plugin later.



using System;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

namespace AutoSave
{
[KSPAddon(KSPAddon.Startup.MainMenu, false)]
public class AutoSave : MonoBehaviour
{
public int max = 3;
protected ConfigNode node = null;
string path = null;

public void Start()
{
GameEvents.onGameSceneLoadRequested.Add(saveBackup);
}

public void saveBackup(GameScenes scene)
{
scene = HighLogic.LoadedScene;
if (scene == GameScenes.MAINMENU)
{
DateTime oldestFile = new DateTime(2050,1,1);
string replaceBackup = null;
string activeDirectory = Path.Combine(Path.Combine(new DirectoryInfo(KSPUtil.ApplicationRootPath).FullName, "saves"), HighLogic.fetch.GameSaveFolder);

path = Path.Combine(new DirectoryInfo(KSPUtil.ApplicationRootPath).FullName, "GameData/AutoSave/Settings.cfg").Replace("\\","/");
FileInfo settings = new FileInfo(path);

if (settings.Exists)
{
max = getMaxSave("MaxSaves");
print("Changing max saves value to " + max.ToString());
}

for (int i = 0; i < max; i++)
{
FileInfo backup = new FileInfo(Path.Combine(activeDirectory, "Persistent Backup " + i.ToString() + ".sfs"));
if (!backup.Exists)
{
replaceBackup = "Persistent Backup " + i.ToString();
break;
}
else
{
DateTime modified = backup.LastAccessTime;
if (modified < oldestFile)
{
replaceBackup = "Persistent Backup " + i.ToString();
oldestFile = modified;
}
}
}
var save = GamePersistence.SaveGame(replaceBackup, HighLogic.fetch.GameSaveFolder, 0);
GameEvents.onGameSceneLoadRequested.Remove(saveBackup);
}
}

public int getMaxSave(string max)
{
node = ConfigNode.Load(path);
if (node != null)
{
string value = node.GetValue(max);
return Convert.ToInt32(value);
}
else return 3;
}
}
}

Link to comment
Share on other sites

Nice work! My only suggestion/thought: you can bypass the number of backups altogether if you append a timestamp (YYYYMMDD.HHMMSS) to make the filename unique each time.

Certainly, these files will build up over time (if it crashes/fails to load constantly, that is) but they're trivially small and can be manually tossed.

Link to comment
Share on other sites

Nice work! My only suggestion/thought: you can bypass the number of backups altogether if you append a timestamp (YYYYMMDD.HHMMSS) to make the filename unique each time.

Certainly, these files will build up over time (if it crashes/fails to load constantly, that is) but they're trivially small and can be manually tossed.

That was how it originally worked, and was removed. It's probably not going to kill your hard drive but is there really any benefit to keeping every persistent.sfs ever? Better to remove it for those who might not even realise what's going on.

Link to comment
Share on other sites

Great job!

I triied the same thing a few months ago and I'd never know it could be done so nicely with GameScenes.onGameSceneLoadRequested() and GamePersistence.SaveGame().

Here is my code:

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using System.Text.RegularExpressions;

using UnityEngine;

namespace Zaky_Inc
{
[KSPAddonFixed(KSPAddon.Startup.MainMenu, true, typeof(SaveBackup))]
class SaveBackup : MonoBehaviour
{
private static string saveFileName = @"persistent.sfs";
private static string savePath = KSPUtil.ApplicationRootPath + @"saves/";
private static int backupNum = getBackupNum(Assembly.GetExecutingAssembly().Location);
private static List<string> gameList = new List<string>();

// get the maximum number of backups, by reading the first number appeared in the name of the dll file, which defined this class
// the number of backups is restricted to [5,20], with a default value = 5
private static int getBackupNum(string assemblyPath)
{
const int minBackupNum = 5;
const int maxBackupNum = 20;
string fileName = Path.GetFileName(assemblyPath);
var numPattern = new Regex(@"\d+");
int backupNum = 5;
Match match;

if ((match = numPattern.Match(fileName)).Success)
backupNum = Convert.ToInt32(match.Value);

if (backupNum < minBackupNum)
backupNum = minBackupNum;
if (backupNum > maxBackupNum)
backupNum = maxBackupNum;

return backupNum;
}

private static string[] getOldBackups(string[] paths)
{
string[] oldBackups;

if (paths.Length > backupNum)
oldBackups = paths.OrderBy(p => File.GetCreationTime(p)).Take(paths.Length - backupNum).ToArray();
else
oldBackups = new string[] {};

return oldBackups;
}

private static string toProperString(DateTime time)
{
return time.ToString("yyyy-MM-dd_HH-mm-ss-ff");
}

void Awake()
{
MonoBehaviour.DontDestroyOnLoad(this);
Debug.Log("[" + this.name + "] No." + this.GetInstanceID() + " is successfully launched.\n" +
"Current maximum number of backups is " + backupNum + ".\n");
}

void OnLevelWasLoaded(int scene)
{
if (scene == (int)GameScenes.SPACECENTER && !gameList.Contains(HighLogic.CurrentGame.Title))
{
gameList.Add(String.Copy(HighLogic.CurrentGame.Title));
backUpSaveFile();
}
}

void backUpSaveFile()
{
string saveFolder = HighLogic.SaveFolder;
string currentSaveDir = savePath + saveFolder + "/";
string[] existingBackups;
string currentBackup = "";
Debug.Log("[" + this.name + "] No." + this.GetInstanceID() + " is copying save files...");

if (File.Exists(currentSaveDir + saveFileName))
{
currentBackup = currentSaveDir + saveFileName + ".backup." + toProperString(DateTime.Now);

if (!File.Exists(currentBackup))
File.Copy(currentSaveDir + saveFileName, currentBackup);

existingBackups = Directory.GetFiles(currentSaveDir, "*.backup.*");
foreach (string oldBackup in getOldBackups(existingBackups))
{
File.Delete(oldBackup);
}
}
Debug.Log("[" + this.name + "] No." + this.GetInstanceID() + ": backup accomplished.");
}
}
}

Edited by zakyggaps
Link to comment
Share on other sites

Nice work! My only suggestion/thought: you can bypass the number of backups altogether if you append a timestamp (YYYYMMDD.HHMMSS) to make the filename unique each time.

Certainly, these files will build up over time (if it crashes/fails to load constantly, that is) but they're trivially small and can be manually tossed.

Yeah, what GavinZac said. I'm not really concerned about disk space either, it's more that you could end up with hundreds of files doing nothing in your save folder, and there's really no need for that.

Great job!

I triied the same thing a few months ago and I'd never know it could be done so nicely with GameScenes.onGameSceneLoadRequested() and GamePersistence.SaveGame().

I mostly made it by working backwards from an autoload plugin that skips right to an active vessel. You can see how simple the basic version is, it's about 10 lines. All of the complexity comes from checking existing backups to create a variable amount, but even still, it's pretty simple.

I think this update is about final. It actually does what I intend it to and locates the oldest file, instead of just pretending to like the last version. I also added a bunch of checks to make sure that no one can screw up the Settings.cfg file enough to break it.

I put it up on Spaceport if anyone's interested: http://kerbalspaceprogram.com/dmagic_autosave/

The only issue is that it doesn't seem to work when used in conjunction with autoload plugins that bypass the main menu. It just saves a "just started" persistent file with almost nothing in it. Some of these plugins might work (there are a lot of different versions floating around), but the one I'm using doesn't.


using System;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

namespace AutoSave
{
[KSPAddon(KSPAddon.Startup.MainMenu, false)]
public class AutoSave : MonoBehaviour
{
private int max = 3;
protected ConfigNode node = null;
private string path = null;

public void Start()
{
GameEvents.onGameSceneLoadRequested.Add(saveBackup);
}

public void saveBackup(GameScenes scene)
{
if (HighLogic.LoadedScene == GameScenes.MAINMENU)
{
DateTime oldestFile = new DateTime(2050,1,1);
string replaceBackup = null;
string activeDirectory = Path.Combine(Path.Combine(new DirectoryInfo(KSPUtil.ApplicationRootPath).FullName, "saves"), HighLogic.fetch.GameSaveFolder);
path = Path.Combine(new DirectoryInfo(KSPUtil.ApplicationRootPath).FullName, "GameData/AutoSave/Settings.cfg").Replace("\\","/");

if (File.Exists(path)) //Load Settings.cfg to check for change in max number of saves
{
max = getMaxSave("MaxSaves");
print("Changing max saves value to " + max.ToString());
}

for (int i = 0; i < max; i++)
{
string filepath = Path.Combine(activeDirectory, "persistent Backup " + i.ToString() + ".sfs");
if (!File.Exists(filepath))
{
replaceBackup = "persistent Backup " + i.ToString();
break;
}
else //If all backups have been written, check for the oldest file and rewrite that one
{
DateTime modified = File.GetLastWriteTime(filepath);
if (modified < oldestFile)
{
replaceBackup = "persistent Backup " + i.ToString();
oldestFile = modified;
}
}
}
var save = GamePersistence.SaveGame(replaceBackup, HighLogic.fetch.GameSaveFolder, 0);
GameEvents.onGameSceneLoadRequested.Remove(saveBackup);
}
}

public int getMaxSave(string entry) //Make sure that no amount of screwing up the Settings file will break the plugin.
{
int number = 3;
node = ConfigNode.Load(path);
if (node != null)
{
string value = node.GetValue(entry);
if (value == null) return number;
else if (Int32.TryParse(value, out number))
return number;
else return 3;
}
else return number;
}
}
}

Link to comment
Share on other sites

The only issue is that it doesn't seem to work when used in conjunction with autoload plugins that bypass the main menu. It just saves a "just started" persistent file with almost nothing in it. Some of these plugins might work (there are a lot of different versions floating around), but the one I'm using doesn't.

Maybe you could try directly copying the existing persistent.sfs (using the methods of System.IO.File), it's quite simple if HighLogic.fetch.GameSaveFolder returns a proper path.

Link to comment
Share on other sites

  • 2 weeks later...
Maybe you could try directly copying the existing persistent.sfs (using the methods of System.IO.File), it's quite simple if HighLogic.fetch.GameSaveFolder returns a proper path.

Good idea. It only took about two weeks, but I switched out one line and used File to create or overwrite a backup file with a copy of the existing persistent.sfs file. It seems to work fine with autoload plugins, everything else is the same. I updated the links on the first post.

Link to comment
Share on other sites

  • 2 months later...

I updated this again. It should finally work better with different quick-load plugins. Previously it seemed to work with some but not others. It uses xEvilReeperx's KSPAddonImproved to allow it to start in multiple different scenes.

It still only generates a backup once per game, but can be reset by going back to the main menu scene.

Link to comment
Share on other sites

This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...