Skip to content

Commit

Permalink
Implemented basic support for V11 file format. (#5)
Browse files Browse the repository at this point in the history
* Implemented basic support for V11 file format.
* Determined that one of the "unknown" fields for this new data is actually a string, not an int - to define it a cryo creature is neutered.
* Alex informed me these should be read as longs and not int.
* Re-factored reading of cryo storage into ArkCryoStore object.
* Generic fix to ignore unknown bytes before hibernation entries.
Identified from official saves which use command line switches:
-newsaveformat -usestore
* + Only read data in ArkCryoStore if it's of type "Dino".
+ Reduced check on byte size for CustomItemDatas and only read in first 10 bytes to include offset to cryo store data.
  • Loading branch information
miragedmuk authored Sep 9, 2023
1 parent 3f60040 commit 612cf06
Show file tree
Hide file tree
Showing 2 changed files with 253 additions and 3 deletions.
81 changes: 81 additions & 0 deletions SavegameToolkit/ArkCryoStore.cs
Original file line number Diff line number Diff line change
@@ -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;
}

}
}
175 changes: 172 additions & 3 deletions SavegameToolkit/ArkSavegame.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
Expand Down Expand Up @@ -46,6 +47,8 @@ public class ArkSavegame : GameObjectContainerMixin, IConversionSupport

private int hibernationOffset;

private int storedOffset;

private int nameTableOffset;

private int propertiesBlockOffset;
Expand Down Expand Up @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -330,6 +357,7 @@ private void readBinaryHibernation(ArkArchive archive, ReadingOptions options)

archive.Position = hibernationOffset;

/*
if (SaveVersion > 7)
{
hibernationV8Unknown1 = archive.ReadInt();
Expand All @@ -355,15 +383,27 @@ 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)
{
return;
}

/*
hibernationUnknown1 = archive.ReadInt();
hibernationUnknown2 = archive.ReadInt();
*/

int hibernatedClassesCount = archive.ReadInt();

Expand Down Expand Up @@ -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<ObjectReference>("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<byte>();

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<string>("TribeName")?.Length == 0 && dinoComponent.GetPropertyValue<string>("TamerString")?.Length > 0)
dinoComponent.Properties.Add(new PropertyString("TribeName", dinoComponent.GetPropertyValue<string>("TamerString")));


//get parent of cryopod owner inventory
var podParentRef = o.GetPropertyValue<ObjectReference>("OwnerInventory");
if (podParentRef != null)
{
var podParent = inventoryContainers.FirstOrDefault(x => x.GetPropertyValue<ObjectReference>("MyInventoryComponent")?.ObjectId == podParentRef.ObjectId);

//determine if we need to re-team the podded animal
if (podParent != null)
{
dinoComponent.Parent = podParent;

int obTeam = dinoComponent.GetPropertyValue<int>("TargetingTeam");
int containerTeam = podParent.GetPropertyValue<int>("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<PropertyObject>("MyCharacterStatusComponent");
statusComponentRef.Value.ObjectId = dinoCharacterStatusComponent.Id;

}

if (dinoInventoryComponent != null)
{
addObject(dinoInventoryComponent, true);

var inventoryComponentRef = dinoComponent.GetTypedProperty<PropertyObject>("MyInventoryComponent");
inventoryComponentRef.Value.ObjectId = dinoInventoryComponent.Id;
}

addObject(dinoComponent, true);

}
}
}
}


private void extractBinaryObjectCryopods(ReadingOptions options)
{
if (!options.CryopodCreatures) return;
Expand Down

0 comments on commit 612cf06

Please sign in to comment.