namespace Giants.EffectCompiler { using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using Giants.BinTools.Macro; using NLog; /// /// Effect binary compiler. /// public class FxCompiler { private static readonly Logger logger = LogManager.GetLogger(nameof(FxCompiler)); private FxMacroDefinitionTable macroDefinitionTable; private int doneOpcode; /// /// Initializes a new instance of the class. /// /// The table of macro and symbol definitions. public FxCompiler(FxMacroDefinitionTable macroDefinitionTable) { this.macroDefinitionTable = macroDefinitionTable; } /// /// Compiles a textual effect file to the specified output path. /// /// The path to the effect file. /// The path to write to. public void Compile( string inputPath, string outputPath) { if (!File.Exists(inputPath)) { throw new InvalidOperationException($"The input file {inputPath} does not exist."); } // Get constants for known symbols this.doneOpcode = Utilities.GetFxSymbolValue(this.macroDefinitionTable.SymbolTable, "FxDone"); using var streamReader = new StreamReader(inputPath); SerializedEffectData serializedEffectData = this.SerializeEffectData(streamReader); using var fileStream = new FileStream(outputPath, FileMode.Create); using var binaryWriter = new BinaryWriter(fileStream); this.WriteHeader(binaryWriter, serializedEffectData); this.WriteTableOfContents(binaryWriter, serializedEffectData); this.WriteEffectData(binaryWriter, serializedEffectData); } private void WriteEffectData(BinaryWriter binaryWriter, SerializedEffectData serializedEffectData) { binaryWriter.Write(serializedEffectData.Data); } private void WriteTableOfContents(BinaryWriter binaryWriter, SerializedEffectData serializedEffectData) { foreach (var entry in serializedEffectData.TableOfContents) { binaryWriter.Write(Encoding.UTF8.GetBytes(entry.Name)); binaryWriter.Write('\0'); binaryWriter.Write(checked(entry.Offset + serializedEffectData.TableOfContentsSize)); } } private void WriteHeader(BinaryWriter binaryWriter, SerializedEffectData serializedEffectData) { binaryWriter.Write(FxBinaryData.CurrentVersion); binaryWriter.Write(checked(serializedEffectData.Data.Length + serializedEffectData.TableOfContentsSize)); binaryWriter.Write(serializedEffectData.TableOfContents.Count); } private void SerializeEffect(string[] tokens, StreamReader reader, BinaryWriter binaryWriter) { while (!reader.EndOfStream) { tokens = reader.ReadLine().Split(Utilities.SplitCharacters, StringSplitOptions.RemoveEmptyEntries); string macroName = tokens[0]; if (macroName == "fxdone") { binaryWriter.Write((byte)this.doneOpcode); break; } FxMacroDefinition macroDefinition = this.macroDefinitionTable.MacroDefinitions .Values .FirstOrDefault(x => x.Name.Equals(macroName, StringComparison.OrdinalIgnoreCase) && x.FxDefGroup.Count() == tokens[1..].Length); binaryWriter.Write((byte)macroDefinition.Opcode); if (macroDefinition == null) { throw new InvalidOperationException("Unknown macro '{macroName}'"); } var parameters = new List(); int parameterIndex = 1; foreach (var line in macroDefinition.FxDefGroup) { if (line is DataDefinitionMacroLine dataDefinitionMacroLine) { dataDefinitionMacroLine.Serialize(tokens[parameterIndex++], this.macroDefinitionTable.SymbolTable, binaryWriter); } } } } private SerializedEffectData SerializeEffectData(StreamReader streamReader) { using var memoryStream = new MemoryStream(); using var binaryWriter = new BinaryWriter(memoryStream); var tableOfContents = new List(); int tableOfContentsSize = 0; // Keep running total of the final (serialized) size of the TOC while (!streamReader.EndOfStream) { string[] tokens = streamReader.ReadLine().Split(Utilities.SplitCharacters, StringSplitOptions.RemoveEmptyEntries); if (!tokens.Any()) { continue; } if (tokens[0] == "fxdef") { var contentEntry = new ContentEntry() { Name = tokens[1], Offset = checked((int)binaryWriter.BaseStream.Position) }; tableOfContentsSize += contentEntry.Name.Length + 1 + sizeof(int); tableOfContents.Add(contentEntry); this.SerializeEffect(tokens, streamReader, binaryWriter); } } return new SerializedEffectData { Data = memoryStream.ToArray(), TableOfContents = tableOfContents, TableOfContentsSize = tableOfContentsSize, }; } } }