diff --git a/SavegameToolkit/ArkArchive.cs b/SavegameToolkit/ArkArchive.cs
index 667704a..4b41224 100644
--- a/SavegameToolkit/ArkArchive.cs
+++ b/SavegameToolkit/ArkArchive.cs
@@ -39,6 +39,8 @@ public class ArkArchive : IDisposable {
private readonly byte[] smallByteBuffer = new byte[bufferSize];
+ public short SaveVersion { get; internal set; }
+
///
/// Enable or disable the current nameTable
///
@@ -81,6 +83,7 @@ private ArkArchive(ArkArchive toClone, int size) {
mbbReader = new BinaryReader(mbb);
state = toClone.state;
isSlice = true;
+ SaveVersion = toClone.SaveVersion;
}
public ArkArchive Clone() => new ArkArchive(this);
diff --git a/SavegameToolkit/ArkSavegame.cs b/SavegameToolkit/ArkSavegame.cs
index 5307afa..f48f586 100644
--- a/SavegameToolkit/ArkSavegame.cs
+++ b/SavegameToolkit/ArkSavegame.cs
@@ -121,6 +121,8 @@ private void readBinaryHeader(ArkArchive archive)
{
SaveVersion = archive.ReadShort();
+ archive.SaveVersion = SaveVersion;
+
if (SaveVersion < 5 || SaveVersion > 12)
{
throw new NotSupportedException("Found unknown Version " + SaveVersion);
@@ -456,30 +458,14 @@ private void readBinaryStoredObjects(ArkArchive archive, ReadingOptions options)
{
if (!(o.Properties.First(p => p.NameString == "CustomItemDatas") is PropertyArray customData)) continue;
- var cryoDataOffset = 0;
- if (customData.Value is ArkArrayUnknown dataByteArray)
- {
- var dataBytes = dataByteArray.ToArray();
-
- if (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;
- }
- }
- }
- }
+ long cryoDataOffset = 0;
+ if (
+ archive.SaveVersion > 10
+ && customData.Value is ArkArrayStruct redirectors
+ && redirectors.All(x => x is StructCustomItemDataRef)
+ ) {
+ // TODO: probably best to: in cryos, take first entry. in souls, take second.
+ cryoDataOffset = ((StructCustomItemDataRef)redirectors[0]).Position;
}
var creatureDataOffset = cryoDataOffset + storedOffset;
diff --git a/SavegameToolkit/Arrays/ArkArrayStruct.cs b/SavegameToolkit/Arrays/ArkArrayStruct.cs
index 9f48834..64acf4c 100644
--- a/SavegameToolkit/Arrays/ArkArrayStruct.cs
+++ b/SavegameToolkit/Arrays/ArkArrayStruct.cs
@@ -18,11 +18,30 @@ public class ArkArrayStruct : ArkArrayBase {
private static readonly ArkName vector = ArkName.ConstantPlain("Vector");
private static readonly ArkName linearColor = ArkName.ConstantPlain("LinearColor");
+
+ private static readonly ArkName customItemDatas = ArkName.ConstantPlain("CustomItemDatas");
public override void Init(ArkArchive archive, PropertyArray property) {
int size = archive.ReadInt();
ArkName structType = StructRegistry.MapArrayNameToTypeName(property.Name);
+
+ // In versions 11 and above, CustomItemDatas properties seem to be redirected into separate archives. This
+ // is likely due to ARK writing different save file segments in memory first to circumvent internal 2GB
+ // limits on in-memory writes.
+ // The redirector seems to be implemented in a similar way to "plain" structs like Vectors, Quats, Colours.
+ // If the conditions match, let's take a detour here.
+ if (archive.SaveVersion > 10 && structType == null && property.Name == customItemDatas)
+ {
+ for (int n = 0; n < size; n++)
+ {
+ StructCustomItemDataRef cidRef = new StructCustomItemDataRef();
+ cidRef.Init(archive);
+ Add(cidRef);
+ }
+ return;
+ }
+
if (structType == null) {
if (size * 4 + 4 == property.DataSize) {
structType = color;
diff --git a/SavegameToolkit/GameObject.cs b/SavegameToolkit/GameObject.cs
index 9a33dd1..f45975d 100644
--- a/SavegameToolkit/GameObject.cs
+++ b/SavegameToolkit/GameObject.cs
@@ -280,7 +280,7 @@ private void readBinary(ArkArchive archive) {
}
public void LoadProperties(ArkArchive archive, GameObject next, int propertiesBlockOffset) {
- int offset = propertiesBlockOffset + propertiesOffset;
+ long offset = propertiesBlockOffset + propertiesOffset;
long nextOffset = propertiesBlockOffset + next?.propertiesOffset ?? archive.Limit;
archive.Position = offset;
diff --git a/SavegameToolkit/Structs/StructCustomItemDataRef.cs b/SavegameToolkit/Structs/StructCustomItemDataRef.cs
new file mode 100644
index 0000000..d822065
--- /dev/null
+++ b/SavegameToolkit/Structs/StructCustomItemDataRef.cs
@@ -0,0 +1,59 @@
+using System;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using SavegameToolkit.Arrays;
+using SavegameToolkit.Types;
+
+namespace SavegameToolkit.Structs {
+
+ [JsonObject(MemberSerialization.OptIn)]
+ public class StructCustomItemDataRef : StructBase {
+
+ [JsonProperty(Order = 0)]
+ public short Unknown0 { get; private set; }
+ [JsonProperty(Order = 1)]
+ public long Position { get; private set; }
+ [JsonProperty(Order = 2)]
+ public ObjectReference[] ObjectRefs { get; private set; }
+ [JsonProperty(Order = 3)]
+ public ObjectReference[] ClassRefs { get; private set; }
+
+ public override void Init(ArkArchive archive)
+ {
+ // The first unknown field may be two fields - perhaps format version and archive index
+ Unknown0 = archive.ReadShort();
+ Position = archive.ReadLong();
+ ObjectRefs = new ObjectReference[archive.ReadInt()];
+ for (int index = 0; index < ObjectRefs.Length; index++)
+ {
+ ObjectRefs[index] = new ObjectReference(archive, 8);
+ }
+ ClassRefs = new ObjectReference[archive.ReadInt()];
+ for (int index = 0; index < ClassRefs.Length; index++)
+ {
+ ClassRefs[index] = new ObjectReference(archive, 8);
+ }
+ }
+
+ public override void Init(JObject node)
+ {
+ throw new NotImplementedException("JSON import of StructCustomItemDataRef has not been implemented");
+ }
+
+ public override void WriteJson(JsonTextWriter generator, WritingOptions writingOptions)
+ {
+ throw new NotImplementedException("JSON export of StructCustomItemDataRef has not been implemented");
+ }
+
+ public override void WriteBinary(ArkArchive archive)
+ {
+ throw new NotImplementedException("Binary export of StructCustomItemDataRef has not been implemented");
+ }
+
+ public override int Size(NameSizeCalculator nameSizer)
+ {
+ return sizeof(short) + sizeof(long) + sizeof(int) * 2 + ObjectRefs.Length * 8 + ClassRefs.Length * 8;
+ }
+ }
+
+}