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(); }
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(); }
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(); }
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; }