Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[UNTESTED] Fix integer overflow when reading late object properties in big files. Better V11 CustomItemDatas redirection support. #6

Merged
merged 3 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions SavegameToolkit/ArkArchive.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public class ArkArchive : IDisposable {

private readonly byte[] smallByteBuffer = new byte[bufferSize];

public short SaveVersion { get; internal set; }

/// <summary>
/// Enable or disable the current nameTable
/// </summary>
Expand Down Expand Up @@ -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);
Expand Down
34 changes: 10 additions & 24 deletions SavegameToolkit/ArkSavegame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<byte>();

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;
Expand Down
19 changes: 19 additions & 0 deletions SavegameToolkit/Arrays/ArkArrayStruct.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,30 @@ public class ArkArrayStruct : ArkArrayBase<IStruct> {
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;
Expand Down
2 changes: 1 addition & 1 deletion SavegameToolkit/GameObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
59 changes: 59 additions & 0 deletions SavegameToolkit/Structs/StructCustomItemDataRef.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}

}