corrade-vassal – Rev 1

Subversion Repositories:
Rev:
using System;
using System.Collections.Generic;
using System.IO;

namespace mapgenerator
{
    class mapgenerator
    {
        static void WriteFieldMember(TextWriter writer, MapField field)
        {
            string type = String.Empty;

            switch (field.Type)
            {
                case FieldType.BOOL:
                    type = "bool";
                    break;
                case FieldType.F32:
                    type = "float";
                    break;
                case FieldType.F64:
                    type = "double";
                    break;
                case FieldType.IPPORT:
                case FieldType.U16:
                    type = "ushort";
                    break;
                case FieldType.IPADDR:
                case FieldType.U32:
                    type = "uint";
                    break;
                case FieldType.LLQuaternion:
                    type = "Quaternion";
                    break;
                case FieldType.LLUUID:
                    type = "UUID";
                    break;
                case FieldType.LLVector3:
                    type = "Vector3";
                    break;
                case FieldType.LLVector3d:
                    type = "Vector3d";
                    break;
                case FieldType.LLVector4:
                    type = "Vector4";
                    break;
                case FieldType.S16:
                    type = "short";
                    break;
                case FieldType.S32:
                    type = "int";
                    break;
                case FieldType.S8:
                    type = "sbyte";
                    break;
                case FieldType.U64:
                    type = "ulong";
                    break;
                case FieldType.U8:
                    type = "byte";
                    break;
                case FieldType.Fixed:
                    type = "byte[]";
                    break;
            }
            if (field.Type != FieldType.Variable)
            {
                //writer.WriteLine("            /// <summary>" + field.Name + " field</summary>");
                writer.WriteLine("            public " + type + " " + field.Name + ";");
            }
            else
            {
                writer.WriteLine("            public byte[] " + field.Name + ";");

                //writer.WriteLine("            private byte[] _" + field.Name.ToLower() + ";");
                ////writer.WriteLine("            /// <summary>" + field.Name + " field</summary>");
                //writer.WriteLine("            public byte[] " + field.Name + Environment.NewLine + "            {");
                //writer.WriteLine("                get { return _" + field.Name.ToLower() + "; }");
                //writer.WriteLine("                set" + Environment.NewLine + "                {");
                //writer.WriteLine("                    if (value == null) { _" +
                //    field.Name.ToLower() + " = null; return; }");
                //writer.WriteLine("                    if (value.Length > " +
                //    ((field.Count == 1) ? "255" : "1100") + ") { throw new OverflowException(" +
                //    "\"Value exceeds " + ((field.Count == 1) ? "255" : "1100") + " characters\"); }");
                //writer.WriteLine("                    else { _" + field.Name.ToLower() +
                //    " = new byte[value.Length]; Buffer.BlockCopy(value, 0, _" +
                //    field.Name.ToLower() + ", 0, value.Length); }");
                //writer.WriteLine("                }" + Environment.NewLine + "            }");
            }
        }

        static void WriteFieldFromBytes(TextWriter writer, MapField field)
        {
            switch (field.Type)
            {
                case FieldType.BOOL:
                    writer.WriteLine("                    " +
                        field.Name + " = (bytes[i++] != 0) ? (bool)true : (bool)false;");
                    break;
                case FieldType.F32:
                    writer.WriteLine("                    " +
                        field.Name + " = Utils.BytesToFloat(bytes, i); i += 4;");
                    break;
                case FieldType.F64:
                    writer.WriteLine("                    " +
                        field.Name + " = Utils.BytesToDouble(bytes, i); i += 8;");
                    break;
                case FieldType.Fixed:
                    writer.WriteLine("                    " + field.Name + " = new byte[" + field.Count + "];");
                    writer.WriteLine("                    Buffer.BlockCopy(bytes, i, " + field.Name +
                        ", 0, " + field.Count + "); i += " + field.Count + ";");
                    break;
                case FieldType.IPADDR:
                case FieldType.U32:
                    writer.WriteLine("                    " + field.Name +
                        " = (uint)(bytes[i++] + (bytes[i++] << 8) + (bytes[i++] << 16) + (bytes[i++] << 24));");
                    break;
                case FieldType.IPPORT:
                    // IPPORT is big endian while U16/S16 are little endian. Go figure
                    writer.WriteLine("                    " + field.Name +
                        " = (ushort)((bytes[i++] << 8) + bytes[i++]);");
                    break;
                case FieldType.U16:
                    writer.WriteLine("                    " + field.Name +
                        " = (ushort)(bytes[i++] + (bytes[i++] << 8));");
                    break;
                case FieldType.LLQuaternion:
                    writer.WriteLine("                    " + field.Name + ".FromBytes(bytes, i, true); i += 12;");
                    break;
                case FieldType.LLUUID:
                    writer.WriteLine("                    " + field.Name + ".FromBytes(bytes, i); i += 16;");
                    break;
                case FieldType.LLVector3:
                    writer.WriteLine("                    " + field.Name + ".FromBytes(bytes, i); i += 12;");
                    break;
                case FieldType.LLVector3d:
                    writer.WriteLine("                    " + field.Name + ".FromBytes(bytes, i); i += 24;");
                    break;
                case FieldType.LLVector4:
                    writer.WriteLine("                    " + field.Name + ".FromBytes(bytes, i); i += 16;");
                    break;
                case FieldType.S16:
                    writer.WriteLine("                    " + field.Name +
                        " = (short)(bytes[i++] + (bytes[i++] << 8));");
                    break;
                case FieldType.S32:
                    writer.WriteLine("                    " + field.Name +
                        " = (int)(bytes[i++] + (bytes[i++] << 8) + (bytes[i++] << 16) + (bytes[i++] << 24));");
                    break;
                case FieldType.S8:
                    writer.WriteLine("                    " + field.Name +
                        " = (sbyte)bytes[i++];");
                    break;
                case FieldType.U64:
                    writer.WriteLine("                    " + field.Name +
                        " = (ulong)((ulong)bytes[i++] + ((ulong)bytes[i++] << 8) + " +
                        "((ulong)bytes[i++] << 16) + ((ulong)bytes[i++] << 24) + " +
                        "((ulong)bytes[i++] << 32) + ((ulong)bytes[i++] << 40) + " +
                        "((ulong)bytes[i++] << 48) + ((ulong)bytes[i++] << 56));");
                    break;
                case FieldType.U8:
                    writer.WriteLine("                    " + field.Name +
                        " = (byte)bytes[i++];");
                    break;
                case FieldType.Variable:
                    if (field.Count == 1)
                    {
                        writer.WriteLine("                    length = bytes[i++];");
                    }
                    else
                    {
                        writer.WriteLine("                    length = (bytes[i++] + (bytes[i++] << 8));");
                    }
                    writer.WriteLine("                    " + field.Name + " = new byte[length];");
                    writer.WriteLine("                    Buffer.BlockCopy(bytes, i, " + field.Name + ", 0, length); i += length;");
                    break;
                default:
                    writer.WriteLine("!!! ERROR: Unhandled FieldType: " + field.Type.ToString() + " !!!");
                    break;
            }
        }

        static void WriteFieldToBytes(TextWriter writer, MapField field)
        {
            writer.Write("                ");

            switch (field.Type)
            {
                case FieldType.BOOL:
                    writer.WriteLine("bytes[i++] = (byte)((" + field.Name + ") ? 1 : 0);");
                    break;
                case FieldType.F32:
                    writer.WriteLine("Utils.FloatToBytes(" + field.Name + ", bytes, i); i += 4;");
                    break;
                case FieldType.F64:
                    writer.WriteLine("Utils.DoubleToBytes(" + field.Name + ", bytes, i); i += 8;");
                    break;
                case FieldType.Fixed:
                    writer.WriteLine("Buffer.BlockCopy(" + field.Name + ", 0, bytes, i, " + field.Count + ");" +
                        "i += " + field.Count + ";");
                    break;
                case FieldType.IPPORT:
                    // IPPORT is big endian while U16/S16 is little endian. Go figure
                    writer.WriteLine("bytes[i++] = (byte)((" + field.Name + " >> 8) % 256);");
                    writer.WriteLine("                bytes[i++] = (byte)(" + field.Name + " % 256);");
                    break;
                case FieldType.U16:
                case FieldType.S16:
                    writer.WriteLine("bytes[i++] = (byte)(" + field.Name + " % 256);");
                    writer.WriteLine("                bytes[i++] = (byte)((" + field.Name + " >> 8) % 256);");
                    break;
                case FieldType.LLQuaternion:
                case FieldType.LLVector3:
                    writer.WriteLine(field.Name + ".ToBytes(bytes, i); i += 12;");
                    break;
                case FieldType.LLUUID:
                case FieldType.LLVector4:
                    writer.WriteLine(field.Name + ".ToBytes(bytes, i); i += 16;");
                    break;
                case FieldType.LLVector3d:
                    writer.WriteLine(field.Name + ".ToBytes(bytes, i); i += 24;");
                    break;
                case FieldType.U8:
                    writer.WriteLine("bytes[i++] = " + field.Name + ";");
                    break;
                case FieldType.S8:
                    writer.WriteLine("bytes[i++] = (byte)" + field.Name + ";");
                    break;
                case FieldType.IPADDR:
                case FieldType.U32:
                    writer.WriteLine("Utils.UIntToBytes(" + field.Name + ", bytes, i); i += 4;");
                    break;
                case FieldType.S32:
                    writer.WriteLine("Utils.IntToBytes(" + field.Name + ", bytes, i); i += 4;");
                    break;
                case FieldType.U64:
                    writer.WriteLine("Utils.UInt64ToBytes(" + field.Name + ", bytes, i); i += 8;");
                    break;
                case FieldType.Variable:
                    //writer.WriteLine("if(" + field.Name + " == null) { Console.WriteLine(\"Warning: " + field.Name + " is null, in \" + this.GetType()); }");
                    //writer.Write("                ");
                    if (field.Count == 1)
                    {
                        writer.WriteLine("bytes[i++] = (byte)" + field.Name + ".Length;");
                    }
                    else
                    {
                        writer.WriteLine("bytes[i++] = (byte)(" + field.Name + ".Length % 256);");
                        writer.WriteLine("                bytes[i++] = (byte)((" +
                            field.Name + ".Length >> 8) % 256);");
                    }
                    writer.WriteLine("                Buffer.BlockCopy(" + field.Name + ", 0, bytes, i, " +
                        field.Name + ".Length); " + "i += " + field.Name + ".Length;");
                    break;
                default:
                    writer.WriteLine("!!! ERROR: Unhandled FieldType: " + field.Type.ToString() + " !!!");
                    break;
            }
        }

        static int GetFieldLength(TextWriter writer, MapField field)
        {
            switch (field.Type)
            {
                case FieldType.BOOL:
                case FieldType.U8:
                case FieldType.S8:
                    return 1;
                case FieldType.U16:
                case FieldType.S16:
                case FieldType.IPPORT:
                    return 2;
                case FieldType.U32:
                case FieldType.S32:
                case FieldType.F32:
                case FieldType.IPADDR:
                    return 4;
                case FieldType.U64:
                case FieldType.F64:
                    return 8;
                case FieldType.LLVector3:
                case FieldType.LLQuaternion:
                    return 12;
                case FieldType.LLUUID:
                case FieldType.LLVector4:
                    return 16;
                case FieldType.LLVector3d:
                    return 24;
                case FieldType.Fixed:
                    return field.Count;
                case FieldType.Variable:
                    return 0;
                default:
                    writer.WriteLine("!!! ERROR: Unhandled FieldType " + field.Type.ToString() + " !!!");
                    return 0;
            }
        }

        static void WriteBlockClass(TextWriter writer, MapBlock block, MapPacket packet)
        {
            int variableFieldCountBytes = 0;

            //writer.WriteLine("        /// <summary>" + block.Name + " block</summary>");
            writer.WriteLine("        /// <exclude/>");
            writer.WriteLine("        public sealed class " + block.Name + "Block : PacketBlock" + Environment.NewLine + "        {");

            foreach (MapField field in block.Fields)
            {
                WriteFieldMember(writer, field);
                if (field.Type == FieldType.Variable) { variableFieldCountBytes += field.Count; }
            }

            // Length property
            writer.WriteLine("");
            //writer.WriteLine("            /// <summary>Length of this block serialized in bytes</summary>");
            writer.WriteLine("            public override int Length" + Environment.NewLine +
                             "            {" + Environment.NewLine +
                             "                get" + Environment.NewLine +
                             "                {");
            int length = variableFieldCountBytes;

            // Figure out the length of this block
            foreach (MapField field in block.Fields)
            {
                length += GetFieldLength(writer, field);
            }

            if (variableFieldCountBytes == 0)
            {
                writer.WriteLine("                    return " + length + ";");
            }
            else
            {
                writer.WriteLine("                    int length = " + length + ";");

                foreach (MapField field in block.Fields)
                {
                    if (field.Type == FieldType.Variable)
                    {
                        writer.WriteLine("                    if (" + field.Name +
                            " != null) { length += " + field.Name + ".Length; }");
                    }
                }

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

            writer.WriteLine("                }" + Environment.NewLine + "            }" + Environment.NewLine);

            // Default constructor
            //writer.WriteLine("            /// <summary>Default constructor</summary>");
            writer.WriteLine("            public " + block.Name + "Block() { }");

            // Constructor for building the class from bytes
            //writer.WriteLine("            /// <summary>Constructor for building the block from a byte array</summary>");
            writer.WriteLine("            public " + block.Name + "Block(byte[] bytes, ref int i)" + Environment.NewLine +
                "            {" + Environment.NewLine +
                "                FromBytes(bytes, ref i);" + Environment.NewLine +
                "            }" + Environment.NewLine);

            // Initiates instance variables from a byte message
            writer.WriteLine("            public override void FromBytes(byte[] bytes, ref int i)" + Environment.NewLine +
                "            {");

            // Declare a length variable if we need it for variable fields in this constructor
            if (variableFieldCountBytes > 0) { writer.WriteLine("                int length;"); }

            // Start of the try catch block
            writer.WriteLine("                try" + Environment.NewLine + "                {");

            foreach (MapField field in block.Fields)
            {
                WriteFieldFromBytes(writer, field);
            }

            writer.WriteLine("                }" + Environment.NewLine +
                "                catch (Exception)" + Environment.NewLine +
                "                {" + Environment.NewLine +
                "                    throw new MalformedDataException();" + Environment.NewLine +
                "                }" + Environment.NewLine + "            }" + Environment.NewLine);

            // ToBytes() function
            //writer.WriteLine("            /// <summary>Serialize this block to a byte array</summary>");
            writer.WriteLine("            public override void ToBytes(byte[] bytes, ref int i)" + Environment.NewLine +
                "            {");

            foreach (MapField field in block.Fields)
            {
                WriteFieldToBytes(writer, field);
            }

            writer.WriteLine("            }" + Environment.NewLine);
            writer.WriteLine("        }" + Environment.NewLine);
        }

        static void WritePacketClass(TextWriter writer, MapPacket packet)
        {
            bool hasVariableBlocks = false;
            string sanitizedName;

            //writer.WriteLine("    /// <summary>" + packet.Name + " packet</summary>");
            writer.WriteLine("    /// <exclude/>");
            writer.WriteLine("    public sealed class " + packet.Name + "Packet : Packet" + Environment.NewLine + "    {");

            // Write out each block class
            foreach (MapBlock block in packet.Blocks)
            {
                WriteBlockClass(writer, block, packet);
            }

            // Length member
            writer.WriteLine("        public override int Length" + Environment.NewLine +
                "        {" + Environment.NewLine + "            get" + Environment.NewLine +
                "            {");

            int length = 0;
            if (packet.Frequency == PacketFrequency.Low) { length = 10; }
            else if (packet.Frequency == PacketFrequency.Medium) { length = 8; }
            else { length = 7; }

            foreach (MapBlock block in packet.Blocks)
            {
                if (block.Count == -1)
                {
                    hasVariableBlocks = true;
                    ++length;
                }
            }

            writer.WriteLine("                int length = " + length + ";");

            foreach (MapBlock block in packet.Blocks)
            {
                if (block.Name == "Header") { sanitizedName = "_" + block.Name; }
                else { sanitizedName = block.Name; }

                if (block.Count == -1)
                {
                    // Variable count block
                    writer.WriteLine("                for (int j = 0; j < " + sanitizedName + ".Length; j++)");
                    writer.WriteLine("                    length += " + sanitizedName + "[j].Length;");
                }
                else if (block.Count == 1)
                {
                    writer.WriteLine("                length += " + sanitizedName + ".Length;");
                }
                else
                {
                    // Multiple count block
                    writer.WriteLine("                for (int j = 0; j < " + block.Count + "; j++)");
                    writer.WriteLine("                    length += " + sanitizedName + "[j].Length;");
                }
            }
            writer.WriteLine("                return length;");
            writer.WriteLine("            }" + Environment.NewLine + "        }");

            // Block members
            foreach (MapBlock block in packet.Blocks)
            {
                // TODO: More thorough name blacklisting
                if (block.Name == "Header") { sanitizedName = "_" + block.Name; }
                else { sanitizedName = block.Name; }

                //writer.WriteLine("        /// <summary>" + block.Name + " block</summary>");
                writer.WriteLine("        public " + block.Name + "Block" +
                    ((block.Count != 1) ? "[]" : "") + " " + sanitizedName + ";");
            }

            writer.WriteLine("");

            // Default constructor
            //writer.WriteLine("        /// <summary>Default constructor</summary>");
            writer.WriteLine("        public " + packet.Name + "Packet()" + Environment.NewLine + "        {");
            writer.WriteLine("            HasVariableBlocks = " + hasVariableBlocks.ToString().ToLowerInvariant() + ";");
            writer.WriteLine("            Type = PacketType." + packet.Name + ";");
            writer.WriteLine("            Header = new Header();");
            writer.WriteLine("            Header.Frequency = PacketFrequency." + packet.Frequency + ";");
            writer.WriteLine("            Header.ID = " + packet.ID + ";");
            writer.WriteLine("            Header.Reliable = true;"); // Turn the reliable flag on by default
            if (packet.Encoded) { writer.WriteLine("            Header.Zerocoded = true;"); }
            foreach (MapBlock block in packet.Blocks)
            {
                if (block.Name == "Header") { sanitizedName = "_" + block.Name; }
                else { sanitizedName = block.Name; }

                if (block.Count == 1)
                {
                    // Single count block
                    writer.WriteLine("            " + sanitizedName + " = new " + block.Name + "Block();");
                }
                else if (block.Count == -1)
                {
                    // Variable count block
                    writer.WriteLine("            " + sanitizedName + " = null;");
                }
                else
                {
                    // Multiple count block
                    writer.WriteLine("            " + sanitizedName + " = new " + block.Name + "Block[" + block.Count + "];");
                }
            }
            writer.WriteLine("        }" + Environment.NewLine);

            // Constructor that takes a byte array and beginning position only (no prebuilt header)
            bool seenVariable = false;
            //writer.WriteLine("        /// <summary>Constructor that takes a byte array and beginning position (no prebuilt header)</summary>");
            writer.WriteLine("        public " + packet.Name + "Packet(byte[] bytes, ref int i) : this()" + Environment.NewLine +
                "        {" + Environment.NewLine +
                "            int packetEnd = bytes.Length - 1;" + Environment.NewLine +
                "            FromBytes(bytes, ref i, ref packetEnd, null);" + Environment.NewLine +
                "        }" + Environment.NewLine);

            writer.WriteLine("        override public void FromBytes(byte[] bytes, ref int i, ref int packetEnd, byte[] zeroBuffer)" + Environment.NewLine + "        {");
            writer.WriteLine("            Header.FromBytes(bytes, ref i, ref packetEnd);");
            writer.WriteLine("            if (Header.Zerocoded && zeroBuffer != null)");
            writer.WriteLine("            {");
            writer.WriteLine("                packetEnd = Helpers.ZeroDecode(bytes, packetEnd + 1, zeroBuffer) - 1;");
            writer.WriteLine("                bytes = zeroBuffer;");
            writer.WriteLine("            }");

            foreach (MapBlock block in packet.Blocks)
            {
                if (block.Name == "Header") { sanitizedName = "_" + block.Name; }
                else { sanitizedName = block.Name; }

                if (block.Count == 1)
                {
                    // Single count block
                    writer.WriteLine("            " + sanitizedName + ".FromBytes(bytes, ref i);");
                }
                else if (block.Count == -1)
                {
                    // Variable count block
                    if (!seenVariable)
                    {
                        writer.WriteLine("            int count = (int)bytes[i++];");
                        seenVariable = true;
                    }
                    else
                    {
                        writer.WriteLine("            count = (int)bytes[i++];");
                    }
                    writer.WriteLine("            if(" + sanitizedName + " == null || " + sanitizedName + ".Length != " + block.Count + ") {");
                    writer.WriteLine("                " + sanitizedName + " = new " + block.Name + "Block[count];");
                    writer.WriteLine("                for(int j = 0; j < count; j++)");
                    writer.WriteLine("                { " + sanitizedName + "[j] = new " + block.Name + "Block(); }");
                    writer.WriteLine("            }");
                    writer.WriteLine("            for (int j = 0; j < count; j++)");
                    writer.WriteLine("            { " + sanitizedName + "[j].FromBytes(bytes, ref i); }");
                }
                else
                {
                    // Multiple count block
                    writer.WriteLine("            if(" + sanitizedName + " == null || " + sanitizedName + ".Length != " + block.Count + ") {");
                    writer.WriteLine("                " + sanitizedName + " = new " + block.Name + "Block[" + block.Count + "];");
                    writer.WriteLine("                for(int j = 0; j < " + block.Count + "; j++)");
                    writer.WriteLine("                { " + sanitizedName + "[j] = new " + block.Name + "Block(); }");
                    writer.WriteLine("            }");
                    writer.WriteLine("            for (int j = 0; j < " + block.Count + "; j++)");
                    writer.WriteLine("            { " + sanitizedName + "[j].FromBytes(bytes, ref i); }");
                }
            }
            writer.WriteLine("        }" + Environment.NewLine);

            seenVariable = false;

            // Constructor that takes a byte array and a prebuilt header
            //writer.WriteLine("        /// <summary>Constructor that takes a byte array and a prebuilt header</summary>");
            writer.WriteLine("        public " + packet.Name + "Packet(Header head, byte[] bytes, ref int i): this()" + Environment.NewLine +
                "        {" + Environment.NewLine +
                "            int packetEnd = bytes.Length - 1;" + Environment.NewLine +
                "            FromBytes(head, bytes, ref i, ref packetEnd);" + Environment.NewLine +
                "        }" + Environment.NewLine);

            writer.WriteLine("        override public void FromBytes(Header header, byte[] bytes, ref int i, ref int packetEnd)" + Environment.NewLine +
                "        {");
            writer.WriteLine("            Header = header;");
            foreach (MapBlock block in packet.Blocks)
            {
                if (block.Name == "Header") { sanitizedName = "_" + block.Name; }
                else { sanitizedName = block.Name; }

                if (block.Count == 1)
                {
                    // Single count block
                    writer.WriteLine("            " + sanitizedName + ".FromBytes(bytes, ref i);");
                }
                else if (block.Count == -1)
                {
                    // Variable count block
                    if (!seenVariable)
                    {
                        writer.WriteLine("            int count = (int)bytes[i++];");
                        seenVariable = true;
                    }
                    else
                    {
                        writer.WriteLine("            count = (int)bytes[i++];");
                    }
                    writer.WriteLine("            if(" + sanitizedName + " == null || " + sanitizedName + ".Length != count) {");
                    writer.WriteLine("                " + sanitizedName + " = new " + block.Name + "Block[count];");
                    writer.WriteLine("                for(int j = 0; j < count; j++)");
                    writer.WriteLine("                { " + sanitizedName + "[j] = new " + block.Name + "Block(); }");
                    writer.WriteLine("            }");
                    writer.WriteLine("            for (int j = 0; j < count; j++)");
                    writer.WriteLine("            { " + sanitizedName + "[j].FromBytes(bytes, ref i); }");
                }
                else
                {
                    // Multiple count block
                    writer.WriteLine("            if(" + sanitizedName + " == null || " + sanitizedName + ".Length != " + block.Count + ") {");
                    writer.WriteLine("                " + sanitizedName + " = new " + block.Name + "Block[" + block.Count + "];");
                    writer.WriteLine("                for(int j = 0; j < " + block.Count + "; j++)");
                    writer.WriteLine("                { " + sanitizedName + "[j] = new " + block.Name + "Block(); }");
                    writer.WriteLine("            }");
                    writer.WriteLine("            for (int j = 0; j < " + block.Count + "; j++)");
                    writer.WriteLine("            { " + sanitizedName + "[j].FromBytes(bytes, ref i); }");
                }
            }
            writer.WriteLine("        }" + Environment.NewLine);

            #region ToBytes() Function

            //writer.WriteLine("        /// <summary>Serialize this packet to a byte array</summary><returns>A byte array containing the serialized packet</returns>");
            writer.WriteLine("        public override byte[] ToBytes()" + Environment.NewLine + "        {");

            writer.Write("            int length = ");
            if (packet.Frequency == PacketFrequency.Low) { writer.WriteLine("10;"); }
            else if (packet.Frequency == PacketFrequency.Medium) { writer.WriteLine("8;"); }
            else { writer.WriteLine("7;"); }

            foreach (MapBlock block in packet.Blocks)
            {
                if (block.Name == "Header") { sanitizedName = "_" + block.Name; }
                else { sanitizedName = block.Name; }

                if (block.Count == 1)
                {
                    // Single count block
                    writer.WriteLine("            length += " + sanitizedName + ".Length;");
                }
            }

            foreach (MapBlock block in packet.Blocks)
            {
                if (block.Name == "Header") { sanitizedName = "_" + block.Name; }
                else { sanitizedName = block.Name; }

                if (block.Count == -1)
                {
                    writer.WriteLine("            length++;");
                    writer.WriteLine("            for (int j = 0; j < " + sanitizedName +
                        ".Length; j++) { length += " + sanitizedName + "[j].Length; }");
                }
                else if (block.Count > 1)
                {
                    writer.WriteLine("            for (int j = 0; j < " + block.Count +
                        "; j++) { length += " + sanitizedName + "[j].Length; }");
                }
            }

            writer.WriteLine("            if (Header.AckList != null && Header.AckList.Length > 0) { length += Header.AckList.Length * 4 + 1; }");
            writer.WriteLine("            byte[] bytes = new byte[length];");
            writer.WriteLine("            int i = 0;");
            writer.WriteLine("            Header.ToBytes(bytes, ref i);");
            foreach (MapBlock block in packet.Blocks)
            {
                if (block.Name == "Header") { sanitizedName = "_" + block.Name; }
                else { sanitizedName = block.Name; }

                if (block.Count == -1)
                {
                    // Variable count block
                    writer.WriteLine("            bytes[i++] = (byte)" + sanitizedName + ".Length;");
                    writer.WriteLine("            for (int j = 0; j < " + sanitizedName +
                        ".Length; j++) { " + sanitizedName + "[j].ToBytes(bytes, ref i); }");
                }
                else if (block.Count == 1)
                {
                    writer.WriteLine("            " + sanitizedName + ".ToBytes(bytes, ref i);");
                }
                else
                {
                    // Multiple count block
                    writer.WriteLine("            for (int j = 0; j < " + block.Count +
                        "; j++) { " + sanitizedName + "[j].ToBytes(bytes, ref i); }");
                }
            }

            writer.WriteLine("            if (Header.AckList != null && Header.AckList.Length > 0) { Header.AcksToBytes(bytes, ref i); }");
            writer.WriteLine("            return bytes;" + Environment.NewLine + "        }" + Environment.NewLine);

            #endregion ToBytes() Function

            WriteToBytesMultiple(writer, packet);

            writer.WriteLine("    }" + Environment.NewLine);
        }

        static void WriteToBytesMultiple(TextWriter writer, MapPacket packet)
        {
            writer.WriteLine(
                "        public override byte[][] ToBytesMultiple()" + Environment.NewLine +
                "        {");

            // Check if there are any variable blocks
            bool hasVariable = false;
            bool cannotSplit = false;
            foreach (MapBlock block in packet.Blocks)
            {
                if (block.Count == -1)
                {
                    hasVariable = true;
                }
                else if (hasVariable)
                {
                    // A fixed or single block showed up after a variable count block.
                    // Our automatic splitting algorithm won't work for this packet
                    cannotSplit = true;
                    break;
                }
            }

            if (hasVariable && !cannotSplit)
            {
                writer.WriteLine(
                    "            System.Collections.Generic.List<byte[]> packets = new System.Collections.Generic.List<byte[]>();");
                writer.WriteLine(
                    "            int i = 0;");
                writer.Write(
                    "            int fixedLength = ");
                if (packet.Frequency == PacketFrequency.Low) { writer.WriteLine("10;"); }
                else if (packet.Frequency == PacketFrequency.Medium) { writer.WriteLine("8;"); }
                else { writer.WriteLine("7;"); }
                writer.WriteLine();

                // ACK serialization
                writer.WriteLine("            byte[] ackBytes = null;");
                writer.WriteLine("            int acksLength = 0;");
                writer.WriteLine("            if (Header.AckList != null && Header.AckList.Length > 0) {");
                writer.WriteLine("                Header.AppendedAcks = true;");
                writer.WriteLine("                ackBytes = new byte[Header.AckList.Length * 4 + 1];");
                writer.WriteLine("                Header.AcksToBytes(ackBytes, ref acksLength);");
                writer.WriteLine("            }");
                writer.WriteLine();

                // Count fixed blocks
                foreach (MapBlock block in packet.Blocks)
                {
                    string sanitizedName;
                    if (block.Name == "Header") { sanitizedName = "_" + block.Name; }
                    else { sanitizedName = block.Name; }

                    if (block.Count == 1)
                    {
                        // Single count block
                        writer.WriteLine("            fixedLength += " + sanitizedName + ".Length;");
                    }
                    else if (block.Count > 0)
                    {
                        // Fixed count block
                        writer.WriteLine("            for (int j = 0; j < " + block.Count + "; j++) { fixedLength += " + sanitizedName + "[j].Length; }");
                    }
                }

                // Serialize fixed blocks
                writer.WriteLine(
                    "            byte[] fixedBytes = new byte[fixedLength];");
                writer.WriteLine(
                    "            Header.ToBytes(fixedBytes, ref i);");
                foreach (MapBlock block in packet.Blocks)
                {
                    string sanitizedName;
                    if (block.Name == "Header") { sanitizedName = "_" + block.Name; }
                    else { sanitizedName = block.Name; }

                    if (block.Count == 1)
                    {
                        // Single count block
                        writer.WriteLine("            " + sanitizedName + ".ToBytes(fixedBytes, ref i);");
                    }
                    else if (block.Count > 0)
                    {
                        // Fixed count block
                        writer.WriteLine("            for (int j = 0; j < " + block.Count + "; j++) { " + sanitizedName + "[j].ToBytes(fixedBytes, ref i); }");
                    }
                }

                int variableCountBlock = 0;
                foreach (MapBlock block in packet.Blocks)
                {
                    string sanitizedName;
                    if (block.Name == "Header") { sanitizedName = "_" + block.Name; }
                    else { sanitizedName = block.Name; }

                    if (block.Count == -1)
                    {
                        // Variable count block
                        ++variableCountBlock;
                    }
                }
                writer.WriteLine("            fixedLength += " + variableCountBlock + ";");
                writer.WriteLine();

                foreach (MapBlock block in packet.Blocks)
                {
                    string sanitizedName;
                    if (block.Name == "Header") { sanitizedName = "_" + block.Name; }
                    else { sanitizedName = block.Name; }

                    if (block.Count == -1)
                    {
                        // Variable count block
                        writer.WriteLine("            int " + sanitizedName + "Start = 0;");
                    }
                }

                writer.WriteLine("            do");
                writer.WriteLine("            {");

                // Count how many variable blocks can go in this packet
                writer.WriteLine("                int variableLength = 0;");

                foreach (MapBlock block in packet.Blocks)
                {
                    string sanitizedName;
                    if (block.Name == "Header") { sanitizedName = "_" + block.Name; }
                    else { sanitizedName = block.Name; }

                    if (block.Count == -1)
                    {
                        // Variable count block
                        writer.WriteLine("                int " + sanitizedName + "Count = 0;");
                    }
                }
                writer.WriteLine();

                foreach (MapBlock block in packet.Blocks)
                {
                    string sanitizedName;
                    if (block.Name == "Header") { sanitizedName = "_" + block.Name; }
                    else { sanitizedName = block.Name; }

                    if (block.Count == -1)
                    {
                        // Variable count block
                        writer.WriteLine("                i = " + sanitizedName + "Start;");
                        writer.WriteLine("                while (fixedLength + variableLength + acksLength < Packet.MTU && i < " + sanitizedName + ".Length) {");
                        writer.WriteLine("                    int blockLength = " + sanitizedName + "[i].Length;");
                        writer.WriteLine("                    if (fixedLength + variableLength + blockLength + acksLength <= MTU) {");
                        writer.WriteLine("                        variableLength += blockLength;");
                        writer.WriteLine("                        ++" + sanitizedName + "Count;");
                        writer.WriteLine("                    }");
                        writer.WriteLine("                    else { break; }");
                        writer.WriteLine("                    ++i;");
                        writer.WriteLine("                }");
                        writer.WriteLine();
                    }
                }

                // Create the packet
                writer.WriteLine("                byte[] packet = new byte[fixedLength + variableLength + acksLength];");
                writer.WriteLine("                int length = fixedBytes.Length;");
                writer.WriteLine("                Buffer.BlockCopy(fixedBytes, 0, packet, 0, length);");
                // Remove the appended ACKs flag from subsequent packets
                writer.WriteLine("                if (packets.Count > 0) { packet[0] = (byte)(packet[0] & ~0x10); }");
                writer.WriteLine();

                foreach (MapBlock block in packet.Blocks)
                {
                    string sanitizedName;
                    if (block.Name == "Header") { sanitizedName = "_" + block.Name; }
                    else { sanitizedName = block.Name; }

                    if (block.Count == -1)
                    {
                        writer.WriteLine("                packet[length++] = (byte)" + sanitizedName + "Count;");
                        writer.WriteLine("                for (i = " + sanitizedName + "Start; i < " + sanitizedName + "Start + "
                            + sanitizedName + "Count; i++) { " + sanitizedName + "[i].ToBytes(packet, ref length); }");
                        writer.WriteLine("                " + sanitizedName + "Start += " + sanitizedName + "Count;");
                        writer.WriteLine();
                    }
                }

                // ACK appending
                writer.WriteLine("                if (acksLength > 0) {");
                writer.WriteLine("                    Buffer.BlockCopy(ackBytes, 0, packet, length, acksLength);");
                writer.WriteLine("                    acksLength = 0;");
                writer.WriteLine("                }");
                writer.WriteLine();

                writer.WriteLine("                packets.Add(packet);");
                
                writer.WriteLine("            } while (");
                bool first = true;
                foreach (MapBlock block in packet.Blocks)
                {
                    string sanitizedName;
                    if (block.Name == "Header") { sanitizedName = "_" + block.Name; }
                    else { sanitizedName = block.Name; }

                    if (block.Count == -1)
                    {
                        if (first) first = false;
                        else writer.WriteLine(" ||");

                        // Variable count block
                        writer.Write("                " + sanitizedName + "Start < " + sanitizedName + ".Length");
                    }
                }
                writer.WriteLine(");");
                writer.WriteLine();
                writer.WriteLine("            return packets.ToArray();");
                writer.WriteLine("        }");
            }
            else
            {
                writer.WriteLine("            return new byte[][] { ToBytes() };");
                writer.WriteLine("        }");
            }
        }

        static int Main(string[] args)
        {
            ProtocolManager protocol;
            List<string> unused = new List<string>();
            TextWriter writer;

            try
            {
                if (args.Length != 4)
                {
                    Console.WriteLine("Usage: [message_template.msg] [template.cs] [unusedpackets.txt] [_Packets_.cs]");
                    return -1;
                }

                writer = new StreamWriter(args[3]);
                protocol = new ProtocolManager(args[0]);

                // Build a list of unused packets
                using (StreamReader unusedReader = new StreamReader(args[2]))
                {
                    while (unusedReader.Peek() >= 0)
                    {
                        unused.Add(unusedReader.ReadLine().Trim());
                    }
                }

                // Read in the template.cs file and write it to our output
                TextReader reader = new StreamReader(args[1]);
                writer.WriteLine(reader.ReadToEnd());
                reader.Close();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
                return -2;
            }


            // Prune all of the unused packets out of the protocol
            int i = 0;
            foreach (MapPacket packet in protocol.LowMaps)
            {
                if (packet != null && unused.Contains(packet.Name))
                    protocol.LowMaps[i] = null;
                i++;
            }
            i = 0;
            foreach (MapPacket packet in protocol.MediumMaps)
            {
                if (packet != null && unused.Contains(packet.Name))
                    protocol.MediumMaps[i] = null;
                i++;
            }
            i = 0;
            foreach (MapPacket packet in protocol.HighMaps)
            {
                if (packet != null && unused.Contains(packet.Name))
                    protocol.HighMaps[i] = null;
                i++;
            }


            // Write the PacketType enum
            writer.WriteLine("    public enum PacketType" + Environment.NewLine + "    {" + Environment.NewLine +
                "        /// <summary>A generic value, not an actual packet type</summary>" + Environment.NewLine +
                "        Default,");
            foreach (MapPacket packet in protocol.LowMaps)
                if (packet != null)
                    writer.WriteLine("        " + packet.Name + " = " + (0x10000 | packet.ID) + ",");
            foreach (MapPacket packet in protocol.MediumMaps)
                if (packet != null)
                    writer.WriteLine("        " + packet.Name + " = " + (0x20000 | packet.ID) + ",");
            foreach (MapPacket packet in protocol.HighMaps)
                if (packet != null)
                    writer.WriteLine("        " + packet.Name + " = " + (0x30000 | packet.ID) + ",");
            writer.WriteLine("    }" + Environment.NewLine);

            // Write the base Packet class
            writer.WriteLine(
                "    public abstract partial class Packet" + Environment.NewLine + "    {" + Environment.NewLine +
                "        public const int MTU = 1200;" + Environment.NewLine +
                Environment.NewLine +
                "        public Header Header;" + Environment.NewLine +
                "        public bool HasVariableBlocks;" + Environment.NewLine +
                "        public PacketType Type;" + Environment.NewLine +
                "        public abstract int Length { get; }" + Environment.NewLine +
                "        public abstract void FromBytes(byte[] bytes, ref int i, ref int packetEnd, byte[] zeroBuffer);" + Environment.NewLine +
                "        public abstract void FromBytes(Header header, byte[] bytes, ref int i, ref int packetEnd);" + Environment.NewLine +
                "        public abstract byte[] ToBytes();" + Environment.NewLine +
                "        public abstract byte[][] ToBytesMultiple();"
            );
            writer.WriteLine();

            // Write the Packet.GetType() function
            writer.WriteLine(
                "        public static PacketType GetType(ushort id, PacketFrequency frequency)" + Environment.NewLine +
                "        {" + Environment.NewLine +
                "            switch (frequency)" + Environment.NewLine +
                "            {" + Environment.NewLine +
                "                case PacketFrequency.Low:" + Environment.NewLine +
                "                    switch (id)" + Environment.NewLine +
                "                    {");
            foreach (MapPacket packet in protocol.LowMaps)
                if (packet != null)
                    writer.WriteLine("                        case " + packet.ID + ": return PacketType." + packet.Name + ";");
            writer.WriteLine("                    }" + Environment.NewLine +
                "                    break;" + Environment.NewLine +
                "                case PacketFrequency.Medium:" + Environment.NewLine +
                "                    switch (id)" + Environment.NewLine + "                    {");
            foreach (MapPacket packet in protocol.MediumMaps)
                if (packet != null)
                    writer.WriteLine("                        case " + packet.ID + ": return PacketType." + packet.Name + ";");
            writer.WriteLine("                    }" + Environment.NewLine +
                "                    break;" + Environment.NewLine +
                "                case PacketFrequency.High:" + Environment.NewLine +
                "                    switch (id)" + Environment.NewLine + "                    {");
            foreach (MapPacket packet in protocol.HighMaps)
                if (packet != null)
                    writer.WriteLine("                        case " + packet.ID + ": return PacketType." + packet.Name + ";");
            writer.WriteLine("                    }" + Environment.NewLine +
                "                    break;" + Environment.NewLine + "            }" + Environment.NewLine + Environment.NewLine +
                "            return PacketType.Default;" + Environment.NewLine + "        }" + Environment.NewLine);

            // Write the Packet.BuildPacket(PacketType) function
            writer.WriteLine("        public static Packet BuildPacket(PacketType type)");
            writer.WriteLine("        {");
            foreach (MapPacket packet in protocol.HighMaps)
                if (packet != null)
                    writer.WriteLine("            if(type == PacketType." + packet.Name + ") return new " + packet.Name + "Packet();");
            foreach (MapPacket packet in protocol.MediumMaps)
                if (packet != null)
                    writer.WriteLine("            if(type == PacketType." + packet.Name + ") return new " + packet.Name + "Packet();");
            foreach (MapPacket packet in protocol.LowMaps)
                if (packet != null)
                    writer.WriteLine("            if(type == PacketType." + packet.Name + ") return new " + packet.Name + "Packet();");
            writer.WriteLine("            return null;" + Environment.NewLine);
            writer.WriteLine("        }");

            // Write the Packet.BuildPacket() function
            writer.WriteLine(@"
        public static Packet BuildPacket(byte[] packetBuffer, ref int packetEnd, byte[] zeroBuffer)
        {
            byte[] bytes;
            int i = 0;
            Header header = Header.BuildHeader(packetBuffer, ref i, ref packetEnd);
            if (header.Zerocoded)
            {
                packetEnd = Helpers.ZeroDecode(packetBuffer, packetEnd + 1, zeroBuffer) - 1;
                bytes = zeroBuffer;
            }
            else
            {
                bytes = packetBuffer;
            }
            Array.Clear(bytes, packetEnd + 1, bytes.Length - packetEnd - 1);

            switch (header.Frequency)
            {
                case PacketFrequency.Low:
                    switch (header.ID)
                    {");
            foreach (MapPacket packet in protocol.LowMaps)
                if (packet != null)
                    writer.WriteLine("                        case " + packet.ID + ": return new " + packet.Name + "Packet(header, bytes, ref i);");
            writer.WriteLine(@"
                    }
                    break;
                case PacketFrequency.Medium:
                    switch (header.ID)
                    {");
            foreach (MapPacket packet in protocol.MediumMaps)
                if (packet != null)
                    writer.WriteLine("                        case " + packet.ID + ": return new " + packet.Name + "Packet(header, bytes, ref i);");
            writer.WriteLine(@"
                    }
                    break;
                case PacketFrequency.High:
                    switch (header.ID)
                    {");
            foreach (MapPacket packet in protocol.HighMaps)
                if (packet != null)
                    writer.WriteLine("                        case " + packet.ID + ": return new " + packet.Name + "Packet(header, bytes, ref i);");
            writer.WriteLine(@"
                    }
                    break;
            }

            throw new MalformedDataException(""Unknown packet ID "" + header.Frequency + "" "" + header.ID);
        }
    }");

            // Write the packet classes
            foreach (MapPacket packet in protocol.LowMaps)
                if (packet != null) { WritePacketClass(writer, packet); }
            foreach (MapPacket packet in protocol.MediumMaps)
                if (packet != null) { WritePacketClass(writer, packet); }
            foreach (MapPacket packet in protocol.HighMaps)
                if (packet != null) { WritePacketClass(writer, packet); }


            // Finish up
            writer.WriteLine("}");
            writer.Close();
            return 0;
        }
    }
}