Skip to content

Commit

Permalink
Support loading/saving older data center format versions.
Browse files Browse the repository at this point in the history
Also moved a couple of sanity checks out of strict mode to prevent unexpected
exception types in some cases.

Closes #19.
  • Loading branch information
alexrp committed Jan 6, 2024
1 parent 74483ee commit 5a82db5
Show file tree
Hide file tree
Showing 33 changed files with 511 additions and 139 deletions.
10 changes: 6 additions & 4 deletions doc/game/data-center-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ struct DataCenterHeader
};
```

`version` is currently `6`.
`version` is currently `6`. A past version `3` also existed. Note that this
field has no distinguishing value for the 32-bit and 64-bit formats, but version
`3` was only 32-bit.

`timestamp` is a Unix timestamp indicating when the file was produced.

Expand All @@ -94,7 +96,7 @@ data centers never include this information.

`revision` indicates the version of the data graph contained within the file. It
is sometimes (but not always) equal to the value sent by the client in the
`C_CHECK_VERSION` packet.
`C_CHECK_VERSION` packet. This field is not present if `version` is `3`.

### File Footer

Expand Down Expand Up @@ -348,7 +350,7 @@ sort should be stable since the order of multiple sibling nodes with the same
name can be significant for the interpretation of the data.

`padding_1` and `padding_2` should be considered undefined. They were added in
the 64-bit data center format.
the 64-bit data center format, and are not present in the 32-bit format.

The root node of the data graph must be located at the address `0:0`. It must
have the name `__root__` and have zero attributes.
Expand Down Expand Up @@ -436,7 +438,7 @@ type codes, the value is written directly and is accessed through the `i`, `b`,
or `f` fields.

`padding_1` should be considered undefined. It was added in the 64-bit data
center format.
center format, and is not present in the 32-bit format.

Some nodes will have a special attribute named `__value__`. In XML terms, this
represents the text of a node. For example, `<Foo>bar</Foo>` would be serialized
Expand Down
11 changes: 9 additions & 2 deletions doc/tools/dc.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ supports the following tasks:
to a fresh data center file usable by the client.
* Format integrity verification of data center files, optionally with strict
compliance checks.
* Support for various iterations of the data center format throughout the game's
history.

In general, novadrop-dc is quite fast: It exploits as many cores as are
available for parallel extraction, validation, and packing. On a modern system,
unpacking an official data center file takes around 20 seconds, while packing
the resulting data sheets takes around 40 seconds.
unpacking an official data center file takes around 15 seconds, while packing
the resulting data sheets takes around 25 seconds.

## novadrop-dc pack

Expand All @@ -37,6 +39,7 @@ The `output` argument specifies the path of the resulting data center file.

| Option | Description |
| - | - |
| `--format <format>` | Specifies the data center format variant (defaults to `V6X64`). |
| `--revision <value>` | Specifies the data tree revision number (defaults to latest known revision). |
| `--compression <level>` | Specifies a compression level (defaults to `Optimal`). |
| `--encryption-key <key>` | Specifies an encryption key (defaults to the latest known key). |
Expand All @@ -58,7 +61,9 @@ specifies the path of the resulting data center file.
| - | - |
| `--decryption-key <key>` | Specifies a decryption key (defaults to the latest known key). |
| `--decryption-iv <iv>` | Specifies a decryption IV (defaults to the latest known IV). |
| `--architecture <architecture>` | Specifies the data center format architecture (defaults to `X64`). |
| `--strict` | Enables strict format compliance checks while reading the input file. |
| `--format <format>` | Specifies the data center format variant (defaults to `V6X64`). |
| `--revision <value>` | Specifies the data tree revision number (defaults to latest known revision). |
| `--compression <level>` | Specifies a compression level (defaults to `Optimal`). |
| `--encryption-key <key>` | Specifies an encryption key (defaults to the latest known key). |
Expand Down Expand Up @@ -108,6 +113,7 @@ specifies the path of the directory to extract data sheets and schemas to.
| - | - |
| `--decryption-key <key>` | Specifies a decryption key (defaults to the latest known key). |
| `--decryption-iv <iv>` | Specifies a decryption IV (defaults to the latest known IV). |
| `--architecture <architecture>` | Specifies the data center format architecture (defaults to `X64`). |
| `--strict` | Enables strict format compliance checks while reading the input file. |

## novadrop-dc validate
Expand Down Expand Up @@ -138,4 +144,5 @@ The `input` argument specifies the input data center file.
| - | - |
| `--decryption-key <key>` | Specifies a decryption key (defaults to the latest known key). |
| `--decryption-iv <iv>` | Specifies a decryption IV (defaults to the latest known IV). |
| `--architecture <architecture>` | Specifies the data center format architecture (defaults to `X64`). |
| `--strict` | Enables strict format compliance checks while reading the input file. |
94 changes: 94 additions & 0 deletions src/common/Buffers/SpanReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
namespace Vezel.Novadrop.Buffers;

internal ref struct SpanReader
{
private ReadOnlySpan<byte> _remaining;

public SpanReader(ReadOnlySpan<byte> span)
{
_remaining = span;
}

public void Advance(int count)
{
_remaining = _remaining[count..];
}

public void Read(scoped Span<byte> buffer)
{
_remaining.CopyTo(buffer);

Advance(buffer.Length);
}

public byte ReadByte()
{
var result = _remaining[0];

Advance(sizeof(byte));

return result;
}

public sbyte ReadSByte()
{
return (sbyte)ReadByte();
}

public ushort ReadUInt16()
{
var result = BinaryPrimitives.ReadUInt16LittleEndian(_remaining);

Advance(sizeof(ushort));

return result;
}

public short ReadInt16()
{
return (short)ReadUInt16();
}

public uint ReadUInt32()
{
var result = BinaryPrimitives.ReadUInt32LittleEndian(_remaining);

Advance(sizeof(uint));

return result;
}

public int ReadInt32()
{
return (int)ReadUInt32();
}

public ulong ReadUInt64()
{
var result = BinaryPrimitives.ReadUInt64LittleEndian(_remaining);

Advance(sizeof(ulong));

return result;
}

public long ReadInt64()
{
return (long)ReadUInt64();
}

public float ReadSingle()
{
return Unsafe.BitCast<uint, float>(ReadUInt32());
}

public double ReadDouble()
{
return Unsafe.BitCast<ulong, double>(ReadUInt64());
}

public char ReadChar()
{
return (char)ReadUInt16();
}
}
86 changes: 86 additions & 0 deletions src/common/Buffers/SpanWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
namespace Vezel.Novadrop.Buffers;

internal ref struct SpanWriter
{
private Span<byte> _remaining;

public SpanWriter(Span<byte> span)
{
_remaining = span;
}

public void Advance(int count)
{
_remaining = _remaining[count..];
}

public void Write(scoped ReadOnlySpan<byte> buffer)
{
buffer.CopyTo(_remaining);

Advance(buffer.Length);
}

public void WriteByte(byte value)
{
_remaining[0] = value;

Advance(sizeof(byte));
}

public void WriteSByte(sbyte value)
{
WriteByte((byte)value);
}

public void WriteUInt16(ushort value)
{
BinaryPrimitives.WriteUInt16LittleEndian(_remaining, value);

Advance(sizeof(ushort));
}

public void WriteInt16(short value)
{
WriteUInt16((ushort)value);
}

public void WriteUInt32(uint value)
{
BinaryPrimitives.WriteUInt32LittleEndian(_remaining, value);

Advance(sizeof(uint));
}

public void WriteInt32(int value)
{
WriteUInt32((uint)value);
}

public void WriteUInt64(ulong value)
{
BinaryPrimitives.WriteUInt64LittleEndian(_remaining, value);

Advance(sizeof(ulong));
}

public void WriteInt64(long value)
{
WriteUInt64((ulong)value);
}

public void WriteSingle(float value)
{
WriteUInt32(Unsafe.BitCast<float, uint>(value));
}

public void WriteDouble(double value)
{
WriteUInt64(Unsafe.BitCast<double, ulong>(value));
}

public void WriteChar(char value)
{
WriteUInt16(value);
}
}
8 changes: 2 additions & 6 deletions src/common/IO/StreamBinaryReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,13 @@ public async ValueTask<long> ReadInt64Async(CancellationToken cancellationToken)
[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))]
public async ValueTask<float> ReadSingleAsync(CancellationToken cancellationToken)
{
var value = await ReadUInt32Async(cancellationToken).ConfigureAwait(false);

return Unsafe.As<uint, float>(ref value);
return Unsafe.BitCast<uint, float>(await ReadUInt32Async(cancellationToken).ConfigureAwait(false));
}

[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))]
public async ValueTask<double> ReadDoubleAsync(CancellationToken cancellationToken)
{
var value = await ReadUInt64Async(cancellationToken).ConfigureAwait(false);

return Unsafe.As<ulong, double>(ref value);
return Unsafe.BitCast<ulong, double>(await ReadUInt64Async(cancellationToken).ConfigureAwait(false));
}

[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))]
Expand Down
4 changes: 2 additions & 2 deletions src/common/IO/StreamBinaryWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,12 @@ public ValueTask WriteInt64Async(long value, CancellationToken cancellationToken

public ValueTask WriteSingleAsync(float value, CancellationToken cancellationToken)
{
return WriteUInt32Async(Unsafe.As<float, uint>(ref value), cancellationToken);
return WriteUInt32Async(Unsafe.BitCast<float, uint>(value), cancellationToken);
}

public ValueTask WriteDoubleAsync(double value, CancellationToken cancellationToken)
{
return WriteUInt64Async(Unsafe.As<double, ulong>(ref value), cancellationToken);
return WriteUInt64Async(Unsafe.BitCast<double, ulong>(value), cancellationToken);
}

[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))]
Expand Down
7 changes: 7 additions & 0 deletions src/formats/Data/DataCenterArchitecture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Vezel.Novadrop.Data;

public enum DataCenterArchitecture
{
X86,
X64,
}
8 changes: 8 additions & 0 deletions src/formats/Data/DataCenterFormat.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Vezel.Novadrop.Data;

public enum DataCenterFormat
{
V3,
V6X86,
V6X64,
}
14 changes: 14 additions & 0 deletions src/formats/Data/DataCenterLoadOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ public sealed class DataCenterLoadOptions

public ReadOnlyMemory<byte> IV { get; private set; } = DataCenter.LatestIV;

public DataCenterArchitecture Architecture { get; private set; } = DataCenterArchitecture.X64;

public bool Strict { get; private set; }

public DataCenterLoaderMode Mode { get; private set; }
Expand All @@ -19,6 +21,7 @@ private DataCenterLoadOptions Clone()
Key = Key,
IV = IV,
Strict = Strict,
Architecture = Architecture,
Mode = Mode,
Mutability = Mutability,
};
Expand Down Expand Up @@ -46,6 +49,17 @@ public DataCenterLoadOptions WithIV(scoped ReadOnlySpan<byte> iv)
return options;
}

public DataCenterLoadOptions WithArchitecture(DataCenterArchitecture architecture)
{
Check.Enum(architecture);

var options = Clone();

options.Architecture = architecture;

return options;
}

public DataCenterLoadOptions WithStrict(bool strict)
{
var options = Clone();
Expand Down
14 changes: 14 additions & 0 deletions src/formats/Data/DataCenterSaveOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ namespace Vezel.Novadrop.Data;

public sealed class DataCenterSaveOptions
{
public DataCenterFormat Format { get; private set; } = DataCenterFormat.V6X64;

public int Revision { get; private set; } = DataCenter.LatestRevision;

public CompressionLevel CompressionLevel { get; private set; }
Expand All @@ -18,9 +20,21 @@ private DataCenterSaveOptions Clone()
CompressionLevel = CompressionLevel,
Key = Key,
IV = IV,
Format = Format,
};
}

public DataCenterSaveOptions WithFormat(DataCenterFormat format)
{
Check.Enum(format);

var options = Clone();

options.Format = format;

return options;
}

public DataCenterSaveOptions WithRevision(int revision)
{
Check.Range(revision >= 0, revision);
Expand Down
Loading

0 comments on commit 5a82db5

Please sign in to comment.