From b7c1a5626a38a74b2e28aae43f4685c26d234752 Mon Sep 17 00:00:00 2001 From: Ian Ashworth Date: Wed, 13 Sep 2023 16:59:31 +0100 Subject: [PATCH] Re-worked read of cryo/trap references to read all data in forward-only before re-parenting and adding to Objects in a parallel loop. GameObject constructor and LoadProperties methods both still use name tables internally which need to be read in single threaded to prevent mis-reads and possible duplication. Added defensive check to ensure redictors array contains the index we're trying to read. Had one that only had a "Settings" redirector for a Soul Trap and no "Dino" redirector. --- SavegameToolkit/ArkSavegame.cs | 181 ++++++++++++---------- SavegameToolkit/Types/StoredItemOffset.cs | 12 ++ 2 files changed, 111 insertions(+), 82 deletions(-) create mode 100644 SavegameToolkit/Types/StoredItemOffset.cs diff --git a/SavegameToolkit/ArkSavegame.cs b/SavegameToolkit/ArkSavegame.cs index b550d93..e3fa81b 100644 --- a/SavegameToolkit/ArkSavegame.cs +++ b/SavegameToolkit/ArkSavegame.cs @@ -460,7 +460,12 @@ private void readBinaryStoredObjects(ArkArchive archive, ReadingOptions options) ) .ToList(); - List storedOffsets = new List(); + //create and initialise dictionary to hold the offset data. + Dictionary> storedItemsWithOffsets = new Dictionary>(); + for (int i = 0; i < StoredDataOffsets.Count; i++) + { + storedItemsWithOffsets.Add(i, new List()); + } validStored.ForEach(x => { @@ -477,122 +482,134 @@ private void readBinaryStoredObjects(ArkArchive archive, ReadingOptions options) { // cryopods use the first redirector, soulballs use the second var redirectorIndex = x.ClassString.Contains("Cryopod") ? 0 : 1; - cryoDataOffset = ((StructCustomItemDataRef)redirectors[redirectorIndex]).Position; - dataFile = ((StructCustomItemDataRef)redirectors[redirectorIndex]).StoreDataIndex; - } + + // ensure redir + if(redirectorIndex < redirectors.Count-1) + { + cryoDataOffset = ((StructCustomItemDataRef)redirectors[redirectorIndex]).Position; + dataFile = ((StructCustomItemDataRef)redirectors[redirectorIndex]).StoreDataIndex; - storedOffsets.Add(new CryoStoreData() { ParentObject = x, Offset = cryoDataOffset, StoreDataIndex = dataFile }); + //store to be read-in forward-only for performance. + storedItemsWithOffsets[dataFile].Add(new StoredItemOffset() { ParentObject = x, ObjectOffset = cryoDataOffset }); + } + } }); validStored = null; - //sort and read bytes into memory between this and next offset - var sortedOffsets = storedOffsets.OrderBy(x => x.StoreDataIndex).ThenBy(x => x.Offset).ToList(); - for (int x = 0; x < sortedOffsets.Count; x++) - { - var storedObject = sortedOffsets[x]; - int storedOffsetIndex = storedObject.StoreDataIndex; - var offsetData = StoredDataOffsets[storedOffsetIndex]; - var storedOffset = offsetData.Item1 + storedObject.Offset; - long nextOffset = offsetData.Item1 + offsetData.Item2; - if (x < sortedOffsets.Count - 1) + + + // parse stored data in offset order, adding parsed objects to list + // to allow the slower re-parenting and addition to the Objects collection + // to be done after the file read. + List> addedCryoObjects = new List>(); + for (int fileIndex = 0; fileIndex < 4; fileIndex++) + { + if (storedItemsWithOffsets[fileIndex].Count > 0) { - var nextItem = sortedOffsets[x + 1]; - if (nextItem.StoreDataIndex == storedObject.StoreDataIndex) + var sortedOffsetItems = storedItemsWithOffsets[fileIndex].OrderBy(o => o.ObjectOffset).ToList(); + + for (int x = 0; x < sortedOffsetItems.Count; x++) { - nextOffset = nextItem.Offset + offsetData.Item1; + var storedObject = sortedOffsetItems[x]; + var offsetData = StoredDataOffsets[fileIndex]; + + var storedOffset = offsetData.Item1 + storedObject.ObjectOffset; + long nextOffset = offsetData.Item1 + offsetData.Item2; + if (x < sortedOffsetItems.Count - 1) + { + int nextItemIndex = x + 1; + var nextItem = sortedOffsetItems[nextItemIndex]; + nextOffset = nextItem.ObjectOffset + offsetData.Item1; + } + + archive.Position = storedOffset; + ArkCryoStore cryoStore = new ArkCryoStore(archive); + if (cryoStore.Objects.Any()) + { + cryoStore.LoadProperties(archive); + + //Store all required GameObjects to correctly read in this cryo creature + addedCryoObjects.Add(new Tuple(storedObject.ParentObject, cryoStore.Objects[0], cryoStore.Objects[1], cryoStore.Objects[2])); + } } } - - archive.Position = storedOffset; - int bytesToRead = (int)(nextOffset - storedOffset); - storedObject.Data = archive.ReadBytes(bytesToRead); } - storedOffsets = new List(); - //load the cryo creature data in parallel to speed up read - Parallel.ForEach(sortedOffsets, o => + if (addedCryoObjects.Count > 0) { - if (o.Data.Length == 0) return; - using (MemoryStream ms = new MemoryStream(o.Data)) + Parallel.ForEach(addedCryoObjects, cryoObjects => { - using (ArkArchive storedArchive = new ArkArchive(ms)) - { - ArkCryoStore cryoStore = new ArkCryoStore(storedArchive); - if (!cryoStore.Objects.Any()) return; - - cryoStore.LoadProperties(storedArchive); - + var dinoStorage = cryoObjects.Item1; + var dinoComponent = cryoObjects.Item2; + var dinoCharacterStatusComponent = cryoObjects.Item3; + var dinoInventoryComponent = cryoObjects.Item3; - 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) return; - //re-map and add properties as appropriate - if (dinoComponent == null) return; + dinoComponent.IsInCryo = true; - dinoComponent.IsInCryo = true; + // 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"))); - // 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 = dinoStorage.GetPropertyValue("OwnerInventory"); + if (podParentRef != null) + { + var podParent = inventoryContainers.FirstOrDefault(x => x.GetPropertyValue("MyInventoryComponent")?.ObjectId == podParentRef.ObjectId); - //get parent of cryopod owner inventory - var podParentRef = o.ParentObject.GetPropertyValue("OwnerInventory"); - if (podParentRef != null) + //determine if we need to re-team the podded animal + if (podParent != null) { - var podParent = inventoryContainers.FirstOrDefault(x => x.GetPropertyValue("MyInventoryComponent")?.ObjectId == podParentRef.ObjectId); + dinoComponent.Parent = podParent; - //determine if we need to re-team the podded animal - if (podParent != null) + int obTeam = dinoComponent.GetPropertyValue("TargetingTeam"); + int containerTeam = podParent.GetPropertyValue("TargetingTeam"); + if (obTeam != containerTeam) { - 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) { - var propertyIndex = dinoComponent.Properties.FindIndex(i => i.NameString == "TargetingTeam"); - if (propertyIndex != -1) - { - dinoComponent.Properties.RemoveAt(propertyIndex); - } - dinoComponent.Properties.Add(new PropertyInt("TargetingTeam", containerTeam)); - + 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 (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, false); + if (dinoCharacterStatusComponent != null) + { + addObject(dinoCharacterStatusComponent, false); - var statusComponentRef = dinoComponent.GetTypedProperty("MyCharacterStatusComponent"); - statusComponentRef.Value.ObjectId = dinoCharacterStatusComponent.Id; + var statusComponentRef = dinoComponent.GetTypedProperty("MyCharacterStatusComponent"); + statusComponentRef.Value.ObjectId = dinoCharacterStatusComponent.Id; - } + } - if (dinoInventoryComponent != null) - { - addObject(dinoInventoryComponent, false); + if (dinoInventoryComponent != null) + { + addObject(dinoInventoryComponent, false); - var inventoryComponentRef = dinoComponent.GetTypedProperty("MyInventoryComponent"); - inventoryComponentRef.Value.ObjectId = dinoInventoryComponent.Id; - } + var inventoryComponentRef = dinoComponent.GetTypedProperty("MyInventoryComponent"); + inventoryComponentRef.Value.ObjectId = dinoInventoryComponent.Id; + } - addObject(dinoComponent, false); + addObject(dinoComponent, false); + + }); + } - } - } - }); } diff --git a/SavegameToolkit/Types/StoredItemOffset.cs b/SavegameToolkit/Types/StoredItemOffset.cs new file mode 100644 index 0000000..25c7490 --- /dev/null +++ b/SavegameToolkit/Types/StoredItemOffset.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SavegameToolkit.Types +{ + internal class StoredItemOffset + { + public GameObject ParentObject { get; internal set; } + public long ObjectOffset { get; internal set; } + } +}