using System; using System.Collections.Generic; using System.IO; using System.Text; namespace Oni { internal sealed class ImporterFile { private static readonly byte[] txcaPadding = new byte[480]; private readonly long templateChecksum = InstanceFileHeader.OniPCTemplateChecksum; private MemoryStream rawStream; private BinaryWriter rawWriter; private List descriptors; private int nameOffset; #region private class FileHeader private class FileHeader { public const int Size = 64; public long TemplateChecksum; public int Version; public int InstanceCount; public int DataTableOffset; public int DataTableSize; public int NameTableOffset; public int NameTableSize; public int RawTableOffset; public int RawTableSize; public void Write(BinaryWriter writer) { writer.Write(TemplateChecksum); writer.Write(Version); writer.Write(InstanceFileHeader.Signature); writer.Write(InstanceCount); writer.Write(0ul); writer.Write(DataTableOffset); writer.Write(DataTableSize); writer.Write(NameTableOffset); writer.Write(NameTableSize); writer.Write(RawTableOffset); writer.Write(RawTableSize); writer.Write(0ul); } } #endregion public ImporterFile() { } public ImporterFile(long templateChecksum) { this.templateChecksum = templateChecksum; } public void BeginImport() { rawStream = null; rawWriter = null; descriptors = new List(); nameOffset = 0; } public BinaryWriter RawWriter { get { if (rawWriter == null) { rawStream = new MemoryStream(); rawWriter = new BinaryWriter(rawStream); rawWriter.Write(new byte[32]); } return rawWriter; } } public ImporterDescriptor CreateInstance(TemplateTag tag, string name = null) { var descriptor = new ImporterFileDescriptor(this, tag, descriptors.Count, MakeInstanceName(tag, name)); descriptors.Add(descriptor); return descriptor; } public int WriteRawPart(byte[] data) { int offset = RawWriter.Align32(); RawWriter.Write(data); return offset; } public int WriteRawPart(string text) { return WriteRawPart(Encoding.UTF8.GetBytes(text)); } private sealed class ImporterFileDescriptor : ImporterDescriptor { public const int Size = 20; private int nameOffset; private int dataOffset; private byte[] data; public ImporterFileDescriptor(ImporterFile file, TemplateTag tag, int index, string name) : base(file, tag, index, name) { if (!string.IsNullOrEmpty(name)) { nameOffset = file.nameOffset; file.nameOffset += name.Length + 1; } } public int NameOffset { get { return nameOffset; } set { nameOffset = value; } } public int DataOffset { get { return dataOffset; } set { dataOffset = value; } } public int DataSize { get { if (data == null) return 0; return data.Length + 8; } } public byte[] Data { get { return data; } } public override BinaryWriter OpenWrite() { if (data != null) throw new InvalidOperationException("Descriptor has already been written to"); return new InstanceWriter(this); } public override BinaryWriter OpenWrite(int offset) { if (data != null) throw new InvalidOperationException("Descriptor has already been written to"); var writer = new InstanceWriter(this); writer.Skip(offset); return writer; } public void Close(byte[] data) { this.data = data; } } private class InstanceWriter : BinaryWriter { private readonly ImporterFileDescriptor descriptor; public InstanceWriter(ImporterFileDescriptor descriptor) : base(new MemoryStream()) { this.descriptor = descriptor; } protected override void Dispose(bool disposing) { var stream = (MemoryStream)BaseStream; if (descriptor.Tag == TemplateTag.TXCA) stream.Write(txcaPadding, 0, txcaPadding.Length); else if (stream.Position > stream.Length) stream.SetLength(stream.Position); descriptor.Close(stream.ToArray()); base.Dispose(disposing); } } public void Write(string outputDirPath) { var filePath = Path.Combine(outputDirPath, Importer.EncodeFileName(descriptors[0].Name) + ".oni"); Directory.CreateDirectory(outputDirPath); int nameTableOffset = Utils.Align32(FileHeader.Size + ImporterFileDescriptor.Size * descriptors.Count); int nameTableSize = nameOffset; int dataTableOffset = Utils.Align32(nameTableOffset + nameOffset); int dataTableSize = 0; foreach (var descriptor in descriptors.Where(d => d.Data != null)) { descriptor.DataOffset = dataTableSize + 8; dataTableSize += Utils.Align32(descriptor.DataSize); } var header = new FileHeader { TemplateChecksum = templateChecksum, Version = InstanceFileHeader.Version32, InstanceCount = descriptors.Count, DataTableOffset = dataTableOffset, DataTableSize = dataTableSize, NameTableOffset = nameTableOffset, NameTableSize = nameTableSize }; using (var stream = File.Create(filePath)) using (var writer = new BinaryWriter(stream)) { bool hasRawParts = (rawStream != null && rawStream.Length > 32); if (hasRawParts) { header.RawTableOffset = Utils.Align32(header.DataTableOffset + header.DataTableSize); header.RawTableSize = (int)rawStream.Length; } header.Write(writer); foreach (var descriptor in descriptors) { WriteDescriptor(writer, descriptor); } writer.Position = header.NameTableOffset; foreach (var entry in descriptors) { if (entry.Name != null) writer.Write(entry.Name, entry.Name.Length + 1); } writer.Position = header.DataTableOffset; foreach (var descriptor in descriptors.Where(d => d.Data != null)) { writer.Align32(); writer.WriteInstanceId(descriptor.Index); writer.Write(0); writer.Write(descriptor.Data); } if (hasRawParts) { writer.Position = header.RawTableOffset; rawStream.WriteTo(stream); } } } private void WriteDescriptor(BinaryWriter writer, ImporterFileDescriptor descriptor) { var flags = InstanceDescriptorFlags.None; if (descriptor.Name == null) flags |= InstanceDescriptorFlags.Private; if (descriptor.Data == null) flags |= InstanceDescriptorFlags.Placeholder; if (descriptor.Name == null && descriptor.Data == null) throw new InvalidOperationException("Link descriptors must have names"); writer.Write((int)descriptor.Tag); writer.Write(descriptor.DataOffset); writer.Write(descriptor.NameOffset); writer.Write(descriptor.DataSize); writer.Write((int)flags); } private static string MakeInstanceName(TemplateTag tag, string name) { if (string.IsNullOrEmpty(name)) return null; var tagName = tag.ToString(); if (!name.StartsWith(tagName, StringComparison.Ordinal)) name = tagName + name; return name; } } }