using System; using System.Collections.Generic; using System.IO; using Oni.Metadata; namespace Oni { internal sealed class InstanceFile { private readonly InstanceFileManager fileManager; private readonly string filePath; private InstanceFileHeader header; private Dictionary rawParts; private Dictionary sepParts; private string rawFilePath; private string sepFilePath; private List descriptors; private IList readOnlyDescriptors; private InstanceFile(InstanceFileManager fileManager, string filePath) { this.fileManager = fileManager; this.filePath = filePath; } public static InstanceFile Read(InstanceFileManager fileManager, string filePath) { var file = new InstanceFile(fileManager, filePath); using (var reader = new BinaryReader(filePath)) { var header = InstanceFileHeader.Read(reader); var descriptors = new List(header.InstanceCount); file.header = header; file.descriptors = descriptors; for (int i = 0; i < file.header.InstanceCount; i++) descriptors.Add(InstanceDescriptor.Read(file, reader, i)); var names = ReadNames(header, reader); for (int i = 0; i < file.header.InstanceCount; i++) descriptors[i].ReadName(names); } // // Force AGDB instances to have a name so they get exported into separate .oni files // instead of being exported inside an AKEV.oni file. This code assumes that there // is only 1 AGDB/AKEV file per .dat file (or none at all). // foreach (var descriptor in file.descriptors) { if (descriptor.Template.Tag != TemplateTag.AGDB) continue; var akevDescriptors = file.GetNamedDescriptors(TemplateTag.AKEV); if (akevDescriptors.Count == 1) { string agdbName = "AGDB" + akevDescriptors[0].Name; descriptor.SetName(agdbName); break; } } return file; } private static Dictionary ReadNames(InstanceFileHeader header, BinaryReader reader) { reader.Position = header.NameTableOffset; var nameTable = reader.ReadBytes(header.NameTableSize); int nameOffset = 0; var names = new Dictionary(header.NameCount); var buffer = new char[64]; while (nameOffset < nameTable.Length) { int i = 0; while (true) { byte c = nameTable[nameOffset + i]; if (c == 0) break; buffer[i++] = (char)c; } names.Add(nameOffset, new string(buffer, 0, i)); nameOffset += i + 1; } return names; } public InstanceFileManager FileManager { get { return fileManager; } } public string FilePath { get { return filePath; } } public InstanceFileHeader Header { get { return header; } } public List GetReferencedDescriptors(InstanceDescriptor descriptor) { var result = new List(); result.Add(descriptor); var stack = new Stack(); var seen = new bool[descriptors.Count]; stack.Push(descriptor); seen[descriptor.Index] = true; using (var reader = new BinaryReader(filePath)) { var linkVisitor = new LinkVisitor(reader); while (stack.Count > 0) { descriptor = stack.Pop(); reader.Position = descriptor.DataOffset; linkVisitor.Links.Clear(); descriptor.Template.Type.Accept(linkVisitor); foreach (int id in linkVisitor.Links) { if (!seen[id >> 8]) { var referencedDescriptor = GetDescriptor(id); if (!referencedDescriptor.IsPlaceholder && !referencedDescriptor.HasName) stack.Push(referencedDescriptor); result.Add(referencedDescriptor); seen[referencedDescriptor.Index] = true; } } } } return result; } public int GetRawPartSize(int offset) { EnsureRawAndSepParts(); return rawParts[offset]; } public int GetSepPartSize(int offset) { EnsureRawAndSepParts(); return sepParts[offset]; } public string RawFilePath { get { if (rawFilePath == null) { if (header.Version == InstanceFileHeader.Version31) rawFilePath = Path.ChangeExtension(filePath, ".raw"); else rawFilePath = filePath; } return rawFilePath; } } public string SepFilePath { get { if (sepFilePath == null) sepFilePath = Path.ChangeExtension(filePath, ".sep"); return sepFilePath; } } public BinaryReader GetRawReader(int offset) { return GetBinaryReader(offset, RawFilePath); } public BinaryReader GetSepReader(int offset) { return GetBinaryReader(offset, SepFilePath); } private void EnsureRawAndSepParts() { if (rawParts != null) return; rawParts = new Dictionary(); sepParts = new Dictionary(); InstanceMetadata.GetRawAndSepParts(this, rawParts, sepParts); } private BinaryReader GetBinaryReader(int offset, string binaryFilePath) { var reader = new BinaryReader(binaryFilePath); reader.Position = offset + header.RawTableOffset; return reader; } public InstanceDescriptor ResolveLink(int id) { var descriptor = GetDescriptor(id); if (descriptor == null || !descriptor.IsPlaceholder) return descriptor; if (!descriptor.HasName) return null; var file = fileManager.FindInstance(descriptor.FullName, this); if (file == null || file == this) { Console.Error.WriteLine("Cannot find instance '{0}'", descriptor.FullName); return null; } if (file.header.Version == InstanceFileHeader.Version32) return file.GetDescriptor(1); foreach (var target in file.descriptors) { if (target.HasName && target.FullName == descriptor.FullName) return target; } return null; } public InstanceDescriptor GetDescriptor(int id) { if (id == 0) return null; return descriptors[id >> 8]; } public IList Descriptors { get { if (readOnlyDescriptors == null) readOnlyDescriptors = descriptors.AsReadOnly(); return readOnlyDescriptors; } } public List GetNamedDescriptors() { return descriptors.FindAll(x => x.HasName && !x.IsPlaceholder); } public List GetNamedDescriptors(TemplateTag tag) { return descriptors.FindAll(x => x.Template.Tag == tag && x.HasName && !x.IsPlaceholder); } public List GetPlaceholders() { return descriptors.FindAll(x => x.HasName && x.IsPlaceholder); } } }