Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/Source/Utils/SaveUtil.cs

437 lines
16 KiB
C#

using System;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Threading;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
partial class SaveUtil
{
public static string SaveFolder = "Data" + Path.DirectorySeparatorChar + "Saves";
public static string MultiplayerSaveFolder = "Data" + Path.DirectorySeparatorChar + "Saves" + Path.DirectorySeparatorChar + "Multiplayer";
public delegate void ProgressDelegate(string sMessage);
public static string TempPath
{
#if SERVER
get { return Path.Combine(SaveFolder, "temp_server"); }
#else
get { return Path.Combine(SaveFolder, "temp"); }
#endif
}
public enum SaveType
{
Singleplayer,
Multiplayer
}
public static void SaveGame(string filePath)
{
DebugConsole.Log("Saving the game to: " + filePath);
Directory.CreateDirectory(TempPath);
try
{
ClearFolder(TempPath, new string[] { GameMain.GameSession.Submarine.FilePath });
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to clear folder", e);
}
try
{
if (Submarine.MainSub != null)
{
string subPath = Path.Combine(TempPath, Submarine.MainSub.Name + ".sub");
if (Submarine.Loaded.Contains(Submarine.MainSub))
{
Submarine.MainSub.FilePath = subPath;
Submarine.MainSub.SaveAs(Submarine.MainSub.FilePath);
}
else if (Submarine.MainSub.FilePath != subPath)
{
if (File.Exists(subPath))
{
File.Delete(subPath);
}
File.Copy(Submarine.MainSub.FilePath, subPath);
Submarine.MainSub.FilePath = subPath;
}
}
}
catch (Exception e)
{
DebugConsole.ThrowError("Error saving submarine", e);
}
try
{
GameMain.GameSession.Save(Path.Combine(TempPath, "gamesession.xml"));
}
catch (Exception e)
{
DebugConsole.ThrowError("Error saving gamesession", e);
}
try
{
CompressDirectory(TempPath, filePath, null);
}
catch (Exception e)
{
DebugConsole.ThrowError("Error compressing save file", e);
}
}
public static void LoadGame(string filePath)
{
DebugConsole.Log("Loading save file: " + filePath);
DecompressToDirectory(filePath, TempPath, null);
XDocument doc = XMLExtensions.TryLoadXml(Path.Combine(TempPath, "gamesession.xml"));
string subPath = Path.Combine(TempPath, doc.Root.GetAttributeString("submarine", "")) + ".sub";
Submarine selectedSub = new Submarine(subPath, "");
GameMain.GameSession = new GameSession(selectedSub, filePath, doc);
}
public static void LoadGame(string filePath, GameSession gameSession)
{
DebugConsole.Log("Loading save file for an existing game session (" + filePath + ")");
DecompressToDirectory(filePath, TempPath, null);
XDocument doc = XMLExtensions.TryLoadXml(Path.Combine(TempPath, "gamesession.xml"));
gameSession.Load(doc.Root);
}
public static XDocument LoadGameSessionDoc(string filePath)
{
DebugConsole.Log("Loading game session doc: " + filePath);
try
{
DecompressToDirectory(filePath, TempPath, null);
}
catch (Exception e)
{
DebugConsole.ThrowError("Error decompressing " + filePath, e);
return null;
}
return XMLExtensions.TryLoadXml(Path.Combine(TempPath, "gamesession.xml"));
}
public static void DeleteSave(string filePath)
{
try
{
File.Delete(filePath);
}
catch (Exception e)
{
DebugConsole.ThrowError("ERROR: deleting save file \"" + filePath + "\" failed.", e);
}
//deleting a multiplayer save file -> also delete character data
if (Path.GetFullPath(Path.GetDirectoryName(filePath)).Equals(Path.GetFullPath(MultiplayerSaveFolder)))
{
string characterDataSavePath = MultiPlayerCampaign.GetCharacterDataSavePath(filePath);
if (File.Exists(characterDataSavePath))
{
try
{
File.Delete(characterDataSavePath);
}
catch (Exception e)
{
DebugConsole.ThrowError("ERROR: deleting character data file \"" + characterDataSavePath + "\" failed.", e);
}
}
}
}
public static string GetSavePath(SaveType saveType, string saveName)
{
string folder = saveType == SaveType.Singleplayer ? SaveFolder : MultiplayerSaveFolder;
return Path.Combine(folder, saveName);
}
public static string[] GetSaveFiles(SaveType saveType)
{
string folder = saveType == SaveType.Singleplayer ? SaveFolder : MultiplayerSaveFolder;
if (!Directory.Exists(folder))
{
DebugConsole.Log("Save folder \"" + folder + " not found! Attempting to create a new folder...");
try
{
Directory.CreateDirectory(folder);
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to create the folder \"" + folder + "\"!", e);
}
}
string[] files = Directory.GetFiles(folder, "*.save");
/*for (int i = 0; i < files.Length; i++)
{
files[i] = Path.GetFileNameWithoutExtension(files[i]);
}*/
return files;
}
public static string CreateSavePath(SaveType saveType, string fileName = "Save_Default")
{
string folder = saveType == SaveType.Singleplayer ? SaveFolder : MultiplayerSaveFolder;
if (fileName == "Save_Default")
{
fileName = TextManager.Get("SaveFile.DefaultName", true);
if (fileName.Length == 0) fileName = "Save";
}
if (!Directory.Exists(folder))
{
DebugConsole.ThrowError("Save folder \"" + folder + "\" not found. Created new folder");
Directory.CreateDirectory(folder);
}
string extension = ".save";
string pathWithoutExtension = Path.Combine(folder, fileName);
if (!File.Exists(pathWithoutExtension + extension))
{
return pathWithoutExtension + extension;
}
int i = 0;
while (File.Exists(pathWithoutExtension + " " + i + extension))
{
i++;
}
return pathWithoutExtension + " " + i + extension;
}
public static void CompressStringToFile(string fileName, string value)
{
// A.
// Write string to temporary file.
string temp = Path.GetTempFileName();
File.WriteAllText(temp, value);
// B.
// Read file into byte array buffer.
byte[] b;
using (FileStream f = new FileStream(temp, FileMode.Open))
{
b = new byte[f.Length];
f.Read(b, 0, (int)f.Length);
}
// C.
// Use GZipStream to write compressed bytes to target file.
using (FileStream f2 = new FileStream(fileName, FileMode.Create))
using (GZipStream gz = new GZipStream(f2, CompressionMode.Compress, false))
{
gz.Write(b, 0, b.Length);
}
}
public static void CompressFile(string sDir, string sRelativePath, GZipStream zipStream)
{
//Compress file name
char[] chars = sRelativePath.ToCharArray();
zipStream.Write(BitConverter.GetBytes(chars.Length), 0, sizeof(int));
foreach (char c in chars)
zipStream.Write(BitConverter.GetBytes(c), 0, sizeof(char));
//Compress file content
byte[] bytes = File.ReadAllBytes(Path.Combine(sDir, sRelativePath));
zipStream.Write(BitConverter.GetBytes(bytes.Length), 0, sizeof(int));
zipStream.Write(bytes, 0, bytes.Length);
}
public static void CompressDirectory(string sInDir, string sOutFile, ProgressDelegate progress)
{
string[] sFiles = Directory.GetFiles(sInDir, "*.*", SearchOption.AllDirectories);
int iDirLen = sInDir[sInDir.Length - 1] == Path.DirectorySeparatorChar ? sInDir.Length : sInDir.Length + 1;
using (FileStream outFile = new FileStream(sOutFile, FileMode.Create, FileAccess.Write, FileShare.None))
using (GZipStream str = new GZipStream(outFile, CompressionMode.Compress))
foreach (string sFilePath in sFiles)
{
string sRelativePath = sFilePath.Substring(iDirLen);
if (progress != null)
progress(sRelativePath);
CompressFile(sInDir, sRelativePath, str);
}
}
public static Stream DecompressFiletoStream(string fileName)
{
if (!File.Exists(fileName))
{
DebugConsole.ThrowError("File \"" + fileName + " doesn't exist!");
return null;
}
using (FileStream originalFileStream = new FileStream(fileName, FileMode.Open))
{
MemoryStream decompressedFileStream = new MemoryStream();
using (GZipStream decompressionStream = new GZipStream(originalFileStream, CompressionMode.Decompress))
{
decompressionStream.CopyTo(decompressedFileStream);
return decompressedFileStream;
}
}
}
public static bool DecompressFile(string sDir, GZipStream zipStream, ProgressDelegate progress)
{
//Decompress file name
byte[] bytes = new byte[sizeof(int)];
int Readed = zipStream.Read(bytes, 0, sizeof(int));
if (Readed < sizeof(int))
return false;
int iNameLen = BitConverter.ToInt32(bytes, 0);
if (iNameLen > 255)
{
throw new Exception("Failed to decompress \""+sDir+"\" (file name length > 255). The file may be corrupted.");
}
bytes = new byte[sizeof(char)];
StringBuilder sb = new StringBuilder();
for (int i = 0; i < iNameLen; i++)
{
zipStream.Read(bytes, 0, sizeof(char));
char c = BitConverter.ToChar(bytes, 0);
sb.Append(c);
}
string sFileName = sb.ToString();
if (progress != null)
progress(sFileName);
//Decompress file content
bytes = new byte[sizeof(int)];
zipStream.Read(bytes, 0, sizeof(int));
int iFileLen = BitConverter.ToInt32(bytes, 0);
bytes = new byte[iFileLen];
zipStream.Read(bytes, 0, bytes.Length);
string sFilePath = Path.Combine(sDir, sFileName);
string sFinalDir = Path.GetDirectoryName(sFilePath);
if (!Directory.Exists(sFinalDir))
Directory.CreateDirectory(sFinalDir);
using (FileStream outFile = new FileStream(sFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
outFile.Write(bytes, 0, iFileLen);
return true;
}
public static void DecompressToDirectory(string sCompressedFile, string sDir, ProgressDelegate progress)
{
DebugConsole.Log("Decompressing " + sCompressedFile + " to " + sDir + "...");
int maxRetries = 4;
for (int i = 0; i <= maxRetries; i++)
{
try
{
using (FileStream inFile = new FileStream(sCompressedFile, FileMode.Open, FileAccess.Read, FileShare.None))
using (GZipStream zipStream = new GZipStream(inFile, CompressionMode.Decompress, true))
while (DecompressFile(sDir, zipStream, progress)) ;
}
catch (IOException e)
{
if (i >= maxRetries || !File.Exists(sCompressedFile)) { throw; }
DebugConsole.NewMessage("Failed decompress file \"" + sCompressedFile + "\" {" + e.Message + "}, retrying in 250 ms...", Color.Red);
Thread.Sleep(250);
}
}
}
public static void CopyFolder(string sourceDirName, string destDirName, bool copySubDirs)
{
// Get the subdirectories for the specified directory.
DirectoryInfo dir = new DirectoryInfo(sourceDirName);
if (!dir.Exists)
{
throw new DirectoryNotFoundException(
"Source directory does not exist or could not be found: "
+ sourceDirName);
}
DirectoryInfo[] dirs = dir.GetDirectories();
// If the destination directory doesn't exist, create it.
if (!Directory.Exists(destDirName))
{
Directory.CreateDirectory(destDirName);
}
// Get the files in the directory and copy them to the new location.
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
string temppath = Path.Combine(destDirName, file.Name);
file.CopyTo(temppath, false);
}
// If copying subdirectories, copy them and their contents to new location.
if (copySubDirs)
{
foreach (DirectoryInfo subdir in dirs)
{
string temppath = Path.Combine(destDirName, subdir.Name);
CopyFolder(subdir.FullName, temppath, copySubDirs);
}
}
}
public static void ClearFolder(string FolderName, string[] ignoredFileNames = null)
{
DirectoryInfo dir = new DirectoryInfo(FolderName);
foreach (FileInfo fi in dir.GetFiles())
{
if (ignoredFileNames != null)
{
bool ignore = false;
foreach (string ignoredFile in ignoredFileNames)
{
if (Path.GetFileName(fi.FullName).Equals(Path.GetFileName(ignoredFile)))
{
ignore = true;
break;
}
}
if (ignore) continue;
}
fi.IsReadOnly = false;
fi.Delete();
}
foreach (DirectoryInfo di in dir.GetDirectories())
{
ClearFolder(di.FullName, ignoredFileNames);
di.Delete();
}
}
}
}