732 lines
20 KiB
C#

using System;
using System.IO;
using System.Text;
using System.ComponentModel;
using System.Collections.Generic;
using SonicAudioLib.IO;
using SonicAudioLib.Module;
namespace SonicAudioLib.CriMw
{
public class CriTableWriter : IDisposable
{
public enum Status
{
Begin,
Start,
FieldCollection,
Row,
Idle,
End,
}
private CriTableWriterSettings settings;
private List<CriTableField> fields;
private Stream destination;
private CriTableHeader header;
private VldPool vldPool;
private StringPool stringPool;
private uint headerPosition;
private Status status = Status.Begin;
public Status CurrentStatus
{
get
{
return status;
}
}
public Stream DestinationStream
{
get
{
return destination;
}
}
public void WriteStartTable()
{
WriteStartTable("(no name)");
}
public void WriteStartTable(string tableName)
{
if (status != Status.Begin)
{
throw new InvalidOperationException("Attempted to start table when the status wasn't Begin");
}
status = Status.Start;
headerPosition = (uint)destination.Position;
header.TableName = tableName;
if (settings.PutBlankString)
{
stringPool.Put(StringPool.AdxBlankString);
}
EndianStream.WriteCString(destination, CriTableHeader.Signature, 4);
WriteUInt32(uint.MinValue);
WriteByte(byte.MinValue);
WriteByte(byte.MinValue);
WriteUInt16(ushort.MinValue);
WriteUInt32(uint.MinValue);
WriteUInt32(uint.MinValue);
WriteString(tableName);
WriteUInt16(ushort.MinValue);
WriteUInt16(ushort.MinValue);
WriteUInt32(uint.MinValue);
}
public void WriteEndTable()
{
if (status == Status.FieldCollection)
{
WriteEndFieldCollection();
}
if (status == Status.Row)
{
WriteEndRow();
}
status = Status.End;
destination.Seek(headerPosition + header.RowsPosition + (header.RowLength * header.NumberOfRows), SeekOrigin.Begin);
stringPool.Write(destination);
header.StringPoolPosition = (uint)stringPool.Position - headerPosition;
EndianStream.Pad(destination, vldPool.Align);
vldPool.Write(destination);
header.DataPoolPosition = (uint)vldPool.Position - headerPosition;
EndianStream.Pad(destination, vldPool.Align);
long previousPosition = destination.Position;
header.Length = (uint)destination.Position - headerPosition;
if (settings.EncodingType == Encoding.GetEncoding("shift-jis"))
{
header.EncodingType = CriTableHeader.EncodingTypeShiftJis;
}
else if (settings.EncodingType == Encoding.UTF8)
{
header.EncodingType = CriTableHeader.EncodingTypeUtf8;
}
destination.Position = headerPosition + 4;
WriteUInt32(header.Length - 8);
WriteByte(header.UnknownByte);
WriteByte(header.EncodingType);
WriteUInt16((ushort)(header.RowsPosition - 8));
WriteUInt32(header.StringPoolPosition - 8);
WriteUInt32(header.DataPoolPosition - 8);
destination.Seek(4, SeekOrigin.Current);
WriteUInt16(header.NumberOfFields);
WriteUInt16(header.RowLength);
WriteUInt32(header.NumberOfRows);
if (settings.EnableMask)
{
destination.Position = headerPosition;
CriTableMasker.Mask(destination, header.Length, settings.MaskXor, settings.MaskXorMultiplier);
}
destination.Seek(previousPosition, SeekOrigin.Begin);
}
public void WriteStartFieldCollection()
{
if (status != Status.Start)
{
throw new InvalidOperationException("Attempted to start field collection when the status wasn't Start");
}
status = Status.FieldCollection;
}
public void WriteField(string fieldName, Type fieldType, object defaultValue)
{
if (status != Status.FieldCollection)
{
WriteStartFieldCollection();
}
CriFieldFlag fieldFlag = (CriFieldFlag)Array.IndexOf(CriField.FieldTypes, fieldType);
if (!string.IsNullOrEmpty(fieldName))
{
fieldFlag |= CriFieldFlag.Name;
}
if (defaultValue != null)
{
fieldFlag |= CriFieldFlag.DefaultValue;
}
CriTableField field = new CriTableField
{
Flag = fieldFlag,
Name = fieldName,
Value = defaultValue
};
WriteByte((byte)field.Flag);
if (!string.IsNullOrEmpty(fieldName))
{
WriteString(field.Name);
}
if (defaultValue != null)
{
WriteValue(defaultValue);
}
fields.Add(field);
header.NumberOfFields++;
}
public void WriteField(string fieldName, Type fieldType)
{
if (status != Status.FieldCollection)
{
WriteStartFieldCollection();
}
CriFieldFlag fieldFlag = (CriFieldFlag)Array.IndexOf(CriField.FieldTypes, fieldType) | CriFieldFlag.RowStorage;
if (!string.IsNullOrEmpty(fieldName))
{
fieldFlag |= CriFieldFlag.Name;
}
CriTableField field = new CriTableField
{
Flag = fieldFlag,
Name = fieldName
};
WriteByte((byte)field.Flag);
if (!string.IsNullOrEmpty(fieldName))
{
WriteString(field.Name);
}
fields.Add(field);
header.NumberOfFields++;
}
public void WriteField(CriField criField)
{
WriteField(criField.FieldName, criField.FieldType);
}
public void WriteEndFieldCollection()
{
if (status != Status.FieldCollection)
{
throw new InvalidOperationException("Attempted to end field collection when the status wasn't FieldCollection");
}
status = Status.Idle;
header.RowsPosition = (ushort)(destination.Position - headerPosition);
header.RowLength = CalculateRowLength();
}
public void WriteStartRow()
{
if (status == Status.FieldCollection)
{
WriteEndFieldCollection();
}
if (status != Status.Idle)
{
throw new InvalidOperationException("Attempted to start row when the status wasn't Idle");
}
status = Status.Row;
header.NumberOfRows++;
destination.Position = headerPosition + header.RowsPosition + (header.NumberOfRows * header.RowLength);
byte[] buffer = new byte[header.RowLength];
destination.Write(buffer, 0, buffer.Length);
}
public void WriteValue(int fieldIndex, object rowValue)
{
if (fieldIndex >= fields.Count || fieldIndex < 0 || !fields[fieldIndex].Flag.HasFlag(CriFieldFlag.RowStorage) || rowValue == null)
{
return;
}
GoToValue(fieldIndex);
WriteValue(rowValue);
}
public void WriteValue(string fieldName, object rowValue)
{
WriteValue(fields.FindIndex(field => field.Name == fieldName), rowValue);
}
private void GoToValue(int fieldIndex)
{
long position = headerPosition + header.RowsPosition + (header.RowLength * (header.NumberOfRows - 1));
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.Single:
case CriFieldFlag.String:
position += 4;
break;
case CriFieldFlag.Int64:
case CriFieldFlag.UInt64:
case CriFieldFlag.Double:
case CriFieldFlag.Data:
position += 8;
break;
}
}
destination.Position = position;
}
private ushort CalculateRowLength()
{
ushort length = 0;
for (int i = 0; i < fields.Count; i++)
{
if (!fields[i].Flag.HasFlag(CriFieldFlag.RowStorage))
{
continue;
}
switch (fields[i].Flag & CriFieldFlag.TypeMask)
{
case CriFieldFlag.Byte:
case CriFieldFlag.SByte:
length += 1;
break;
case CriFieldFlag.Int16:
case CriFieldFlag.UInt16:
length += 2;
break;
case CriFieldFlag.Int32:
case CriFieldFlag.UInt32:
case CriFieldFlag.Single:
case CriFieldFlag.String:
length += 4;
break;
case CriFieldFlag.Int64:
case CriFieldFlag.UInt64:
case CriFieldFlag.Double:
case CriFieldFlag.Data:
length += 8;
break;
}
}
return length;
}
public void WriteEndRow()
{
if (status != Status.Row)
{
throw new InvalidOperationException("Attempted to end row when the status wasn't Row");
}
status = Status.Idle;
}
public void WriteRow(bool close, params object[] rowValues)
{
WriteStartRow();
for (int i = 0; i < Math.Min(rowValues.Length, fields.Count); i++)
{
WriteValue(i, rowValues[i]);
}
if (close)
{
WriteEndRow();
}
}
private void WriteByte(byte value)
{
EndianStream.WriteByte(destination, value);
}
private void WriteBoolean(bool value)
{
EndianStream.WriteBoolean(destination, value);
}
private void WriteSByte(sbyte value)
{
EndianStream.WriteSByte(destination, value);
}
private void WriteUInt16(ushort value)
{
EndianStream.WriteUInt16BE(destination, value);
}
private void WriteInt16(short value)
{
EndianStream.WriteInt16BE(destination, value);
}
private void WriteUInt32(uint value)
{
EndianStream.WriteUInt32BE(destination, value);
}
private void WriteInt32(int value)
{
EndianStream.WriteInt32BE(destination, value);
}
private void WriteUInt64(ulong value)
{
EndianStream.WriteUInt64BE(destination, value);
}
private void WriteInt64(long value)
{
EndianStream.WriteInt64BE(destination, value);
}
private void WriteSingle(float value)
{
EndianStream.WriteSingleBE(destination, value);
}
private void WriteDouble(double value)
{
EndianStream.WriteDoubleBE(destination, value);
}
private void WriteString(string value)
{
if (settings.RemoveDuplicateStrings && stringPool.ContainsString(value))
{
WriteUInt32((uint)stringPool.GetStringPosition(value));
}
else
{
WriteUInt32((uint)stringPool.Put(value));
}
}
private void WriteData(byte[] data)
{
WriteUInt32((uint)vldPool.Put(data));
WriteUInt32((uint)data.Length);
}
private void WriteStream(Stream stream)
{
WriteUInt32((uint)vldPool.Put(stream));
WriteUInt32((uint)stream.Length);
}
private void WriteFile(FileInfo fileInfo)
{
WriteUInt32((uint)vldPool.Put(fileInfo));
WriteUInt32((uint)fileInfo.Length);
}
private void WriteModule(ModuleBase module)
{
WriteUInt32((uint)vldPool.Put(module));
WriteUInt32(0);
}
private void WriteGuid(Guid guid)
{
byte[] buffer = guid.ToByteArray();
destination.Write(buffer, 0, buffer.Length);
}
private void WriteValue(object val)
{
switch (val)
{
case byte value:
WriteByte(value);
break;
case sbyte value:
WriteSByte(value);
break;
case ushort value:
WriteUInt16(value);
break;
case short value:
WriteInt16(value);
break;
case uint value:
WriteUInt32(value);
break;
case int value:
WriteInt32(value);
break;
case ulong value:
WriteUInt64(value);
break;
case long value:
WriteInt64(value);
break;
case float value:
WriteSingle(value);
break;
case double value:
WriteDouble(value);
break;
case string value:
WriteString(value);
break;
case byte[] value:
WriteData(value);
break;
case Guid value:
WriteGuid(value);
break;
case Stream stream:
WriteStream(stream);
break;
case ModuleBase module:
WriteModule(module);
break;
case FileInfo fileInfo:
WriteFile(fileInfo);
break;
}
}
public void Dispose()
{
if (status != Status.End)
{
WriteEndTable();
}
fields.Clear();
stringPool.Clear();
vldPool.Clear();
if (!settings.LeaveOpen)
{
destination.Close();
}
}
public static CriTableWriter Create(string destinationFileName)
{
return Create(destinationFileName, new CriTableWriterSettings());
}
public static CriTableWriter Create(string destinationFileName, CriTableWriterSettings settings)
{
Stream destination = File.Create(destinationFileName);
return new CriTableWriter(destination, settings);
}
public static CriTableWriter Create(Stream destination)
{
return new CriTableWriter(destination, new CriTableWriterSettings());
}
public static CriTableWriter Create(Stream destination, CriTableWriterSettings settings)
{
return new CriTableWriter(destination, settings);
}
private CriTableWriter(Stream destination, CriTableWriterSettings settings)
{
this.destination = destination;
this.settings = settings;
header = new CriTableHeader();
fields = new List<CriTableField>();
stringPool = new StringPool(settings.EncodingType);
vldPool = new VldPool(settings.Align);
}
}
public class CriTableWriterSettings
{
private uint align = 1;
private bool putBlankString = true;
private bool leaveOpen = false;
private Encoding encodingType = Encoding.GetEncoding("shift-jis");
private bool removeDuplicateStrings = true;
private bool enableMask = false;
public uint Align
{
get
{
return align;
}
set
{
if (value <= 0)
{
value = 1;
}
align = value;
}
}
public bool PutBlankString
{
get
{
return putBlankString;
}
set
{
putBlankString = value;
}
}
public bool LeaveOpen
{
get
{
return leaveOpen;
}
set
{
leaveOpen = value;
}
}
public Encoding EncodingType
{
get
{
return encodingType;
}
set
{
if (value != Encoding.UTF8 || value != Encoding.GetEncoding("shift-jis"))
{
return;
}
encodingType = value;
}
}
public bool RemoveDuplicateStrings
{
get
{
return removeDuplicateStrings;
}
set
{
removeDuplicateStrings = value;
}
}
public bool EnableMask
{
get
{
return enableMask;
}
set
{
enableMask = value;
}
}
public uint MaskXor { get; set; }
public uint MaskXorMultiplier { get; set; }
public static CriTableWriterSettings AdxSettings
{
get
{
return new CriTableWriterSettings()
{
Align = 8,
PutBlankString = true,
RemoveDuplicateStrings = true,
};
}
}
public static CriTableWriterSettings Adx2Settings
{
get
{
return new CriTableWriterSettings()
{
Align = 32,
PutBlankString = false,
RemoveDuplicateStrings = false,
};
}
}
}
}