// TODO: Improve the error messages public static UndertaleInstruction AssembleOne(string source, IList <UndertaleFunction> funcs, IList <UndertaleVariable> vars, IList <UndertaleString> strg, Dictionary <string, UndertaleVariable> localvars = null, UndertaleData data = null) { string label; UndertaleInstruction instr = AssembleOne(source, funcs, vars, strg, localvars, out label, data, null); if (label != null) { throw new Exception("Cannot use labels in this context"); } return(instr); }
private static UndertaleInstruction.Reference <UndertaleVariable> ParseVariableReference(string line, IList <UndertaleVariable> vars, Dictionary <string, UndertaleVariable> localvars, ref UndertaleInstruction.InstanceType instance, UndertaleInstruction instr, Func <int, UndertaleInstruction.InstanceType?> lookOnStack = null, UndertaleData data = null) { string str = line; string inst = null; int instdot = str.IndexOf('.'); if (instdot >= 0) { inst = str.Substring(0, instdot); str = str.Substring(instdot + 1); if (inst == "") { throw new Exception("Whoops?"); } } if (inst != null) { short instnum; if (Int16.TryParse(inst, out instnum)) { instance = (UndertaleInstruction.InstanceType)instnum; } else { instance = (UndertaleInstruction.InstanceType)Enum.Parse(typeof(UndertaleInstruction.InstanceType), inst, true); } } else { instance = UndertaleInstruction.InstanceType.Undefined; } UndertaleInstruction.VariableType type = UndertaleInstruction.VariableType.Normal; if (str[0] == '[') { int typeend = str.IndexOf(']'); if (typeend >= 0) { string typestr = str.Substring(1, typeend - 1); str = str.Substring(typeend + 1); type = (UndertaleInstruction.VariableType)Enum.Parse(typeof(UndertaleInstruction.VariableType), typestr, true); } } UndertaleInstruction.InstanceType realinstance = instance; // for arrays, the type is on the stack which totally breaks things // This is an ugly hack to handle that // see https://github.com/krzys-h/UndertaleModTool/issues/27#issuecomment-426637438 if (type == UndertaleInstruction.VariableType.Array && lookOnStack != null) { var instTypeOnStack = lookOnStack(instr.Kind == UndertaleInstruction.Opcode.Pop && instr.Type1 == UndertaleInstruction.DataType.Int32 ? 3 : 2); if (instTypeOnStack.HasValue) { realinstance = instTypeOnStack.Value; } } if (realinstance >= 0) { realinstance = UndertaleInstruction.InstanceType.Self; } else if (realinstance == UndertaleInstruction.InstanceType.Other) { realinstance = UndertaleInstruction.InstanceType.Self; } if (data?.GeneralInfo?.BytecodeVersion <= 14) { realinstance = UndertaleInstruction.InstanceType.Undefined; } UndertaleVariable varobj; if (realinstance == UndertaleInstruction.InstanceType.Local) { varobj = localvars.ContainsKey(str) ? localvars[str] : null; } else { varobj = vars.Where((x) => x.Name.Content == str && x.InstanceType == realinstance).FirstOrDefault(); } if (varobj == null) { throw new Exception("Bad variable: " + realinstance.ToString().ToLower() + "." + str); } return(new UndertaleInstruction.Reference <UndertaleVariable>(varobj, type)); }
public static UndertaleInstruction AssembleOne(string source, IList <UndertaleFunction> funcs, IList <UndertaleVariable> vars, IList <UndertaleString> strg, Dictionary <string, UndertaleVariable> localvars, out string label, UndertaleData data = null, Func <int, UndertaleInstruction.InstanceType?> lookOnStack = null) { label = null; string line = source; UndertaleInstruction instr = new UndertaleInstruction(); string opcode = line; int space = opcode.IndexOf(' '); if (space >= 0) { opcode = line.Substring(0, space); line = line.Substring(space + 1).Trim(); } else { line = ""; } string[] types = opcode.Split('.'); if (types.Length > 3) { throw new Exception("Too many type parameters"); } instr.Kind = (UndertaleInstruction.Opcode)Enum.Parse(typeof(UndertaleInstruction.Opcode), types[0], true); if (types.Length >= 2) { instr.Type1 = UndertaleInstructionUtil.FromOpcodeParam(types[1]); } if (types.Length >= 3) { instr.Type2 = UndertaleInstructionUtil.FromOpcodeParam(types[2]); } switch (UndertaleInstruction.GetInstructionType(instr.Kind)) { case UndertaleInstruction.InstructionType.SingleTypeInstruction: if (instr.Kind == UndertaleInstruction.Opcode.Dup) { instr.DupExtra = Byte.Parse(line); line = ""; } break; case UndertaleInstruction.InstructionType.DoubleTypeInstruction: break; case UndertaleInstruction.InstructionType.ComparisonInstruction: instr.ComparisonKind = (UndertaleInstruction.ComparisonType)Enum.Parse(typeof(UndertaleInstruction.ComparisonType), line, true); line = ""; break; case UndertaleInstruction.InstructionType.GotoInstruction: if (line[0] == '$') { instr.JumpOffset = Int32.Parse(line.Substring(1)); } else { if (line == "[drop]") { instr.JumpOffsetPopenvExitMagic = true; if (data?.GeneralInfo?.BytecodeVersion <= 14) { instr.JumpOffset = -1048576; // I really don't know at this point. Magic for little endian 00 00 F0 } } else { label = line; } } line = ""; break; case UndertaleInstruction.InstructionType.PopInstruction: if (instr.Type1 == UndertaleInstruction.DataType.Int16) { // Special scenario - the swap instruction // TODO: Figure out the proper syntax, see #129 instr.SwapExtra = Byte.Parse(line); } else { UndertaleInstruction.InstanceType inst = instr.TypeInst; instr.Destination = ParseVariableReference(line, vars, localvars, ref inst, instr, lookOnStack, data); instr.TypeInst = inst; } line = ""; break; case UndertaleInstruction.InstructionType.PushInstruction: switch (instr.Type1) { case UndertaleInstruction.DataType.Double: instr.Value = Double.Parse(line, CultureInfo.InvariantCulture); break; case UndertaleInstruction.DataType.Float: instr.Value = Single.Parse(line, CultureInfo.InvariantCulture); break; case UndertaleInstruction.DataType.Int32: int ival; if (Int32.TryParse(line, out ival)) { instr.Value = ival; } else { instr.Value = (int)ParseResourceName(line, data); } break; case UndertaleInstruction.DataType.Int64: long lval; if (Int64.TryParse(line, out lval)) { instr.Value = lval; } else { instr.Value = (long)ParseResourceName(line, data); } break; case UndertaleInstruction.DataType.Boolean: instr.Value = bool.Parse(line); break; case UndertaleInstruction.DataType.Variable: UndertaleInstruction.InstanceType inst2 = instr.TypeInst; instr.Value = ParseVariableReference(line, vars, localvars, ref inst2, instr, lookOnStack, data); instr.TypeInst = inst2; break; case UndertaleInstruction.DataType.String: instr.Value = ParseStringReference(line, strg); break; case UndertaleInstruction.DataType.Int16: short sval; if (Int16.TryParse(line, out sval)) { instr.Value = sval; } else { instr.Value = (short)ParseResourceName(line, data); } break; } line = ""; break; case UndertaleInstruction.InstructionType.CallInstruction: Match match = Regex.Match(line, @"^(.*)\(argc=(.*)\)$"); if (!match.Success) { throw new Exception("Call instruction format error"); } UndertaleFunction func = funcs.ByName(match.Groups[1].Value); if (func == null) { throw new Exception("Function not found: " + match.Groups[1].Value); } instr.Function = new UndertaleInstruction.Reference <UndertaleFunction>() { Target = func }; instr.ArgumentsCount = UInt16.Parse(match.Groups[2].Value); line = ""; break; case UndertaleInstruction.InstructionType.BreakInstruction: instr.Value = Int16.Parse(line); line = ""; break; } if (line != "") { throw new Exception("Excess parameters"); } return(instr); }
public static List <UndertaleInstruction> Assemble(string source, IList <UndertaleFunction> funcs, IList <UndertaleVariable> vars, IList <UndertaleString> strg, UndertaleData data = null) { var lines = source.Split('\n'); uint addr = 0; Dictionary <string, uint> labels = new Dictionary <string, uint>(); Dictionary <UndertaleInstruction, string> labelTargets = new Dictionary <UndertaleInstruction, string>(); List <UndertaleInstruction> instructions = new List <UndertaleInstruction>(); Dictionary <string, UndertaleVariable> localvars = new Dictionary <string, UndertaleVariable>(); foreach (var fullline in lines) { string line = fullline; if (line.Length > 0 && line[0] == ';') { continue; } string label = null; int labelEnd = line.IndexOf(':'); if (labelEnd > 0) { bool isLabel = true; for (var i = 0; i < labelEnd; i++) { if (!Char.IsLetterOrDigit(line[i]) && line[i] != '_') { isLabel = false; break; } } if (isLabel) { label = line.Substring(0, labelEnd).Trim(); line = line.Substring(labelEnd + 1); } } line = line.Trim(); if (String.IsNullOrEmpty(line)) { if (!String.IsNullOrEmpty(label)) { throw new Exception("Label with no instruction"); } else { continue; } } if (line.StartsWith(".")) { // Assembler directive // TODO: Does not update the CodeLocals block yet!! string[] aaa = line.Split(' '); if (aaa[0] == ".localvar") { if (aaa.Length >= 4) { var varii = vars[Int32.Parse(aaa[3])]; if (data?.GeneralInfo?.BytecodeVersion >= 15 && varii.InstanceType != UndertaleInstruction.InstanceType.Local) { throw new Exception("Not a local var"); } if (varii.Name.Content != aaa[2]) { throw new Exception("Name mismatch"); } localvars.Add(aaa[2], varii); } } else { throw new Exception("Unknown assembler directive: " + aaa[0]); } continue; } // Really ugly hack for compiling array variable references // See https://github.com/krzys-h/UndertaleModTool/issues/27#issuecomment-426637438 Func <int, UndertaleInstruction.InstanceType?> lookOnStack = (int amt) => { int stackCounter = amt; foreach (var i in instructions.Cast <UndertaleInstruction>().Reverse()) { if (stackCounter == 1) // This needs to be here because otherwise sth[aaa].another[bbb] doesn't work (damn this workaround is getting crazy, CHAOS, CHAOS) { if (i.Kind == UndertaleInstruction.Opcode.Push) { return(UndertaleInstruction.InstanceType.Self); // This is probably an instance variable then (e.g. pushi.e 1337; push.v self.someinstance; conv.v.i; pushi.e 0; pop.v.v [array]alarm) } else if (i.Kind == UndertaleInstruction.Opcode.PushLoc) { return(UndertaleInstruction.InstanceType.Local); } } //int old = stackCounter; stackCounter -= UndertaleInstruction.CalculateStackDiff(i); //Debug.WriteLine(i.ToString() + "; " + old + " -> " + stackCounter); if (stackCounter == 0) { if (i.Kind == UndertaleInstruction.Opcode.PushI) { return((UndertaleInstruction.InstanceType?)(short) i.Value); } else if (i.Kind == UndertaleInstruction.Opcode.Dup) { stackCounter += 1 + i.DupExtra; // Keep looking for the value that was duplicated } else { throw new Exception("My workaround still sucks"); } } } return(null); }; string labelTgt; UndertaleInstruction instr = AssembleOne(line, funcs, vars, strg, localvars, out labelTgt, data, lookOnStack); instr.Address = addr; if (labelTgt != null) { labelTargets.Add(instr, labelTgt); } if (!String.IsNullOrEmpty(label)) { if (labels.ContainsKey(label)) { throw new Exception("Duplicate label: " + label); } labels.Add(label, instr.Address); } instructions.Add(instr); addr += instr.CalculateInstructionSize(); } if (labels.ContainsKey("func_end")) { throw new Exception("func_end is a reserved label name"); } labels.Add("func_end", addr); foreach (var pair in labelTargets) { pair.Key.JumpOffset = (int)labels[pair.Value] - (int)pair.Key.Address; } return(instructions); }
private void DisassembleCode(UndertaleCode code) { code.UpdateAddresses(); FlowDocument document = new FlowDocument(); document.PagePadding = new Thickness(0); document.PageWidth = 2048; // Speed-up. document.FontFamily = new FontFamily("Lucida Console"); Paragraph par = new Paragraph(); par.Margin = new Thickness(0); if (code.Instructions.Count > 5000) { // Disable syntax highlighting. Loading it can take a few MINUTES on large scripts. var data = (Application.Current.MainWindow as MainWindow).Data; string[] split = code.Disassemble(data.Variables, data.CodeLocals.For(code)).Split('\n'); for (var i = 0; i < split.Length; i++) { // Makes it possible to select text. if (i > 0 && (i % 100) == 0) { document.Blocks.Add(par); par = new Paragraph(); par.Margin = new Thickness(0); } par.Inlines.Add(split[i] + (split.Length > i + 1 && ((i + 1) % 100) != 0 ? "\n" : "")); } } else { Brush addressBrush = new SolidColorBrush(Color.FromRgb(50, 50, 50)); Brush opcodeBrush = new SolidColorBrush(Color.FromRgb(0, 100, 0)); Brush argBrush = new SolidColorBrush(Color.FromRgb(0, 0, 150)); Brush typeBrush = new SolidColorBrush(Color.FromRgb(0, 0, 50)); var data = (Application.Current.MainWindow as MainWindow).Data; par.Inlines.Add(new Run(code.GenerateLocalVarDefinitions(data.Variables, data.CodeLocals.For(code))) { Foreground = addressBrush }); foreach (var instr in code.Instructions) { par.Inlines.Add(new Run(instr.Address.ToString("D5") + ": ") { Foreground = addressBrush }); par.Inlines.Add(new Run(instr.Kind.ToString().ToLower()) { Foreground = opcodeBrush, FontWeight = FontWeights.Bold }); switch (UndertaleInstruction.GetInstructionType(instr.Kind)) { case UndertaleInstruction.InstructionType.SingleTypeInstruction: par.Inlines.Add(new Run("." + instr.Type1.ToOpcodeParam()) { Foreground = typeBrush }); if (instr.Kind == UndertaleInstruction.Opcode.Dup || instr.Kind == UndertaleInstruction.Opcode.CallV) { par.Inlines.Add(new Run(" ")); par.Inlines.Add(new Run(instr.Extra.ToString()) { Foreground = argBrush }); } break; case UndertaleInstruction.InstructionType.DoubleTypeInstruction: par.Inlines.Add(new Run("." + instr.Type1.ToOpcodeParam()) { Foreground = typeBrush }); par.Inlines.Add(new Run("." + instr.Type2.ToOpcodeParam()) { Foreground = typeBrush }); break; case UndertaleInstruction.InstructionType.ComparisonInstruction: par.Inlines.Add(new Run("." + instr.Type1.ToOpcodeParam()) { Foreground = typeBrush }); par.Inlines.Add(new Run("." + instr.Type2.ToOpcodeParam()) { Foreground = typeBrush }); par.Inlines.Add(new Run(" ")); par.Inlines.Add(new Run(instr.ComparisonKind.ToString()) { Foreground = opcodeBrush }); break; case UndertaleInstruction.InstructionType.GotoInstruction: par.Inlines.Add(new Run(" ")); string tgt = (instr.Address + instr.JumpOffset).ToString("D5"); if (instr.Address + instr.JumpOffset == code.Length / 4) { tgt = "func_end"; } if (instr.JumpOffsetPopenvExitMagic) { tgt = "[drop]"; } par.Inlines.Add(new Run(tgt) { Foreground = argBrush, ToolTip = "$" + instr.JumpOffset.ToString("+#;-#;0") }); break; case UndertaleInstruction.InstructionType.PopInstruction: par.Inlines.Add(new Run("." + instr.Type1.ToOpcodeParam()) { Foreground = typeBrush }); par.Inlines.Add(new Run("." + instr.Type2.ToOpcodeParam()) { Foreground = typeBrush }); par.Inlines.Add(new Run(" ")); if (instr.Type1 == UndertaleInstruction.DataType.Int16) { // Special scenario - the swap instruction // TODO: Figure out the proper syntax, see #129 Run runType = new Run(instr.SwapExtra.ToString().ToLower()) { Foreground = argBrush }; par.Inlines.Add(runType); } else { if (instr.Type1 == UndertaleInstruction.DataType.Variable && instr.TypeInst != UndertaleInstruction.InstanceType.Undefined) { par.Inlines.Add(new Run(instr.TypeInst.ToString().ToLower()) { Foreground = typeBrush }); par.Inlines.Add(new Run(".")); } Run runDest = new Run(instr.Destination.ToString()) { Foreground = argBrush, Cursor = Cursors.Hand }; runDest.MouseDown += (sender, e) => { (Application.Current.MainWindow as MainWindow).ChangeSelection(instr.Destination); }; par.Inlines.Add(runDest); } break; case UndertaleInstruction.InstructionType.PushInstruction: par.Inlines.Add(new Run("." + instr.Type1.ToOpcodeParam()) { Foreground = typeBrush }); par.Inlines.Add(new Run(" ")); if (instr.Type1 == UndertaleInstruction.DataType.Variable && instr.TypeInst != UndertaleInstruction.InstanceType.Undefined) { par.Inlines.Add(new Run(instr.TypeInst.ToString().ToLower()) { Foreground = typeBrush }); par.Inlines.Add(new Run(".")); } Run valueRun = new Run((instr.Value as IFormattable)?.ToString(null, CultureInfo.InvariantCulture) ?? instr.Value.ToString()) { Foreground = argBrush, Cursor = (instr.Value is UndertaleObject || instr.Value is UndertaleResourceRef) ? Cursors.Hand : Cursors.Arrow }; if (instr.Value is UndertaleResourceRef) { valueRun.MouseDown += (sender, e) => { (Application.Current.MainWindow as MainWindow).ChangeSelection((instr.Value as UndertaleResourceRef).Resource); }; } else if (instr.Value is UndertaleObject) { valueRun.MouseDown += (sender, e) => { (Application.Current.MainWindow as MainWindow).ChangeSelection(instr.Value); }; } par.Inlines.Add(valueRun); break; case UndertaleInstruction.InstructionType.CallInstruction: par.Inlines.Add(new Run("." + instr.Type1.ToOpcodeParam()) { Foreground = typeBrush }); par.Inlines.Add(new Run(" ")); par.Inlines.Add(new Run(instr.Function.ToString()) { Foreground = argBrush }); par.Inlines.Add(new Run("(argc=")); par.Inlines.Add(new Run(instr.ArgumentsCount.ToString()) { Foreground = argBrush }); par.Inlines.Add(new Run(")")); break; case UndertaleInstruction.InstructionType.BreakInstruction: par.Inlines.Add(new Run("." + instr.Type1.ToOpcodeParam()) { Foreground = typeBrush }); par.Inlines.Add(new Run(" ")); par.Inlines.Add(new Run(instr.Value.ToString()) { Foreground = argBrush }); break; } if (par.Inlines.Count >= 250) { // Makes selecting text possible. document.Blocks.Add(par); par = new Paragraph(); par.Margin = new Thickness(0); } else { par.Inlines.Add(new Run("\n")); } } } document.Blocks.Add(par); DisassemblyView.Document = document; CurrentDisassembled = code; }
private static UndertaleInstruction.Reference <UndertaleVariable> ParseVariableReference(string line, IList <UndertaleVariable> vars, Dictionary <string, UndertaleVariable> localvars, ref UndertaleInstruction.InstanceType instance, UndertaleInstruction instr, UndertaleData data = null) { string str = line; UndertaleInstruction.VariableType type = UndertaleInstruction.VariableType.Normal; UndertaleInstruction.InstanceType realinstance = instance; if (str[0] != '[') { string inst = null; int instdot = str.IndexOf('.'); if (instdot >= 0) { inst = str.Substring(0, instdot); str = str.Substring(instdot + 1); if (inst == "") { throw new Exception("Whoops?"); } } if (inst != null) { short instnum; if (Int16.TryParse(inst, out instnum)) { instance = (UndertaleInstruction.InstanceType)instnum; } else { instance = (UndertaleInstruction.InstanceType)Enum.Parse(typeof(UndertaleInstruction.InstanceType), inst, true); } } else { instance = UndertaleInstruction.InstanceType.Undefined; } realinstance = instance; if (realinstance >= 0) { realinstance = UndertaleInstruction.InstanceType.Self; } else if (realinstance == UndertaleInstruction.InstanceType.Other) { realinstance = UndertaleInstruction.InstanceType.Self; } else if (realinstance == UndertaleInstruction.InstanceType.Arg) { realinstance = UndertaleInstruction.InstanceType.Builtin; } else if (realinstance == UndertaleInstruction.InstanceType.Builtin) { realinstance = UndertaleInstruction.InstanceType.Self; // used with @@This@@ } else if (realinstance == UndertaleInstruction.InstanceType.Stacktop) { realinstance = UndertaleInstruction.InstanceType.Self; // used with @@GetInstance@@ } } else { int typeend = str.IndexOf(']'); if (typeend >= 0) { string typestr = str.Substring(1, typeend - 1); str = str.Substring(typeend + 1); type = (UndertaleInstruction.VariableType)Enum.Parse(typeof(UndertaleInstruction.VariableType), typestr, true); int instanceEnd = str.IndexOf('.'); if (instanceEnd >= 0) { string instancestr = str.Substring(0, instanceEnd); str = str.Substring(instanceEnd + 1); realinstance = (UndertaleInstruction.InstanceType)Enum.Parse(typeof(UndertaleInstruction.InstanceType), instancestr, true); } else { if (type == UndertaleInstruction.VariableType.Array || type == UndertaleInstruction.VariableType.StackTop) { throw new Exception("Old instruction format is incompatible (missing instance type in array or stacktop)"); } if (realinstance >= 0) { realinstance = UndertaleInstruction.InstanceType.Self; } else if (realinstance == UndertaleInstruction.InstanceType.Other) { realinstance = UndertaleInstruction.InstanceType.Self; } } } else { throw new Exception("Missing ']' character in variable reference"); } } if (data?.GeneralInfo?.BytecodeVersion <= 14) { realinstance = UndertaleInstruction.InstanceType.Undefined; } UndertaleVariable varobj; if (realinstance == UndertaleInstruction.InstanceType.Local) { varobj = localvars.ContainsKey(str) ? localvars[str] : null; } else { varobj = vars.Where((x) => x.Name.Content == str && x.InstanceType == realinstance).FirstOrDefault(); } if (varobj == null) { throw new Exception("Bad variable: " + realinstance.ToString().ToLower() + "." + str); } return(new UndertaleInstruction.Reference <UndertaleVariable>(varobj, type)); }
public static List <UndertaleInstruction> Assemble(string source, IList <UndertaleFunction> funcs, IList <UndertaleVariable> vars, IList <UndertaleString> strg, UndertaleData data = null) { var lines = source.Replace("\r", "").Split('\n'); uint addr = 0; Dictionary <string, uint> labels = new Dictionary <string, uint>(); Dictionary <UndertaleInstruction, string> labelTargets = new Dictionary <UndertaleInstruction, string>(); List <UndertaleInstruction> instructions = new List <UndertaleInstruction>(); Dictionary <string, UndertaleVariable> localvars = new Dictionary <string, UndertaleVariable>(); foreach (var fullline in lines) { string line = fullline; if (line.Length == 0) { continue; } if (line[0] == ';') { continue; } if (line[0] == '>') { // Code entry inside of this one line = line.Substring(2, line.Length - 2).Trim(); int space = line.IndexOf(' '); string codeName = line.Substring(0, space); var code = data.Code.ByName(codeName); if (code == null) { throw new Exception($"Failed to find code entry with name \"{codeName}\"."); } string info = line.Substring(space + 1); Match match = Regex.Match(info, @"^\(locals=(.*)\,\s*argc=(.*)\)$"); if (!match.Success) { throw new Exception("Sub-code entry format error"); } code.LocalsCount = ushort.Parse(match.Groups[1].Value); code.ArgumentsCount = ushort.Parse(match.Groups[2].Value); code.Offset = addr * 4; continue; } if (line[0] == ':' && line.Length >= 3) { if (line[1] == '[') { string label = line.Substring(2, line.IndexOf(']') - 2); if (!string.IsNullOrEmpty(label)) { if (labels.ContainsKey(label)) { throw new Exception("Duplicate label: " + label); } labels.Add(label, addr); } continue; } } if (line[0] == '.') { // Assembler directive // TODO: Does not update the CodeLocals block yet!! string[] aaa = line.Split(' '); if (aaa[0] == ".localvar") { if (aaa.Length >= 4) { var varii = vars[Int32.Parse(aaa[3])]; if (data?.GeneralInfo?.BytecodeVersion >= 15 && varii.InstanceType != UndertaleInstruction.InstanceType.Local) { throw new Exception("Not a local var"); } if (varii.Name.Content != aaa[2]) { throw new Exception("Name mismatch"); } localvars.Add(aaa[2], varii); } } else { throw new Exception("Unknown assembler directive: " + aaa[0]); } continue; } string labelTgt; UndertaleInstruction instr = AssembleOne(line, funcs, vars, strg, localvars, out labelTgt, data); instr.Address = addr; if (labelTgt != null) { labelTargets.Add(instr, labelTgt); } instructions.Add(instr); addr += instr.CalculateInstructionSize(); } foreach (var pair in labelTargets) { pair.Key.JumpOffset = (int)labels[pair.Value] - (int)pair.Key.Address; } return(instructions); }
public static UndertaleInstruction AssembleOne(string source, IList <UndertaleFunction> funcs, IList <UndertaleVariable> vars, IList <UndertaleString> strg, Dictionary <string, UndertaleVariable> localvars, out string label, UndertaleInstruction.InstanceType?instTypeOnStack) { label = null; string line = source; UndertaleInstruction instr = new UndertaleInstruction(); string opcode = line; int space = opcode.IndexOf(' '); if (space >= 0) { opcode = line.Substring(0, space); line = line.Substring(space + 1).Trim(); } else { line = ""; } string[] types = opcode.Split('.'); if (types.Length > 3) { throw new Exception("Too many type parameters"); } instr.Kind = (UndertaleInstruction.Opcode)Enum.Parse(typeof(UndertaleInstruction.Opcode), types[0], true); if (types.Length >= 2) { instr.Type1 = UndertaleInstructionUtil.FromOpcodeParam(types[1]); } if (types.Length >= 3) { instr.Type2 = UndertaleInstructionUtil.FromOpcodeParam(types[2]); } switch (UndertaleInstruction.GetInstructionType(instr.Kind)) { case UndertaleInstruction.InstructionType.SingleTypeInstruction: if (instr.Kind == UndertaleInstruction.Opcode.Dup) { instr.DupExtra = Byte.Parse(line); line = ""; } break; case UndertaleInstruction.InstructionType.DoubleTypeInstruction: break; case UndertaleInstruction.InstructionType.ComparisonInstruction: instr.ComparisonKind = (UndertaleInstruction.ComparisonType)Enum.Parse(typeof(UndertaleInstruction.ComparisonType), line, true); line = ""; break; case UndertaleInstruction.InstructionType.GotoInstruction: if (line[0] == '$') { instr.JumpOffset = Int32.Parse(line.Substring(1)); } else { label = line; } line = ""; break; case UndertaleInstruction.InstructionType.PopInstruction: UndertaleInstruction.InstanceType inst = instr.TypeInst; instr.Destination = ParseVariableReference(line, vars, localvars, ref inst, instTypeOnStack); instr.TypeInst = inst; line = ""; break; case UndertaleInstruction.InstructionType.PushInstruction: switch (instr.Type1) { case UndertaleInstruction.DataType.Double: instr.Value = Double.Parse(line, CultureInfo.InvariantCulture); break; case UndertaleInstruction.DataType.Float: instr.Value = Single.Parse(line, CultureInfo.InvariantCulture); break; case UndertaleInstruction.DataType.Int32: instr.Value = Int32.Parse(line); break; case UndertaleInstruction.DataType.Int64: instr.Value = Int64.Parse(line); break; case UndertaleInstruction.DataType.Boolean: instr.Value = Boolean.Parse(line); break; case UndertaleInstruction.DataType.Variable: UndertaleInstruction.InstanceType inst2 = instr.TypeInst; instr.Value = ParseVariableReference(line, vars, localvars, ref inst2, instTypeOnStack); instr.TypeInst = inst2; break; case UndertaleInstruction.DataType.String: instr.Value = ParseStringReference(line, strg); break; case UndertaleInstruction.DataType.Int16: instr.Value = Int16.Parse(line); break; } line = ""; break; case UndertaleInstruction.InstructionType.CallInstruction: Match match = Regex.Match(line, @"^(.*)\(argc=(.*)\)$"); if (!match.Success) { throw new Exception("Call instruction format error"); } UndertaleFunction func = funcs.ByName(match.Groups[1].Value); if (func == null) { throw new Exception("Function not found: " + match.Groups[1].Value); } instr.Function = new UndertaleInstruction.Reference <UndertaleFunction>() { Target = func }; instr.ArgumentsCount = UInt16.Parse(match.Groups[2].Value); line = ""; break; case UndertaleInstruction.InstructionType.BreakInstruction: instr.Value = Int16.Parse(line); line = ""; break; } if (line != "") { throw new Exception("Excess parameters"); } return(instr); }
public static List <UndertaleInstruction> Assemble(string source, IList <UndertaleFunction> funcs, IList <UndertaleVariable> vars, IList <UndertaleString> strg) { var lines = source.Split('\n'); uint addr = 0; Dictionary <string, uint> labels = new Dictionary <string, uint>(); Dictionary <UndertaleInstruction, string> labelTargets = new Dictionary <UndertaleInstruction, string>(); List <UndertaleInstruction> instructions = new List <UndertaleInstruction>(); Dictionary <string, UndertaleVariable> localvars = new Dictionary <string, UndertaleVariable>(); foreach (var fullline in lines) { string line = fullline; if (line.Length > 0 && line[0] == ';') { continue; } string label = null; int labelEnd = line.IndexOf(':'); if (labelEnd >= 0) { label = line.Substring(0, labelEnd).Trim(); line = line.Substring(labelEnd + 1); if (String.IsNullOrEmpty(label)) { throw new Exception("Empty label"); } } line = line.Trim(); if (String.IsNullOrEmpty(line)) { if (!String.IsNullOrEmpty(label)) { throw new Exception("Label with no instruction"); } else { continue; } } if (line.StartsWith(".")) { // Assembler directive // TODO: Does not update the CodeLocals block yet!! string[] aaa = line.Split(' '); if (aaa[0] == ".localvar") { if (aaa.Length >= 4) { var varii = vars[Int32.Parse(aaa[3])]; if (varii.InstanceType != UndertaleInstruction.InstanceType.Local) { throw new Exception("Not a local var"); } if (varii.Name.Content != aaa[2]) { throw new Exception("Name mismatch"); } localvars.Add(aaa[2], varii); } } else { throw new Exception("Unknown assembler directive: " + aaa[0]); } continue; } // Really ugly hack for compiling array variable references // See https://github.com/krzys-h/UndertaleModTool/issues/27#issuecomment-426637438 UndertaleInstruction instTypePush = instructions.Cast <UndertaleInstruction>().Reverse().Where((x) => UndertaleInstruction.GetInstructionType(x.Kind) == UndertaleInstruction.InstructionType.PushInstruction).ElementAtOrDefault(1); UndertaleInstruction.InstanceType?instTypeOnStack = instTypePush != null && instTypePush.Kind == UndertaleInstruction.Opcode.PushI ? (UndertaleInstruction.InstanceType?)(short) instTypePush.Value : null; string labelTgt; UndertaleInstruction instr = AssembleOne(line, funcs, vars, strg, localvars, out labelTgt, instTypeOnStack); instr.Address = addr; if (labelTgt != null) { labelTargets.Add(instr, labelTgt); } if (!String.IsNullOrEmpty(label)) { if (labels.ContainsKey(label)) { throw new Exception("Duplicate label: " + label); } labels.Add(label, instr.Address); } instructions.Add(instr); addr += instr.CalculateInstructionSize(); } if (labels.ContainsKey("func_end")) { throw new Exception("func_end is a reserved label name"); } labels.Add("func_end", addr); foreach (var pair in labelTargets) { pair.Key.JumpOffset = (int)labels[pair.Value] - (int)pair.Key.Address; } return(instructions); }