public void Serialize(Stream output)
{
var endian = this.Endian;
const uint headerSize = 32;
output.WriteValueU32(0x42424947, endian);
output.WriteValueU32(this.Version, endian);
var keys = new List<string>() {""};
int maxValueLength = 0;
var blob = new StringBuilder();
foreach (var file in this.Files)
{
keys.Add(file.Name);
foreach (var section in file.Sections)
{
keys.Add(section.Key);
foreach (var value in section.Value)
{
keys.Add(value.Key);
foreach (var item in value.Value)
{
if (item.Value != null)
{
blob.Append(item.Value + '\0');
maxValueLength = Math.Max(maxValueLength, item.Value.Length);
}
}
}
}
}
var huffmanEncoder = new Huffman.Encoder();
huffmanEncoder.Build(blob.ToString());
keys = keys.Distinct().OrderBy(k => k.HashCrc32()).ToList();
int maxKeyLength = keys.Max(k => k.Length);
uint stringTableSize;
using (var data = new MemoryStream())
{
data.Position = 4;
data.WriteValueS32(keys.Count, endian);
data.Position = 4 + 4 + (8 * keys.Count);
var offsets = new List<KeyValuePair<uint, uint>>();
foreach (var key in keys)
{
var offset = (uint)data.Position;
data.WriteValueU16((ushort)key.Length, endian);
data.WriteString(key, Encoding.UTF8);
offsets.Add(new KeyValuePair<uint, uint>(key.HashCrc32(), offset));
}
data.Position = 8;
foreach (var kv in offsets)
{
data.WriteValueU32(kv.Key, endian);
data.WriteValueU32(kv.Value - 8, endian);
}
data.Position = 0;
data.WriteValueU32((uint)data.Length, endian);
data.Position = 0;
stringTableSize = (uint)data.Length;
output.Seek(headerSize, SeekOrigin.Begin);
output.WriteFromStream(data, data.Length);
}
uint huffmanSize;
using (var data = new MemoryStream())
{
var pairs = huffmanEncoder.GetPairs();
data.WriteValueU16((ushort)pairs.Length, endian);
foreach (var pair in pairs)
{
data.WriteValueS32(pair.Left, endian);
data.WriteValueS32(pair.Right, endian);
}
data.Position = 0;
huffmanSize = (uint)data.Length;
output.Seek(headerSize + stringTableSize, SeekOrigin.Begin);
output.WriteFromStream(data, data.Length);
}
var bits = new BitArray(huffmanEncoder.TotalBits);
var bitOffset = 0;
uint indexSize;
using (var index = new MemoryStream())
{
var fileDataOffset = 2 + (this.Files.Count * 6);
var files = new List<KeyValuePair<ushort, int>>();
foreach (var file in this.Files.OrderBy(f => keys.IndexOf(f.Name)))
{
files.Add(new KeyValuePair<ushort, int>(
(ushort)keys.IndexOf(file.Name), fileDataOffset));
var sectionDataOffset = 2 + (file.Sections.Count * 6);
var sections = new List<KeyValuePair<ushort, int>>();
foreach (var section in file.Sections.OrderBy(s => keys.IndexOf(s.Key)))
{
sections.Add(new KeyValuePair<ushort, int>(
(ushort)keys.IndexOf(section.Key), sectionDataOffset));
var valueDataOffset = 2 + (section.Value.Count * 6);
var values = new List<KeyValuePair<ushort, int>>();
foreach (var value in section.Value.OrderBy(v => keys.IndexOf(v.Key)))
{
index.Position = fileDataOffset + sectionDataOffset + valueDataOffset;
values.Add(new KeyValuePair<ushort, int>(
(ushort)keys.IndexOf(value.Key), valueDataOffset));
index.WriteValueU16((ushort)value.Value.Count, endian);
valueDataOffset += 2;
foreach (var item in value.Value)
{
if (item.Type == 1)
{
index.WriteValueS32((1 << 29) | bitOffset, endian);
}
else if (item.Type == 0 || item.Type == 2 || item.Type == 3 || item.Type == 4)
{
index.WriteValueS32((item.Type << 29) | bitOffset, endian);
bitOffset += huffmanEncoder.Encode((item.Value ?? "") + '\0', bits, bitOffset);
}
valueDataOffset += 4;
}
}
index.Position = fileDataOffset + sectionDataOffset;
index.WriteValueU16((ushort)values.Count, endian);
sectionDataOffset += 2;
foreach (var value in values)
{
index.WriteValueU16(value.Key, endian);
index.WriteValueS32(value.Value, endian);
sectionDataOffset += 6;
}
sectionDataOffset += valueDataOffset;
}
index.Position = fileDataOffset;
index.WriteValueU16((ushort)sections.Count, endian);
fileDataOffset += 2;
foreach (var section in sections)
{
index.WriteValueU16(section.Key, endian);
index.WriteValueS32(section.Value, endian);
fileDataOffset += 6;
}
fileDataOffset += sectionDataOffset;
}
index.Position = 0;
index.WriteValueU16((ushort)files.Count, endian);
foreach (var file in files)
{
index.WriteValueU16(file.Key, endian);
index.WriteValueS32(file.Value, endian);
}
index.Position = 0;
indexSize = (uint)index.Length;
output.Seek(headerSize + stringTableSize + huffmanSize, SeekOrigin.Begin);
output.WriteFromStream(index, index.Length);
}
output.Seek(headerSize + stringTableSize + huffmanSize + indexSize, SeekOrigin.Begin);
output.WriteValueS32(bits.Length, endian);
var bytes = new byte[(bits.Length - 1) / 8 + 1];
bits.CopyTo(bytes, 0);
output.WriteBytes(bytes);
output.Seek(8, SeekOrigin.Begin);
output.WriteValueS32(maxKeyLength, endian);
output.WriteValueS32(maxValueLength, endian);
output.WriteValueU32(stringTableSize, endian);
output.WriteValueU32(huffmanSize, endian);
output.WriteValueU32(indexSize, endian);
output.WriteValueS32(bytes.Length, endian);
output.Seek(0, SeekOrigin.Begin);
output.WriteValueU32(0x666D726D, endian);
}