using System.IO;
namespace NecromancerTome
{
///
/// Raw byte-level half of the Spatial Bracelet's vault persistence. Lives in this satellite
/// assembly for exactly the reason PyramidWardWriteHelper.cs documents: PooledBinaryWriter's
/// Write overload set cannot be resolved from the main project at all (CS7069), so anything
/// that actually touches a PooledBinaryWriter/PooledBinaryReader has to be compiled here,
/// against the game's own mscorlib.
///
/// The split is deliberately drawn so that ONLY primitives cross it: this file knows about
/// byte arrays and stream positions, nothing else. Bag/ItemStack serialization stays in the
/// main project, where `Bag.Write(BinaryWriter)` against netstandard's own BinaryWriter
/// already compiles fine (proven - that is how the vault blob is built). Keeping Bag out of
/// here also keeps UnityEngine out of here, which this project's reference setup (NoStdLib +
/// the game's mscorlib, no UnityEngine at all) cannot tolerate.
///
/// BLOB LAYOUT, appended after everything vanilla PlayerDataFile.Write produces:
///
/// int64 Magic "NECROVLT"
/// int32 payloadLength
/// byte[] payload (opaque here; the main project builds and parses it)
///
/// The magic plus the explicit length is what makes this safe to append to somebody else's
/// format. On read we remember the stream position first: if the magic is not there (an old
/// save written before this feature, or a player-data packet from a party that does not have
/// the mod) the position is put back exactly where it was and the caller is told "no vault" -
/// so whatever the game reads next still reads the right bytes. That matters concretely:
/// PlayerDataFile.ReadNetwork calls Read and then goes on to read PlayerMetaInfo from the
/// same stream, and PlayerDataFile.Load treats ANY exception out of Read as "file is broken,
/// roll back to the .bak". Neither may be disturbed, so nothing here throws.
///
public static class SpatialVaultBlobIO
{
/// ASCII "NECROVLT" as one int64 - distinctive enough that stray bytes will not
/// be mistaken for our block.
public const long Magic = 0x4E4543524F564C54L;
/// Magic (8) + length (4).
public const int HeaderSize = 12;
public static void Write(PooledBinaryWriter _bw, byte[] _payload)
{
if (_bw == null || _payload == null)
{
return;
}
_bw.Write(Magic);
_bw.Write(_payload.Length);
_bw.Write(_payload);
}
/// Returns the payload, or null when this stream carries no vault block. Never
/// throws, and never leaves the stream anywhere the caller did not expect: either just
/// past our whole block, or exactly back where it started.
public static byte[] TryRead(PooledBinaryReader _br)
{
if (_br == null)
{
return null;
}
Stream stream = _br.BaseStream;
if (stream == null || !stream.CanSeek)
{
return null;
}
long startPosition = stream.Position;
try
{
if (stream.Length - startPosition < HeaderSize)
{
return null;
}
if (_br.ReadInt64() != Magic)
{
stream.Position = startPosition;
return null;
}
int length = _br.ReadInt32();
if (length < 0 || stream.Length - stream.Position < length)
{
stream.Position = startPosition;
return null;
}
return _br.ReadBytes(length);
}
catch
{
stream.Position = startPosition;
return null;
}
}
}
}