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 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);
        }
Exemple #3
0
        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;
        }
        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);
        }