private async void DecompileCode(UndertaleCode code) { LoaderDialog dialog = new LoaderDialog("Decompiling", "Decompiling, please wait... This can take a while on complex scripts"); dialog.Owner = Window.GetWindow(this); FlowDocument document = new FlowDocument(); document.PagePadding = new Thickness(0); document.FontFamily = new FontFamily("Lucida Console"); Paragraph par = new Paragraph(); UndertaleCode gettextCode = null; if (gettext == null) { gettextCode = (Application.Current.MainWindow as MainWindow).Data.Code.ByName("gml_Script_textdata_en"); } string gettextJsonPath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName((Application.Current.MainWindow as MainWindow).FilePath), "lang/lang_en.json"); var dataa = (Application.Current.MainWindow as MainWindow).Data; Task t = Task.Run(() => { string decompiled = null; Exception e = null; try { decompiled = Decompiler.Decompile(code, dataa).Replace("\r\n", "\n"); } catch (Exception ex) { e = ex; } if (gettextCode != null) { UpdateGettext(gettextCode); } if (gettextJSON == null && File.Exists(gettextJsonPath)) { UpdateGettextJSON(File.ReadAllText(gettextJsonPath)); } Dispatcher.Invoke(() => { if (e != null) { Brush exceptionBrush = new SolidColorBrush(Color.FromRgb(255, 0, 0)); par.Inlines.Add(new Run("EXCEPTION!\n") { Foreground = exceptionBrush, FontWeight = FontWeights.Bold }); par.Inlines.Add(new Run(e.ToString()) { Foreground = exceptionBrush }); } else if (decompiled != null) { string[] lines = decompiled.Split('\n'); if (lines.Length > 5000) { par.Inlines.Add(new Run(decompiled)); } else { Brush keywordBrush = new SolidColorBrush(Color.FromRgb(0, 0, 150)); Brush stringBrush = new SolidColorBrush(Color.FromRgb(0, 0, 200)); Brush commentBrush = new SolidColorBrush(Color.FromRgb(0, 150, 0)); Brush funcBrush = new SolidColorBrush(Color.FromRgb(100, 100, 0)); Brush assetBrush = new SolidColorBrush(Color.FromRgb(0, 150, 100)); Dictionary <string, UndertaleFunction> funcs = new Dictionary <string, UndertaleFunction>(); foreach (var x in (Application.Current.MainWindow as MainWindow).Data.Functions) { funcs.Add(x.Name.Content, x); } foreach (var line in lines) { char[] special = { '.', ',', ')', '(', '[', ']', '>', '<', ':', ';', '=', '"' }; Func <char, bool> IsSpecial = (c) => Char.IsWhiteSpace(c) || special.Contains(c); List <string> split = new List <string>(); string tok = ""; bool readingString = false; for (int i = 0; i < line.Length; i++) { if (tok == "//") { tok += line.Substring(i); break; } if (!readingString && tok.Length > 0 && ( (Char.IsWhiteSpace(line[i]) != Char.IsWhiteSpace(tok[tok.Length - 1])) || (special.Contains(line[i]) != special.Contains(tok[tok.Length - 1])) || (special.Contains(line[i]) && special.Contains(tok[tok.Length - 1])) || line[i] == '"' )) { split.Add(tok); tok = ""; } tok += line[i]; if (line[i] == '"') { if (readingString) { split.Add(tok); tok = ""; } readingString = !readingString; } } if (tok != "") { split.Add(tok); } Dictionary <string, object> usedObjects = new Dictionary <string, object>(); for (int i = 0; i < split.Count; i++) { string token = split[i]; if (token == "if" || token == "else" || token == "return" || token == "break" || token == "continue" || token == "while" || token == "with" || token == "switch" || token == "case" || token == "default") { par.Inlines.Add(new Run(token) { Foreground = keywordBrush, FontWeight = FontWeights.Bold }); } else if (token == "self" || token == "global" || token == "local" || token == "other" || token == "noone" || token == "true" || token == "false") { par.Inlines.Add(new Run(token) { Foreground = keywordBrush }); } else if (token.StartsWith("\"")) { par.Inlines.Add(new Run(token) { Foreground = stringBrush }); } else if (token.StartsWith("//")) { par.Inlines.Add(new Run(token) { Foreground = commentBrush }); } else if (token.StartsWith("@") && split[i - 1][0] == '"' && split[i - 1][split[i - 1].Length - 1] == '"') { par.Inlines.LastInline.Cursor = Cursors.Hand; par.Inlines.LastInline.MouseDown += (sender, ev) => { MainWindow mw = Application.Current.MainWindow as MainWindow; mw.ChangeSelection(mw.Data.Strings[Int32.Parse(token.Substring(1))]); }; } else if (dataa.ByName(token) != null) { par.Inlines.Add(new Run(token) { Foreground = assetBrush, Cursor = Cursors.Hand }); par.Inlines.LastInline.MouseDown += (sender, ev) => (Application.Current.MainWindow as MainWindow).ChangeSelection(dataa.ByName(token)); if (token == "scr_gettext" && gettext != null) { if (split[i + 1] == "(" && split[i + 2].StartsWith("\"") && split[i + 3].StartsWith("@") && split[i + 4] == ")") { string id = split[i + 2].Substring(1, split[i + 2].Length - 2); if (!usedObjects.ContainsKey(id)) { usedObjects.Add(id, (Application.Current.MainWindow as MainWindow).Data.Strings[gettext[id]]); } } } if (token == "scr_84_get_lang_string" && gettextJSON != null) { if (split[i + 1] == "(" && split[i + 2].StartsWith("\"") && split[i + 3].StartsWith("@") && split[i + 4] == ")") { string id = split[i + 2].Substring(1, split[i + 2].Length - 2); if (!usedObjects.ContainsKey(id)) { usedObjects.Add(id, gettextJSON[id]); } } } } else if (funcs.ContainsKey(token)) { par.Inlines.Add(new Run(token) { Foreground = funcBrush, Cursor = Cursors.Hand }); par.Inlines.LastInline.MouseDown += (sender, ev) => (Application.Current.MainWindow as MainWindow).ChangeSelection(funcs[token]); } else if (Char.IsDigit(token[0])) { par.Inlines.Add(new Run(token) { Cursor = Cursors.Hand }); par.Inlines.LastInline.MouseDown += (sender, ev) => { // TODO: Add type resolving to the decompiler so that this is handled mostly automatically UndertaleData data = (Application.Current.MainWindow as MainWindow).Data; int id = Int32.Parse(token); List <UndertaleObject> possibleObjects = new List <UndertaleObject>(); if (id < data.Sprites.Count) { possibleObjects.Add(data.Sprites[id]); } if (id < data.Rooms.Count) { possibleObjects.Add(data.Rooms[id]); } if (id < data.GameObjects.Count) { possibleObjects.Add(data.GameObjects[id]); } if (id < data.Backgrounds.Count) { possibleObjects.Add(data.Backgrounds[id]); } if (id < data.Scripts.Count) { possibleObjects.Add(data.Scripts[id]); } if (id < data.Paths.Count) { possibleObjects.Add(data.Paths[id]); } if (id < data.Fonts.Count) { possibleObjects.Add(data.Fonts[id]); } if (id < data.Sounds.Count) { possibleObjects.Add(data.Sounds[id]); } if (id < data.Shaders.Count) { possibleObjects.Add(data.Shaders[id]); } // if (id < data.Extensions.Count) // possibleObjects.Add(data.Extensions[id]); if (id < data.Timelines.Count) { possibleObjects.Add(data.Timelines[id]); } ContextMenu contextMenu = new ContextMenu(); foreach (UndertaleObject obj in possibleObjects) { MenuItem item = new MenuItem(); item.Header = obj.ToString().Replace("_", "__"); item.Click += (sender2, ev2) => (Application.Current.MainWindow as MainWindow).ChangeSelection(obj); contextMenu.Items.Add(item); } if (id > 0x00050000) { contextMenu.Items.Add(new MenuItem() { Header = "#" + id.ToString("X6") + " (color)", IsEnabled = false }); } contextMenu.Items.Add(new MenuItem() { Header = id + " (number)", IsEnabled = false }); (sender as Run).ContextMenu = contextMenu; contextMenu.IsOpen = true; ev.Handled = true; }; } else { par.Inlines.Add(new Run(token)); } if (token == "." && (Char.IsLetter(split[i + 1][0]) || split[i + 1][0] == '_')) { int id; if (Int32.TryParse(split[i - 1], out id)) { var gos = (Application.Current.MainWindow as MainWindow).Data.GameObjects; if (!usedObjects.ContainsKey(split[i - 1]) && id >= 0 && id < gos.Count) { usedObjects.Add(split[i - 1], gos[id]); } } } } foreach (var gt in usedObjects) { par.Inlines.Add(new Run(" // ") { Foreground = commentBrush }); par.Inlines.Add(new Run(gt.Key) { Foreground = commentBrush }); par.Inlines.Add(new Run(" = ") { Foreground = commentBrush }); par.Inlines.Add(new Run(gt.Value is string? "\"" + (string)gt.Value + "\"" : gt.Value.ToString()) { Foreground = commentBrush, Cursor = Cursors.Hand }); if (gt.Value is UndertaleObject) { par.Inlines.LastInline.MouseDown += (sender, ev) => (Application.Current.MainWindow as MainWindow).ChangeSelection(gt.Value); } } par.Inlines.Add(new Run("\n")); } } } document.Blocks.Add(par); DecompiledView.Document = document; CurrentDecompiled = code; dialog.Hide(); }); }); dialog.ShowDialog(); await t; }