mirror of
https://github.com/blueskythlikesclouds/SonicAudioTools.git
synced 2024-11-24 15:10:11 +01:00
703 lines
18 KiB
C#
703 lines
18 KiB
C#
using SonicAudioLib.Collections;
|
|
using SonicAudioLib.IO;
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace SonicAudioLib.CriMw
|
|
{
|
|
public class CriTableReader : IDisposable
|
|
{
|
|
private OrderedDictionary<string, CriTableField> fields;
|
|
private Stream source;
|
|
private CriTableHeader header;
|
|
private int rowIndex = -1;
|
|
private uint headerPosition;
|
|
private bool leaveOpen;
|
|
|
|
public object this[int fieldIndex]
|
|
{
|
|
get
|
|
{
|
|
return GetValue(fieldIndex);
|
|
}
|
|
}
|
|
|
|
public object this[string fieldName]
|
|
{
|
|
get
|
|
{
|
|
return GetValue(fieldName);
|
|
}
|
|
}
|
|
|
|
public ushort NumberOfFields
|
|
{
|
|
get
|
|
{
|
|
return header.NumberOfFields;
|
|
}
|
|
}
|
|
|
|
public uint NumberOfRows
|
|
{
|
|
get
|
|
{
|
|
return header.NumberOfRows;
|
|
}
|
|
}
|
|
|
|
public string TableName
|
|
{
|
|
get
|
|
{
|
|
return header.TableName;
|
|
}
|
|
}
|
|
|
|
public int CurrentRow
|
|
{
|
|
get
|
|
{
|
|
return rowIndex;
|
|
}
|
|
}
|
|
|
|
public Stream SourceStream
|
|
{
|
|
get
|
|
{
|
|
return source;
|
|
}
|
|
}
|
|
|
|
private void ReadTable()
|
|
{
|
|
headerPosition = (uint)source.Position;
|
|
|
|
if (EndianStream.ReadCString(source, 4) != CriTableHeader.Signature)
|
|
{
|
|
throw new Exception("No @UTF signature found.");
|
|
}
|
|
|
|
header.Length = ReadUInt32() + 0x8;
|
|
header.FirstBoolean = ReadBoolean();
|
|
header.SecondBoolean = ReadBoolean();
|
|
header.RowsPosition = (ushort)(ReadUInt16() + 0x8);
|
|
header.StringPoolPosition = ReadUInt32() + 0x8;
|
|
header.DataPoolPosition = ReadUInt32() + 0x8;
|
|
header.TableName = ReadString();
|
|
header.NumberOfFields = ReadUInt16();
|
|
header.RowLength = ReadUInt16();
|
|
header.NumberOfRows = ReadUInt32();
|
|
|
|
if (header.FirstBoolean)
|
|
{
|
|
throw new Exception($"Invalid boolean ({header.FirstBoolean}. Please report the error with the file.");
|
|
}
|
|
|
|
for (ushort i = 0; i < header.NumberOfFields; i++)
|
|
{
|
|
CriTableField field = new CriTableField();
|
|
|
|
field.Flag = (CriFieldFlag)ReadByte();
|
|
|
|
if (field.Flag.HasFlag(CriFieldFlag.Name))
|
|
{
|
|
field.Name = ReadString();
|
|
}
|
|
|
|
if (field.Flag.HasFlag(CriFieldFlag.DefaultValue))
|
|
{
|
|
if (field.Flag.HasFlag(CriFieldFlag.Data))
|
|
{
|
|
uint vldPosition;
|
|
uint vldLength;
|
|
|
|
ReadData(out vldPosition, out vldLength);
|
|
|
|
field.Position = vldPosition;
|
|
field.Length = vldLength;
|
|
}
|
|
|
|
else
|
|
{
|
|
field.Value = ReadValue(field.Flag);
|
|
}
|
|
}
|
|
|
|
// Not even per row, and not even constant value? Then there's no storage.
|
|
else if (!field.Flag.HasFlag(CriFieldFlag.RowStorage) && !field.Flag.HasFlag(CriFieldFlag.DefaultValue))
|
|
{
|
|
if (field.Flag.HasFlag(CriFieldFlag.Data))
|
|
{
|
|
field.Position = 0;
|
|
field.Length = 0;
|
|
}
|
|
|
|
else
|
|
{
|
|
field.Value = CriField.NullValues[(byte)field.Flag & 0x0F];
|
|
}
|
|
}
|
|
|
|
fields.Add(field.Name, field);
|
|
}
|
|
}
|
|
|
|
public string GetFieldName(int fieldIndex)
|
|
{
|
|
return fields[fieldIndex].Name;
|
|
}
|
|
|
|
public Type GetFieldType(int fieldIndex)
|
|
{
|
|
return CriField.FieldTypes[(byte)fields[fieldIndex].Flag & 0x0F];
|
|
}
|
|
|
|
public Type GetFieldType(string fieldName)
|
|
{
|
|
return CriField.FieldTypes[(byte)fields[fieldName].Flag & 0x0F];
|
|
}
|
|
|
|
public object GetFieldValue(int fieldIndex)
|
|
{
|
|
return fields[fieldIndex].Value;
|
|
}
|
|
|
|
public byte GetFieldFlag(string fieldName)
|
|
{
|
|
return (byte)fields[fieldName].Flag;
|
|
}
|
|
|
|
public byte GetFieldFlag(int fieldIndex)
|
|
{
|
|
return (byte)fields[fieldIndex].Flag;
|
|
}
|
|
|
|
public object GetFieldValue(string fieldName)
|
|
{
|
|
return fields[fieldName].Value;
|
|
}
|
|
|
|
public CriField GetField(int fieldIndex)
|
|
{
|
|
return new CriField(GetFieldName(fieldIndex), GetFieldType(fieldIndex), GetFieldValue(fieldIndex));
|
|
}
|
|
|
|
public CriField GetField(string fieldName)
|
|
{
|
|
return new CriField(fieldName, GetFieldType(fieldName), GetFieldValue(fieldName));
|
|
}
|
|
|
|
private void GoToValue(int fieldIndex)
|
|
{
|
|
long position = headerPosition + header.RowsPosition + (header.RowLength * rowIndex);
|
|
|
|
for (int i = 0; i < fieldIndex; i++)
|
|
{
|
|
if (!fields[i].Flag.HasFlag(CriFieldFlag.RowStorage))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
switch (fields[i].Flag & CriFieldFlag.TypeMask)
|
|
{
|
|
case CriFieldFlag.Byte:
|
|
case CriFieldFlag.SByte:
|
|
position += 1;
|
|
break;
|
|
case CriFieldFlag.Int16:
|
|
case CriFieldFlag.UInt16:
|
|
position += 2;
|
|
break;
|
|
case CriFieldFlag.Int32:
|
|
case CriFieldFlag.UInt32:
|
|
case CriFieldFlag.Float:
|
|
case CriFieldFlag.String:
|
|
position += 4;
|
|
break;
|
|
case CriFieldFlag.Int64:
|
|
case CriFieldFlag.UInt64:
|
|
case CriFieldFlag.Double:
|
|
case CriFieldFlag.Data:
|
|
position += 8;
|
|
break;
|
|
}
|
|
}
|
|
|
|
source.Position = position;
|
|
}
|
|
|
|
public bool Read()
|
|
{
|
|
if (rowIndex + 1 >= header.NumberOfRows)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
rowIndex++;
|
|
return true;
|
|
}
|
|
|
|
public bool MoveToRow(int rowIndex)
|
|
{
|
|
if (rowIndex >= header.NumberOfRows)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
this.rowIndex = rowIndex;
|
|
return true;
|
|
}
|
|
|
|
public object[] GetValueArray()
|
|
{
|
|
object[] values = new object[header.NumberOfFields];
|
|
|
|
for (int i = 0; i < header.NumberOfFields; i++)
|
|
{
|
|
if (fields[i].Flag.HasFlag(CriFieldFlag.Data))
|
|
{
|
|
values[i] = GetData(i);
|
|
}
|
|
|
|
else
|
|
{
|
|
values[i] = GetValue(i);
|
|
}
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
public object GetValue(int fieldIndex)
|
|
{
|
|
if (fieldIndex < 0 || fieldIndex >= fields.Count)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (!fields[fieldIndex].Flag.HasFlag(CriFieldFlag.RowStorage))
|
|
{
|
|
if (fields[fieldIndex].Flag.HasFlag(CriFieldFlag.Data))
|
|
{
|
|
return new Substream(source, 0, 0);
|
|
}
|
|
|
|
return fields[fieldIndex].Value;
|
|
}
|
|
|
|
GoToValue(fieldIndex);
|
|
return ReadValue(fields[fieldIndex].Flag);
|
|
}
|
|
|
|
public object GetValue(string fieldName)
|
|
{
|
|
return GetValue(fields.IndexOf(fieldName));
|
|
}
|
|
|
|
public T GetValue<T>(int fieldIndex)
|
|
{
|
|
return (T)GetValue(fieldIndex);
|
|
}
|
|
|
|
public T GetValue<T>(string fieldName)
|
|
{
|
|
return (T)GetValue(fieldName);
|
|
}
|
|
|
|
public byte GetByte(int fieldIndex)
|
|
{
|
|
return (byte)GetValue(fieldIndex);
|
|
}
|
|
|
|
public byte GetByte(string fieldName)
|
|
{
|
|
return (byte)GetValue(fieldName);
|
|
}
|
|
|
|
public sbyte GetSByte(int fieldIndex)
|
|
{
|
|
return (sbyte)GetValue(fieldIndex);
|
|
}
|
|
|
|
public sbyte GetSByte(string fieldName)
|
|
{
|
|
return (sbyte)GetValue(fieldName);
|
|
}
|
|
|
|
public ushort GetUInt16(int fieldIndex)
|
|
{
|
|
return (ushort)GetValue(fieldIndex);
|
|
}
|
|
|
|
public ushort GetUInt16(string fieldName)
|
|
{
|
|
return (ushort)GetValue(fieldName);
|
|
}
|
|
|
|
public short GetInt16(int fieldIndex)
|
|
{
|
|
return (short)GetValue(fieldIndex);
|
|
}
|
|
|
|
public short GetInt16(string fieldName)
|
|
{
|
|
return (short)GetValue(fieldName);
|
|
}
|
|
|
|
public uint GetUInt32(int fieldIndex)
|
|
{
|
|
return (uint)GetValue(fieldIndex);
|
|
}
|
|
|
|
public uint GetUInt32(string fieldName)
|
|
{
|
|
return (uint)GetValue(fieldName);
|
|
}
|
|
|
|
public int GetInt32(int fieldIndex)
|
|
{
|
|
return (int)GetValue(fieldIndex);
|
|
}
|
|
|
|
public int GetInt32(string fieldName)
|
|
{
|
|
return (int)GetValue(fieldName);
|
|
}
|
|
|
|
public ulong GetUInt64(int fieldIndex)
|
|
{
|
|
return (ulong)GetValue(fieldIndex);
|
|
}
|
|
|
|
public ulong GetUInt64(string fieldName)
|
|
{
|
|
return (ulong)GetValue(fieldName);
|
|
}
|
|
|
|
public long GetInt64(int fieldIndex)
|
|
{
|
|
return (long)GetValue(fieldIndex);
|
|
}
|
|
|
|
public long GetInt64(string fieldName)
|
|
{
|
|
return (long)GetValue(fieldName);
|
|
}
|
|
|
|
public float GetFloat(int fieldIndex)
|
|
{
|
|
return (float)GetValue(fieldIndex);
|
|
}
|
|
|
|
public float GetFloat(string fieldName)
|
|
{
|
|
return (float)GetValue(fieldName);
|
|
}
|
|
|
|
public double GetDouble(int fieldIndex)
|
|
{
|
|
return (double)GetValue(fieldIndex);
|
|
}
|
|
|
|
public double GetDouble(string fieldName)
|
|
{
|
|
return (double)GetValue(fieldName);
|
|
}
|
|
|
|
public string GetString(int fieldIndex)
|
|
{
|
|
return (string)GetValue(fieldIndex);
|
|
}
|
|
|
|
public string GetString(string fieldName)
|
|
{
|
|
return (string)GetValue(fieldName);
|
|
}
|
|
|
|
public Substream GetSubstream(int fieldIndex)
|
|
{
|
|
return (Substream)GetValue(fieldIndex);
|
|
}
|
|
|
|
public Substream GetSubstream(string fieldName)
|
|
{
|
|
return (Substream)GetValue(fieldName);
|
|
}
|
|
|
|
public byte[] GetData(int fieldIndex)
|
|
{
|
|
return GetSubstream(fieldIndex).ToArray();
|
|
}
|
|
|
|
public byte[] GetData(string fieldName)
|
|
{
|
|
return GetData(fields.IndexOf(fieldName));
|
|
}
|
|
|
|
public uint GetLength(int fieldIndex)
|
|
{
|
|
if (fieldIndex < 0 || fieldIndex >= fields.Count)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (!fields[fieldIndex].Flag.HasFlag(CriFieldFlag.RowStorage))
|
|
{
|
|
return fields[fieldIndex].Length;
|
|
}
|
|
|
|
uint vldPosition;
|
|
uint vldLength;
|
|
|
|
GoToValue(fieldIndex);
|
|
ReadData(out vldPosition, out vldLength);
|
|
return vldLength;
|
|
}
|
|
|
|
public uint GetLength(string fieldName)
|
|
{
|
|
return GetLength(fields.IndexOf(fieldName));
|
|
}
|
|
|
|
public uint GetPosition(int fieldIndex)
|
|
{
|
|
if (fieldIndex < 0 || fieldIndex >= fields.Count)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (!fields[fieldIndex].Flag.HasFlag(CriFieldFlag.RowStorage))
|
|
{
|
|
return fields[fieldIndex].Position;
|
|
}
|
|
|
|
uint vldPosition;
|
|
uint vldLength;
|
|
|
|
GoToValue(fieldIndex);
|
|
ReadData(out vldPosition, out vldLength);
|
|
return (uint)(headerPosition + header.DataPoolPosition + vldPosition);
|
|
}
|
|
|
|
public uint GetPosition(string fieldName)
|
|
{
|
|
return GetPosition(fields.IndexOf(fieldName));
|
|
}
|
|
|
|
public bool GetBoolean(int fieldIndex)
|
|
{
|
|
return (byte)GetValue(fieldIndex) > 0;
|
|
}
|
|
|
|
public bool GetBoolean(string fieldName)
|
|
{
|
|
return (byte)GetValue(fieldName) > 0;
|
|
}
|
|
|
|
public Guid GetGuid(int fieldIndex)
|
|
{
|
|
return (Guid)GetValue(fieldIndex);
|
|
}
|
|
|
|
public Guid GetGuid(string fieldName)
|
|
{
|
|
return (Guid)GetValue(fieldName);
|
|
}
|
|
|
|
private byte[] ReadBytes(int length)
|
|
{
|
|
byte[] buff = new byte[length];
|
|
source.Read(buff, 0, length);
|
|
return buff;
|
|
}
|
|
|
|
private byte ReadByte()
|
|
{
|
|
return EndianStream.ReadByte(source);
|
|
}
|
|
|
|
private bool ReadBoolean()
|
|
{
|
|
return EndianStream.ReadBoolean(source);
|
|
}
|
|
|
|
private sbyte ReadSByte()
|
|
{
|
|
return EndianStream.ReadSByte(source);
|
|
}
|
|
|
|
private ushort ReadUInt16()
|
|
{
|
|
return EndianStream.ReadUInt16BE(source);
|
|
}
|
|
|
|
private short ReadInt16()
|
|
{
|
|
return EndianStream.ReadInt16BE(source);
|
|
}
|
|
|
|
private uint ReadUInt32()
|
|
{
|
|
return EndianStream.ReadUInt32BE(source);
|
|
}
|
|
|
|
private int ReadInt32()
|
|
{
|
|
return EndianStream.ReadInt32BE(source);
|
|
}
|
|
|
|
private ulong ReadUInt64()
|
|
{
|
|
return EndianStream.ReadUInt64BE(source);
|
|
}
|
|
|
|
private long ReadInt64()
|
|
{
|
|
return EndianStream.ReadInt64BE(source);
|
|
}
|
|
|
|
private float ReadFloat()
|
|
{
|
|
return EndianStream.ReadFloatBE(source);
|
|
}
|
|
|
|
private double ReadDouble()
|
|
{
|
|
return EndianStream.ReadDoubleBE(source);
|
|
}
|
|
|
|
private string ReadString()
|
|
{
|
|
int stringPosition = ReadInt32();
|
|
|
|
long previousPosition = source.Position;
|
|
|
|
source.Position = headerPosition + header.StringPoolPosition + stringPosition;
|
|
string strResult = EndianStream.ReadCString(source, Encoding.Default);
|
|
source.Position = previousPosition;
|
|
|
|
if (strResult == "<NULL>" ||
|
|
(strResult == header.TableName && stringPosition == 0))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return strResult;
|
|
}
|
|
|
|
private void ReadData(out uint vldPosition, out uint vldLength)
|
|
{
|
|
vldPosition = ReadUInt32();
|
|
vldLength = ReadUInt32();
|
|
}
|
|
|
|
private Guid ReadGuid()
|
|
{
|
|
byte[] buffer = new byte[16];
|
|
source.Read(buffer, 0, buffer.Length);
|
|
return new Guid(buffer);
|
|
}
|
|
|
|
private object ReadValue(CriFieldFlag fieldFlag)
|
|
{
|
|
switch (fieldFlag & CriFieldFlag.TypeMask)
|
|
{
|
|
case CriFieldFlag.Byte:
|
|
return ReadByte();
|
|
case CriFieldFlag.SByte:
|
|
return ReadSByte();
|
|
case CriFieldFlag.UInt16:
|
|
return ReadUInt16();
|
|
case CriFieldFlag.Int16:
|
|
return ReadInt16();
|
|
case CriFieldFlag.UInt32:
|
|
return ReadUInt32();
|
|
case CriFieldFlag.Int32:
|
|
return ReadInt32();
|
|
case CriFieldFlag.UInt64:
|
|
return ReadUInt64();
|
|
case CriFieldFlag.Int64:
|
|
return ReadInt64();
|
|
case CriFieldFlag.Float:
|
|
return ReadFloat();
|
|
case CriFieldFlag.Double:
|
|
return ReadDouble();
|
|
case CriFieldFlag.String:
|
|
return ReadString();
|
|
case CriFieldFlag.Data:
|
|
{
|
|
uint vldPosition;
|
|
uint vldLength;
|
|
|
|
ReadData(out vldPosition, out vldLength);
|
|
|
|
// SecondBoolean being true, check if utf table
|
|
if (vldPosition > 0 && vldLength == 0)
|
|
{
|
|
source.Position = headerPosition + header.DataPoolPosition + vldPosition;
|
|
|
|
if (Encoding.ASCII.GetString(ReadBytes(4)) == "@UTF")
|
|
{
|
|
vldLength = ReadUInt32() + 8;
|
|
}
|
|
}
|
|
|
|
return new Substream(source, headerPosition + header.DataPoolPosition + vldPosition, vldLength);
|
|
}
|
|
case CriFieldFlag.Guid:
|
|
return ReadGuid();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
fields.Clear();
|
|
|
|
if (!leaveOpen)
|
|
{
|
|
source.Close();
|
|
}
|
|
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
public static CriTableReader Create(byte[] sourceByteArray)
|
|
{
|
|
Stream source = new MemoryStream(sourceByteArray);
|
|
return Create(source);
|
|
}
|
|
|
|
public static CriTableReader Create(string sourceFileName)
|
|
{
|
|
Stream source = File.OpenRead(sourceFileName);
|
|
return Create(source);
|
|
}
|
|
|
|
public static CriTableReader Create(Stream source)
|
|
{
|
|
return Create(source, false);
|
|
}
|
|
|
|
public static CriTableReader Create(Stream source, bool leaveOpen)
|
|
{
|
|
return new CriTableReader(source, leaveOpen);
|
|
}
|
|
|
|
private CriTableReader(Stream source, bool leaveOpen)
|
|
{
|
|
this.source = source;
|
|
header = new CriTableHeader();
|
|
fields = new OrderedDictionary<string, CriTableField>();
|
|
this.leaveOpen = leaveOpen;
|
|
|
|
ReadTable();
|
|
}
|
|
}
|
|
}
|