public void Save(DataTable table, Table def, string path)
{
using (var fs = new FileStream(path, FileMode.Create))
using (var ms = new MemoryStream())
using (var bw = new BinaryWriter(ms))
{
bw.Write(DBCFmtSig); // magic
bw.Write(table.Rows.Count);
bw.Write(FieldsCount);
bw.Write(RecordSize);
bw.Write(1); // stringTableSize placeholder
var columnTypeCodes = table.Columns.Cast<DataColumn>().Select(c => Type.GetTypeCode(c.DataType)).ToArray();
var stringLookup = new Dictionary<string, int>();
stringLookup[""] = 0;
var stringTable = new MemoryStream();
stringTable.WriteByte(0);
var fields = def.Fields;
var fieldsCount = fields.Count;
var fieldsArraySizes = fields.Select(f => f.ArraySize).ToArray();
Func<TypeCode, bool> isSmallType = (t) =>
{
if (t == TypeCode.SByte || t == TypeCode.Byte || t == TypeCode.Int16 || t == TypeCode.UInt16)
return true;
return false;
};
foreach (DataRow row in table.Rows)
{
int colIndex = 0;
for (int j = 0; j < fieldsCount; j++)
{
int arraySize = fieldsArraySizes[j];
for (int k = 0; k < arraySize; k++)
{
switch (columnTypeCodes[colIndex])
{
case TypeCode.Byte:
bw.Write(row.Field<byte>(colIndex));
break;
case TypeCode.SByte:
bw.Write(row.Field<sbyte>(colIndex));
break;
case TypeCode.Int16:
bw.Write(row.Field<short>(colIndex));
break;
case TypeCode.UInt16:
bw.Write(row.Field<ushort>(colIndex));
break;
case TypeCode.Int32:
bw.Write(row.Field<int>(colIndex));
break;
case TypeCode.UInt32:
bw.Write(row.Field<uint>(colIndex));
break;
case TypeCode.Int64:
bw.Write(row.Field<long>(colIndex));
break;
case TypeCode.UInt64:
bw.Write(row.Field<ulong>(colIndex));
break;
case TypeCode.Single:
bw.Write(row.Field<float>(colIndex));
break;
case TypeCode.Double:
bw.Write(row.Field<double>(colIndex));
break;
case TypeCode.String:
string str = row.Field<string>(colIndex);
int offset;
if (stringLookup.TryGetValue(str, out offset))
{
bw.Write(offset);
}
else
{
byte[] strBytes = Encoding.UTF8.GetBytes(str);
if (strBytes.Length == 0)
{
throw new Exception("should not happen");
}
stringLookup[str] = (int)stringTable.Position;
bw.Write((int)stringTable.Position);
stringTable.Write(strBytes, 0, strBytes.Length);
stringTable.WriteByte(0);
}
break;
default:
throw new Exception("Unknown TypeCode " + columnTypeCodes[colIndex]);
}
// small fields are padded with zeros in old format versions if next field isn't small
long frem = ms.Position % 4;
if (frem != 0 && isSmallType(columnTypeCodes[colIndex]) && (colIndex + 1 < columnTypeCodes.Length) && !isSmallType(columnTypeCodes[colIndex + 1]))
ms.Position += (4 - frem);
colIndex++;
}
}
// padding at the end of the row
long rem = ms.Position % 4;
if (rem != 0)
ms.Position += (4 - rem);
}
// update stringTableSize in the header
long oldPos = ms.Position;
ms.Position = 0x10;
bw.Write((int)stringTable.Length);
ms.Position = oldPos;
// write strings
stringTable.Position = 0;
stringTable.CopyTo(ms);
// copy data to file
ms.Position = 0;
ms.CopyTo(fs);
}
}