Unstable 1.1.14.0
This commit is contained in:
@@ -7,7 +7,7 @@ namespace Barotrauma
|
||||
{
|
||||
public delegate void TaskDelegate();
|
||||
|
||||
private class Task
|
||||
private sealed class Task
|
||||
{
|
||||
public TaskDelegate Deleg;
|
||||
public ManualResetEvent Mre;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using Barotrauma.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -98,6 +99,16 @@ namespace Barotrauma
|
||||
return new Md5Hash(hash);
|
||||
}
|
||||
|
||||
public static Md5Hash MergeHashes(IEnumerable<Md5Hash> hashes)
|
||||
{
|
||||
using IncrementalHash incrementalHash = IncrementalHash.CreateHash(HashAlgorithmName.MD5);
|
||||
foreach (var hash in hashes)
|
||||
{
|
||||
incrementalHash.AppendData(hash.ByteRepresentation);
|
||||
}
|
||||
return BytesAsHash(incrementalHash.GetHashAndReset());
|
||||
}
|
||||
|
||||
public static Md5Hash CalculateForBytes(byte[] bytes)
|
||||
{
|
||||
return new Md5Hash(bytes, calculate: true);
|
||||
|
||||
@@ -41,6 +41,19 @@ namespace Barotrauma
|
||||
public Option<TType> Bind<TType>(Func<T, Option<TType>> binder) where TType : notnull
|
||||
=> TryUnwrap(out T? selfValue) ? binder(selfValue) : Option.None;
|
||||
|
||||
public T Match(Func<T, T> some, Func<T> none)
|
||||
=> TryUnwrap(out T? selfValue) ? some(selfValue) : none();
|
||||
|
||||
public void Match(Action<T> some, Action none)
|
||||
{
|
||||
if (TryUnwrap(out T? selfValue))
|
||||
{
|
||||
some(selfValue);
|
||||
return;
|
||||
}
|
||||
none();
|
||||
}
|
||||
|
||||
public T Fallback(T fallback)
|
||||
=> TryUnwrap(out var v) ? v : fallback;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
@@ -9,11 +10,14 @@ using System.Text.RegularExpressions;
|
||||
using Barotrauma.IO;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
static class SaveUtil
|
||||
{
|
||||
public const string GameSessionFileName = "gamesession.xml";
|
||||
|
||||
private static readonly string LegacySaveFolder = Path.Combine("Data", "Saves");
|
||||
private static readonly string LegacyMultiplayerSaveFolder = Path.Combine(LegacySaveFolder, "Multiplayer");
|
||||
|
||||
@@ -39,8 +43,6 @@ namespace Barotrauma
|
||||
public static readonly string SubmarineDownloadFolder = Path.Combine("Submarines", "Downloaded");
|
||||
public static readonly string CampaignDownloadFolder = Path.Combine("Data", "Saves", "Multiplayer_Downloaded");
|
||||
|
||||
public delegate void ProgressDelegate(string sMessage);
|
||||
|
||||
public static string TempPath
|
||||
{
|
||||
#if SERVER
|
||||
@@ -72,7 +74,7 @@ namespace Barotrauma
|
||||
|
||||
try
|
||||
{
|
||||
GameMain.GameSession.Save(Path.Combine(TempPath, "gamesession.xml"));
|
||||
GameMain.GameSession.Save(Path.Combine(TempPath, GameSessionFileName));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -82,7 +84,7 @@ namespace Barotrauma
|
||||
|
||||
try
|
||||
{
|
||||
string mainSubPath = null;
|
||||
string? mainSubPath = null;
|
||||
if (GameMain.GameSession.SubmarineInfo != null)
|
||||
{
|
||||
mainSubPath = Path.Combine(TempPath, GameMain.GameSession.SubmarineInfo.Name + ".sub");
|
||||
@@ -115,7 +117,7 @@ namespace Barotrauma
|
||||
|
||||
try
|
||||
{
|
||||
CompressDirectory(TempPath, filePath, null);
|
||||
CompressDirectory(TempPath, filePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -130,9 +132,9 @@ namespace Barotrauma
|
||||
Submarine.Unload();
|
||||
GameMain.GameSession = null;
|
||||
DebugConsole.Log("Loading save file: " + filePath);
|
||||
DecompressToDirectory(filePath, TempPath, null);
|
||||
DecompressToDirectory(filePath, TempPath);
|
||||
|
||||
XDocument doc = XMLExtensions.TryLoadXml(Path.Combine(TempPath, "gamesession.xml"));
|
||||
XDocument doc = XMLExtensions.TryLoadXml(Path.Combine(TempPath, GameSessionFileName));
|
||||
if (doc == null) { return; }
|
||||
|
||||
if (!IsSaveFileCompatible(doc))
|
||||
@@ -149,40 +151,26 @@ namespace Barotrauma
|
||||
string subPath = Path.Combine(TempPath, saveDoc.Root.GetAttributeString("submarine", "")) + ".sub";
|
||||
selectedSub = new SubmarineInfo(subPath);
|
||||
|
||||
List<SubmarineInfo> ownedSubmarines = null;
|
||||
var ownedSubsElement = saveDoc.Root.Element("ownedsubmarines");
|
||||
if (ownedSubsElement != null)
|
||||
List<SubmarineInfo> ownedSubmarines = new List<SubmarineInfo>();
|
||||
|
||||
var ownedSubsElement = saveDoc.Root?.Element("ownedsubmarines");
|
||||
if (ownedSubsElement == null) { return ownedSubmarines; }
|
||||
|
||||
foreach (var subElement in ownedSubsElement.Elements())
|
||||
{
|
||||
ownedSubmarines = new List<SubmarineInfo>();
|
||||
foreach (var subElement in ownedSubsElement.Elements())
|
||||
{
|
||||
string subName = subElement.GetAttributeString("name", "");
|
||||
string ownedSubPath = Path.Combine(TempPath, subName + ".sub");
|
||||
ownedSubmarines.Add(new SubmarineInfo(ownedSubPath));
|
||||
}
|
||||
string subName = subElement.GetAttributeString("name", "");
|
||||
string ownedSubPath = Path.Combine(TempPath, subName + ".sub");
|
||||
ownedSubmarines.Add(new SubmarineInfo(ownedSubPath));
|
||||
}
|
||||
return ownedSubmarines;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
public static bool IsSaveFileCompatible(XDocument? saveDoc)
|
||||
=> IsSaveFileCompatible(saveDoc?.Root);
|
||||
|
||||
return XMLExtensions.TryLoadXml(Path.Combine(TempPath, "gamesession.xml"));
|
||||
}
|
||||
|
||||
public static bool IsSaveFileCompatible(XDocument saveDoc)
|
||||
public static bool IsSaveFileCompatible(XElement? saveDocRoot)
|
||||
{
|
||||
if (saveDoc?.Root?.Attribute("version") == null) { return false; }
|
||||
if (saveDocRoot?.Attribute("version") == null) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -192,14 +180,13 @@ namespace Barotrauma
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("ERROR: deleting save file \"" + filePath + "\" failed.", e);
|
||||
}
|
||||
|
||||
//deleting a multiplayer save file -> also delete character data
|
||||
var fullPath = Path.GetFullPath(Path.GetDirectoryName(filePath));
|
||||
var fullPath = Path.GetFullPath(Path.GetDirectoryName(filePath) ?? "");
|
||||
|
||||
if (fullPath.Equals(Path.GetFullPath(DefaultMultiplayerSaveFolder)) ||
|
||||
fullPath == Path.GetFullPath(GetSaveFolder(SaveType.Multiplayer)))
|
||||
@@ -286,12 +273,12 @@ namespace Barotrauma
|
||||
List<CampaignMode.SaveInfo> saveInfos = new List<CampaignMode.SaveInfo>();
|
||||
foreach (string file in files)
|
||||
{
|
||||
XDocument doc = LoadGameSessionDoc(file);
|
||||
if (!includeInCompatible && !IsSaveFileCompatible(doc))
|
||||
var docRoot = ExtractGameSessionRootElementFromSaveFile(file);
|
||||
if (!includeInCompatible && !IsSaveFileCompatible(docRoot))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (doc?.Root == null)
|
||||
if (docRoot == null)
|
||||
{
|
||||
saveInfos.Add(new CampaignMode.SaveInfo(
|
||||
FilePath: file,
|
||||
@@ -304,7 +291,7 @@ namespace Barotrauma
|
||||
List<string> enabledContentPackageNames = new List<string>();
|
||||
|
||||
//backwards compatibility
|
||||
string enabledContentPackagePathsStr = doc.Root.GetAttributeStringUnrestricted("selectedcontentpackages", string.Empty);
|
||||
string enabledContentPackagePathsStr = docRoot.GetAttributeStringUnrestricted("selectedcontentpackages", string.Empty);
|
||||
foreach (string packagePath in enabledContentPackagePathsStr.Split('|'))
|
||||
{
|
||||
if (string.IsNullOrEmpty(packagePath)) { continue; }
|
||||
@@ -312,7 +299,7 @@ namespace Barotrauma
|
||||
string fileName = Path.GetFileNameWithoutExtension(packagePath);
|
||||
if (fileName == "filelist")
|
||||
{
|
||||
enabledContentPackageNames.Add(Path.GetFileName(Path.GetDirectoryName(packagePath)));
|
||||
enabledContentPackageNames.Add(Path.GetFileName(Path.GetDirectoryName(packagePath) ?? ""));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -320,7 +307,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
string enabledContentPackageNamesStr = doc.Root.GetAttributeStringUnrestricted("selectedcontentpackagenames", string.Empty);
|
||||
string enabledContentPackageNamesStr = docRoot.GetAttributeStringUnrestricted("selectedcontentpackagenames", string.Empty);
|
||||
//split on pipes, excluding pipes preceded by \
|
||||
foreach (string packageName in Regex.Split(enabledContentPackageNamesStr, @"(?<!(?<!\\)*\\)\|"))
|
||||
{
|
||||
@@ -330,8 +317,8 @@ namespace Barotrauma
|
||||
|
||||
saveInfos.Add(new CampaignMode.SaveInfo(
|
||||
FilePath: file,
|
||||
SaveTime: doc.Root.GetAttributeDateTime("savetime"),
|
||||
SubmarineName: doc?.Root?.GetAttributeStringUnrestricted("submarine", ""),
|
||||
SaveTime: docRoot.GetAttributeDateTime("savetime"),
|
||||
SubmarineName: docRoot.GetAttributeStringUnrestricted("submarine", ""),
|
||||
EnabledContentPackageNames: enabledContentPackageNames.ToImmutableArray()));
|
||||
}
|
||||
}
|
||||
@@ -381,20 +368,28 @@ namespace Barotrauma
|
||||
|
||||
// B.
|
||||
// Use GZipStream to write compressed bytes to target file.
|
||||
using (FileStream f2 = File.Open(fileName, System.IO.FileMode.Create))
|
||||
using (GZipStream gz = new GZipStream(f2, CompressionMode.Compress, false))
|
||||
{
|
||||
gz.Write(b, 0, b.Length);
|
||||
}
|
||||
using FileStream f2 = File.Open(fileName, System.IO.FileMode.Create)
|
||||
?? throw new Exception($"Failed to create file \"{fileName}\"");;
|
||||
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)
|
||||
private 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));
|
||||
if (sRelativePath.Length > 255)
|
||||
{
|
||||
throw new Exception(
|
||||
$"Failed to compress \"{sDir}\" (file name length > 255).");
|
||||
}
|
||||
// File name length is encoded as a 32-bit little endian integer here
|
||||
zipStream.WriteByte((byte)sRelativePath.Length);
|
||||
zipStream.WriteByte(0);
|
||||
zipStream.WriteByte(0);
|
||||
zipStream.WriteByte(0);
|
||||
// File name content is encoded as little-endian UTF-16
|
||||
var strBytes = Encoding.Unicode.GetBytes(sRelativePath.CleanUpPathCrossPlatform(correctFilenameCase: false));
|
||||
zipStream.Write(strBytes, 0, strBytes.Length);
|
||||
|
||||
//Compress file content
|
||||
byte[] bytes = File.ReadAllBytes(Path.Combine(sDir, sRelativePath));
|
||||
@@ -402,25 +397,26 @@ namespace Barotrauma
|
||||
zipStream.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
public static void CompressDirectory(string sInDir, string sOutFile, ProgressDelegate progress)
|
||||
public static void CompressDirectory(string sInDir, string sOutFile)
|
||||
{
|
||||
IEnumerable<string> sFiles = Directory.GetFiles(sInDir, "*.*", System.IO.SearchOption.AllDirectories);
|
||||
int iDirLen = sInDir[sInDir.Length - 1] == Path.DirectorySeparatorChar ? sInDir.Length : sInDir.Length + 1;
|
||||
int iDirLen = sInDir[^1] == Path.DirectorySeparatorChar ? sInDir.Length : sInDir.Length + 1;
|
||||
|
||||
using (FileStream outFile = File.Open(sOutFile, System.IO.FileMode.Create, System.IO.FileAccess.Write))
|
||||
using (GZipStream str = new GZipStream(outFile, CompressionMode.Compress))
|
||||
foreach (string sFilePath in sFiles)
|
||||
{
|
||||
string sRelativePath = sFilePath.Substring(iDirLen);
|
||||
progress?.Invoke(sRelativePath);
|
||||
CompressFile(sInDir, sRelativePath, str);
|
||||
}
|
||||
using var outFile = File.Open(sOutFile, System.IO.FileMode.Create, System.IO.FileAccess.Write)
|
||||
?? throw new Exception($"Failed to create file \"{sOutFile}\"");
|
||||
using GZipStream str = new GZipStream(outFile, CompressionMode.Compress);
|
||||
foreach (string sFilePath in sFiles)
|
||||
{
|
||||
string sRelativePath = sFilePath.Substring(iDirLen);
|
||||
CompressFile(sInDir, sRelativePath, str);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static System.IO.Stream DecompressFileToStream(string fileName)
|
||||
{
|
||||
using FileStream originalFileStream = File.Open(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read);
|
||||
using FileStream originalFileStream = File.Open(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read)
|
||||
?? throw new Exception($"Failed to open file \"{fileName}\"");
|
||||
System.IO.MemoryStream streamToReturn = new System.IO.MemoryStream();
|
||||
|
||||
using GZipStream gzipStream = new GZipStream(originalFileStream, CompressionMode.Decompress);
|
||||
@@ -443,10 +439,11 @@ namespace Barotrauma
|
||||
|
||||
return fileDirFull.StartsWith(rootDirFull, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool DecompressFile(bool writeFile, string sDir, System.IO.BinaryReader reader, ProgressDelegate progress, out string fileName)
|
||||
|
||||
private static bool DecompressFile(System.IO.BinaryReader reader, [NotNullWhen(returnValue: true)]out string? fileName, [NotNullWhen(returnValue: true)]out byte[]? fileContent)
|
||||
{
|
||||
fileName = null;
|
||||
fileContent = null;
|
||||
|
||||
if (reader.PeekChar() < 0) { return false; }
|
||||
|
||||
@@ -455,7 +452,7 @@ namespace Barotrauma
|
||||
if (nameLen > 255)
|
||||
{
|
||||
throw new Exception(
|
||||
$"Failed to decompress \"{sDir}\" (file name length > 255). The file may be corrupted.");
|
||||
$"Failed to decompress (file name length > 255). The file may be corrupted.");
|
||||
}
|
||||
|
||||
byte[] strBytes = reader.ReadBytes(nameLen * sizeof(char));
|
||||
@@ -463,57 +460,40 @@ namespace Barotrauma
|
||||
.Replace('\\', '/');
|
||||
|
||||
fileName = sFileName;
|
||||
progress?.Invoke(sFileName);
|
||||
|
||||
//Decompress file content
|
||||
int contentLen = reader.ReadInt32();
|
||||
byte[] contentBytes = reader.ReadBytes(contentLen);
|
||||
fileContent = reader.ReadBytes(contentLen);
|
||||
|
||||
string sFilePath = Path.Combine(sDir, sFileName);
|
||||
string sFinalDir = Path.GetDirectoryName(sFilePath);
|
||||
|
||||
if (!IsExtractionPathValid(sDir, sFinalDir))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Error extracting \"{sFileName}\": cannot be extracted to parent directory");
|
||||
}
|
||||
|
||||
if (!writeFile) { return true; }
|
||||
|
||||
Directory.CreateDirectory(sFinalDir);
|
||||
int maxRetries = 4;
|
||||
for (int i = 0; i <= maxRetries; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (FileStream outFile = File.Open(sFilePath, System.IO.FileMode.Create, System.IO.FileAccess.Write))
|
||||
{
|
||||
outFile.Write(contentBytes, 0, contentLen);
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (System.IO.IOException e)
|
||||
{
|
||||
if (i >= maxRetries || !File.Exists(sFilePath)) { throw; }
|
||||
DebugConsole.NewMessage("Failed decompress file \"" + sFilePath + "\" {" + e.Message + "}, retrying in 250 ms...", Color.Red);
|
||||
Thread.Sleep(250);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void DecompressToDirectory(string sCompressedFile, string sDir, ProgressDelegate progress)
|
||||
public static void DecompressToDirectory(string sCompressedFile, string sDir)
|
||||
{
|
||||
DebugConsole.Log("Decompressing " + sCompressedFile + " to " + sDir + "...");
|
||||
int maxRetries = 4;
|
||||
const int maxRetries = 4;
|
||||
for (int i = 0; i <= maxRetries; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var memStream = DecompressFileToStream(sCompressedFile))
|
||||
using (System.IO.BinaryReader reader = new System.IO.BinaryReader(memStream))
|
||||
while (DecompressFile(true, sDir, reader, progress, out _)) { };
|
||||
using var memStream = DecompressFileToStream(sCompressedFile);
|
||||
using var reader = new System.IO.BinaryReader(memStream);
|
||||
while (DecompressFile(reader, out var fileName, out var contentBytes))
|
||||
{
|
||||
string sFilePath = Path.Combine(sDir, fileName);
|
||||
string sFinalDir = Path.GetDirectoryName(sFilePath) ?? "";
|
||||
|
||||
if (!IsExtractionPathValid(sDir, sFinalDir))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Error extracting \"{fileName}\": cannot be extracted to parent directory");
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(sFinalDir);
|
||||
using var outFile = File.Open(sFilePath, System.IO.FileMode.Create, System.IO.FileAccess.Write)
|
||||
?? throw new Exception($"Failed to create file \"{sFilePath}\"");
|
||||
outFile.Write(contentBytes, 0, contentBytes.Length);
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (System.IO.IOException e)
|
||||
@@ -527,18 +507,20 @@ namespace Barotrauma
|
||||
|
||||
public static IEnumerable<string> EnumerateContainedFiles(string sCompressedFile)
|
||||
{
|
||||
int maxRetries = 4;
|
||||
const int maxRetries = 4;
|
||||
HashSet<string> paths = new HashSet<string>();
|
||||
for (int i = 0; i <= maxRetries; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var memStream = DecompressFileToStream(sCompressedFile))
|
||||
using (System.IO.BinaryReader reader = new System.IO.BinaryReader(memStream))
|
||||
while (DecompressFile(false, "", reader, null, out string fileName))
|
||||
{
|
||||
paths.Add(fileName);
|
||||
}
|
||||
paths.Clear();
|
||||
using var memStream = DecompressFileToStream(sCompressedFile);
|
||||
using var reader = new System.IO.BinaryReader(memStream);
|
||||
while (DecompressFile(reader, out var fileName, out _))
|
||||
{
|
||||
paths.Add(fileName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (System.IO.IOException e)
|
||||
{
|
||||
@@ -554,44 +536,96 @@ namespace Barotrauma
|
||||
return paths;
|
||||
}
|
||||
|
||||
public static void CopyFolder(string sourceDirName, string destDirName, bool copySubDirs, bool overwriteExisting = false)
|
||||
/// <summary>
|
||||
/// Extracts the save file (including all the subs in it) to a temporary folder and returns the game session document.
|
||||
/// If you only need the gamesession doc, use <see cref="ExtractGameSessionRootElementFromSaveFile"/> instead.
|
||||
/// </summary>
|
||||
/// <param name="savePath"></param>
|
||||
/// <returns></returns>
|
||||
public static XDocument? DecompressSaveAndLoadGameSessionDoc(string savePath)
|
||||
{
|
||||
// Get the subdirectories for the specified directory.
|
||||
DirectoryInfo dir = new DirectoryInfo(sourceDirName);
|
||||
|
||||
if (!dir.Exists)
|
||||
DebugConsole.Log("Loading game session doc: " + savePath);
|
||||
try
|
||||
{
|
||||
throw new System.IO.DirectoryNotFoundException(
|
||||
"Source directory does not exist or could not be found: "
|
||||
+ sourceDirName);
|
||||
DecompressToDirectory(savePath, TempPath);
|
||||
}
|
||||
|
||||
IEnumerable<DirectoryInfo> dirs = dir.GetDirectories();
|
||||
// If the destination directory doesn't exist, create it.
|
||||
if (!Directory.Exists(destDirName))
|
||||
catch (Exception e)
|
||||
{
|
||||
Directory.CreateDirectory(destDirName);
|
||||
DebugConsole.ThrowError("Error decompressing " + savePath, e);
|
||||
return null;
|
||||
}
|
||||
return XMLExtensions.TryLoadXml(Path.Combine(TempPath, "gamesession.xml"));
|
||||
}
|
||||
|
||||
// Get the files in the directory and copy them to the new location.
|
||||
IEnumerable<FileInfo> files = dir.GetFiles();
|
||||
foreach (FileInfo file in files)
|
||||
/// <summary>
|
||||
/// Extract *only* the root element of the gamesession.xml file in the given save.
|
||||
/// For performance reasons, none of its child elements are returned.
|
||||
/// </summary>
|
||||
public static XElement? ExtractGameSessionRootElementFromSaveFile(string savePath)
|
||||
{
|
||||
const int maxRetries = 4;
|
||||
for (int i = 0; i <= maxRetries; i++)
|
||||
{
|
||||
string tempPath = Path.Combine(destDirName, file.Name);
|
||||
if (!overwriteExisting && File.Exists(tempPath)) { continue; }
|
||||
file.CopyTo(tempPath, true);
|
||||
}
|
||||
|
||||
// If copying subdirectories, copy them and their contents to new location.
|
||||
if (copySubDirs)
|
||||
{
|
||||
foreach (DirectoryInfo subdir in dirs)
|
||||
try
|
||||
{
|
||||
string tempPath = Path.Combine(destDirName, subdir.Name);
|
||||
CopyFolder(subdir.FullName, tempPath, copySubDirs, overwriteExisting);
|
||||
using var memStream = DecompressFileToStream(savePath);
|
||||
using var reader = new System.IO.BinaryReader(memStream);
|
||||
while (DecompressFile(reader, out var fileName, out var fileContent))
|
||||
{
|
||||
if (fileName != GameSessionFileName) { continue; }
|
||||
|
||||
// Found the file! Here's a quick byte-wise parser to find the root element
|
||||
int tagOpenerStartIndex = -1;
|
||||
for (int j = 0; j < fileContent.Length; j++)
|
||||
{
|
||||
if (fileContent[j] == '<')
|
||||
{
|
||||
// Found a tag opener: return null if we had already found one
|
||||
if (tagOpenerStartIndex >= 0) { return null; }
|
||||
tagOpenerStartIndex = j;
|
||||
}
|
||||
else if (j > 0 && fileContent[j] == '?' && fileContent[j - 1] == '<')
|
||||
{
|
||||
// Found the XML version element, skip this
|
||||
tagOpenerStartIndex = -1;
|
||||
}
|
||||
else if (fileContent[j] == '>')
|
||||
{
|
||||
// Found a tag closer, if we know where the tag opener is then we've found the root element
|
||||
if (tagOpenerStartIndex < 0) { continue; }
|
||||
|
||||
string elemStr = Encoding.UTF8.GetString(fileContent.AsSpan()[tagOpenerStartIndex..j]) + "/>";
|
||||
try
|
||||
{
|
||||
return XElement.Parse(elemStr);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.NewMessage(
|
||||
$"Failed to parse gamesession root in \"{savePath}\": {{{e.Message}}}.",
|
||||
Color.Red);
|
||||
// Parsing the element failed! Return null instead of crashing here
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (System.IO.IOException e)
|
||||
{
|
||||
if (i >= maxRetries || !File.Exists(savePath)) { throw; }
|
||||
|
||||
DebugConsole.NewMessage(
|
||||
$"Failed to decompress file \"{savePath}\" for root extraction {{{e.Message}}}, retrying in 250 ms...",
|
||||
Color.Red);
|
||||
Thread.Sleep(250);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void DeleteDownloadedSubs()
|
||||
{
|
||||
if (Directory.Exists(SubmarineDownloadFolder))
|
||||
@@ -614,7 +648,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClearFolder(string folderName, string[] ignoredFileNames = null)
|
||||
public static void ClearFolder(string folderName, string[]? ignoredFileNames = null)
|
||||
{
|
||||
DirectoryInfo dir = new DirectoryInfo(folderName);
|
||||
|
||||
@@ -640,7 +674,7 @@ namespace Barotrauma
|
||||
foreach (DirectoryInfo di in dir.GetDirectories())
|
||||
{
|
||||
ClearFolder(di.FullName, ignoredFileNames);
|
||||
int maxRetries = 4;
|
||||
const int maxRetries = 4;
|
||||
for (int i = 0; i <= maxRetries; i++)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -27,6 +27,31 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly record struct SquareLine(Vector2[] Points, SquareLine.LineType Type)
|
||||
{
|
||||
internal enum LineType
|
||||
{
|
||||
/// <summary>
|
||||
/// Normal 4 point line
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// ┏━━━ end
|
||||
/// ┃
|
||||
/// start ━━━┛
|
||||
/// </example>
|
||||
FourPointForwardsLine,
|
||||
/// <summary>
|
||||
/// A line where the end is behind the start and 2 extra points are used to draw it
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// start ━┓
|
||||
/// ┏━━━━━━┛
|
||||
/// ┗━ end
|
||||
/// </example>
|
||||
SixPointBackwardsLine
|
||||
}
|
||||
}
|
||||
|
||||
static partial class ToolBox
|
||||
{
|
||||
public static bool IsProperFilenameCase(string filename)
|
||||
@@ -396,6 +421,10 @@ namespace Barotrauma
|
||||
|
||||
public static T SelectWeightedRandom<T>(IEnumerable<T> objects, Func<T, float> weightMethod, Random random)
|
||||
{
|
||||
if (typeof(PrefabWithUintIdentifier).IsAssignableFrom(typeof(T)))
|
||||
{
|
||||
objects = objects.OrderBy(p => (p as PrefabWithUintIdentifier)?.UintIdentifier ?? 0);
|
||||
}
|
||||
List<T> objectList = objects.ToList();
|
||||
List<float> weights = objectList.Select(o => weightMethod(o)).ToList();
|
||||
return SelectWeightedRandom(objectList, weights, random);
|
||||
@@ -408,7 +437,7 @@ namespace Barotrauma
|
||||
|
||||
public static T SelectWeightedRandom<T>(IList<T> objects, IList<float> weights, Random random)
|
||||
{
|
||||
if (objects.Count == 0) { return default(T); }
|
||||
if (objects.Count == 0) { return default; }
|
||||
|
||||
if (objects.Count != weights.Count)
|
||||
{
|
||||
@@ -427,7 +456,7 @@ namespace Barotrauma
|
||||
}
|
||||
randomNum -= weights[i];
|
||||
}
|
||||
return default(T);
|
||||
return default;
|
||||
}
|
||||
|
||||
public static UInt32 IdentifierToUint32Hash(Identifier id, MD5 md5)
|
||||
@@ -806,5 +835,43 @@ namespace Barotrauma
|
||||
if (other.IsIPv4MappedToIPv6) { other = other.MapToIPv4(); }
|
||||
return self.Equals(other);
|
||||
}
|
||||
|
||||
public static SquareLine GetSquareLineBetweenPoints(Vector2 start, Vector2 end, float knobLength = 24f)
|
||||
{
|
||||
Vector2[] points = new Vector2[6];
|
||||
|
||||
// set the start and end points
|
||||
points[0] = points[1] = points[2] = start;
|
||||
points[5] = points[4] = points[3] = end;
|
||||
|
||||
points[2].X += (points[3].X - points[2].X) / 2;
|
||||
points[2].X = Math.Max(points[2].X, points[0].X + knobLength);
|
||||
points[3].X = points[2].X;
|
||||
|
||||
bool isBehind = false;
|
||||
|
||||
// if the node is "behind" us do some magic to make the line curve to prevent overlapping
|
||||
if (points[2].X <= points[0].X + knobLength)
|
||||
{
|
||||
isBehind = true;
|
||||
points[1].X += knobLength;
|
||||
points[2].X = points[2].X;
|
||||
points[2].Y += (points[4].Y - points[1].Y) / 2;
|
||||
}
|
||||
|
||||
if (points[3].X >= points[5].X - knobLength)
|
||||
{
|
||||
isBehind = true;
|
||||
points[4].X -= knobLength;
|
||||
points[3].X = points[4].X;
|
||||
points[3].Y -= points[3].Y - points[2].Y;
|
||||
}
|
||||
|
||||
SquareLine.LineType type = isBehind
|
||||
? SquareLine.LineType.SixPointBackwardsLine
|
||||
: SquareLine.LineType.FourPointForwardsLine;
|
||||
|
||||
return new SquareLine(points, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user