LogEntryFMT ReadLogFormat(Stream br)
        {
            int len = Marshal.SizeOf <log_Format>();

            if (_logFormatPtr == IntPtr.Zero)
            {
                _logFormatPtr    = Marshal.AllocCoTaskMem(len);
                _logFormatBuffer = new byte[len];
            }

            br.Read(_logFormatBuffer, 0, _logFormatBuffer.Length);

            // copy byte array to ptr
            Marshal.Copy(_logFormatBuffer, 0, _logFormatPtr, len);

            log_Format logfmt = Marshal.PtrToStructure <log_Format>(_logFormatPtr);

            string lgname = ASCIIEncoding.ASCII.GetString(logfmt.name).Trim(NullTerminator);


            var result = new LogEntryFMT()
            {
                Length       = logfmt.length,
                Type         = logfmt.type,
                Name         = ASCIIEncoding.ASCII.GetString(logfmt.name).Trim(NullTerminator),
                FormatString = ASCIIEncoding.ASCII.GetString(logfmt.format).Trim(NullTerminator),
                Columns      = ASCIIEncoding.ASCII.GetString(logfmt.labels).Trim(NullTerminator).Split(',')
            };

            if (result.Columns.Length == 1 && string.IsNullOrEmpty(result.Columns[0]))
            {
                result.Columns = new string[0];
            }
            packettypecache[logfmt.type] = result;
            Debug.WriteLine("// FMT {0}, {1}, {2}, {3}", result.Name, result.Length, result.FormatString, string.Join(",", result.Columns));

            if (GenerateParser)
            {
                StringWriter sw = new StringWriter();
                GenerateLogEntryClass(result, sw);
                Debug.WriteLine(sw.ToString());
            }
            return(result);
        }
        void GenerateLogEntryParser(TextWriter writer)
        {
            writer.WriteLine("class LogEntryParser");
            writer.WriteLine("{");

            writer.WriteLine("    public static object ParseLogEntry(byte type, BinaryReader reader) {");

            writer.WriteLine("        switch(type) {");
            foreach (var pair in packettypecache)
            {
                LogEntryFMT fmt       = pair.Value;
                string      className = LogEntryPrefix + fmt.Name;

                writer.WriteLine("        case " + pair.Key + ":");
                writer.WriteLine("            return " + className + ".Read(reader);");
            }
            writer.WriteLine("        }");
            writer.WriteLine("        return null;");
            writer.WriteLine("    }");
            writer.WriteLine("}");
        }
        /// <summary>
        /// Process each log entry
        /// </summary>
        /// <param name="packettype">packet type</param>
        /// <param name="br">input file</param>
        /// <returns>string of converted data</returns>
        string logEntry(byte packettype, Stream br)
        {
            switch (packettype)
            {
            case 0x80:     // FMT

                LogEntryFMT logfmt = ReadLogFormat(br);
                string      line   = String.Format("FMT, {0}, {1}, {2}, {3}, {4}\r\n", logfmt.Type, logfmt.Length, logfmt.Name,
                                                   logfmt.FormatString, string.Join(",", logfmt.Columns));
                return(line);

            default:
                string format = "";
                string name   = "";
                int    size   = 0;

                if (packettypecache.ContainsKey(packettype))
                {
                    var fmt = packettypecache[packettype];
                    name   = fmt.Name;
                    format = fmt.FormatString;
                    size   = fmt.Length;
                }

                // didn't find a match, return unknown packet type
                if (size == 0)
                {
                    return("UNKW, " + packettype);
                }

                byte[] data = new byte[size - 3];     // size - 3 = message - messagetype - (header *2)

                br.Read(data, 0, data.Length);

                return(ProcessMessage(data, name, format));
            }
        }
        object ReadRow(byte packettype, Stream br)
        {
            switch (packettype)
            {
            case 0x80:     // FMT
                ReadLogFormat(br);
                return(null);

            default:
                string format = "";
                string name   = "";
                int    size   = 0;

                if (!generatedParser && GenerateParser)
                {
                    StringWriter writer = new StringWriter();
                    GenerateLogEntryParser(writer);
                    Debug.WriteLine(writer.ToString());
                    generatedParser = true;
                }

                LogEntryFMT fmt = null;
                if (packettypecache.ContainsKey(packettype))
                {
                    fmt    = packettypecache[packettype];
                    name   = fmt.Name;
                    format = fmt.FormatString;
                    size   = fmt.Length;
                }

                // didn't find a match, return unknown packet type
                if (fmt == null)
                {
                    return(null);
                }

                int len = size - 3;     // size - 3 = message - messagetype - (header *2)

                if (buffer == null || buffer.Length < len)
                {
                    buffer = new byte[len];
                }
                br.Read(buffer, 0, len);

                MemoryStream ms     = new MemoryStream(buffer, 0, len);
                BinaryReader reader = new BinaryReader(ms);

                LogEntry entry = new LogEntry()
                {
                    Format = fmt,
                    Blob   = ms.ToArray(),
                    Name   = fmt.Name
                };
                if (name == "TIME")
                {
                    ulong time = entry.GetField <ulong>("StartTime");
                    if (time > 0)
                    {
                        this.currentTime = time;
                    }
                }
                if (this.logType == LogType.binFile)
                {
                    ulong time = entry.GetField <ulong>("TimeMS");
                    if (time > 0)
                    {
                        this.currentTime = time;
                    }
                    else if (time == 0)
                    {
                        entry.SetField("TimeMS", this.currentTime);
                    }
                }
                entry.Timestamp = currentTime;

                return(entry);
            }
        }
        void GenerateLogEntryClass(LogEntryFMT fmt, TextWriter writer)
        {
            const string LogEntryPrefix = "LogEntry";
            string       className      = LogEntryPrefix + fmt.Name;

            writer.WriteLine("class " + className + " : LogEntry {");


            writer.WriteLine("    public override string GetName() { return \"" + fmt.Name + "\"; }");
            writer.WriteLine("    public override DataValue GetDataValue(string field) {");
            writer.WriteLine("        switch (field) {");
            bool   hasTime = fmt.Columns.Contains("TimeMS");
            string x       = hasTime ? "this.TimeMS" : "0";

            int i = 0;

            foreach (var c in fmt.Columns)
            {
                writer.WriteLine("            case \"" + c + "\":");
                switch (fmt.FormatString[i])
                {
                case 'n':
                case 'N':
                case 'Z':
                    writer.WriteLine("                return new DataValue() { X = " + x + ", Y = 0 };");
                    break;

                default:
                    writer.WriteLine("                return new DataValue() { X = " + x + ", Y = this." + c + " };");
                    break;
                }
                i++;
            }
            writer.WriteLine("        }");
            writer.WriteLine("        return null;");
            writer.WriteLine("    }");

            int    k      = 0;
            int    n      = 0;
            string format = fmt.FormatString;

            for (k = 0, n = format.Length; k < n; k++)
            {
                char ch = format[k];
                switch (ch)
                {
                case 'b':
                    writer.WriteLine("    public sbyte " + fmt.Columns[k] + ";");
                    break;

                case 'B':
                    writer.WriteLine("    public byte " + fmt.Columns[k] + ";");
                    break;

                case 'h':
                    writer.WriteLine("    public Int16 " + fmt.Columns[k] + ";");
                    break;

                case 'H':
                    writer.WriteLine("    public UInt16 " + fmt.Columns[k] + ";");
                    break;

                case 'i':
                    writer.WriteLine("    public Int32 " + fmt.Columns[k] + ";");
                    break;

                case 'I':
                    writer.WriteLine("    public UInt32 " + fmt.Columns[k] + ";");
                    break;

                case 'q':
                    writer.WriteLine("    public Int64 " + fmt.Columns[k] + ";");
                    break;

                case 'Q':
                    writer.WriteLine("    public UInt64 " + fmt.Columns[k] + ";");
                    break;

                case 'f':
                    writer.WriteLine("    public float " + fmt.Columns[k] + ";");
                    break;

                case 'd':
                    writer.WriteLine("    public double " + fmt.Columns[k] + ";");
                    break;

                case 'c':
                    writer.WriteLine("    public double " + fmt.Columns[k] + ";");     // divide by 100.0
                    break;

                case 'C':
                    writer.WriteLine("    public double " + fmt.Columns[k] + ";");     // divide by 100.0
                    break;

                case 'e':
                    writer.WriteLine("    public double " + fmt.Columns[k] + ";");     // divide by 100.0
                    break;

                case 'E':
                    writer.WriteLine("    public double " + fmt.Columns[k] + ";");     // divide by 100.0
                    break;

                case 'L':
                    writer.WriteLine("    public double " + fmt.Columns[k] + ";");     // divide by 10000000.0
                    break;

                case 'n':
                    writer.WriteLine("    public string " + fmt.Columns[k] + ";");
                    break;

                case 'N':
                    writer.WriteLine("    public string " + fmt.Columns[k] + ";");
                    break;

                case 'M':
                    writer.WriteLine("    public byte " + fmt.Columns[k] + ";");
                    break;

                case 'Z':
                    writer.WriteLine("    public string " + fmt.Columns[k] + ";");
                    break;

                default:
                    writer.WriteLine("    // Unexpected format specifier '{0}'", ch);
                    break;
                }
            }

            writer.WriteLine("");
            writer.WriteLine("    public static " + className + " Read(BinaryReader reader) {");
            writer.WriteLine("        " + className + " result = new " + className + "();");
            for (k = 0, n = format.Length; k < n; k++)
            {
                char ch = format[k];
                writer.Write("        result." + fmt.Columns[k] + " = ");
                switch (ch)
                {
                case 'b':
                    writer.WriteLine("(sbyte)reader.ReadByte();");
                    break;

                case 'B':
                    writer.WriteLine("reader.ReadByte();");
                    break;

                case 'h':
                    writer.WriteLine("reader.ReadInt16();");
                    break;

                case 'H':
                    writer.WriteLine("reader.ReadUInt16();");
                    break;

                case 'i':
                    writer.WriteLine("reader.ReadInt32();");
                    break;

                case 'I':
                    writer.WriteLine("reader.ReadUInt32();");
                    break;

                case 'q':
                    writer.WriteLine("reader.ReadInt64();");
                    break;

                case 'Q':
                    writer.WriteLine("reader.ReadUInt64();");
                    break;

                case 'f':
                    writer.WriteLine("reader.ReadSingle();");
                    break;

                case 'd':
                    writer.WriteLine("reader.ReadDouble();");
                    break;

                case 'c':
                    writer.WriteLine("reader.ReadInt16() / 100.0;");
                    break;

                case 'C':
                    writer.WriteLine("reader.ReadUInt16() / 100.0;");
                    break;

                case 'e':
                    writer.WriteLine("reader.ReadInt32() / 100.0;");
                    break;

                case 'E':
                    writer.WriteLine("reader.ReadUInt32() / 100.0;");
                    break;

                case 'L':
                    writer.WriteLine("reader.ReadInt32() / 10000000.0;");
                    break;

                case 'n':
                    writer.WriteLine("ReadAsciiString(reader, 4);");
                    break;

                case 'N':
                    writer.WriteLine("ReadAsciiString(reader, 16);");
                    break;

                case 'M':
                    writer.WriteLine("reader.ReadByte();");
                    break;

                case 'Z':
                    writer.WriteLine("ReadAsciiString(reader, 64);");
                    break;

                default:
                    break;
                }
            }

            writer.WriteLine("        return result;");
            writer.WriteLine("    }");
            writer.WriteLine("}");
        }