diff --git a/SavegameToolkit/ArkCryoStore.cs b/SavegameToolkit/ArkCryoStore.cs new file mode 100644 index 0000000..f0445d2 --- /dev/null +++ b/SavegameToolkit/ArkCryoStore.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace SavegameToolkit +{ + internal class ArkCryoStore : GameObjectContainerMixin + { + private long propertiesOffset = 0; + + public ArkCryoStore(ArkArchive archive) + { + ReadBinary(archive); + } + + public void ReadBinary(ArkArchive archive) + { + var objectType = archive.ReadString(); //type? + if (objectType.Equals("dino", StringComparison.InvariantCultureIgnoreCase)) + { + var stringPropertyCount = archive.ReadInt(); + while(stringPropertyCount-- > 0) + { + archive.ReadString(); + } + + var floatPropertyCount = archive.ReadInt(); + while(floatPropertyCount-- > 0) + { + archive.ReadFloat(); + } + + var colorNameCount = archive.ReadInt(); //color name count + while (colorNameCount-- > 0) + { + var colorName = archive.ReadString(); + } + + + var cryoStoreUnknown1 = archive.ReadLong(); + + //store properties offset + propertiesOffset = archive.Position; + + //load GameObjects + Objects.Clear(); + + bool useNameTable = archive.UseNameTable; + archive.UseNameTable = false; + + var objectCount = archive.ReadInt(); + while (objectCount-- > 0) + { + Objects.Add(new GameObject(archive)); + } + + archive.UseNameTable = useNameTable; + } + + + } + + public void LoadProperties(ArkArchive archive) + { + bool useNameTable = archive.UseNameTable; + archive.UseNameTable = false; + + var pos = archive.Position; + + for(int i=0; i < Objects.Count;i++) + { + Objects[i].LoadProperties(archive, new GameObject(), (int)propertiesOffset); + } + + archive.Position = pos; + archive.UseNameTable = useNameTable; + } + + } +} diff --git a/SavegameToolkit/ArkSavegame.cs b/SavegameToolkit/ArkSavegame.cs index 16f0c07..f5d05da 100644 --- a/SavegameToolkit/ArkSavegame.cs +++ b/SavegameToolkit/ArkSavegame.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -46,6 +47,8 @@ public class ArkSavegame : GameObjectContainerMixin, IConversionSupport private int hibernationOffset; + private int storedOffset; + private int nameTableOffset; private int propertiesBlockOffset; @@ -101,8 +104,14 @@ public void ReadBinary(ArkArchive archive, ReadingOptions options) readBinaryHibernation(archive, options); } - extractBinaryObjectCryopods(options); - + if (SaveVersion > 10) + { + readBinaryStoredObjects(archive, options); + } + else + { + extractBinaryObjectCryopods(options); + } OldNameList = archive.HasUnknownNames ? archive.NameTable : null; HasUnknownData = archive.HasUnknownData; @@ -112,13 +121,31 @@ private void readBinaryHeader(ArkArchive archive) { SaveVersion = archive.ReadShort(); - if (SaveVersion < 5 || SaveVersion > 9) + if (SaveVersion < 5 || SaveVersion > 12) { throw new NotSupportedException("Found unknown Version " + SaveVersion); } if (SaveVersion > 6) { + + if (SaveVersion > 10) + { + storedOffset = (int)archive.ReadLong(); + var storedDataSize = archive.ReadLong(); + + var v11Unknown1 = archive.ReadLong(); //file size or some other pointer + var v11Unknown2 = archive.ReadLong(); //0 + var v11Unknown3 = archive.ReadLong(); //file size or some other pointer + var v11Unknown4 = archive.ReadLong(); //0 + var v11Unknown5 = archive.ReadLong(); //file size or some other pointer + var v11Unknown6 = archive.ReadLong(); //0 + } + else + { + storedOffset = 0; + } + hibernationOffset = archive.ReadInt(); int shouldBeZero = archive.ReadInt(); if (shouldBeZero != 0) @@ -330,6 +357,7 @@ private void readBinaryHibernation(ArkArchive archive, ReadingOptions options) archive.Position = hibernationOffset; + /* if (SaveVersion > 7) { hibernationV8Unknown1 = archive.ReadInt(); @@ -355,6 +383,16 @@ private void readBinaryHibernation(ArkArchive archive, ReadingOptions options) throw new NotSupportedException("349.10 workaround for duplicate unknown hibernation bytes failed"); } } + */ + + while (archive.Position < nameTableOffset) + { + //regardless of anything before the hibernated class count always seems to follow immediately after this byte in all save formats I have worked on. + if (archive.ReadSByte() == -52) + { + break; + } + } // No hibernate section if we reached the nameTable if (archive.Position == nameTableOffset) @@ -362,8 +400,10 @@ private void readBinaryHibernation(ArkArchive archive, ReadingOptions options) return; } + /* hibernationUnknown1 = archive.ReadInt(); hibernationUnknown2 = archive.ReadInt(); + */ int hibernatedClassesCount = archive.ReadInt(); @@ -399,6 +439,135 @@ private void readBinaryHibernation(ArkArchive archive, ReadingOptions options) } } + private void readBinaryStoredObjects(ArkArchive archive, ReadingOptions options) + { + if (!options.CryopodCreatures) return; + + var inventoryContainers = Objects.Where(x => x.GetPropertyValue("MyInventoryComponent") != null).ToList(); + + + var validStored = Objects + .Where(o => + (o.ClassName.Name.Contains("Cryopod") || o.ClassString.Contains("SoulTrap_") || o.ClassString.Contains("Vivarium_")) + && o.HasAnyProperty("CustomItemDatas") + ) + .ToList(); + + foreach (var o in validStored) + { + var customData = o.Properties.First(p => p.NameString == "CustomItemDatas") as PropertyArray; + var cryoDataOffset = 0; + + if (customData != null) + { + + var dataByteArray = customData?.Value as ArkArrayUnknown; + if (dataByteArray != null) + { + var dataBytes = dataByteArray?.ToArray(); + + if (dataBytes != null && dataBytes.Length > 0) + { + using (MemoryStream ms = new MemoryStream(dataBytes)) + { + using (ArkArchive a = new ArkArchive(ms)) + { + + if (dataByteArray?.Count >= 10) //only care about first 10 bytes, not sure sure what comes after that. + { + var cryoUnknown1 = a.ReadShort(); + var cryoUnknown2 = a.ReadInt(); + cryoDataOffset = a.ReadInt(); + + } + else + { + cryoDataOffset = 0; + } + } + } + } + } + + + var creatureDataOffset = cryoDataOffset + storedOffset; + archive.Position = creatureDataOffset; + + ArkCryoStore cryoStore = new ArkCryoStore(archive); + cryoStore.LoadProperties(archive); + + var dinoComponent = cryoStore?.Objects[0]; + var dinoCharacterStatusComponent = cryoStore?.Objects[1]; + var dinoInventoryComponent = cryoStore?.Objects[2]; + + //re-map and add properties as appropriate + if (dinoComponent != null) + { + dinoComponent.IsCryo = o.ClassString.ToLowerInvariant().Contains ("cryo"); + + // the tribe name is stored in `TamerString`, non-cryoed creatures have the property `TribeName` for that. + if (dinoComponent.GetPropertyValue("TribeName")?.Length == 0 && dinoComponent.GetPropertyValue("TamerString")?.Length > 0) + dinoComponent.Properties.Add(new PropertyString("TribeName", dinoComponent.GetPropertyValue("TamerString"))); + + + //get parent of cryopod owner inventory + var podParentRef = o.GetPropertyValue("OwnerInventory"); + if (podParentRef != null) + { + var podParent = inventoryContainers.FirstOrDefault(x => x.GetPropertyValue("MyInventoryComponent")?.ObjectId == podParentRef.ObjectId); + + //determine if we need to re-team the podded animal + if (podParent != null) + { + dinoComponent.Parent = podParent; + + int obTeam = dinoComponent.GetPropertyValue("TargetingTeam"); + int containerTeam = podParent.GetPropertyValue("TargetingTeam"); + if (obTeam != containerTeam) + { + var propertyIndex = dinoComponent.Properties.FindIndex(i => i.NameString == "TargetingTeam"); + if (propertyIndex != -1) + { + dinoComponent.Properties.RemoveAt(propertyIndex); + } + dinoComponent.Properties.Add(new PropertyInt("TargetingTeam", containerTeam)); + + + if (dinoComponent.HasAnyProperty("TamingTeamID")) + { + dinoComponent.Properties.RemoveAt(dinoComponent.Properties.FindIndex(i => i.NameString == "TamingTeamID")); + dinoComponent.Properties.Add(new PropertyInt("TamingTeamID", containerTeam)); + } + + } + } + } + + if (dinoCharacterStatusComponent != null) + { + addObject(dinoCharacterStatusComponent, true); + + var statusComponentRef = dinoComponent.GetTypedProperty("MyCharacterStatusComponent"); + statusComponentRef.Value.ObjectId = dinoCharacterStatusComponent.Id; + + } + + if (dinoInventoryComponent != null) + { + addObject(dinoInventoryComponent, true); + + var inventoryComponentRef = dinoComponent.GetTypedProperty("MyInventoryComponent"); + inventoryComponentRef.Value.ObjectId = dinoInventoryComponent.Id; + } + + addObject(dinoComponent, true); + + } + } + } + } + + private void extractBinaryObjectCryopods(ReadingOptions options) { if (!options.CryopodCreatures) return;