Skip to content

Commit

Permalink
Read ArkTribe and ArkProfile data (--UseStore) (#9)
Browse files Browse the repository at this point in the history
* 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.

* Parse .arktribe and .arkprofile data from .ark save when using --UseStore.
  • Loading branch information
miragedmuk authored Sep 16, 2023
1 parent 128c688 commit 1b15a5c
Show file tree
Hide file tree
Showing 6 changed files with 408 additions and 28 deletions.
128 changes: 101 additions & 27 deletions SavegameToolkit/ArkSavegame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,15 @@ public class ArkSavegame : GameObjectContainerMixin, IConversionSupport

[JsonProperty(Order = 7)]
public override List<GameObject> Objects { get; } = new List<GameObject>();
public List<GameObject> Tribes { get; } = new List<GameObject>();
public List<GameObject> Profiles { get; } = new List<GameObject>();

public List<Tuple<long, long>> StoredDataOffsets = new List<Tuple<long, long>>();

private ChunkedStore TribeDataStore { get; set; } = new ChunkedStore();
private ChunkedStore PlayerDataStore { get; set; } = new ChunkedStore();


private int hibernationOffset;

private int nameTableOffset;
Expand Down Expand Up @@ -87,6 +93,8 @@ public class ArkSavegame : GameObjectContainerMixin, IConversionSupport
private int hibernationV9Check5;
private int hibernationV9Check6;



#region readBinary

public void ReadBinary(ArkArchive archive, ReadingOptions options)
Expand Down Expand Up @@ -371,36 +379,15 @@ private void readBinaryHibernation(ArkArchive archive, ReadingOptions options)
// it's assumed there are two new int32, making it a total of 6 unknown int32 along with the 4 version8 int32, the first two are -1 and 2, and all of these 6 int32 are repeated once.
if (SaveVersion > 8 && hibernationV8Unknown1 == -1 && hibernationV8Unknown2 == 2)
{
archive.DebugMessage("non-zero unknown V9 fields, expecting duplicated set per 349.10");
hibernationV9_34910Unknown1 = archive.ReadInt();
hibernationV9_34910Unknown2 = archive.ReadInt();
// Move back by those four integers, so we can use ChunkedStore
archive.Position -= sizeof(int) * 4;

TribeDataStore = new ChunkedStore();
PlayerDataStore = new ChunkedStore();

hibernationV9Check1 = archive.ReadInt();
hibernationV9Check2 = archive.ReadInt();
hibernationV9Check3 = archive.ReadInt();
hibernationV9Check4 = archive.ReadInt();
hibernationV9Check5 = archive.ReadInt();
hibernationV9Check6 = archive.ReadInt();

if (!(hibernationV8Unknown1 == hibernationV9Check1
&& hibernationV8Unknown2 == hibernationV9Check2
&& hibernationV8Unknown3 == hibernationV9Check3
&& hibernationV8Unknown4 == hibernationV9Check4
&& hibernationV9_34910Unknown1 == hibernationV9Check5
&& hibernationV9_34910Unknown2 == hibernationV9Check6))
{
if (SaveVersion > 10)
{
//TODO:// more data reads - is it always 112 bytes?
archive.SkipBytes(112);
TribeDataStore.ReadBinary(archive, options);
PlayerDataStore.ReadBinary(archive, options);

}
else
{
throw new NotSupportedException("349.10 workaround for duplicate unknown hibernation bytes failed");
}
}
}

// No hibernate section if we reached the nameTable
Expand Down Expand Up @@ -447,6 +434,93 @@ private void readBinaryHibernation(ArkArchive archive, ReadingOptions options)
}
}

private void readBinaryStoredTribes(ArkArchive archive, ReadingOptions options)
{
if (!options.StoredTribes) return;

Tribes.Clear();

if (TribeDataStore != null && TribeDataStore.IndexChunks.Count > 0)
{
for (int tribeFileIndex = 0; tribeFileIndex < TribeDataStore.IndexChunks.Count; tribeFileIndex++)
{
long storedIndexOffset = StoredDataOffsets[tribeFileIndex].Item1 + TribeDataStore.IndexChunks[tribeFileIndex].ArchiveOffset;
long storedIndexSize = TribeDataStore.IndexChunks[tribeFileIndex].Size;
long storedDataOffset = StoredDataOffsets[tribeFileIndex].Item1 + TribeDataStore.DataChunks[tribeFileIndex].ArchiveOffset;


archive.Position = storedIndexOffset;
long indexLimit = storedIndexSize + storedIndexOffset;

List<long> tribeOffsets = new List<long>();
while (archive.Position < indexLimit)
{
long tribeId = archive.ReadLong();
long tribeOffset = archive.ReadLong();
long tribeSize = archive.ReadLong();

long tribeDataOffset = storedDataOffset + tribeOffset;
tribeOffsets.Add(tribeDataOffset);
}

foreach (var tribeOffset in tribeOffsets)
{
archive.Position = tribeOffset;
ArkStoreTribe storedTribe = new ArkStoreTribe();
storedTribe.ReadBinary(archive, options);

Tribes.AddRange(storedTribe.Objects);
}
}

}
}

private void readBinaryStoredProfiles(ArkArchive archive, ReadingOptions options)
{
if (!options.StoredProfiles) return;

Profiles.Clear();

if (PlayerDataStore != null && PlayerDataStore.IndexChunks.Count > 0)
{
for (int playerFileIndex = 0; playerFileIndex < PlayerDataStore.IndexChunks.Count; playerFileIndex++)
{
long storedIndexOffset = StoredDataOffsets[playerFileIndex].Item1 + PlayerDataStore.IndexChunks[playerFileIndex].ArchiveOffset;
long storedIndexSize = PlayerDataStore.IndexChunks[playerFileIndex].Size;
long storedDataOffset = StoredDataOffsets[playerFileIndex].Item1 + PlayerDataStore.DataChunks[playerFileIndex].ArchiveOffset;


archive.Position = storedIndexOffset;
long indexLimit = storedIndexSize + storedIndexOffset;

List<long> playerOffsets = new List<long>();
while (archive.Position < indexLimit)
{
long playerId = archive.ReadLong();
long playerOffset = archive.ReadLong();
long playerSize = archive.ReadLong();

long playerDataOffset = storedDataOffset + playerOffset;
playerOffsets.Add(playerDataOffset);
}

foreach (var playerOffset in playerOffsets)
{
archive.Position = playerOffset;
ArkStoreProfile storedProfile = new ArkStoreProfile();
storedProfile.ReadBinary(archive, options);

Profiles.AddRange(storedProfile.Objects);
}
}

}


}


private void readBinaryStoredObjects(ArkArchive archive, ReadingOptions options)
{
if (!options.CryopodCreatures) return;
Expand Down
119 changes: 119 additions & 0 deletions SavegameToolkit/ArkStoreProfile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SavegameToolkit.Propertys;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SavegameToolkit
{
internal class ArkStoreProfile : GameObjectContainerMixin, IConversionSupport, IPropertyContainer
{

public int ProfileVersion { get; set; }

public List<IProperty> Properties => profile.Properties;

private GameObject profile;
private long propertiesBlockOffset;

public GameObject Profile
{
get => profile;
set
{
if (profile != null)
{
int oldIndex = Objects.IndexOf(profile);
if (oldIndex > -1)
{
Objects.RemoveAt(oldIndex);
}
}

profile = value;
if (value != null && Objects.IndexOf(value) == -1)
{
Objects.Insert(0, value);
}
}
}

public void ReadBinary(ArkArchive archive, ReadingOptions options)
{

propertiesBlockOffset = archive.Position;
var useNameTable = archive.UseNameTable;
archive.UseNameTable = false;
try
{
ProfileVersion = archive.ReadInt();

if (ProfileVersion != 1)
{
//throw new NotSupportedException("Unknown Profile Version " + ProfileVersion);
}

int profilesCount = archive.ReadInt();

Objects.Clear();
ObjectMap.Clear();
for (int i = 0; i < profilesCount; i++)
{
addObject(new GameObject(archive), options.BuildComponentTree);
}

for (int i = 0; i < profilesCount; i++)
{
GameObject gameObject = Objects[i];
if (gameObject.ClassString == "PrimalPlayerData" || gameObject.ClassString == "PrimalPlayerDataBP_C")
{
profile = gameObject;
}

gameObject.LoadProperties(archive, new GameObject(), propertiesBlockOffset);
}
}
catch
{

}
archive.UseNameTable = useNameTable;
//var tekGrams = Objects.Where(o => o.ClassString.ToLower().Contains("canteen")).ToList();

}


public int CalculateSize()
{
int size = sizeof(int) * 2;

NameSizeCalculator nameSizer = ArkArchive.GetNameSizer(false);

size += Objects.Sum(o => o.Size(nameSizer));

propertiesBlockOffset = size;

size += Objects.Sum(o => o.PropertiesSize(nameSizer));
return size;
}

public void WriteBinary(ArkArchive archive, WritingOptions options)
{
throw new NotImplementedException();
}

public void ReadJson(JToken node, ReadingOptions options)
{
throw new NotImplementedException();
}

public void WriteJson(JsonTextWriter generator, WritingOptions options)
{
throw new NotImplementedException();
}
}

}
82 changes: 82 additions & 0 deletions SavegameToolkit/ArkStoreTribe.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SavegameToolkit.Propertys;

namespace SavegameToolkit
{

public class ArkStoreTribe : GameObjectContainerMixin, IConversionSupport, IPropertyContainer
{

public int TribeVersion { get; set; }

public GameObject Tribe { get; set; }

public List<IProperty> Properties => Tribe.Properties;

private long propertiesBlockOffset;

public void ReadBinary(ArkArchive archive, ReadingOptions options)
{

propertiesBlockOffset = archive.Position;
var useNameTable = archive.UseNameTable;
archive.UseNameTable = false;

TribeVersion = archive.ReadInt();

if (TribeVersion != 1)
{
throw new NotSupportedException("Unknown Tribe Version " + TribeVersion);
}

int tribesCount = archive.ReadInt();

Objects.Clear();
ObjectMap.Clear();
for (int i = 0; i < tribesCount; i++)
{
var newObject = new GameObject(archive);

addObject(newObject, false);
}

for (int i = 0; i < tribesCount; i++)
{
GameObject gameObject = Objects[i];
if (gameObject.ClassString == "PrimalTribeData")
{
Tribe = gameObject;
}

gameObject.LoadProperties(archive, new GameObject(), propertiesBlockOffset);
}

archive.UseNameTable = useNameTable;
}

public void WriteBinary(ArkArchive archive, WritingOptions options)
{
throw new NotImplementedException();
}

public int CalculateSize()
{
throw new NotImplementedException();
}

public void ReadJson(JToken node, ReadingOptions options)
{
throw new NotImplementedException();
}

public void WriteJson(JsonTextWriter generator, WritingOptions options)
{
throw new NotImplementedException();
}
}

}
2 changes: 1 addition & 1 deletion SavegameToolkit/GameObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ private void readBinary(ArkArchive archive) {
archive.HasUnknownData = true;
}

public void LoadProperties(ArkArchive archive, GameObject next, int propertiesBlockOffset) {
public void LoadProperties(ArkArchive archive, GameObject next, long propertiesBlockOffset) {
long offset = propertiesBlockOffset + propertiesOffset;
long nextOffset = propertiesBlockOffset + next?.propertiesOffset ?? archive.Limit;

Expand Down
Loading

0 comments on commit 1b15a5c

Please sign in to comment.