Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/Source/Utils/SaveUtil.cs
T
Joonas Rikkonen c2e8263927 f9e8100...ccacceb
commit ccacceb16a184f00ecd384eede64ca9c4fab08a0
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 14:05:59 2019 +0200

    NetEntityEventManager checks the length of the event data (and logs an error if it's too long) before checking if there's still room to keep writing events in the packet. Checking the available room first could lead to situations where an excessively large event can't fit to any packet, "soft-locking" the EventManager without any error messages.

commit 5ac8259372aa900adc724aa4da1fd81af41ca195
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 13:41:52 2019 +0200

    Don't display disabled limbs on sonar (i.e. severed limbs that have "faded out")

commit 5f84df73ad86be96f3678c450351b3905e7317a4
Merge: b981f1635 dc429d6c4
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 13:41:16 2019 +0200

    Merge branch 'dev' of https://github.com/Regalis11/Barotrauma-development into dev

commit b981f163575b2bfc9a83b9925c94eca19b9d4554
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 13:36:19 2019 +0200

    Multiplayer campaign fixes:
    - Server uses a different temp folder to decompress save/sub files into than the clients. Should fix files occasionally getting corrupted and exceptions when trying to read the files when hosting a server from the main executable.
    - Some additional debug logging.
    - Use the base names of the adjacent locations as level seeds (i.e. "Vorta" instead of "Vorta Outpost"). The levels should not change when the type (and full name) of the location changes.

commit 42c5d18df77fc7acd5873d8e25f20bdd31b1ed76
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 13:31:06 2019 +0200

    Don't transfer files through the network when sending them to the owner of the server (i.e. a client hosting directly from the main executable), but simply tell the client where the file is located.

commit dc429d6c450f4893fe29c51d3c830527e587a871
Author: Daniel Asteljoki <daniel.asteljoki@gmail.com>
Date:   Mon Mar 25 13:30:26 2019 +0200

    Added labels next to periscopes in Humpback and Dugong

commit 789f02a87a2917dd2ae378f136cbe8dd3236c60d
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 13:29:29 2019 +0200

    If loading a submarine fails, wait a bit and retry up to 4 times. Fixes loading occasionally failing when running multiple instances of the game from the same directory.

commit be9ea3a58832992b6226917117247e1bf1efeff9
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 11:03:36 2019 +0200

    Fixed a bunch of disconnection messages being in an incorrect format & DisconnectUnauthClient not getting the messages from the xml

commit c6f744b4d6b3520720010f5cd4f22a25b42bfc8b
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 10:43:10 2019 +0200

    Log entity event errors into server logs when verbose logging is enabled
2019-03-25 14:30:00 +02:00

413 lines
14 KiB
C#

using System;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Xml.Linq;
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)
{
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)
{
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)
{
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)
{
DecompressToDirectory(filePath, TempPath, null);
XDocument doc = XMLExtensions.TryLoadXml(Path.Combine(TempPath, "gamesession.xml"));
gameSession.Load(doc.Root);
}
public static XDocument LoadGameSessionDoc(string 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)
{
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)) ;
}
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[] ignoredFiles = null)
{
DirectoryInfo dir = new DirectoryInfo(FolderName);
foreach (FileInfo fi in dir.GetFiles())
{
if (ignoredFiles != null)
{
bool ignore = false;
foreach (string ignoredFile in ignoredFiles)
{
if (Path.GetFullPath(fi.FullName).Equals(Path.GetFullPath(ignoredFile)))
{
ignore = true;
break;
}
}
if (ignore) continue;
}
fi.IsReadOnly = false;
fi.Delete();
}
foreach (DirectoryInfo di in dir.GetDirectories())
{
ClearFolder(di.FullName, ignoredFiles);
di.Delete();
}
}
}
}