예제 #1
0
        public static string DecompileCode(GMFileContent content, RefData rdata, CodeInfo code)
        {
            if (code.Instructions.Length == 0)
                return String.Empty;

            var sb = new StringBuilder();

            var stack = new Stack<Expression>();
            var dupts = new List <Expression>();

            var firstI = (long)code.Instructions[0];

            var graph = BuildCFGraph(content, code);

            //TODO: CFG kind recognition stuff (if, if/else, for, while, etc)
            var i = 0;
            foreach (var g in graph)
            {
                var stmts = ParseStatements(content, rdata, code, g.Instructions, stack);

                sb  .Append(SR.HEX_PRE)
                    .Append(((long)g.Instructions[0] - firstI).ToString(SR.HEX_FM6))
                    .AppendLine(SR.COLON);

                foreach (var s in stmts)
                    sb.Append(SR.INDENT4).AppendLine(s.ToString());

                i++;
            }

            sb.Append(SR.INDENT4).AppendLine(FinalRet.ToString());

            return sb.ToString();
        }
예제 #2
0
        public static string DisplayInstructions(GMFileContent content, RefData rdata, CodeInfo code, AnyInstruction*[] instructions = null)
        {
            var instrs = instructions ?? code.Instructions;

            if (instrs.Length == 0)
                return String.Empty;

            var sb = new StringBuilder();

            var firstI = code.Instructions[0];

            for (int i = 0; i < instrs.Length; i++)
            {
                var iptr = instrs[i];
                var relInstr = (long)iptr - (long)firstI;

                sb  .Append(HEX_PRE).Append(relInstr.ToString(HEX_FM6))
                    .Append(' ').Append(iptr->Code().ToPrettyString()).Append(' ');

                switch (iptr->Kind())
                {
                    case InstructionKind.SingleType:
                        var st = iptr->SingleType;

                        sb.Append(st.Type.ToPrettyString());
                        break;
                    case InstructionKind.DoubleType:
                        var dt = iptr->DoubleType;

                        sb.Append(dt.Types);
                        break;
                    case InstructionKind.Goto:
                        var g = iptr->Goto;

                        sb.Append(HEX_PRE).Append((relInstr + g.Offset * 4L).ToString(HEX_FM6));
                        break;

                    #region set
                    case InstructionKind.Set:
                        var s = iptr->Set;

                        sb.Append(s.Types).Append(' ');

                        if (s.Instance <= InstanceType.StackTopOrGlobal)
                            sb.Append(s.Instance.ToPrettyString());
                        else
                        {
                            var o = SectionReader.GetObjectInfo(content, (uint)s.Instance);

                            sb.Append('[').Append(o.Name).Append(']');
                        }

                        sb.Append(':');

                        sb.Append(rdata.Variables[rdata.VarAccessors[(IntPtr)iptr]].Name);
                        sb.Append(s.DestVar.Type.ToPrettyString());
                        break;
                    #endregion
                    #region push
                    case InstructionKind.Push:
                        var pp = (PushInstruction*)iptr;
                        var p = iptr->Push;

                        sb.Append(p.Type.ToPrettyString()).Append(' ');

                        var r = p.ValueRest;

                        switch (p.Type)
                        {
                            case DataType.Int16:
                                sb.Append(p.Value.ToString(CultureInfo.InvariantCulture));
                                break;
                            case DataType.Variable:
                                var rv = *(Reference*)&r;

                                var inst = (InstanceType)p.Value;

                                if (inst <= InstanceType.StackTopOrGlobal)
                                    sb.Append(inst.ToPrettyString());
                                else
                                {
                                    var o = SectionReader.GetObjectInfo(content, (uint)inst);

                                    sb.Append('[').Append(o.Name).Append(']');
                                }
                                sb.Append(':');

                                sb.Append(rdata.Variables[rdata.VarAccessors[(IntPtr)iptr]].Name);
                                sb.Append(rv.Type.ToPrettyString());
                                break;
                            case DataType.Boolean:
                                sb.Append(((DwordBool*)&r)->ToPrettyString());
                                break;
                            case DataType.Double:
                                sb.Append(((double*)&r)->ToString(CultureInfo.InvariantCulture));
                                break;
                            case DataType.Single:
                                sb.Append(((float*)&r)->ToString(CultureInfo.InvariantCulture));
                                break;
                            case DataType.Int32:
                                sb.Append(unchecked((int)r).ToString(CultureInfo.InvariantCulture));
                                break;
                            case DataType.Int64:
                                sb.Append(((long*)&pp->ValueRest)->ToString(CultureInfo.InvariantCulture));
                                break;
                            case DataType.String:
                                sb.Append(SectionReader.GetStringInfo(content, p.ValueRest).Escape());
                                break;
                        }
                        break;
                    #endregion
                    #region call
                    case InstructionKind.Call:
                        var c = iptr->Call;

                        sb.Append(c.ReturnType.ToPrettyString()).Append(':')
                            .Append(c.Arguments).Append(' ');

                        sb.Append(rdata.Functions[rdata.FuncAccessors[(IntPtr)iptr]].Name);
                        sb.Append(c.Function.Type.ToPrettyString());
                        break;
                    #endregion

                    case InstructionKind.Break:
                        var b = iptr->Break;

                        sb.Append(b.Type.ToPrettyString()).Append(' ').Append(b.Signal);
                        break;
                }

                sb.AppendLine();
            }

            return sb.ToString();
        }
예제 #3
0
        public static Statement[] ParseStatements(GMFileContent content, RefData rdata, CodeInfo code, AnyInstruction*[] instr = null, Stack<Expression> stack = null, List<Expression> dupTars = null)
        {
            //! here be dragons

            stack   = stack   ?? new Stack<Expression>();
            dupTars = dupTars ?? new List <Expression>();
            instr   = instr   ?? code.Instructions;

            if (instr.Length == 0)
                return EmptyStmtArray;

            var stmts = new List<Statement>();

            var firstI = code.Instructions[0];

            //TODO: use locals

            Func<Expression> Pop  = () => stack.Count == 0 ? PopExpr : stack.Pop ();
          //Func<Expression> Peek = () => stack.Count == 0 ? PopExpr : stack.Peek();
            Func<int, IEnumerable<Expression>> PopMany = i =>
            {
                var ret = new List<Expression>();

                for (int j = 0; j < i; j++)
                    ret.Add(Pop());

                return ret;
            };
            #region Action FlushStack = () => { };
            Action FlushStack = () =>
            {
                var readd = new Stack<Expression>();

                //? not sure if this is a good idea (random 'push'es in the wild) (see TODO)
                stmts.AddRange(stack.PopAll().Where(e =>
                {
                    if (dupTars.Contains(e))
                    {
                        readd.Push(e);
                        return false;
                    }

                    return !(e is PopExpression); // 'push pop' is obviously stupid to emit
                }).Reverse().Select(e =>
                    e is UnaryOperatorExpression &&
                            ((UnaryOperatorExpression)e).Operator == UnaryOperator.Duplicate
                        ? (Statement)new DupStatement() : new PushStatement { Expr = e }));

                stack.PushRange(readd);
            };
            #endregion
            Action<Statement> AddStmt = s =>
            {
                FlushStack();

                stmts.Add(s);
            };
            Func<VariableType, Expression[]> TryGetIndices = vt =>
            {
                Expression index = null;

                var dimentions = 0;
                if (vt == VariableType.Array)
                {
                    index = Pop();

                    var arrInd = Pop();

                    if ((arrInd is LiteralExpression) && ((LiteralExpression)arrInd).Value is short)
                    {
                        var s = (short)((LiteralExpression)arrInd).Value;

                        switch (s)
                        {
                            case -1:
                                dimentions = 2;
                                break;
                            case -5:
                                dimentions = 1;
                                break;
                        }
                    }

                    if (dimentions == 0)
                    {
                        stack.Push(arrInd);
                        stack.Push(index);

                        index = null;
                    }
                }

                if (index == null)
                    return null;

                // analyse index for specified dimention
                switch (dimentions)
                {
                    case 2:
                        if (index is BinaryOperatorExpression && ((BinaryOperatorExpression)index).Operator == BinaryOperator.Addition)
                        {
                            var boe = (BinaryOperatorExpression)index;

                            var a = boe.Arg1;
                            var b = boe.Arg2;

                            if (a is BinaryOperatorExpression && ((BinaryOperatorExpression)a).Operator == BinaryOperator.Multiplication)
                            {
                                var a_ = (BinaryOperatorExpression)a;
                                var c = a_.Arg2;

                                if (c is LiteralExpression && ((LiteralExpression)c).ReturnType == DataType.Int32
                                        && (int /* should be */)((LiteralExpression)c).Value == 32000)
                                    return new[] { a_.Arg1, b };
                            }
                        }
                        break;
                }

                return new[] { index };
            };

            for (int i = 0; i < instr.Length; i++)
            {
                var ins = instr[i];

                #region stuff
                var pst = (SingleTypeInstruction*)ins;
                var pdt = (DoubleTypeInstruction*)ins;
                var pcl = (CallInstruction      *)ins;
                var pps = (PushInstruction      *)ins;
                var pse = (SetInstruction       *)ins;
                var pbr = (GotoInstruction      *)ins;
                var pbk = (BreakInstruction     *)ins;

                var st = ins->SingleType;
                var dt = ins->DoubleType;
                var cl = ins->Call      ;
                var ps = ins->Push      ;
                var se = ins->Set       ;

                var t1 = ins->Kind() == InstructionKind.SingleType ? st.Type
                      : (ins->Kind() == InstructionKind.DoubleType ? dt.Types.Type1 : 0);
                var t2 = ins->Kind() == InstructionKind.DoubleType ? dt.Types.Type2
                      : (ins->Kind() == InstructionKind.SingleType ? st.Type        : 0);
                #endregion

                switch (ins->Code())
                {
                    #region dup, pop
                    case OpCode.Dup:
                        var normal = true;
                        if (i < instr.Length - 1 && instr[i + 1]->OpCode == OpCode.Push)
                        {
                            var n = &instr[i + 1]->Push;
                            var t = ((Reference*)&n->ValueRest)->Type;

                            if (t == VariableType.Array && stack.Count > 1)
                            {
                                normal = false;

                                stack.Push(stack.Skip(1).First()); // second item
                                stack.Push(stack.Skip(1).First()); // first  item (original stack top)
                            }
                        }

                        if (!normal)
                            break;

                        if (!dupTars.Contains(stack.Peek()))
                            dupTars.Add(stack.Peek());

                        if (stack.Peek().WalkExprTree(e => e is CallExpression).Any(_ => _))
                        {
                            stack.Push(new UnaryOperatorExpression
                            {
                                Input        = stack.Peek(),
                                Operator     = UnaryOperator.Duplicate,
                                OriginalType = stack.Peek().ReturnType,
                                ReturnType   = st.Type
                            });

                            //AddStmt(new DupStatement());
                        }
                        else
                            stack.Push(stack.Peek());
                        break;
                    case OpCode.Pop:
                        if (stack.Count > 0 && stack.Peek() is CallExpression)
                            AddStmt(new CallStatement
                            {
                                Call = stack.Pop() as CallExpression
                            });
                        else
                            AddStmt(new PopStatement());
                        break;
                    #endregion
                    #region env
                    //TODO: use actual '(with obj ...)' syntax
                    //! it might mess with the CFG structure
                    case OpCode.PushEnv:
                        AddStmt(new PushEnvStatement
                        {
                            Target       = (AnyInstruction*)((byte*)ins + pbr->Offset * 4L),
                            TargetOffset = (byte*)ins + pbr->Offset * 4L - (byte*)firstI,
                            Parent       = stack.Pop()
                        });
                        break;
                    case OpCode.PopEnv :
                        AddStmt(new PopEnvStatement
                        {
                            Target       = (AnyInstruction*)((byte*)ins + pbr->Offset * 4L),
                            TargetOffset = (byte*)ins + pbr->Offset * 4L - (byte*)firstI
                        });
                        break;
                    #endregion
                    #region branch
                    case OpCode.Brt:
                    case OpCode.Brf:
                    case OpCode.Br:
                        AddStmt(new BranchStatement
                        {
                            Type         = pbr->Type(),
                            Conditional  = pbr->Type() == BranchType.Unconditional ? null : Pop(),
                            Target       = (AnyInstruction*)((byte*)ins + pbr->Offset * 4L),
                            TargetOffset = (byte*)ins + pbr->Offset * 4L - (byte*)firstI
                        });
                        break;
                    #endregion
                    #region break, ret, exit
                    case OpCode.Break:
                        stack.Push(new AssertExpression
                        {
                            ControlValue = pbk->Signal,
                            ReturnType   = pbk->Type,
                            Expr         = Pop()
                        });
                        break;
                    case OpCode.Ret:
                        AddStmt(new ReturnStatement
                        {
                            ReturnType = pst->Type,
                            RetValue   = Pop()
                        });
                        break;
                    case OpCode.Exit:
                        AddStmt(new ExitStatement());
                        break;
                    #endregion
                    #region set
                    case OpCode.Set:
                        var ind = TryGetIndices(se.DestVar.Type); // call before Value's pop
                        AddStmt(new SetStatement
                        {
                            OriginalType = se.Types.Type1,
                            ReturnType   = se.Types.Type2,
                            Type         = se.DestVar.Type,
                            OwnerType    = se.Instance,
                            OwnerName    = se.Instance > InstanceType.StackTopOrGlobal ? SectionReader.GetObjectInfo(content, (uint)se.Instance).Name : null,
                            Target       = rdata.Variables[rdata.VarAccessors[(IntPtr)ins]],
                            Value        = Pop(),
                            ArrayIndices = ind ?? TryGetIndices(se.DestVar.Type)
                        });
                        break;
                    #endregion
                    default:
                        switch (ins->ExprType())
                        {
                            #region variable
                            case ExpressionType.Variable:
                                var vt = ((Reference*)&pps->ValueRest)->Type;

                                if (vt == VariableType.StackTop && (InstanceType)ps.Value == InstanceType.StackTopOrGlobal)
                                {
                                    stack.Push(new MemberExpression
                                    {
                                        Owner        = Pop(),
                                        ReturnType   = ps.Type,
                                        Type         = vt,
                                        OwnerType    = (InstanceType)ps.Value,
                                        OwnerName    = se.Instance > InstanceType.StackTopOrGlobal ? SectionReader.GetObjectInfo(content, (uint)se.Instance).Name : null,
                                        Variable     = rdata.Variables[rdata.VarAccessors[(IntPtr)ins]],
                                        ArrayIndices = TryGetIndices(vt)
                                    });
                                }
                                else
                                    stack.Push(new VariableExpression
                                    {
                                        ReturnType   = ps.Type,
                                        Type         = vt,
                                        OwnerType    = (InstanceType)ps.Value,
                                        Variable     = rdata.Variables[rdata.VarAccessors[(IntPtr)ins]],
                                        ArrayIndices = TryGetIndices(vt)
                                    });
                                break;
                            #endregion
                            #region literal
                            case ExpressionType.Literal:
                                object v         = null;
                                var rest         = &pps->ValueRest;

                                #region get value
                                switch (ps.Type)
                                {
                                    case DataType.Int16:
                                        v = ps.Value;
                                        break;
                                    case DataType.Boolean:
                                        v = ((DwordBool*)rest)->IsTrue();
                                        break;
                                    case DataType.Double:
                                        v = *(double*)rest;
                                        break;
                                    case DataType.Single:
                                        v = *(float*)rest;
                                        break;
                                    case DataType.Int32:
                                        v = *(int*)rest;
                                        break;
                                    case DataType.Int64:
                                        v = *(long*)rest;
                                        break;
                                    case DataType.String:
                                        v = SectionReader.GetStringInfo(content, ps.ValueRest);
                                        break;
                                }
                                #endregion

                                stack.Push(new LiteralExpression
                                {
                                    ReturnType = ps.Type,
                                    Value      = v
                                });
                                break;
                            #endregion
                            #region call
                            case ExpressionType.Call:
                                stack.Push(new CallExpression
                                {
                                    ReturnType = cl.ReturnType,
                                    Type       = cl.Function.Type,
                                    Function   = rdata.Functions[rdata.FuncAccessors[(IntPtr)ins]],
                                    Arguments  = PopMany(cl.Arguments).Reverse().ToArray()
                                });
                                break;
                            #endregion
                            #region binaryop
                            case ExpressionType.BinaryOp:
                                var a1 = Pop();
                                var a2 = Pop();

                                stack.Push(new BinaryOperatorExpression
                                {
                                    OriginalType = t1,
                                    ReturnType   = t2,
                                    Arg1         = a2,
                                    Arg2         = a1,
                                    Operator     = ins->BinaryOp()
                                });
                                break;
                            #endregion
                            #region unaryop
                            case ExpressionType.UnaryOp:
                                stack.Push(new UnaryOperatorExpression
                                {
                                    OriginalType = t1,
                                    ReturnType   = t2,
                                    Input        = Pop(),
                                    Operator     = ins->UnaryOp()
                                });
                                break;
                            #endregion
                        }
                        break;
                }
            }

            FlushStack();

            return stmts.ToArray();
        }
예제 #4
0
        unsafe static void Main(string[] args)
        {
            var file = Path.GetFullPath(args.Length == 0 ? DATA_WIN : args[0]);

            if (Directory.Exists(file) && !File.Exists(file))
                file += Path.DirectorySeparatorChar + DATA_WIN;

            if (!File.Exists(file))
                Console.WriteLine(ERR_FILE_NF_1 + file + ERR_FILE_NF_2);

            var cd = Path.GetFullPath(Environment.CurrentDirectory);
            Environment.CurrentDirectory = Path.GetDirectoryName(file);

            foreach (var s in dirs)
                if (!Directory.Exists(s))
                    Directory.CreateDirectory(s);

            Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;

            using (var f = GMFile.GetFile(File.ReadAllBytes(file)))
            {
                var sb = new StringBuilder();

                #region init stuff
                //TODO: serialize
                var gen8 = SectionReader.GetGeneralInfo(f);
                var optn = SectionReader.GetOptionInfo(f);

                var vars = gen8.CanDisassembleCode ? SectionReader.GetRefDefs(f, f.Variables) : SectionReader.GetRefDefsWithOthers(f, f.Variables);
                var fns  = gen8.CanDisassembleCode ? SectionReader.GetRefDefs(f, f.Functions) : SectionReader.GetRefDefsWithLength(f, f.Functions);

                var varAccs = Disassembler.GetReferenceTable(f, vars);
                var  fnAccs = Disassembler.GetReferenceTable(f, fns );

                var rdata = new RefData
                {
                    Variables = vars,
                    Functions = fns ,

                    VarAccessors  = varAccs,
                    FuncAccessors =  fnAccs
                };
                #endregion

                //var c__ = Disassembler.DisassembleCode(f, 0xE0D);
                //var d = Decompiler.DecompileCode(f, rdata, c__);
                ////var d = Disassembler.DisplayInstructions(f, rdata, c__);

                //System.Windows.Forms.Clipboard.SetText(d);

                //if (f.Audio->Count >= 0)
                //    return;

                //TODO: use an actual serialization lib or something

                //goto SKIP;

                #region general
                {
                    Console.Write("Reading header data... ");

                    var gi = SectionReader.GetGeneralInfo(f);

                    sb.Clear()

                        .Append("Name="       ).AppendLine(gi.Name         )
                        .Append("FileName="   ).AppendLine(gi.FileName     )
                        .Append("Config="     ).AppendLine(gi.Configuration)
                        .Append("DisplayName=").AppendLine(gi.DisplayName  )

                        .Append("Debug="     ).Append(gi.IsDebug        ).AppendLine()
                        .Append("BCVersion=" ).Append(gi.BytecodeVersion).AppendLine()
                        .Append("GameId="    ).Append(gi.GameId         ).AppendLine()
                        .Append("Version="   ).Append(gi.Version        ).AppendLine()
                        .Append("WindowSize=").Append(gi.WindowSize     ).AppendLine()

                        .Append("Timestamp=").AppendLine(gi.Timestamp.ToString(SHORT_L /* 's': sortable */))

                        .Append("LicenseMD5=[").Append(String.Join(COMMA_S, gi.LicenseMD5Hash)).AppendLine(C_BRACKET)
                        .Append("LicenseCRC=").Append(HEX_PRE).AppendLine(gi.LicenceCRC32.ToString(HEX_FM8))

                        .Append("WeirdNums=[").Append(String.Join(COMMA_S, gi.WeirdNumbers)).AppendLine(C_BRACKET);

                    File.WriteAllText("general.txt", sb.ToString());

                    Console.WriteLine(DONE);
                }
                #endregion
                #region options
                {
                    Console.Write("Reading option data... ");

                    var oi = SectionReader.GetOptionInfo(f);

                    sb.Clear().AppendLine("Constants=[");

                    foreach (var kvp in oi.Constants)
                        sb.Append(INDENT2).Append(kvp.Key).Append(EQ_S)
                            .Append(kvp.Value.Escape()).AppendLine(COMMA_S);

                    sb.AppendLine(C_BRACKET);

                    File.WriteAllText("option.txt", sb.ToString());

                    Console.WriteLine(DONE);
                }
                #endregion

                #region strings
                if (f.Strings->Count > 0)
                {
                    var sep = Environment.NewLine; //Environment.NewLine + new string('-', 80) + Environment.NewLine;

                    Console.Write("Reading strings... ");

                    var strings = new string[(int)f.Strings->Count];

                    for (uint i = 0; i < f.Strings->Count; i++)
                        strings[i] = SectionReader.GetStringInfo(f, i);

                    File.WriteAllText(FILE_STR, String.Join(sep, strings));

                    Console.WriteLine(DONE);
                }
                #endregion

                #region textures
                if (f.Textures->Count > 0)
                {
                    Console.Write("Reading textures... ");

                    for (uint i = 0; i < f.Textures->Count; i++)
                    {
                        var ti = SectionReader.GetTextureInfo(f, i);

                        File.WriteAllBytes(DIR_TEX + i + EXT_PNG, ti.PngData);
                    }

                    Console.WriteLine(DONE);
                }
                #endregion
                #region texture pages
                if (f.TexturePages->Count > 0)
                {
                    Console.Write("Reading texture pages (maps)... ");

                    for (uint i = 0; i < f.TexturePages->Count; i++)
                    {
                        var tpi = SectionReader.GetTexPageInfo(f, i);

                        sb.Clear()
                            .Append("Position="    ).Append(tpi.Position     ).AppendLine()
                            .Append("Size="        ).Append(tpi.Size         ).AppendLine()
                            .Append("RenderOffset=").Append(tpi.RenderOffset ).AppendLine()
                            .Append("BoundingBox=" ).Append(tpi.BoundingBox  ).AppendLine()
                            .Append("SheetId="     ).Append(tpi.SpritesheetId).AppendLine();

                        File.WriteAllText(DIR_TXP + i + EXT_TXT, sb.ToString());
                    }

                    Console.WriteLine(DONE);
                }
                #endregion
                #region sprite
                if (f.Sprites->Count > 0)
                {
                    Console.Write("Reading sprites... ");

                    for (uint i = 0; i < f.Sprites->Count; i++)
                    {
                        var si = SectionReader.GetSpriteInfo(f, i);

                        sb.Clear()
                            .Append("Size="    ).Append(si.Size    ).AppendLine()
                            .Append("Bounding=").Append(si.Bounding).AppendLine()
                            .Append("BBoxMode=").Append(si.BBoxMode).AppendLine()
                            .Append("SepMasks=").Append(si.SepMasks).AppendLine()
                            .Append("Origin="  ).Append(si.Origin  ).AppendLine()

                            .Append("TextureIndices=[").Append(String.Join(COMMA_S, si.TextureIndices)).Append(']').AppendLine();

                        File.WriteAllText(DIR_SPR + si.Name + EXT_TXT, sb.ToString());
                    }

                    Console.WriteLine(DONE);
                }
                #endregion

                #region sound
                if (f.Sounds->Count > 0)
                {
                    Console.Write("Reading sounds... ");

                    for (uint i = 0; i < f.Sounds->Count; i++)
                    {
                        var si = SectionReader.GetSoundInfo(f, i);

                        sb.Clear()
                            .Append("Type="      ).AppendLine(si.Type)
                            .Append("File="      ).AppendLine(si.File)
                            .Append("Embedded="  ).Append(si.IsEmbedded  ).AppendLine()
                            .Append("Compressed=").Append(si.IsCompressed).AppendLine()
                            .Append("AudioId="   ).Append(si.AudioId     ).AppendLine()
                            .Append("Volume="    ).Append(si.VolumeMod   ).AppendLine()
                            .Append("Pitch="     ).Append(si.PitchMod    ).AppendLine()
                            .Append("Pan="       ).Append(si.PanMod      ).AppendLine();

                        File.WriteAllText(DIR_SND + si.Name + EXT_TXT, sb.ToString());
                    }

                    Console.WriteLine(DONE);
                }
                #endregion
                #region audio
                if (f.Audio->Count > 0)
                {
                    Console.Write("Reading audio... ");

                    var sounds = Enumerable.Range(0, (int)f.Sounds->Count)
                                    .Select(i => SectionReader.GetSoundInfo(f, (uint)i));

                    var infoTable = new Dictionary<int, SoundInfo>();

                    foreach (var s in sounds)
                        if ((s.IsEmbedded || s.IsCompressed) && s.AudioId != -1)
                            infoTable[s.AudioId] = s;

                    for (int i = 0; i < f.Audio->Count; i++)
                    {
                        var ai = SectionReader.GetAudioInfo(f, (uint)i);

                        File.WriteAllBytes(DIR_WAV + infoTable[i].Name + EXT_WAV, ai.Wave);
                    }

                    Console.WriteLine(DONE);
                }
                #endregion

                #region objects
                if (f.Objects->Count > 0)
                {
                    Console.Write("Reading objects... ");

                    for (uint i = 0; i < f.Objects->Count; i++)
                    {
                        var oi = SectionReader.GetObjectInfo(f, i);

                        sb.Clear()
                            .Append("SpriteIndex=").Append(oi.SpriteIndex ).AppendLine()
                            .Append("Visible="    ).Append(oi.IsVisible   ).AppendLine()
                            .Append("Solid="      ).Append(oi.IsSolid     ).AppendLine()
                            .Append("Depth="      ).Append(oi.Depth       ).AppendLine()
                            .Append("Persistent=" ).Append(oi.IsPersistent).AppendLine()

                            .Append("ParentId=" ).AppendLine(oi.ParentId ?.ToString() ?? String.Empty)
                            .Append("TexMaskId=").AppendLine(oi.TexMaskId?.ToString() ?? String.Empty)

                            .AppendLine("Physics={")
                            .Append(INDENT2).Append("Density="       ).Append(oi.Physics.Density       ).AppendLine()
                            .Append(INDENT2).Append("Restitution="   ).Append(oi.Physics.Restitution   ).AppendLine()
                            .Append(INDENT2).Append("Group="         ).Append(oi.Physics.Group         ).AppendLine()
                            .Append(INDENT2).Append("LinearDamping=" ).Append(oi.Physics.LinearDamping ).AppendLine()
                            .Append(INDENT2).Append("AngularDamping=").Append(oi.Physics.AngularDamping).AppendLine()
                            .Append(INDENT2).Append("Unknown0="      ).Append(oi.Physics.Unknown0      ).AppendLine()
                            .Append(INDENT2).Append("Friction="      ).Append(oi.Physics.Friction      ).AppendLine()
                            .Append(INDENT2).Append("Unknown1="      ).Append(oi.Physics.Unknown1      ).AppendLine()
                            .Append(INDENT2).Append("Kinematic="     ).Append(oi.Physics.Kinematic     ).AppendLine()
                            .AppendLine(C_BRACE)

                            .Append("OtherFloats=[").Append(String.Join(COMMA_S, oi.OtherFloats)).AppendLine(C_BRACKET)
                            .AppendLine("ShapePoints=[");

                        foreach (var p in oi.ShapePoints)
                            sb.Append(INDENT2).Append(p.ToString()).AppendLine(COMMA_S);
                        sb.AppendLine(C_BRACKET);

                        File.WriteAllText(DIR_OBJ + oi.Name + EXT_TXT, sb.ToString());
                    }

                    Console.WriteLine(DONE);
                }
                #endregion
                #region backgrounds
                if (f.Backgrounds->Count > 0)
                {
                    Console.Write("Reading backgrounds... ");

                    for (uint i = 0; i < f.Backgrounds->Count; i++)
                    {
                        var bi = SectionReader.GetBgInfo(f, i);

                        File.WriteAllText(DIR_BG + bi.Name + EXT_TXT, "TPagIndex=" + bi.TexPageIndex);
                    }

                    Console.WriteLine(DONE);
                }
                #endregion
                #region rooms
                if (f.Rooms->Count > 0)
                {
                    Console.Write("Reading rooms... ");

                    for (uint i = 0; i < f.Rooms->Count; i++)
                    {
                        var ri = SectionReader.GetRoomInfo(f, i);

                        var t = "Size=" + ri.Size + Environment.NewLine + "Colour=" + ri.Colour.ToHexString() + "\0";

                        sb.Clear()
                            .Append("Caption=").AppendLine(ri.Caption)

                            .Append("Size="       ).Append(ri.Size        ).AppendLine()
                            .Append("Speed="      ).Append(ri.Speed       ).AppendLine()
                            .Append("Persist="    ).Append(ri.IsPersistent).AppendLine()
                            .Append("Colour="     ).Append(ri.Colour      ).AppendLine()
                            .Append("EnableViews=").Append(ri.EnableViews ).AppendLine()
                            .Append("ShowColour=" ).Append(ri.ShowColour  ).AppendLine()

                            .Append("World="         ).Append(ri.World         ).AppendLine()
                            .Append("Bounding="      ).Append(ri.Bounding      ).AppendLine()
                            .Append("Gravity="       ).Append(ri.Gravity       ).AppendLine()
                            .Append("MetresPerPixel=").Append(ri.MetresPerPixel).AppendLine();

                        //TODO: serialize arrays
                        File.WriteAllText(DIR_ROOM + ri.Name + EXT_TXT, sb.ToString());
                    }

                    Console.WriteLine(DONE);
                }
                #endregion

                #region variables
                if (vars.Length > 0)
                {
                    Console.Write("Reading variables... ");

                    sb.Clear();

                    for (int i = 0; i < vars.Length; i++)
                    {
                        var v = vars[i];

                        sb.AppendLine(v.Name);
                    }

                    File.WriteAllText(FILE_VAR, sb.ToString());

                    Console.WriteLine(DONE);
                }
                #endregion
                #region functions
                if (fns.Length > 0)
                {
                    Console.Write("Reading functions... ");

                    sb.Clear();

                    for (int i = 0; i < fns.Length; i++)
                    {
                        var fn = fns[i];

                        sb.AppendLine(fn.Name);
                    }

                    File.WriteAllText(FILE_FNS, sb.ToString());

                    Console.WriteLine(DONE);
                }
                #endregion

            //SKIP:

                #region script
                if (f.Scripts->Count > 0)
                {
                    Console.Write("Reading scripts... ");

                    for (uint i = 0; i < f.Scripts->Count; i++)
                    {
                        var si = SectionReader.GetScriptInfo(f, i);

                        sb.Clear().Append("CodeId=").Append(si.CodeId).AppendLine();

                        File.WriteAllText(DIR_SCR + si.Name + EXT_TXT, sb.ToString());
                    }

                    Console.WriteLine(DONE);
                }
                #endregion
                #region code
                if (f.Code->Count > 0)
                {
                    if (!gen8.CanDisassembleCode)
                        Console.WriteLine("Cannot decompile bytecode with version >0xE, skipping...");
                    else
                    {
                        Console.Write("Reading code... ");

                        for (uint i = 0; i < f.Code->Count; i++)
                        {
                            var ci = Disassembler.DisassembleCode(f, i);
                            var s  = Decompiler.DecompileCode(f, rdata, ci);

                            File.WriteAllText(DIR_CODE + ci.Name + EXT_GML_LSP, s);
                        }

                        Console.WriteLine(DONE);
                    }
                }
                #endregion

                #region fonts
                if (f.Fonts->Count > 0)
                {
                    Console.Write("Reading fonts... ");

                    for (uint i = 0; i < f.Fonts->Count; i++)
                    {
                        var fi = SectionReader.GetFontInfo(f, i);

                        sb.Clear()
                            .Append("SysName=").AppendLine(fi.SystemName)
                            .Append("EmSize=" ).Append(fi.EmSize  ).AppendLine()
                            .Append("Bold="   ).Append(fi.IsBold  ).AppendLine()
                            .Append("Italic=" ).Append(fi.IsItalic).AppendLine()

                            .Append("AntiAlias=").Append(fi.AntiAliasing).AppendLine()
                            .Append("Charset="  ).Append(fi.Charset     ).AppendLine()

                            .Append("TexPagId=").Append(fi.TexPagId).AppendLine()
                            .Append("Scale="   ).Append(fi.Scale   ).AppendLine()

                            .Append("Charset=").AppendLine(O_BRACKET);

                        foreach (var c in fi.Characters)
                        {
                            sb.Append(INDENT2).AppendLine(O_BRACE);

                            sb.Append(INDENT4).Append("Char='");

                            switch (c.Character)
                            {
                                case (char)0x7F:
                                    sb.Append(DEL_CHAR);
                                    break;
                                case '\n':
                                    sb.Append(LF_CHAR);
                                    break;
                                case '\r':
                                    sb.Append(CR_CHAR);
                                    break;
                                case '\t':
                                    sb.Append(TAB_CHAR);
                                    break;
                                case '\b':
                                    sb.Append(BELL_CHAR);
                                    break;
                                case '\0':
                                    sb.Append(NUL_CHAR);
                                    break;
                                default:
                                    sb.Append(c.Character);
                                    break;
                            }

                            sb.Append('\'').AppendLine()
                                .Append(INDENT4).Append("Frame=" ).Append(c.TPagFrame).AppendLine()
                                .Append(INDENT4).Append("Shift=" ).Append(c.Shift    ).AppendLine()
                                .Append(INDENT4).Append("Offset=").Append(c.Offset   ).AppendLine();

                            sb.Append(INDENT2).Append(C_BRACE).AppendLine(COMMA_S);
                        }
                        sb.AppendLine(C_BRACKET);

                        File.WriteAllText(DIR_FNT + fi.CodeName + EXT_TXT, sb.ToString());
                    }

                    Console.WriteLine(DONE);
                }
                #endregion
                #region paths
                if (f.Paths->Count > 0)
                {
                    Console.Write("Reading paths... ");

                    for (uint i = 0; i < f.Paths->Count; i++)
                    {
                        var pi = SectionReader.GetPathInfo(f, i);

                        sb.Clear()
                            .Append("Smooth="   ).Append(pi.IsSmooth ).AppendLine()
                            .Append("Closed="   ).Append(pi.IsClosed ).AppendLine()
                            .Append("Precision=").Append(pi.Precision).AppendLine()
                            .AppendLine("Points=[");

                        foreach (var p in pi.Points)
                        {
                            sb  .Append(INDENT2).AppendLine(O_BRACE)
                                .Append(INDENT4).Append("Position=").Append(p.Position).AppendLine()
                                .Append(INDENT4).Append("Speed="   ).Append(p.Speed   ).AppendLine()
                                .Append(INDENT2).Append(C_BRACE).AppendLine(COMMA_S);
                        }

                        sb.AppendLine(C_BRACKET);

                        File.WriteAllText(DIR_PATH + pi.Name + EXT_TXT, sb.ToString());
                    }

                    Console.WriteLine(DONE);
                }
                #endregion
            }

            Environment.CurrentDirectory = cd;
        }