private void ParseVariableDeclaration()
        {
            while (file.Match(new byte[] { 0x00, 0x02 }))
            {
                if (file.IsMatch(new byte[] { 0x00 }) ||
                    file.IsMatch(new byte[] { 0x01 }) ||
                    file.IsMatch(new byte[] { 0x02 }) ||
                    file.IsMatch(new byte[] { 0x03 }) ||
                    file.IsMatch(new byte[] { 0x04 }) ||
                    file.IsMatch(new byte[] { 0x05 }) ||
                    file.IsMatch(new byte[] { 0x06 }) ||
                    file.IsMatch(new byte[] { 0x07 }) ||
                    file.IsMatch(new byte[] { 0x08 }))
                {
                    CotophaAST.CotophaVariable obj = new CotophaAST.CotophaVariable();

                    byte typeId = file.ReadByte();

                    obj.context = 0;
                    obj.type    = typeId;

                    if (typeId == 0)
                    {
                        obj.objectName = GetConstString();

                        //Console.WriteLine("PUSHCONST {0} {1:x2} {2} {3:x8}", variableTypes[typeId], objectNameLen, FindConstString(curOffset, imageOffset), unk3);
                    }

                    obj.name = file.ReadString();
                    functions.Last().stack.Add(obj);
                }
                else
                {
                    Console.WriteLine("Inspect code located at 0x{0:x8}", (int)file.GetPosition() - 1);
                    Environment.Exit(1);
                }

                ParseExpression();
            }
        }
        private void ParseExpression()
        {
            ParseVariableDeclaration();

            if (showDebug)
            {
                Console.WriteLine("pos[{0:x8}] op[{1:x2}]", file.GetPosition(), file.PeekByte());
            }

            if (file.Match(new byte[] { 0x02, 0x02, 0x01 })) // push "this"
            {
                CotophaAST.CotophaVariable obj = new CotophaAST.CotophaVariable();
                obj.name = "this";
                functions.Last().stack.Add(obj);
            }
            else if (file.Match(new byte[] { 0x02, 0x03, 0x04 })) // push global variable
            {
                functions.Last().stack.Add(GetGlobalVariable());
            }
            else if (file.Match(new byte[] { 0x02, 0x02, 0x06 }))
            {
                // variable initialization
                string variableName = file.ReadString();
                bool   hasParent    = false;
                bool   hasThis      = false;

                if (variableName == "parent")
                {
                    hasParent = true;
                }

                foreach (var cmd in functions.Last().arguments)
                {
                    if (cmd.argumentName == "this")
                    {
                        hasThis = true;
                        break;
                    }
                }

                CotophaAST.CotophaVariable obj = new CotophaAST.CotophaVariable();
                byte typeId  = 0;
                bool hasType = true;
                obj.hasValue = true;

                if (hasParent)
                {
                    obj.objectName = "parent";
                }
                else
                {
                    obj.objectName = "this";
                }

                obj.name = variableName;
                obj.type = typeId;

                if (obj.objectName == "parent")
                {
                    obj.type     = 0;
                    obj.showType = false;
                }

                if (file.Match(new byte[] { 0x02, 0x00 }))
                {
                    typeId = file.ReadByte();

                    //functions.Last().stack.Last().Print();
                    //obj.Print();

                    if (typeId == 0) // object
                    {
                        obj.stringValue = GetConstString();
                    }
                    else if (typeId == 4) // integer
                    {
                        obj.intValue = file.ReadInt32();
                    }
                    else if (typeId == 5) // real
                    {
                        obj.realValue = file.ReadDouble();
                    }
                    else if (typeId == 6) // string
                    {
                        obj.stringValue = file.ReadString();
                    }
                    else
                    {
                        functions.Last().stack.Last().Print();
                        Console.WriteLine("Unhandled variable assignment: stringValue type id {0} at 0x{1:x8}",
                                          typeId,
                                          (int)file.GetPosition());
                        Environment.Exit(1);
                    }
                }
                else
                {
                    obj.hasValue = false;
                    obj.showType = false;
                }

                functions.Last().stack.Add(obj);
            }
            else if (file.Match(new byte[] { 0x0b })) // subscript index
            {
                if (functions.Last().stack.Last().objType == typeof(CotophaAST.CotophaVariable))
                {
                    var obj = (CotophaAST.CotophaVariable)functions.Last().stack.Last();
                    functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                    var idxObj = new CotophaAST.CotophaInteger();
                    idxObj.intValue = obj.intValue;
                    obj.SetIndex(idxObj);

                    obj.hasValue = false;
                    //obj.showType = false;
                    functions.Last().stack.Add(obj);
                }
                else if (functions.Last().stack.Last().objType == typeof(CotophaAST.CotophaInteger))
                {
                    var obj1 = (CotophaAST.CotophaInteger)functions.Last().stack.Last();
                    functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                    var obj2 = (CotophaAST.CotophaVariable)functions.Last().stack.Last();
                    functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                    var obj3 = new CotophaAST.CotophaVariable();

                    var idxObj = new CotophaAST.CotophaInteger();
                    idxObj.intValue = obj1.intValue;
                    obj3.SetIndex(idxObj);

                    obj3.name     = obj2.name;
                    obj3.type     = 4;
                    obj3.showType = false;
                    obj3.hasValue = false;

                    functions.Last().stack.Add(obj3);
                }
                else if (functions.Last().stack.Last().objType == typeof(CotophaAST.CotophaString))
                {
                    var obj3 = new CotophaAST.CotophaVariable();

                    var obj1 = (CotophaAST.CotophaString)functions.Last().stack.Last();
                    functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                    obj3.type     = 4;
                    obj3.showType = false;
                    obj3.hasValue = false;
                    obj3.SetIndex(obj1);

                    if (functions.Last().stack.Last().objType == typeof(CotophaAST.CotophaString))
                    {
                        var obj2 = (CotophaAST.CotophaString)functions.Last().stack.Last();
                        functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);
                        obj3.name = obj2.stringValue;
                    }
                    else if (functions.Last().stack.Last().objType == typeof(CotophaAST.CotophaVariable))
                    {
                        var obj2 = (CotophaAST.CotophaVariable)functions.Last().stack.Last();
                        functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);
                        obj3.name = obj2.name;
                    }

                    functions.Last().stack.Add(obj3);
                }
                else
                {
                    Console.WriteLine("Don't know how to handle type {0}", functions.Last().stack.Last().objType);
                    Environment.Exit(1);
                }
            }
            else if (file.Match(new byte[] { 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00 })) // EndFunc
            {
                functions.Last().isStruct = false;
                //functions.Last().Print();
            }
            else if (file.Match(new byte[] { 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01 })) // EndStruct
            {
                functions.Last().isStruct = true;
                //functions.Last().Print();
            }
            else if (file.Match(new byte[] { 0x09, 0x00 })) // return
            {
                CotophaAST.CotophaReturn obj = new CotophaAST.CotophaReturn();
                var lastObject = functions.Last().stack.Last();
                functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                obj.returnObject = lastObject;
                //obj.returnObject.Print();

                functions.Last().stack.Add(obj);
            }
            else if (file.Match(new byte[] { 0x09, 0x01 })) // end of file
            {
                endOfFile = true;
            }
            else if (file.Match(new byte[] { 0x02, 0x00, 0x00 })) // object
            {
                string value = GetConstString();
                ;

                CotophaAST.CotophaObject obj = new CotophaAST.CotophaObject();
                obj.stringValue = value;
                functions.Last().stack.Add(obj);
            }
            else if (file.Match(new byte[] { 0x02, 0x00, 0x04 })) // integer
            {
                int value = file.ReadInt32();

                CotophaAST.CotophaInteger obj = new CotophaAST.CotophaInteger();
                obj.intValue = value;
                functions.Last().stack.Add(obj);
            }
            else if (file.Match(new byte[] { 0x02, 0x00, 0x05 })) // real
            {
                double value = file.ReadDouble();

                CotophaAST.CotophaReal obj = new CotophaAST.CotophaReal();
                obj.realValue = value;
                functions.Last().stack.Add(obj);
            }
            else if (file.Match(new byte[] { 0x02, 0x00, 0x06 })) // string
            {
                string value = file.ReadString();

                CotophaAST.CotophaString obj = new CotophaAST.CotophaString();
                obj.stringValue = value;
                functions.Last().stack.Add(obj);
            }
            else if (file.Match(new byte[] { 0x02, 0x01, 0x04 })) // variable reference
            {
                int idx           = file.ReadInt32();
                int variableCount = 0;

                CotophaAST.CotophaVariable variableName = new CotophaAST.CotophaVariable();

                bool found = false;
                foreach (var cmd in functions.Last().variables)
                {
                    if (idx == variableCount)
                    {
                        variableName.name = cmd.name;
                        found             = true;
                        break;
                    }

                    variableCount++;
                }

                if (!found)
                {
                    functions.Last().Print();

                    Console.WriteLine("Could not find variable");
                    Environment.Exit(1);
                }

                functions.Last().stack.Add(variableName);
            }
            else if (file.Match(new byte[] { 0x0a, 0x06 })) // member variable
            {
                if (functions.Last().stack.Last().objType == typeof(CotophaAST.CotophaString))
                {
                    var obj2 = (CotophaAST.CotophaString)functions.Last().stack.Last();
                    functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                    string name = GetConstString();

                    //functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                    obj2.stringValue += "." + name;
                    obj2.Print();

                    functions.Last().stack.Add(obj2);
                }
                else if (functions.Last().stack.Last().objType == typeof(CotophaAST.CotophaVariable))
                {
                    var obj2 = (CotophaAST.CotophaVariable)functions.Last().stack.Last();
                    functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                    string name = GetConstString();

                    //functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                    var nameObject = new CotophaAST.CotophaString();
                    nameObject.stringValue = name;

                    obj2.leftObject = nameObject;

                    functions.Last().stack.Add(obj2);
                }
                else
                {
                    Console.WriteLine("Unexpected left name type: {0}", functions.Last().stack.Last().objType);
                    Environment.Exit(1);
                }
            }
            else if (file.Match(new byte[] { 0x03, 0xff }))
            {
                //Console.WriteLine("Reached 0x03 0xff {0:x2}", file.PeekByte());

                bool showType = !file.Match(new byte[] { 0x01 });

                var obj = functions.Last().stack.Last();
                functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                if (functions.Last().stack.Last().objType == typeof(CotophaAST.CotophaVariable))
                {
                    var obj2 = (CotophaAST.CotophaVariable)functions.Last().stack.Last();
                    functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                    obj2.hasValue    = true;
                    obj2.showType    = showType;
                    obj2.rightObject = obj;

                    functions.Last().stack.Add(obj2);
                    //obj2.Print();
                }
                else if (functions.Last().stack.Last().objType == typeof(CotophaAST.CotophaString))
                {
                    var obj2 = (CotophaAST.CotophaString)functions.Last().stack.Last();
                    functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                    var obj3 = new CotophaAST.CotophaVariable();

                    obj3.name        = obj2.stringValue;
                    obj3.hasValue    = true;
                    obj3.showType    = showType;
                    obj3.rightObject = obj;

                    functions.Last().stack.Add(obj3);
                    //obj3.Print();
                }
                else if (functions.Last().stack.Last().objType == typeof(CotophaAST.CotophaReal))
                {
                    var obj2 = (CotophaAST.CotophaReal)functions.Last().stack.Last();
                    functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                    var obj3 = new CotophaAST.CotophaCall();

                    obj3.left = "Real";
                    obj3.args.Add(obj);

                    functions.Last().stack.Add(obj3);
                }
                else
                {
                    Console.WriteLine("Unexpected left hand type: {0}", functions.Last().stack.Last().objType);
                    Environment.Exit(1);
                }

                if (file.Match(new byte[] { 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 })) // repeat
                {
                    functions.Last().stack.Add(new CotophaAST.CotophaRepeatStatement());
                    ParseVariableDeclaration();
                }
            }
            else if (file.Match(new byte[] { 0x00, 0x01 })) // local variable
            {
                if (file.IsMatch(new byte[] { 0x00 }) ||
                    file.IsMatch(new byte[] { 0x01 }) ||
                    file.IsMatch(new byte[] { 0x02 }) ||
                    file.IsMatch(new byte[] { 0x03 }) ||
                    file.IsMatch(new byte[] { 0x04 }) ||
                    file.IsMatch(new byte[] { 0x05 }) ||
                    file.IsMatch(new byte[] { 0x06 }) ||
                    file.IsMatch(new byte[] { 0x07 }) ||
                    file.IsMatch(new byte[] { 0x08 }))
                {
                    CotophaAST.CotophaVariable obj = new CotophaAST.CotophaVariable();

                    byte typeId = file.ReadByte();

                    obj.context = 0;
                    obj.type    = typeId;

                    if (typeId == 0)
                    {
                        obj.objectName = GetConstString();
                    }

                    obj.name = file.ReadString();
                    functions.Last().stack.Add(obj);
                }
                else
                {
                    Console.WriteLine("Handle non-0x02 0x00 case in 0x00 0x01 @ 0x{0:x8}", (int)file.GetPosition() - 1);
                    Environment.Exit(1);
                }
            }
            else if (file.Match(new byte[] { 0x03 })) // assignment operation
            {
                CotophaAST.CotophaAssignment assignment = new CotophaAST.CotophaAssignment();
                int op = file.ReadByte();

                var obj1 = functions.Last().stack.Last();
                functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                var obj2 = functions.Last().stack.Last();
                functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                //Console.Write("0x03 obj1: ");
                //obj1.Print();

                //Console.Write("0x03 obj2: ");
                //obj2.Print();

                assignment.op          = op;
                assignment.leftObject  = obj2;
                assignment.rightObject = obj1;

                functions.Last().stack.Add(assignment);

                //assignment.Print();
            }
            else if (file.Match(new byte[] { 0x0c })) // math operation
            {
                CotophaAST.CotophaMathOp mathop = new CotophaAST.CotophaMathOp();
                int op = file.ReadByte();

                var obj1 = functions.Last().stack.Last();
                functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                var obj2 = functions.Last().stack.Last();
                functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                //Console.Write("0x0c obj1: ");
                //obj1.Print();

                //Console.Write("0x0c obj2: ");
                //obj2.Print();

                mathop.op          = op;
                mathop.leftObject  = obj2;
                mathop.rightObject = obj1;

                functions.Last().stack.Add(mathop);

                //mathop.Print();
            }
            else if (file.Match(new byte[] { 0x0e })) // comparison operation
            {
                CotophaAST.CotophaComparison comparison = new CotophaAST.CotophaComparison();
                int op = file.ReadByte();

                var obj1 = functions.Last().stack.Last();
                functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                var obj2 = functions.Last().stack.Last();
                functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                comparison.op          = op;
                comparison.leftObject  = obj2;
                comparison.rightObject = obj1;

                functions.Last().stack.Add(comparison);
            }
            else if (file.Match(new byte[] { 0x07, 0x00 }))
            {
                int  blockSize       = file.ReadInt32();
                long currentPosition = file.GetPosition();

                var comparisonObj = functions.Last().stack.Last();
                functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                if (functions.Last().stack.Count > 0 &&
                    functions.Last().stack.Last().objType == typeof(CotophaAST.CotophaElseIfStatement))
                {
                    CotophaAST.CotophaElseIfStatement obj =
                        (CotophaAST.CotophaElseIfStatement)functions.Last().stack.Last();
                    functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                    obj.leftObject = comparisonObj;
                    functions.Last().stack.Add(obj);
                }
                else
                {
                    //Console.WriteLine("If statement {0:x8} ({0} bytes) in length", blockSize);

                    CotophaAST.CotophaIfStatement obj = new CotophaAST.CotophaIfStatement();
                    obj.leftObject = comparisonObj;
                    functions.Last().stack.Add(obj);
                }

                if (blockSize < 0)
                {
                    return;
                }

                do
                {
                    ParseExpression();
                } while (file.GetPosition() - currentPosition < blockSize);

                if (file.GetPosition() - currentPosition == blockSize)
                {
                    functions.Last().stack.Add(new CotophaAST.CotophaEndIfStatement());
                }
                else
                {
                    Console.WriteLine("If block wrong size: {2:x8} {0:x8} {1:x8}", file.GetPosition() - currentPosition, blockSize, file.GetPosition());
                    Environment.Exit(1);
                }
            }
            else if (file.Match(new byte[] { 0x06 }))
            {
                int  blockSize       = file.ReadInt32();
                long currentPosition = file.GetPosition();

                if (functions.Last().stack.Last().objType == typeof(CotophaAST.CotophaEndIfStatement))
                {
                    functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);
                    functions.Last().stack.Add(new CotophaAST.CotophaElseIfStatement());
                }

                //Console.WriteLine("ElseIf statement {0:x8} ({0} bytes) in length", blockSize);

                return;

                /*
                 * Console.WriteLine("{0:x8} {1:x2}", file.GetPosition(), file.PeekByte());
                 * functions.Last().Print();
                 *
                 * Environment.Exit(1);
                 */
            }
            else if (file.Match(new byte[] { 0x08, 0x02 })) // member function
            {
                int    arguments = file.ReadInt32();
                string name      = GetConstString();

                CotophaAST.CotophaCall obj = new CotophaAST.CotophaCall();
                obj.left = name;

                for (int i = 0; i < arguments; i++)
                {
                    var obj2 = functions.Last().stack.Last();
                    functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                    if (i + 1 < arguments)
                    {
                        obj.args.Add(obj2);
                    }
                    else
                    {
                        obj.leftObject = obj2;
                    }
                }

                obj.args.Reverse();

                functions.Last().stack.Add(obj);

                //file.Match(new byte[] {0x03, 0xff, 0x01});
            }
            else if (file.Match(new byte[] { 0x08 }))
            {
                byte   type      = file.ReadByte();
                int    arguments = file.ReadInt32();
                string name      = GetConstString();

                CotophaAST.CotophaCall obj = new CotophaAST.CotophaCall();

                obj.left  = name;
                obj.right = "";

                CotophaAST.CotophaAssignment assignment = new CotophaAST.CotophaAssignment();
                assignment.op = 8;

                /*
                 * if (functions.Last().stack.Count != 1)
                 * {
                 *  assignment.leftObject = functions.Last().stack.Last();
                 *  functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);
                 * }
                 */

                for (int i = 0; i < arguments; i++)
                {
                    var obj2 = functions.Last().stack.Last();
                    functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                    if (assignment.leftObject != null)
                    {
                        if (i + 1 < arguments)
                        {
                            obj.args.Add(obj2);
                        }
                        else
                        {
                            obj.leftObject = obj2;
                        }
                    }
                    else
                    {
                        obj.args.Add(obj2);
                    }
                }

                if (assignment.leftObject != null)
                {
                    assignment.rightObject = obj;
                    functions.Last().stack.Add(assignment);

                    //assignment.Print();
                }
                else
                {
                    functions.Last().stack.Add(obj);

                    //obj.Print();
                }

                //file.Match(new byte[] {0x03, 0xff, 0x01});
            }
            else if (file.Match(new byte[] { 0x05 })) // until
            {
                var obj = new CotophaAST.CotophaUntilStatement();

                do
                {
                    ParseExpression();
                } while (!file.IsMatch(new byte[] { 0x07, 0x00 }));

                ParseExpression(); // if statement

                if (functions.Last().stack.Last().objType != typeof(CotophaAST.CotophaIfStatement))
                {
                    Console.WriteLine("Unexpected type on right of Until. Found {0}", functions.Last().stack.Last().objType);
                    Environment.Exit(1);
                }

                var obj2 = functions.Last().stack.Last();
                functions.Last().stack.RemoveAt(functions.Last().stack.Count - 1);

                obj.rightObject = obj2.leftObject;

                functions.Last().stack.Add(obj);
            }
            else if (file.Match(new byte[] { 0x01 })) // ?
            {
                //Console.WriteLine("0x01, ignoring...");
            }
            else
            {
                /*
                 * Console.WriteLine("{0:x8} {1:x2}", file.GetPosition(), file.PeekByte());
                 * Console.WriteLine("Don't know where to go from here");
                 * Environment.Exit(1);
                 */
                return;
            }

            //ParseExpression();

            if (showDebug)
            {
                if (functions.Last().stack.Count > 0)
                {
                    Console.Write("{0} -> ", functions.Last().stack.Last().objType);
                    functions.Last().stack.Last().Print();
                }

                functions.Last().Print();
            }
        }