public static List <int> FindBlockAddresses(GMCode.Bytecode bytecode, bool slow = true) { HashSet <int> addresses = new HashSet <int>(); if (bytecode.Instructions.Count != 0) { addresses.Add(0); for (int i = 0; i < bytecode.Instructions.Count; i++) { Instruction instr = bytecode.Instructions[i]; switch (instr.Kind) { case Instruction.Opcode.B: case Instruction.Opcode.Bf: case Instruction.Opcode.Bt: case Instruction.Opcode.PushEnv: addresses.Add(instr.Address + 4); addresses.Add(instr.Address + (instr.JumpOffset * 4)); break; case Instruction.Opcode.PopEnv: if (!instr.PopenvExitMagic) { addresses.Add(instr.Address + (instr.JumpOffset * 4)); } break; case Instruction.Opcode.Exit: case Instruction.Opcode.Ret: addresses.Add(instr.Address + 4); break; case Instruction.Opcode.Call: if (slow && i >= 4 && instr.Function.Target.Name?.Content == "@@try_hook@@") { int finallyBlock = (int)bytecode.Instructions[i - 4].Value; addresses.Add(finallyBlock); int catchBlock = (int)bytecode.Instructions[i - 2].Value; if (catchBlock != -1) { addresses.Add(catchBlock); } // Technically not usually a block here (before/after the call), but for our purposes, // this is easier to split into its own section to isolate it now. addresses.Add(instr.Address - 24); addresses.Add(instr.Address + 12); } break; } } } List <int> res = addresses.ToList(); res.Sort(); return(res); }
public static List <int> FindBlockAddresses(GMCode.Bytecode bytecode) { HashSet <int> addresses = new HashSet <int>(); if (bytecode.Instructions.Count != 0) { addresses.Add(0); } foreach (var i in bytecode.Instructions) { switch (i.Kind) { case GMCode.Bytecode.Instruction.Opcode.B: case GMCode.Bytecode.Instruction.Opcode.Bf: case GMCode.Bytecode.Instruction.Opcode.Bt: case GMCode.Bytecode.Instruction.Opcode.PushEnv: addresses.Add(i.Address + 4); addresses.Add(i.Address + (i.JumpOffset * 4)); break; case GMCode.Bytecode.Instruction.Opcode.PopEnv: if (!i.PopenvExitMagic) { addresses.Add(i.Address + (i.JumpOffset * 4)); } break; case GMCode.Bytecode.Instruction.Opcode.Exit: case GMCode.Bytecode.Instruction.Opcode.Ret: addresses.Add(i.Address + 4); break; } } List <int> res = addresses.ToList(); res.Sort(); return(res); }
public static string Disassemble(GMCode codeEntry, GMData data) { GMCode.Bytecode bytecode = codeEntry.BytecodeEntry; IList <GMString> strings = ((GMChunkSTRG)data.Chunks["STRG"]).List; StringBuilder sb = new StringBuilder(); sb.AppendLine($"# Name: {codeEntry.Name.Content}"); if (codeEntry.BytecodeOffset != 0) // Usually should be 0, but for information sake write this { sb.AppendLine($"# Offset: {codeEntry.BytecodeOffset}"); } List <int> blocks = FindBlockAddresses(bytecode); foreach (var i in bytecode.Instructions) { int ind = blocks.IndexOf(i.Address); if (ind != -1) { sb.AppendLine(); sb.AppendLine($":[{ind}]"); } if (i.Kind != GMCode.Bytecode.Instruction.Opcode.Break) { sb.Append(i.Kind.ToString().ToLower()); } switch (GMCode.Bytecode.Instruction.GetInstructionType(i.Kind)) { case GMCode.Bytecode.Instruction.InstructionType.SingleType: sb.Append($".{DataTypeToChar[i.Type1]}"); if (i.Kind == GMCode.Bytecode.Instruction.Opcode.Dup || i.Kind == GMCode.Bytecode.Instruction.Opcode.CallV) { sb.Append($" {i.Extra}"); } break; case GMCode.Bytecode.Instruction.InstructionType.DoubleType: sb.Append($".{DataTypeToChar[i.Type1]}.{DataTypeToChar[i.Type2]}"); break; case GMCode.Bytecode.Instruction.InstructionType.Comparison: sb.Append($".{DataTypeToChar[i.Type1]}.{DataTypeToChar[i.Type2]} {i.ComparisonKind}"); break; case GMCode.Bytecode.Instruction.InstructionType.Branch: if (i.Address + (i.JumpOffset * 4) == codeEntry.Length) { sb.Append(" [end]"); } else if (i.PopenvExitMagic) { sb.Append(" [magic]"); // magic popenv instruction when returning early inside a with statement } else { sb.Append($" [{blocks.IndexOf(i.Address + (i.JumpOffset * 4))}]"); } break; case GMCode.Bytecode.Instruction.InstructionType.Pop: sb.Append($".{DataTypeToChar[i.Type1]}.{DataTypeToChar[i.Type2]} "); if (i.Type1 == GMCode.Bytecode.Instruction.DataType.Int16) { sb.Append(i.SwapExtra.ToString()); // Special swap instruction } else { if (i.Type1 == GMCode.Bytecode.Instruction.DataType.Variable && i.TypeInst != GMCode.Bytecode.Instruction.InstanceType.Undefined) { sb.Append($"{i.TypeInst.ToString().ToLower()}."); } sb.Append(StringifyVariableRef(i.Variable)); } break; case GMCode.Bytecode.Instruction.InstructionType.Push: sb.Append($".{DataTypeToChar[i.Type1]} "); if (i.Type1 == GMCode.Bytecode.Instruction.DataType.Variable) { if (i.TypeInst != GMCode.Bytecode.Instruction.InstanceType.Undefined) { sb.Append($"{i.TypeInst.ToString().ToLower()}."); } sb.Append(StringifyVariableRef(i.Variable)); } else if (i.Type1 == GMCode.Bytecode.Instruction.DataType.String) { sb.Append($"\"{SanitizeString(strings[(int)i.Value].Content)}\""); } else if (i.Function != null) { sb.Append(i.Function.Target.Name.Content); } else { sb.Append((i.Value as IFormattable)?.ToString(null, CultureInfo.InvariantCulture) ?? i.Value.ToString()); } break; case GMCode.Bytecode.Instruction.InstructionType.Call: sb.Append($".{DataTypeToChar[i.Type1]} {i.Function.Target.Name.Content} {i.ArgumentsCount}"); break; case GMCode.Bytecode.Instruction.InstructionType.Break: sb.Append($"{BreakIDToName[(short)i.Value]}.{DataTypeToChar[i.Type1]}"); break; } sb.AppendLine(); } sb.AppendLine(); sb.Append(":[end]"); return(sb.ToString()); }