mirror of
https://mirror.ghproxy.com/https://github.com/dexyfex/CodeWalker
synced 2024-11-16 20:17:30 +08:00
769 lines
21 KiB
C#
769 lines
21 KiB
C#
|
|
|
|
/*
|
|
Copyright(c) 2015 Neodymium
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
*/
|
|
|
|
//shamelessly stolen
|
|
|
|
|
|
|
|
using SharpDX;
|
|
using System;
|
|
using System.IO;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
using CodeWalker.Core.Utils;
|
|
using System.Buffers.Binary;
|
|
using System.Buffers;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading.Tasks;
|
|
using System.Threading;
|
|
using SharpDX.Win32;
|
|
using CommunityToolkit.HighPerformance;
|
|
using System.Linq;
|
|
|
|
namespace CodeWalker.GameFiles
|
|
{
|
|
public enum Endianess
|
|
{
|
|
LittleEndian = 0,
|
|
BigEndian = 1,
|
|
}
|
|
|
|
public enum DataType
|
|
{
|
|
Byte = 0,
|
|
Int16 = 1,
|
|
Int32 = 2,
|
|
Int64 = 3,
|
|
Uint16 = 4,
|
|
Uint32 = 5,
|
|
Uint64 = 6,
|
|
Float = 7,
|
|
Double = 8,
|
|
String = 9,
|
|
}
|
|
|
|
[SkipLocalsInit]
|
|
public class DataReader : IDisposable
|
|
{
|
|
private Stream baseStream;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the endianess of the underlying stream.
|
|
/// </summary>
|
|
public readonly Endianess Endianess = Endianess.LittleEndian;
|
|
|
|
/// <summary>
|
|
/// Gets the length of the underlying stream.
|
|
/// </summary>
|
|
public virtual long Length => baseStream.Length;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the position within the underlying stream.
|
|
/// </summary>
|
|
public virtual long Position
|
|
{
|
|
get => baseStream.Position;
|
|
set => baseStream.Position = value;
|
|
}
|
|
|
|
public DataReader(Stream stream)
|
|
{
|
|
if (stream is not null)
|
|
{
|
|
this.baseStream = Stream.Synchronized(stream);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new data reader for the specified stream.
|
|
/// </summary>
|
|
public DataReader(Stream stream, Endianess endianess)
|
|
: this(stream)
|
|
{
|
|
this.Endianess = endianess;
|
|
}
|
|
|
|
public DataReader(byte[] data, Endianess endianess = Endianess.LittleEndian)
|
|
: this(new MemoryStream(data))
|
|
{
|
|
this.Endianess = endianess;
|
|
}
|
|
|
|
public virtual Stream GetStream() => baseStream;
|
|
internal virtual void SetPositionAfterRead(Stream stream)
|
|
{
|
|
return;
|
|
}
|
|
|
|
public virtual void ReadFromStream(Span<byte> buffer, bool ignoreEndianess = false)
|
|
{
|
|
var stream = GetStream();
|
|
|
|
try
|
|
{
|
|
stream.Read(buffer);
|
|
}
|
|
finally
|
|
{
|
|
SetPositionAfterRead(stream);
|
|
}
|
|
|
|
if (Endianess == Endianess.BigEndian && !ignoreEndianess)
|
|
{
|
|
buffer.Reverse();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads data from the underlying stream. This is the only method that directly accesses
|
|
/// the data in the underlying stream.
|
|
/// </summary>
|
|
protected virtual byte[] ReadFromStream(int count, bool ignoreEndianess = false, byte[]? buffer = null)
|
|
{
|
|
var stream = GetStream();
|
|
buffer ??= GC.AllocateUninitializedArray<byte>(count);
|
|
|
|
try
|
|
{
|
|
stream.Read(buffer, 0, count);
|
|
}
|
|
finally
|
|
{
|
|
SetPositionAfterRead(stream);
|
|
}
|
|
|
|
// handle endianess
|
|
if (Endianess == Endianess.BigEndian && !ignoreEndianess)
|
|
{
|
|
Array.Reverse(buffer, 0, count);
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a byte.
|
|
/// </summary>
|
|
public virtual byte ReadByte()
|
|
{
|
|
var stream = GetStream();
|
|
int result;
|
|
try
|
|
{
|
|
result = stream.ReadByte();
|
|
}
|
|
finally
|
|
{
|
|
SetPositionAfterRead(stream);
|
|
}
|
|
|
|
if (result == -1)
|
|
{
|
|
throw new InvalidOperationException("Tried to read from stream beyond end!");
|
|
}
|
|
|
|
return (byte) result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a sequence of bytes.
|
|
/// </summary>
|
|
public byte[] ReadBytes(int count, byte[] buffer = null)
|
|
{
|
|
return ReadFromStream(count, true, buffer);
|
|
}
|
|
|
|
public void ReadBytes(Span<byte> buffer)
|
|
{
|
|
ReadFromStream(buffer, true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a signed 16-bit value.
|
|
/// </summary>
|
|
[SkipLocalsInit]
|
|
public short ReadInt16()
|
|
{
|
|
Span<byte> _buffer = stackalloc byte[sizeof(short)];
|
|
ReadFromStream(_buffer, true);
|
|
|
|
if (Endianess == Endianess.LittleEndian)
|
|
{
|
|
return BinaryPrimitives.ReadInt16LittleEndian(_buffer);
|
|
}
|
|
else
|
|
{
|
|
return BinaryPrimitives.ReadInt16BigEndian(_buffer);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a signed 32-bit value.
|
|
/// </summary>
|
|
[SkipLocalsInit]
|
|
public int ReadInt32()
|
|
{
|
|
Span<byte> _buffer = stackalloc byte[sizeof(int)];
|
|
ReadFromStream(_buffer, true);
|
|
|
|
if (Endianess == Endianess.LittleEndian)
|
|
{
|
|
return BinaryPrimitives.ReadInt32LittleEndian(_buffer);
|
|
}
|
|
else
|
|
{
|
|
return BinaryPrimitives.ReadInt32BigEndian(_buffer);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a signed 64-bit value.
|
|
/// </summary>
|
|
[SkipLocalsInit]
|
|
public long ReadInt64()
|
|
{
|
|
Span<byte> _buffer = stackalloc byte[sizeof(long)];
|
|
ReadFromStream(_buffer, true);
|
|
|
|
if (Endianess == Endianess.LittleEndian)
|
|
{
|
|
return BinaryPrimitives.ReadInt64LittleEndian(_buffer);
|
|
}
|
|
else
|
|
{
|
|
return BinaryPrimitives.ReadInt64BigEndian(_buffer);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads an unsigned 16-bit value.
|
|
/// </summary>
|
|
[SkipLocalsInit]
|
|
public ushort ReadUInt16()
|
|
{
|
|
Span<byte> _buffer = stackalloc byte[sizeof(ushort)];
|
|
ReadFromStream(_buffer, true);
|
|
|
|
if (Endianess == Endianess.LittleEndian)
|
|
{
|
|
return BinaryPrimitives.ReadUInt16LittleEndian(_buffer);
|
|
}
|
|
else
|
|
{
|
|
return BinaryPrimitives.ReadUInt16BigEndian(_buffer);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads an unsigned 32-bit value.
|
|
/// </summary>
|
|
[SkipLocalsInit]
|
|
public uint ReadUInt32()
|
|
{
|
|
Span<byte> _buffer = stackalloc byte[sizeof(uint)];
|
|
ReadFromStream(_buffer, true);
|
|
|
|
if (Endianess == Endianess.LittleEndian)
|
|
{
|
|
return BinaryPrimitives.ReadUInt32LittleEndian(_buffer);
|
|
}
|
|
else
|
|
{
|
|
return BinaryPrimitives.ReadUInt32BigEndian(_buffer);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads an unsigned 64-bit value.
|
|
/// </summary>
|
|
[SkipLocalsInit]
|
|
public ulong ReadUInt64()
|
|
{
|
|
Span<byte> _buffer = stackalloc byte[sizeof(ulong)];
|
|
ReadFromStream(_buffer, true);
|
|
|
|
if (Endianess == Endianess.LittleEndian)
|
|
{
|
|
return BinaryPrimitives.ReadUInt64LittleEndian(_buffer);
|
|
}
|
|
else
|
|
{
|
|
return BinaryPrimitives.ReadUInt64BigEndian(_buffer);
|
|
}
|
|
//return BitConverter.ToUInt64(ReadFromStream(_buffer), 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a single precision floating point value.
|
|
/// </summary>
|
|
[SkipLocalsInit]
|
|
public float ReadSingle()
|
|
{
|
|
Span<byte> _buffer = stackalloc byte[sizeof(float)];
|
|
ReadFromStream(_buffer, true);
|
|
|
|
if (Endianess == Endianess.LittleEndian)
|
|
{
|
|
return BinaryPrimitives.ReadSingleLittleEndian(_buffer);
|
|
}
|
|
else
|
|
{
|
|
return BinaryPrimitives.ReadSingleBigEndian(_buffer);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a double precision floating point value.
|
|
/// </summary>
|
|
[SkipLocalsInit]
|
|
public double ReadDouble()
|
|
{
|
|
Span<byte> _buffer = stackalloc byte[sizeof(double)];
|
|
ReadFromStream(_buffer, true);
|
|
|
|
if (Endianess == Endianess.LittleEndian)
|
|
{
|
|
return BinaryPrimitives.ReadDoubleLittleEndian(_buffer);
|
|
}
|
|
else
|
|
{
|
|
return BinaryPrimitives.ReadDoubleBigEndian(_buffer);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a string.
|
|
/// </summary>
|
|
[SkipLocalsInit]
|
|
unsafe public string ReadStringLength(int length)
|
|
{
|
|
if (length == 0)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
Span<byte> bytes = stackalloc byte[length];
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
bytes[i] = ReadByte();
|
|
}
|
|
|
|
return Encoding.UTF8.GetString(bytes);
|
|
//return Encoding.UTF8.GetString(bytes, Math.Min(charsRead, maxLength));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a string.
|
|
/// </summary>
|
|
[SkipLocalsInit]
|
|
unsafe public string ReadString(int maxLength = 1024)
|
|
{
|
|
Span<byte> bytes = stackalloc byte[Math.Min(maxLength, 1024)];
|
|
var temp = ReadByte();
|
|
var bytesRead = 0;
|
|
var length = Length;
|
|
while (temp != 0 && (length == -1 || Position <= length))
|
|
{
|
|
if (bytesRead < maxLength && bytesRead < 1024)
|
|
{
|
|
bytes[bytesRead] = temp;
|
|
}
|
|
temp = ReadByte();
|
|
bytesRead++;
|
|
}
|
|
|
|
return Encoding.UTF8.GetString(bytes.Slice(0, bytesRead));
|
|
//return Encoding.UTF8.GetString(bytes, Math.Min(charsRead, maxLength));
|
|
}
|
|
|
|
[SkipLocalsInit]
|
|
public Vector3 ReadVector3()
|
|
{
|
|
Vector3 v = new Vector3();
|
|
v.X = ReadSingle();
|
|
v.Y = ReadSingle();
|
|
v.Z = ReadSingle();
|
|
return v;
|
|
}
|
|
|
|
[SkipLocalsInit]
|
|
public Vector4 ReadVector4()
|
|
{
|
|
Vector4 v = new Vector4();
|
|
v.X = ReadSingle();
|
|
v.Y = ReadSingle();
|
|
v.Z = ReadSingle();
|
|
v.W = ReadSingle();
|
|
return v;
|
|
}
|
|
|
|
[SkipLocalsInit]
|
|
public Matrix ReadMatrix()
|
|
{
|
|
Matrix m = new Matrix();
|
|
m.M11 = ReadSingle();
|
|
m.M21 = ReadSingle();
|
|
m.M31 = ReadSingle();
|
|
m.M41 = ReadSingle();
|
|
m.M12 = ReadSingle();
|
|
m.M22 = ReadSingle();
|
|
m.M32 = ReadSingle();
|
|
m.M42 = ReadSingle();
|
|
m.M13 = ReadSingle();
|
|
m.M23 = ReadSingle();
|
|
m.M33 = ReadSingle();
|
|
m.M43 = ReadSingle();
|
|
m.M14 = ReadSingle();
|
|
m.M24 = ReadSingle();
|
|
m.M34 = ReadSingle();
|
|
m.M44 = ReadSingle();
|
|
return m;
|
|
}
|
|
|
|
|
|
|
|
|
|
//TODO: put this somewhere else...
|
|
public static uint SizeOf(DataType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
default:
|
|
case DataType.Byte: return 1;
|
|
case DataType.Int16: return 2;
|
|
case DataType.Int32: return 4;
|
|
case DataType.Int64: return 8;
|
|
case DataType.Uint16: return 2;
|
|
case DataType.Uint32: return 4;
|
|
case DataType.Uint64: return 8;
|
|
case DataType.Float: return 4;
|
|
case DataType.Double: return 8;
|
|
case DataType.String: return 0; //how long is a string..?
|
|
}
|
|
}
|
|
|
|
public virtual void Dispose()
|
|
{
|
|
baseStream?.Dispose();
|
|
}
|
|
}
|
|
|
|
public class DataWriter : IDisposable
|
|
{
|
|
private Stream baseStream;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the endianess of the underlying stream.
|
|
/// </summary>
|
|
public Endianess Endianess
|
|
{
|
|
get;
|
|
init;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the length of the underlying stream.
|
|
/// </summary>
|
|
public virtual long Length
|
|
{
|
|
get
|
|
{
|
|
return baseStream.Length;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the position within the underlying stream.
|
|
/// </summary>
|
|
public virtual long Position
|
|
{
|
|
get
|
|
{
|
|
return baseStream.Position;
|
|
}
|
|
set
|
|
{
|
|
baseStream.Position = value;
|
|
}
|
|
}
|
|
|
|
internal virtual Stream GetStream()
|
|
{
|
|
return baseStream;
|
|
}
|
|
|
|
internal virtual void SetPositionAfterWrite(Stream stream)
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// Initializes a new data writer for the specified stream.
|
|
/// </summary>
|
|
public DataWriter(Stream stream, Endianess endianess = Endianess.LittleEndian)
|
|
{
|
|
if (stream is not null)
|
|
{
|
|
this.baseStream = Stream.Synchronized(stream);
|
|
}
|
|
this.Endianess = endianess;
|
|
}
|
|
|
|
protected bool ShouldReverse(bool ignoreEndianness) => !ignoreEndianness && Endianess == Endianess.BigEndian;
|
|
|
|
/// <summary>
|
|
/// Writes data to the underlying stream. This is the only method that directly accesses
|
|
/// the data in the underlying stream.
|
|
/// </summary>
|
|
protected virtual void WriteToStream(byte[] value, bool ignoreEndianess = false, int count = -1, int offset = 0)
|
|
{
|
|
var stream = GetStream();
|
|
if (count == -1)
|
|
{
|
|
count = value.Length;
|
|
}
|
|
if (ShouldReverse(ignoreEndianess))
|
|
{
|
|
var buffer = ArrayPool<byte>.Shared.Rent(count);
|
|
try
|
|
{
|
|
Array.Copy(value, offset, buffer, 0, count);
|
|
Array.Reverse(buffer, 0, count);
|
|
stream.Write(buffer, 0, count);
|
|
}
|
|
finally
|
|
{
|
|
ArrayPool<byte>.Shared.Return(buffer);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
stream.Write(value, offset, count);
|
|
}
|
|
SetPositionAfterWrite(stream);
|
|
}
|
|
|
|
protected virtual void WriteToStream(ReadOnlySpan<byte> value, bool ignoreEndianess = false)
|
|
{
|
|
if (!ShouldReverse(ignoreEndianess))
|
|
{
|
|
var stream = GetStream();
|
|
stream.Write(value);
|
|
SetPositionAfterWrite(stream);
|
|
return;
|
|
}
|
|
|
|
byte[] sharedBuffer = ArrayPool<byte>.Shared.Rent(value.Length);
|
|
try
|
|
{
|
|
value.CopyTo(sharedBuffer);
|
|
Array.Reverse(sharedBuffer, 0, value.Length);
|
|
var stream = GetStream();
|
|
|
|
stream.Write(sharedBuffer.AsSpan());
|
|
|
|
SetPositionAfterWrite(stream);
|
|
}
|
|
finally
|
|
{
|
|
ArrayPool<byte>.Shared.Return(sharedBuffer);
|
|
}
|
|
}
|
|
|
|
protected virtual async ValueTask WriteToStreamAsync(ReadOnlyMemory<byte> buffer, bool ignoreEndianess = false)
|
|
{
|
|
if (!ShouldReverse(ignoreEndianess))
|
|
{
|
|
var stream = GetStream();
|
|
await stream.WriteAsync(buffer);
|
|
SetPositionAfterWrite(stream);
|
|
return;
|
|
}
|
|
|
|
var arr = ArrayPool<byte>.Shared.Rent(buffer.Length);
|
|
try
|
|
{
|
|
buffer.CopyTo(arr);
|
|
Array.Reverse(arr, 0, buffer.Length);
|
|
|
|
var stream = GetStream();
|
|
|
|
await stream.WriteAsync(arr.AsMemory(0, buffer.Length));
|
|
|
|
SetPositionAfterWrite(stream);
|
|
}
|
|
finally
|
|
{
|
|
ArrayPool<byte>.Shared.Return(arr);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a byte.
|
|
/// </summary>
|
|
public void Write(byte value)
|
|
{
|
|
WriteToStream(new byte[] { value });
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a sequence of bytes.
|
|
/// </summary>
|
|
public void Write(byte[] value)
|
|
{
|
|
WriteToStream(value, true);
|
|
}
|
|
|
|
public void Write(ReadOnlySpan<byte> value)
|
|
{
|
|
WriteToStream(value, true);
|
|
}
|
|
|
|
public ValueTask WriteAsync(Memory<byte> value)
|
|
{
|
|
return WriteToStreamAsync(value, true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a signed 16-bit value.
|
|
/// </summary>
|
|
public void Write(short value)
|
|
{
|
|
WriteToStream(BitConverter.GetBytes(value));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a signed 32-bit value.
|
|
/// </summary>
|
|
public void Write(int value)
|
|
{
|
|
WriteToStream(BitConverter.GetBytes(value));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a signed 64-bit value.
|
|
/// </summary>
|
|
public void Write(long value)
|
|
{
|
|
WriteToStream(BitConverter.GetBytes(value));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes an unsigned 16-bit value.
|
|
/// </summary>
|
|
public void Write(ushort value)
|
|
{
|
|
WriteToStream(BitConverter.GetBytes(value));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes an unsigned 32-bit value.
|
|
/// </summary>
|
|
public void Write(uint value)
|
|
{
|
|
WriteToStream(BitConverter.GetBytes(value));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes an unsigned 64-bit value.
|
|
/// </summary>
|
|
public void Write(ulong value)
|
|
{
|
|
WriteToStream(BitConverter.GetBytes(value));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a single precision floating point value.
|
|
/// </summary>
|
|
public void Write(float value)
|
|
{
|
|
WriteToStream(BitConverter.GetBytes(value));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a double precision floating point value.
|
|
/// </summary>
|
|
public void Write(double value)
|
|
{
|
|
WriteToStream(BitConverter.GetBytes(value));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a string.
|
|
/// </summary>
|
|
public void Write(string value)
|
|
{
|
|
foreach (var c in value)
|
|
Write((byte)c);
|
|
Write((byte)0);
|
|
}
|
|
|
|
|
|
|
|
public void Write(Vector3 value)
|
|
{
|
|
Write(value.X);
|
|
Write(value.Y);
|
|
Write(value.Z);
|
|
}
|
|
public void Write(Vector4 value)
|
|
{
|
|
Write(value.X);
|
|
Write(value.Y);
|
|
Write(value.Z);
|
|
Write(value.W);
|
|
}
|
|
|
|
public void Write(Matrix value)
|
|
{
|
|
Write(value.M11);
|
|
Write(value.M21);
|
|
Write(value.M31);
|
|
Write(value.M41);
|
|
Write(value.M12);
|
|
Write(value.M22);
|
|
Write(value.M32);
|
|
Write(value.M42);
|
|
Write(value.M13);
|
|
Write(value.M23);
|
|
Write(value.M33);
|
|
Write(value.M43);
|
|
Write(value.M14);
|
|
Write(value.M24);
|
|
Write(value.M34);
|
|
Write(value.M44);
|
|
}
|
|
|
|
public virtual void Dispose()
|
|
{
|
|
baseStream?.Dispose();
|
|
}
|
|
|
|
public virtual void Close()
|
|
{
|
|
baseStream?.Close();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|