SonicAudioTools/Source/SonicAudioLib/CriMw/CriTableWriter.cs
2017-06-21 01:19:47 +03:00

592 lines
16 KiB
C#

using System;
using System.IO;
using System.Text;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
using SonicAudioLib.IO;
using SonicAudioLib.FileBases;
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 DataPool 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;
if (settings.PutBlankString)
{
stringPool.Put(StringPool.AdxBlankString);
}
header.TableNamePosition = (uint)stringPool.Put(tableName);
var buffer = new byte[32];
destination.Write(buffer, 0, 32);
}
public void WriteEndTable()
{
if (status == Status.FieldCollection)
{
WriteEndFieldCollection();
}
if (status == Status.Row)
{
WriteEndRow();
}
status = Status.End;
destination.Seek(headerPosition + header.RowsPosition + (header.RowLength * header.RowCount), SeekOrigin.Begin);
stringPool.Write(destination);
header.StringPoolPosition = (uint)stringPool.Position - headerPosition;
DataStream.Pad(destination, vldPool.Align);
vldPool.Write(destination);
header.DataPoolPosition = (uint)vldPool.Position - headerPosition;
DataStream.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.Seek(headerPosition, SeekOrigin.Begin);
destination.Write(CriTableHeader.SignatureBytes, 0, 4);
DataStream.WriteUInt32BE(destination, header.Length - 8);
DataStream.WriteByte(destination, header.UnknownByte);
DataStream.WriteByte(destination, header.EncodingType);
DataStream.WriteUInt16BE(destination, (ushort)(header.RowsPosition - 8));
DataStream.WriteUInt32BE(destination, header.StringPoolPosition - 8);
DataStream.WriteUInt32BE(destination, header.DataPoolPosition - 8);
DataStream.WriteUInt32BE(destination, header.TableNamePosition);
DataStream.WriteUInt16BE(destination, header.FieldCount);
DataStream.WriteUInt16BE(destination, header.RowLength);
DataStream.WriteUInt32BE(destination, header.RowCount);
if (settings.EnableMask)
{
destination.Seek(headerPosition, SeekOrigin.Begin);
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
};
DataStream.WriteByte(destination, (byte)field.Flag);
if (!string.IsNullOrEmpty(fieldName))
{
WriteString(field.Name);
}
if (defaultValue != null)
{
WriteValue(defaultValue);
}
fields.Add(field);
header.FieldCount++;
}
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
};
DataStream.WriteByte(destination, (byte)field.Flag);
if (!string.IsNullOrEmpty(fieldName))
{
WriteString(field.Name);
}
field.Offset = header.RowLength;
switch (field.Flag & CriFieldFlag.TypeMask)
{
case CriFieldFlag.Byte:
case CriFieldFlag.SByte:
header.RowLength += 1;
break;
case CriFieldFlag.Int16:
case CriFieldFlag.UInt16:
header.RowLength += 2;
break;
case CriFieldFlag.Int32:
case CriFieldFlag.UInt32:
case CriFieldFlag.Single:
case CriFieldFlag.String:
header.RowLength += 4;
break;
case CriFieldFlag.Int64:
case CriFieldFlag.UInt64:
case CriFieldFlag.Double:
case CriFieldFlag.Data:
header.RowLength += 8;
break;
}
fields.Add(field);
header.FieldCount++;
}
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);
}
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.RowCount++;
destination.Seek(headerPosition + header.RowsPosition + (header.RowCount * header.RowLength), SeekOrigin.Begin);
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)
{
destination.Seek(headerPosition + header.RowsPosition + (header.RowLength * (header.RowCount - 1)) + fields[fieldIndex].Offset, SeekOrigin.Begin);
}
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 WriteString(string value)
{
if (settings.RemoveDuplicateStrings && stringPool.ContainsString(value))
{
DataStream.WriteUInt32BE(destination, (uint)stringPool.GetStringPosition(value));
}
else
{
DataStream.WriteUInt32BE(destination, (uint)stringPool.Put(value));
}
}
private void WriteValue(object val)
{
switch (val)
{
case byte value:
DataStream.WriteByte(destination, value);
break;
case sbyte value:
DataStream.WriteSByte(destination, value);
break;
case ushort value:
DataStream.WriteUInt16BE(destination, value);
break;
case short value:
DataStream.WriteInt16BE(destination, value);
break;
case uint value:
DataStream.WriteUInt32BE(destination, value);
break;
case int value:
DataStream.WriteInt32BE(destination, value);
break;
case ulong value:
DataStream.WriteUInt64BE(destination, value);
break;
case long value:
DataStream.WriteInt64BE(destination, value);
break;
case float value:
DataStream.WriteSingleBE(destination, value);
break;
case double value:
DataStream.WriteDoubleBE(destination, value);
break;
case string value:
WriteString(value);
break;
case byte[] value:
DataStream.WriteUInt32BE(destination, (uint)vldPool.Put(value));
DataStream.WriteUInt32BE(destination, (uint)value.Length);
break;
case Guid value:
destination.Write(value.ToByteArray(), 0, 16);
break;
case Stream value:
DataStream.WriteUInt32BE(destination, (uint)vldPool.Put(value));
DataStream.WriteUInt32BE(destination, (uint)value.Length);
break;
case FileInfo value:
DataStream.WriteUInt32BE(destination, (uint)vldPool.Put(value));
DataStream.WriteUInt32BE(destination, (uint)value.Length);
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 DataPool(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,
};
}
}
}
}