Survivalcraft API 1.8.2.3 v1.8.2.3
Survivalcraft 2.4
载入中...
搜索中...
未找到
WorldsManager.cs
浏览该文件的文档.
1using System.Xml.Linq;
2using Engine;
5using XmlUtilities;
6
7namespace Game {
8 public static class WorldsManager {
9 public static List<WorldInfo> m_worldInfos = [];
10
11 public static ReadOnlyList<string> m_newWorldNames;
12
14
15 public const int MaxAllowedWorlds = 30;
16
17 public static ReadOnlyList<string> NewWorldNames => m_newWorldNames;
18
19 public static ReadOnlyList<WorldInfo> WorldInfos => new(m_worldInfos);
20
21 public static event Action<string> WorldDeleted;
22 public static bool Loaded;
23
24 public static void Initialize() {
25 if (Loaded) {
26 return;
27 }
29 string text = ContentManager.Get<string>("NewWorldNames");
30 m_newWorldNames = new ReadOnlyList<string>(text.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries));
31 Loaded = true;
32 }
33
34 public static string ImportWorld(Stream sourceStream) {
36 throw new InvalidOperationException("Cannot import worlds in trial mode.");
37 }
38 string unusedWorldDirectoryName = GetUnusedWorldDirectoryName();
39 Storage.CreateDirectory(unusedWorldDirectoryName);
40 UnpackWorld(unusedWorldDirectoryName, sourceStream, true);
41 if (!TestXmlFile(Storage.CombinePaths(unusedWorldDirectoryName, "Project.xml"), "Project")) {
42 try {
43 DeleteWorld(unusedWorldDirectoryName);
44 }
45 catch {
46 // ignored
47 }
48 throw new InvalidOperationException("Cannot import world because it does not contain valid world data.");
49 }
50 return unusedWorldDirectoryName;
51 }
52
53 public static void ExportWorld(string directoryName, Stream targetStream) {
54 PackWorld(directoryName, targetStream, null, true);
55 }
56
57 public static void DeleteWorld(string directoryName) {
58 if (Storage.DirectoryExists(directoryName)) {
59 DeleteWorldContents(directoryName, null);
60 Storage.DeleteDirectory(directoryName);
61 }
62 WorldDeleted?.Invoke(directoryName);
63 }
64
65 public static void RepairWorldIfNeeded(string directoryName) {
66 try {
67 string text = Storage.CombinePaths(directoryName, "Project.xml");
68 if (!TestXmlFile(text, "Project")) {
70 $"Project file at \"{text}\" is corrupt or nonexistent. Will try copying data from the backup file. If that fails, will try making a recovery project file."
71 );
72 string text2 = Storage.CombinePaths(directoryName, "Project.bak");
73 if (TestXmlFile(text2, "Project")) {
74 Storage.CopyFile(text2, text);
75 }
76 else {
77 string path = Storage.CombinePaths(directoryName, "Chunks.dat");
78 if (!Storage.FileExists(path)) {
79 throw new InvalidOperationException("Recovery project file could not be generated because chunks file does not exist.");
80 }
81 XElement xElement = ContentManager.Get<XElement>("RecoveryProject");
82 using (Stream stream = Storage.OpenFile(path, OpenFileMode.Read)) {
83 TerrainSerializer14.ReadTOCEntry(stream, out int cx, out int cz, out int _);
84 Vector3 vector = new(16 * cx, 255f, 16 * cz);
85 xElement?.Element("Subsystems")
86 ?.Element("Values")
87 ?.Element("Value")
88 ?.Attribute("Value")
89 ?.SetValue(HumanReadableConverter.ConvertToString(vector));
90 }
91 using (Stream stream2 = Storage.OpenFile(text, OpenFileMode.Create)) {
92 XmlUtils.SaveXmlToStream(xElement, stream2, null, true);
93 }
94 }
95 }
96 }
97 catch (Exception ex) {
98 Log.Error(ex);
99 throw new InvalidOperationException("The world files are corrupt and could not be repaired.");
100 }
101 }
102
103 public static void MakeQuickWorldBackup(string directoryName) {
104 string text = Storage.CombinePaths(directoryName, "Project.xml");
105 if (Storage.FileExists(text)) {
106 string destinationPath = Storage.CombinePaths(directoryName, "Project.bak");
107 Storage.CopyFile(text, destinationPath);
108 }
109 }
110
111 public static bool SnapshotExists(string directoryName, string snapshotName) =>
112 Storage.FileExists(MakeSnapshotFilename(directoryName, snapshotName));
113
114 public static void TakeWorldSnapshot(string directoryName, string snapshotName) {
115 using (Stream targetStream = Storage.OpenFile(MakeSnapshotFilename(directoryName, snapshotName), OpenFileMode.Create)) {
116 PackWorld(directoryName, targetStream, fn => Path.GetExtension(fn).ToLower() != ".snapshot", false);
117 }
118 }
119
120 public static void RestoreWorldFromSnapshot(string directoryName, string snapshotName) {
121 if (SnapshotExists(directoryName, snapshotName)) {
122 DeleteWorldContents(directoryName, fn => Storage.GetExtension(fn).ToLower() != ".snapshot");
123 using (Stream sourceStream = Storage.OpenFile(MakeSnapshotFilename(directoryName, snapshotName), OpenFileMode.Read)) {
124 UnpackWorld(directoryName, sourceStream, false);
125 }
126 }
127 }
128
129 public static void DeleteWorldSnapshot(string directoryName, string snapshotName) {
130 string path = MakeSnapshotFilename(directoryName, snapshotName);
131 if (Storage.FileExists(path)) {
132 Storage.DeleteFile(path);
133 }
134 }
135
136 public static void UpdateWorldsList() {
137 m_worldInfos.Clear();
138 foreach (string item in Storage.ListDirectoryNames(WorldsDirectoryName)) {
140 if (worldInfo != null) {
141 m_worldInfos.Add(worldInfo);
142 }
143 }
144 }
145
146 public static bool ValidateWorldName(string name) {
147 if (string.IsNullOrEmpty(name)
148 || name.Contains('\\')
149 || name.Contains('\n')
150 || name.Length > 128) {
151 return false;
152 }
153 return true;
154 }
155
156 public static WorldInfo GetWorldInfo(string directoryName) {
157 WorldInfo worldInfo = new() { DirectoryName = directoryName, LastSaveTime = DateTime.MinValue };
158 List<string> list = new();
159 RecursiveEnumerateDirectory(directoryName, list, null, null);
160 if (list.Count > 0) {
161 foreach (string item in list) {
162 DateTime fileLastWriteTime = Storage.GetFileLastWriteTime(item);
163 if (fileLastWriteTime > worldInfo.LastSaveTime) {
164 worldInfo.LastSaveTime = fileLastWriteTime;
165 }
166 try {
167 worldInfo.Size += Storage.GetFileSize(item);
168 }
169 catch (Exception e2) {
170 Log.Error(ExceptionManager.MakeFullErrorMessage($"Error getting size of file \"{item}\".", e2));
171 }
172 }
173 string text = Storage.CombinePaths(directoryName, "Project.xml");
174 try {
175 if (Storage.FileExists(text)) {
176 using (Stream stream = Storage.OpenFile(text, OpenFileMode.Read)) {
177 XElement xElement = XmlUtils.LoadXmlFromStream(stream, null, true);
178 worldInfo.SerializationVersion = XmlUtils.GetAttributeValue(xElement, "Version", "1.0");
179 worldInfo.APIVersion = XmlUtils.GetAttributeValue(xElement, "APIVersion", string.Empty);
181 XElement gameInfoNode = GetGameInfoNode(xElement);
182 ValuesDictionary valuesDictionary = new();
183 valuesDictionary.ApplyOverrides(gameInfoNode);
184 worldInfo.WorldSettings.Load(valuesDictionary);
185 foreach (XElement item2 in (from e in GetPlayersNode(xElement).Elements()
186 where XmlUtils.GetAttributeValue<string>(e, "Name") == "Players"
187 select e).First()
188 .Elements()) {
189 PlayerInfo playerInfo = new();
190 worldInfo.PlayerInfos.Add(playerInfo);
191 XElement xElement2 = (from e in item2.Elements()
192 where XmlUtils.GetAttributeValue(e, "Name", string.Empty) == "CharacterSkinName"
193 select e).FirstOrDefault();
194 if (xElement2 != null) {
195 playerInfo.CharacterSkinName = XmlUtils.GetAttributeValue(xElement2, "Value", string.Empty);
196 }
197 }
198 return worldInfo;
199 }
200 }
201 return worldInfo;
202 }
203 catch (Exception e3) {
204 Log.Error(ExceptionManager.MakeFullErrorMessage($"Error getting data from project file \"{text}\".", e3));
205 return worldInfo;
206 }
207 }
208 return null;
209 }
210
211 public static WorldInfo CreateWorld(WorldSettings worldSettings) {
212 string unusedWorldDirectoryName = GetUnusedWorldDirectoryName();
213 Storage.CreateDirectory(unusedWorldDirectoryName);
214 if (!ValidateWorldName(worldSettings.Name)) {
215 throw new InvalidOperationException($"World name \"{worldSettings.Name}\" is invalid.");
216 }
217 int worldSeed;
218 if (worldSettings.CustomWorldSeed) {
219 worldSeed = worldSettings.WorldSeed;
220 }
221 else if (string.IsNullOrEmpty(worldSettings.Seed)) {
222 worldSeed = (int)(long)(Time.RealTime * 1000.0);
223 }
224 else if (worldSettings.Seed == "0") {
225 worldSeed = 0;
226 }
227 else {
228 worldSeed = 0;
229 int num2 = 1;
230 string seed = worldSettings.Seed;
231 foreach (char c in seed) {
232 worldSeed += c * num2;
233 num2 += 29;
234 }
235 }
236 ValuesDictionary valuesDictionary = new();
237 worldSettings.Save(valuesDictionary, false);
238 valuesDictionary.SetValue("WorldDirectoryName", unusedWorldDirectoryName);
239 valuesDictionary.SetValue("WorldSeed", worldSeed);
240 ValuesDictionary valuesDictionary2 = new();
241 valuesDictionary2.SetValue("Players", new ValuesDictionary());
242 DatabaseObject databaseObject = DatabaseManager.GameDatabase.Database.FindDatabaseObject(
243 "GameProject",
244 DatabaseManager.GameDatabase.ProjectTemplateType,
245 true
246 );
247 XElement xElement = new("Project");
248 XmlUtils.SetAttributeValue(xElement, "Guid", databaseObject.Guid);
249 XmlUtils.SetAttributeValue(xElement, "Name", "GameProject");
252 XElement xElement2 = new("Subsystems");
253 xElement.Add(xElement2);
254 XElement xElement3 = new("Values");
255 XmlUtils.SetAttributeValue(xElement3, "Name", "GameInfo");
256 valuesDictionary.Save(xElement3);
257 xElement2.Add(xElement3);
258 XElement xElement4 = new("Values");
259 XmlUtils.SetAttributeValue(xElement4, "Name", "Players");
260 valuesDictionary2.Save(xElement4);
261 xElement2.Add(xElement4);
262 using (Stream stream = Storage.OpenFile(Storage.CombinePaths(unusedWorldDirectoryName, "Project.xml"), OpenFileMode.Create)) {
263 XmlUtils.SaveXmlToStream(xElement, stream, null, true);
264 }
265 return GetWorldInfo(unusedWorldDirectoryName);
266 }
267
268 public static void ChangeWorld(string directoryName, WorldSettings worldSettings) {
269 string path = Storage.CombinePaths(directoryName, "Project.xml");
270 if (!Storage.FileExists(path)) {
271 return;
272 }
273 XElement xElement;
274 using (Stream stream = Storage.OpenFile(path, OpenFileMode.Read)) {
275 xElement = XmlUtils.LoadXmlFromStream(stream, null, true);
276 }
277 XElement gameInfoNode = GetGameInfoNode(xElement);
278 ValuesDictionary valuesDictionary = new();
279 valuesDictionary.ApplyOverrides(gameInfoNode);
280 GameMode value = valuesDictionary.GetValue<GameMode>("GameMode");
281 worldSettings.Save(valuesDictionary, true);
282 gameInfoNode.RemoveNodes();
283 valuesDictionary.Save(gameInfoNode);
284 using (Stream stream2 = Storage.OpenFile(path, OpenFileMode.Create)) {
285 XmlUtils.SaveXmlToStream(xElement, stream2, null, true);
286 }
287 if (worldSettings.GameMode != value) {
288 if (worldSettings.GameMode == GameMode.Adventure) {
289 TakeWorldSnapshot(directoryName, "AdventureRestart");
290 }
291 else {
292 DeleteWorldSnapshot(directoryName, "AdventureRestart");
293 }
294 }
295 }
296
297 public static string GetUnusedWorldDirectoryName() {
298 string text = Storage.CombinePaths(WorldsDirectoryName, "World");
299 for (int i = 0; i < 1000; i++) {
301 string extension = Storage.GetExtension(text);
302 string text2 = $"{arg}{(i > 0 ? i.ToString() : string.Empty)}{extension}";
303 if (!Storage.DirectoryExists(text2)
304 && !Storage.FileExists(text2)) {
305 return text2;
306 }
307 }
308 throw new InvalidOperationException($"Out of filenames for root \"{text}\".");
309 }
310
311 public static void RecursiveEnumerateDirectory(string directoryName,
312 List<string> files,
313 List<string> directories,
314 Func<string, bool> filesFilter) {
315 try {
316 foreach (string item in Storage.ListDirectoryNames(directoryName)) {
317 string text = Storage.CombinePaths(directoryName, item);
318 RecursiveEnumerateDirectory(text, files, directories, filesFilter);
319 directories?.Add(text);
320 }
321 if (files != null) {
322 foreach (string item2 in Storage.ListFileNames(directoryName)) {
323 string text2 = Storage.CombinePaths(directoryName, item2);
324 if (filesFilter == null
325 || filesFilter(text2)) {
326 files.Add(text2);
327 }
328 }
329 }
330 }
331 catch (Exception ex) {
332 Log.Error($"Error enumerating files/directories. Reason: {ex.Message}");
333 }
334 }
335
336 public static XElement GetGameInfoNode(XElement projectNode) {
337 XElement xElement = (from n in projectNode.Element("Subsystems")?.Elements("Values")
338 where XmlUtils.GetAttributeValue(n, "Name", string.Empty) == "GameInfo"
339 select n).FirstOrDefault();
340 if (xElement != null) {
341 return xElement;
342 }
343 throw new InvalidOperationException("GameInfo node not found in project.");
344 }
345
346 public static XElement GetSubsystemNode(XElement projectNode, string subsystemName) => GetSubsystemNode(projectNode, subsystemName, true);
347
351 public static XElement GetSubsystemNode(XElement projectNode, string subsystemName, bool throwOnError) {
352 XElement xElement = (from n in projectNode.Element("Subsystems")?.Elements("Values")
353 where XmlUtils.GetAttributeValue(n, "Name", string.Empty) == subsystemName
354 select n).FirstOrDefault();
355 if (xElement != null) {
356 return xElement;
357 }
358 if (throwOnError) {
359 throw new InvalidOperationException($"{subsystemName} node not found in project.");
360 }
361 return null;
362 }
363
364 public static XElement GetPlayersNode(XElement projectNode) {
365 XElement xElement = (from n in projectNode.Element("Subsystems")?.Elements("Values")
366 where XmlUtils.GetAttributeValue(n, "Name", string.Empty) == "Players"
367 select n).FirstOrDefault();
368 if (xElement != null) {
369 return xElement;
370 }
371 throw new InvalidOperationException("Players node not found in project.");
372 }
373
374 public static XElement GetProjectNode(WorldInfo worldInfo) {
375 string text = Storage.CombinePaths(worldInfo.DirectoryName, "Project.xml");
376 try {
377 if (Storage.FileExists(text)) {
378 using (Stream stream = Storage.OpenFile(text, OpenFileMode.Read)) {
379 XElement xElement = XmlUtils.LoadXmlFromStream(stream, null, true);
380 return xElement;
381 }
382 }
383 return null;
384 }
385 catch (Exception) {
386 return null;
387 }
388 }
389
390 public static void PackWorld(string directoryName, Stream targetStream, Func<string, bool> filter, bool embedExternalContent) {
391 WorldInfo worldInfo = GetWorldInfo(directoryName);
392 if (worldInfo == null) {
393 throw new InvalidOperationException("Directory does not contain a world.");
394 }
395 List<string> list = new();
396 RecursiveEnumerateDirectory(directoryName, list, null, filter);
397 using ZipArchive zipArchive = ZipArchive.Create(targetStream, true);
398 foreach (string item in list) {
399 using Stream source = Storage.OpenFile(item, OpenFileMode.Read);
400 string fileName = Storage.GetFileName(item);
401 fileName = fileName.StartsWith("Region") ? Storage.CombinePaths("Regions", fileName) : item.Replace($"{directoryName}/", "");
402 zipArchive.AddStream(fileName, source);
403 }
404 if (embedExternalContent) {
405 if (!BlocksTexturesManager.IsBuiltIn(worldInfo.WorldSettings.BlocksTextureName)) {
406 try {
407 using Stream source2 = Storage.OpenFile(
408 BlocksTexturesManager.GetFileName(worldInfo.WorldSettings.BlocksTextureName),
409 OpenFileMode.Read
410 );
411 string filenameInZip = Storage.CombinePaths(
412 "EmbeddedContent",
413 $"{Storage.GetFileNameWithoutExtension(worldInfo.WorldSettings.BlocksTextureName)}.scbtex"
414 );
415 zipArchive.AddStream(filenameInZip, source2);
416 }
417 catch (Exception ex) {
418 Log.Warning($"Failed to embed blocks texture \"{worldInfo.WorldSettings.BlocksTextureName}\". Reason: {ex.Message}");
419 }
420 }
421 foreach (PlayerInfo playerInfo in worldInfo.PlayerInfos) {
423 try {
424 using Stream source3 = Storage.OpenFile(
426 OpenFileMode.Read
427 );
428 string filenameInZip2 = Storage.CombinePaths(
429 "EmbeddedContent",
430 $"{Storage.GetFileNameWithoutExtension(playerInfo.CharacterSkinName)}.scskin"
431 );
432 zipArchive.AddStream(filenameInZip2, source3);
433 }
434 catch (Exception ex2) {
435 Log.Warning($"Failed to embed character skin \"{playerInfo.CharacterSkinName}\". Reason: {ex2.Message}");
436 }
437 }
438 }
439 }
440 }
441
442 public static void UnpackWorld(string directoryName, Stream sourceStream, bool importEmbeddedExternalContent) {
443 if (!Storage.DirectoryExists(directoryName)) {
444 throw new InvalidOperationException($"Cannot import world into \"{directoryName}\" because this directory does not exist.");
445 }
446 using ZipArchive zipArchive = ZipArchive.Open(sourceStream, true);
447 foreach (ZipArchiveEntry item in zipArchive.ReadCentralDir()) {
448 if (item.FileSize == 0) {
449 continue;
450 }
451 string text = item.FilenameInZip.Replace('\\', '/');
452 string extension = Storage.GetExtension(text);
453 if (text.StartsWith("EmbeddedContent")) {
454 try {
455 if (importEmbeddedExternalContent) {
456 MemoryStream memoryStream = new();
457 zipArchive.ExtractFile(item, memoryStream);
458 memoryStream.Position = 0L;
461 }
462 }
463 catch (Exception ex) {
464 Log.Warning($"Failed to import embedded content \"{text}\". Reason: {ex.Message}");
465 }
466 }
467 else {
468 string fileName = Storage.GetFileName(text);
469 if (fileName.StartsWith("Region")) {
470 if (!Storage.DirectoryExists(Storage.CombinePaths(directoryName, "Regions"))) {
471 Storage.CreateDirectory(Storage.CombinePaths(directoryName, "Regions"));
472 }
473 fileName = Storage.CombinePaths("Regions", fileName);
474 }
475 else {
476 string directory = Path.GetDirectoryName(Storage.CombinePaths(directoryName, text));
477 if (!Storage.DirectoryExists(directory)) {
478 Storage.CreateDirectory(directory);
479 }
480 fileName = text;
481 }
482 using Stream stream = Storage.OpenFile(Storage.CombinePaths(directoryName, fileName), OpenFileMode.Create);
483 zipArchive.ExtractFile(item, stream);
484 }
485 }
486 }
487
488 public static void DeleteWorldContents(string directoryName, Func<string, bool> filter) {
489 List<string> list = new();
490 List<string> list2 = new();
491 RecursiveEnumerateDirectory(directoryName, list, list2, filter);
492 foreach (string item in list) {
493 Storage.DeleteFile(item);
494 }
495 foreach (string item2 in list2) {
497 }
498 }
499
500 public static string MakeSnapshotFilename(string directoryName, string snapshotName) =>
501 Storage.CombinePaths(directoryName, $"{snapshotName}.snapshot");
502
503 public static bool TestXmlFile(string fileName, string rootNodeName) {
504 try {
505 if (Storage.FileExists(fileName)) {
506 using Stream stream = Storage.OpenFile(fileName, OpenFileMode.Read);
507 XElement xElement = XmlUtils.LoadXmlFromStream(stream, null, false);
508 return xElement != null && xElement.Name == rootNodeName;
509 }
510 return false;
511 }
512 catch (Exception) {
513 return false;
514 }
515 }
516 }
517}
static void Error(object message)
定义 Log.cs:80
static void Warning(object message)
定义 Log.cs:68
static void CreateDirectory(string path)
static bool DirectoryExists(string path)
static IEnumerable< string > ListDirectoryNames(string path)
static IEnumerable< string > ListFileNames(string path)
static Stream OpenFile(string path, OpenFileMode openFileMode)
static string GetExtension(string path)
static DateTime GetFileLastWriteTime(string path)
static bool FileExists(string path)
static string GetDirectoryName(string path)
static string GetFileNameWithoutExtension(string path)
static string GetFileName(string path)
static long GetFileSize(string path)
static void DeleteDirectory(string path)
static void DeleteFile(string path)
static string CombinePaths(params string[] paths)
static void CopyFile(string sourcePath, string destinationPath)
static double RealTime
定义 Time.cs:38
static string GetFileName(string name)
static string GetFileName(string name)
static object Get(Type type, string name)
static GameDatabase GameDatabase
static string MakeFullErrorMessage(Exception e)
static ExternalContentType ExtensionToType(string extension)
static string ImportExternalContentSync(Stream stream, ExternalContentType type, string name)
static void ReadTOCEntry(Stream stream, out int cx, out int cz, out int offset)
static void UpgradeProjectXml(XElement projectNode)
WorldSettings WorldSettings
DateTime LastSaveTime
List< PlayerInfo > PlayerInfos
void Save(ValuesDictionary valuesDictionary, bool liveModifiableParametersOnly)
void Load(ValuesDictionary valuesDictionary)
static List< WorldInfo > m_worldInfos
static void RestoreWorldFromSnapshot(string directoryName, string snapshotName)
static void UpdateWorldsList()
static Action< string > WorldDeleted
static ReadOnlyList< WorldInfo > WorldInfos
static void DeleteWorldContents(string directoryName, Func< string, bool > filter)
static void TakeWorldSnapshot(string directoryName, string snapshotName)
static bool TestXmlFile(string fileName, string rootNodeName)
static XElement GetProjectNode(WorldInfo worldInfo)
static XElement GetSubsystemNode(XElement projectNode, string subsystemName)
static string WorldsDirectoryName
static string MakeSnapshotFilename(string directoryName, string snapshotName)
static void UnpackWorld(string directoryName, Stream sourceStream, bool importEmbeddedExternalContent)
static void MakeQuickWorldBackup(string directoryName)
static XElement GetPlayersNode(XElement projectNode)
static void RepairWorldIfNeeded(string directoryName)
static string GetUnusedWorldDirectoryName()
static string ImportWorld(Stream sourceStream)
static bool SnapshotExists(string directoryName, string snapshotName)
static WorldInfo CreateWorld(WorldSettings worldSettings)
static bool ValidateWorldName(string name)
static XElement GetSubsystemNode(XElement projectNode, string subsystemName, bool throwOnError)
保证和旧引用的兼容性,这里不使用默认参数
static void ExportWorld(string directoryName, Stream targetStream)
static ReadOnlyList< string > NewWorldNames
static ReadOnlyList< string > m_newWorldNames
static void PackWorld(string directoryName, Stream targetStream, Func< string, bool > filter, bool embedExternalContent)
static WorldInfo GetWorldInfo(string directoryName)
static void DeleteWorldSnapshot(string directoryName, string snapshotName)
static void DeleteWorld(string directoryName)
static void ChangeWorld(string directoryName, WorldSettings worldSettings)
static void RecursiveEnumerateDirectory(string directoryName, List< string > files, List< string > directories, Func< string, bool > filesFilter)
static XElement GetGameInfoNode(XElement projectNode)
static ZipArchive Create(Stream stream, bool keepStreamOpen=false)
static ZipArchive Open(Stream stream, bool keepStreamOpen=false)
static string WorldsDirectoryName
static string APIVersionString
void ApplyOverrides(ValuesDictionary overridesValuesDictionary)
static void SetAttributeValue(XElement node, string attributeName, object value)
static object GetAttributeValue(XElement node, string attributeName, Type type)
static XElement LoadXmlFromStream(Stream stream, Encoding encoding, bool throwOnError)
static void SaveXmlToStream(XElement node, Stream stream, Encoding encoding, bool throwOnError)