private async Task BatchDecompile(string outdir) { LoaderDialog dialog = new LoaderDialog("Decompiling", "Decompiling, please wait..."); dialog.Owner = this; Task t = Task.Run(() => { foreach (var code in Data.Code) { Debug.WriteLine(code.Name.Content); string path = System.IO.Path.Combine(outdir, code.Name.Content + ".gml"); try { string decomp = Decompiler.Decompile(code); File.WriteAllText(path, decomp); } catch (Exception e) { File.WriteAllText(path, "/*\nDECOMPILER FAILED!\n\n" + e.ToString() + "\n*/"); } } Dispatcher.Invoke(() => { dialog.Hide(); }); }); dialog.ShowDialog(); await t; }
private async void GraphCode(UndertaleCode code) { if (code.DuplicateEntry) { GraphView.Source = null; CurrentGraphed = code; return; } LoaderDialog dialog = new LoaderDialog("Generating graph", "Generating graph, please wait..."); dialog.Owner = Window.GetWindow(this); Task t = Task.Run(() => { ImageSource image = null; try { code.UpdateAddresses(); var blocks = Decompiler.DecompileFlowGraph(code); string dot = Decompiler.ExportFlowGraph(blocks); try { var getStartProcessQuery = new GetStartProcessQuery(); var getProcessStartInfoQuery = new GetProcessStartInfoQuery(); var registerLayoutPluginCommand = new RegisterLayoutPluginCommand(getProcessStartInfoQuery, getStartProcessQuery); var wrapper = new GraphGeneration(getStartProcessQuery, getProcessStartInfoQuery, registerLayoutPluginCommand); byte[] output = wrapper.GenerateGraph(dot, Enums.GraphReturnType.Png); // TODO: Use SVG instead image = new ImageSourceConverter().ConvertFrom(output) as ImageSource; } catch (Exception e) { Debug.WriteLine(e.ToString()); if (MessageBox.Show("Unable to execute GraphViz: " + e.Message + "\nMake sure you have downloaded it and set the path in settings.\nDo you want to open the download page now?", "Graph generation failed", MessageBoxButton.YesNo, MessageBoxImage.Error) == MessageBoxResult.Yes) { Process.Start("https://graphviz.gitlab.io/_pages/Download/Download_windows.html"); } } } catch (Exception e) { Debug.WriteLine(e.ToString()); MessageBox.Show(e.Message, "Graph generation failed", MessageBoxButton.OK, MessageBoxImage.Error); } Dispatcher.Invoke(() => { GraphView.Source = image; CurrentGraphed = code; dialog.Hide(); }); }); dialog.ShowDialog(); await t; }
public void InitializeScriptDialog() { if (scriptDialog == null) { scriptDialog = new LoaderDialog("Script in progress...", "Please wait..."); scriptDialog.Owner = this; scriptDialog.PreventClose = true; } }
private async void MenuItem_OffsetMap_Click(object sender, RoutedEventArgs e) { OpenFileDialog dlg = new OpenFileDialog(); dlg.DefaultExt = "win"; dlg.Filter = "Game Maker Studio data files (.win, .unx, .ios)|*.win;*.unx;*.ios|All files|*"; if (dlg.ShowDialog() == true) { SaveFileDialog dlgout = new SaveFileDialog(); dlgout.DefaultExt = "txt"; dlgout.Filter = "Text files (.txt)|*.txt|All files|*"; dlgout.FileName = dlg.FileName + ".offsetmap.txt"; if (dlgout.ShowDialog() == true) { LoaderDialog dialog = new LoaderDialog("Generating", "Loading, please wait..."); dialog.Owner = this; Task t = Task.Run(() => { try { using (var stream = new FileStream(dlg.FileName, FileMode.Open, FileAccess.Read)) { var offsets = UndertaleIO.GenerateOffsetMap(stream); using (var writer = File.CreateText(dlgout.FileName)) { foreach (var off in offsets.OrderBy((x) => x.Key)) { writer.WriteLine(off.Key.ToString("X8") + " " + off.Value.ToString().Replace("\n", "\\\n")); } } } } catch (Exception ex) { MessageBox.Show("An error occured while trying to load:\n" + ex.Message, "Load error", MessageBoxButton.OK, MessageBoxImage.Error); } Dispatcher.Invoke(() => { dialog.Hide(); }); }); dialog.ShowDialog(); await t; } } }
private async Task LoadFile(string filename) { LoaderDialog dialog = new LoaderDialog("Loading", "Loading, please wait..."); dialog.Owner = this; Task t = Task.Run(() => { UndertaleData data = null; try { using (var stream = new FileStream(filename, FileMode.Open)) { data = UndertaleIO.Read(stream); } } catch (Exception e) { MessageBox.Show("An error occured while trying to load:\n" + e.Message, "Load error", MessageBoxButton.OK, MessageBoxImage.Error); } Dispatcher.Invoke(() => { if (data != null) { if (data.GeneralInfo.Major >= 2) { MessageBox.Show("Game Maker: Studio 2 game loaded! I just hacked this together quickly for the Nintendo Switch release of Undertale, so some things may be broken. Saving should work, but not all editors have the new data. Expect a release with fixes soon!", "GMS2 game loaded", MessageBoxButton.OK, MessageBoxImage.Warning); } this.Data = data; this.FilePath = filename; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Data")); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsGMS2")); ChangeSelection(Highlighted = new DescriptionView("Welcome to UndertaleModTool!", "Double click on the items on the left to view them!")); SelectionHistory.Clear(); } dialog.Hide(); }); }); dialog.ShowDialog(); await t; }
private async Task BatchDecompile(string outdir) { LoaderDialog dialog = new LoaderDialog("Decompiling", "Decompiling, please wait..."); dialog.Owner = this; dialog.Maximum = Data.Code.Count; IProgress <Tuple <int, string> > progress = new Progress <Tuple <int, string> >(i => { dialog.ReportProgress(i.Item2, i.Item1); }); int count = 0; object countLock = new object(); Task t = Task.Run(() => { Parallel.ForEach(Data.Code, (code) => { //Debug.WriteLine(code.Name.Content); string path = System.IO.Path.Combine(outdir, code.Name.Content + ".gml"); try { string decomp = Decompiler.Decompile(code, Data); File.WriteAllText(path, decomp); } catch (Exception e) { File.WriteAllText(path, "/*\nDECOMPILER FAILED!\n\n" + e.ToString() + "\n*/"); } lock (countLock) { progress.Report(new Tuple <int, string>(++count, code.Name.Content)); } }); Dispatcher.Invoke(() => { dialog.Hide(); }); }); dialog.ShowDialog(); await t; }
private async Task SaveFile(string filename) { if (Data == null || Data.UnsupportedBytecodeVersion) { return; } LoaderDialog dialog = new LoaderDialog("Saving", "Saving, please wait..."); IProgress <Tuple <int, string> > progress = new Progress <Tuple <int, string> >(i => { dialog.ReportProgress(i.Item2, i.Item1); }); IProgress <double?> setMax = new Progress <double?>(i => { dialog.Maximum = i; }); dialog.Owner = this; FilePath = filename; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("FilePath")); if (System.IO.Path.GetDirectoryName(FilePath) != System.IO.Path.GetDirectoryName(filename)) { CloseChildFiles(); } DebugDataDialog.DebugDataMode debugMode = DebugDataDialog.DebugDataMode.NoDebug; if (!Data.GeneralInfo.DisableDebugger) // TODO: I think the game itself can also use the .yydebug file on crash reports { DebugDataDialog debugDialog = new DebugDataDialog(); debugDialog.Owner = this; debugDialog.ShowDialog(); debugMode = debugDialog.Result; } Task t = Task.Run(() => { try { using (var stream = new FileStream(filename, FileMode.Create, FileAccess.Write)) { UndertaleIO.Write(stream, Data); } if (debugMode != DebugDataDialog.DebugDataMode.NoDebug) { Debug.WriteLine("Generating debugger data..."); UndertaleDebugData debugData = UndertaleDebugData.CreateNew(); setMax.Report(Data.Code.Count); int count = 0; object countLock = new object(); string[] outputs = new string[Data.Code.Count]; UndertaleDebugInfo[] outputsOffsets = new UndertaleDebugInfo[Data.Code.Count]; Parallel.For(0, Data.Code.Count, (i) => { var code = Data.Code[i]; if (debugMode == DebugDataDialog.DebugDataMode.Decompiled) { //Debug.WriteLine("Decompiling " + code.Name.Content); string output; try { output = Decompiler.Decompile(code, Data); } catch (Exception e) { Debug.WriteLine(e.Message); output = "/*\nEXCEPTION!\n" + e.ToString() + "\n*/"; } outputs[i] = output; UndertaleDebugInfo debugInfo = new UndertaleDebugInfo(); debugInfo.Add(new UndertaleDebugInfo.DebugInfoPair() { SourceCodeOffset = 0, BytecodeOffset = 0 }); // TODO: generate this too! :D outputsOffsets[i] = debugInfo; } else { StringBuilder sb = new StringBuilder(); UndertaleDebugInfo debugInfo = new UndertaleDebugInfo(); foreach (var instr in code.Instructions) { if (debugMode == DebugDataDialog.DebugDataMode.FullAssembler || instr.Kind == UndertaleInstruction.Opcode.Pop || instr.Kind == UndertaleInstruction.Opcode.Popz || instr.Kind == UndertaleInstruction.Opcode.B || instr.Kind == UndertaleInstruction.Opcode.Bt || instr.Kind == UndertaleInstruction.Opcode.Bf || instr.Kind == UndertaleInstruction.Opcode.Ret || instr.Kind == UndertaleInstruction.Opcode.Exit) { debugInfo.Add(new UndertaleDebugInfo.DebugInfoPair() { SourceCodeOffset = (uint)sb.Length, BytecodeOffset = instr.Address * 4 }); } sb.Append(instr.ToString(code, Data.Variables)); sb.Append("\n"); } outputs[i] = sb.ToString(); outputsOffsets[i] = debugInfo; } lock (countLock) { progress.Report(new Tuple <int, string>(++count, code.Name.Content)); } }); setMax.Report(null); for (int i = 0; i < Data.Code.Count; i++) { debugData.SourceCode.Add(new UndertaleScriptSource() { SourceCode = debugData.Strings.MakeString(outputs[i]) }); debugData.DebugInfo.Add(outputsOffsets[i]); debugData.LocalVars.Add(Data.CodeLocals[i]); if (debugData.Strings.IndexOf(Data.CodeLocals[i].Name) < 0) { debugData.Strings.Add(Data.CodeLocals[i].Name); } foreach (var local in Data.CodeLocals[i].Locals) { if (debugData.Strings.IndexOf(local.Name) < 0) { debugData.Strings.Add(local.Name); } } } using (UndertaleWriter writer = new UndertaleWriter(new FileStream(System.IO.Path.ChangeExtension(FilePath, ".yydebug"), FileMode.Create, FileAccess.Write))) { debugData.FORM.Serialize(writer); writer.ThrowIfUnwrittenObjects(); } } } catch (Exception e) { MessageBox.Show("An error occured while trying to save:\n" + e.Message, "Save error", MessageBoxButton.OK, MessageBoxImage.Error); } Dispatcher.Invoke(() => { dialog.Hide(); }); }); dialog.ShowDialog(); await t; }
private async Task LoadFile(string filename) { LoaderDialog dialog = new LoaderDialog("Loading", "Loading, please wait..."); dialog.Owner = this; Task t = Task.Run(() => { bool hadWarnings = false; UndertaleData data = null; try { using (var stream = new FileStream(filename, FileMode.Open, FileAccess.Read)) { data = UndertaleIO.Read(stream, warning => { MessageBox.Show(warning, "Loading warning", MessageBoxButton.OK, MessageBoxImage.Warning); hadWarnings = true; }); } } catch (Exception e) { MessageBox.Show("An error occured while trying to load:\n" + e.Message, "Load error", MessageBoxButton.OK, MessageBoxImage.Error); } Dispatcher.Invoke(() => { if (data != null) { if (data.UnsupportedBytecodeVersion) { MessageBox.Show("Only bytecode versions 15, 16, and (partially) 17 are supported for now, you are trying to load " + data.GeneralInfo.BytecodeVersion + ". A lot of code is disabled and will likely break something. Saving/exporting is disabled.", "Unsupported bytecode version", MessageBoxButton.OK, MessageBoxImage.Warning); CanSave = false; } else if (hadWarnings) { MessageBox.Show("Warnings occurred during loading. Saving has been disabled.", "Saving disabled", MessageBoxButton.OK, MessageBoxImage.Warning); CanSave = false; } else { CanSave = true; } if (data.Code == null) { MessageBox.Show("This game uses YYC (YoYo Compiler), which means the code is embedded into the game .exe. This configuration is currently not fully supported, continue at your own risk", "YYC", MessageBoxButton.OK, MessageBoxImage.Warning); } if (data.GeneralInfo?.BytecodeVersion == 17) { MessageBox.Show("Bytecode version 17 has been loaded. There may be some problems remaining, as thorough research into the changes are yet to be done.", "Bytecode version 17", MessageBoxButton.OK, MessageBoxImage.Warning); } if (System.IO.Path.GetDirectoryName(FilePath) != System.IO.Path.GetDirectoryName(filename)) { CloseChildFiles(); } this.Data = data; this.FilePath = filename; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Data")); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("FilePath")); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsGMS2")); ChangeSelection(Highlighted = new DescriptionView("Welcome to UndertaleModTool!", "Double click on the items on the left to view them!")); SelectionHistory.Clear(); } dialog.Hide(); }); }); dialog.ShowDialog(); await t; }
public void CrashCheck() { try { string lastEditedLocation = Path.Combine(ProfilesFolder, "LastEdited.txt"); if (Data == null && File.Exists(lastEditedLocation)) { CrashedWhileEditing = true; string[] crashRecoveryData = File.ReadAllText(lastEditedLocation).Split('\n'); string dataRecoverLocation = Path.Combine(ProfilesFolder, crashRecoveryData[0].Trim(), "Temp"); string profileHashOfCrashedFile; string reportedHashOfCrashedFile = crashRecoveryData[0].Trim(); string pathOfCrashedFile = crashRecoveryData[1]; string pathOfRecoverableCode = Path.Combine(ProfilesFolder, reportedHashOfCrashedFile); if (File.Exists(pathOfCrashedFile)) { using (var md5Instance = MD5.Create()) { using (var stream = File.OpenRead(pathOfCrashedFile)) { profileHashOfCrashedFile = BitConverter.ToString(md5Instance.ComputeHash(stream)).Replace("-", "").ToLowerInvariant(); } } if (Directory.Exists(Path.Combine(ProfilesFolder, reportedHashOfCrashedFile)) && profileHashOfCrashedFile == reportedHashOfCrashedFile) { if (MessageBox.Show("UndertaleModTool crashed during usage last time while editing " + pathOfCrashedFile + ", would you like to recover your code now?", "UndertaleModTool", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes) { LoadFile(pathOfCrashedFile, true).ContinueWith((t) => { }); if (Data == null) { MessageBox.Show("Failed to load data when recovering."); return; } string[] dirFiles = Directory.GetFiles(dataRecoverLocation); int progress = 0; LoaderDialog codeLoadDialog = new LoaderDialog("Script in progress...", "Please wait..."); codeLoadDialog.PreventClose = true; codeLoadDialog.Update(null, "Code entries processed: ", progress++, dirFiles.Length); codeLoadDialog.Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate { })); // Updates the UI, so you can see the progress. foreach (string file in dirFiles) { ImportGMLFile(file); codeLoadDialog.Update(null, "Code entries processed: ", progress++, dirFiles.Length); codeLoadDialog.Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate { })); // Updates the UI, so you can see the progress. } codeLoadDialog.TryClose(); MessageBox.Show("Completed."); } else if (MessageBox.Show("Would you like to move this code to the \"Recovered\" folder now? Any previous code there will be cleared!", "UndertaleModTool", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes) { MessageBox.Show("Your code can be recovered from the \"Recovered\" folder at any time."); string recoveredDir = Path.Combine(AppDataFolder, "Recovered", reportedHashOfCrashedFile); if (!Directory.Exists(Path.Combine(AppDataFolder, "Recovered"))) { Directory.CreateDirectory(Path.Combine(AppDataFolder, "Recovered")); } if (Directory.Exists(recoveredDir)) { Directory.Delete(recoveredDir, true); } Directory.Move(pathOfRecoverableCode, recoveredDir); ApplyCorrections(); } else { MessageBox.Show("A crash has been detected from last session. Please check the Profiles folder for recoverable data now."); } } } else { MessageBox.Show("A crash has been detected from last session. Please check the Profiles folder for recoverable data now."); } } } catch (Exception exc) { MessageBox.Show("CrashCheck error! Send this to Grossley#2869 and make an issue on Github\n" + exc.ToString()); } }
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.PageWidth = 2048; // Speed-up. document.FontFamily = new FontFamily("Lucida Console"); Paragraph par = new Paragraph(); par.Margin = new Thickness(0); UndertaleCode gettextCode = null; if (gettext == null) { gettextCode = (Application.Current.MainWindow as MainWindow).Data.Code.ByName("gml_Script_textdata_en"); } string dataPath = System.IO.Path.GetDirectoryName((Application.Current.MainWindow as MainWindow).FilePath); string gettextJsonPath = (dataPath != null) ? System.IO.Path.Combine(dataPath, "lang/lang_en.json") : null; var dataa = (Application.Current.MainWindow as MainWindow).Data; Task t = Task.Run(() => { int estimatedLineCount = (int)Math.Round(code.Length * .056D); bool skipFormatting = (estimatedLineCount > 5000); DecompileContext context = new DecompileContext(dataa, !skipFormatting); string decompiled = null; Exception e = null; try { decompiled = Decompiler.Decompile(code, context).Replace("\r\n", "\n"); } catch (Exception ex) { e = ex; } if (gettextCode != null) { UpdateGettext(gettextCode); } if (gettextJSON == null && gettextJsonPath != null && File.Exists(gettextJsonPath)) { string err = UpdateGettextJSON(File.ReadAllText(gettextJsonPath)); if (err != null) { e = new Exception(err); } } 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 (skipFormatting) { for (var i = 0; i < lines.Length; i++) { string toWrite = lines[i]; if (((i + 1) % 100) != 0 && lines.Length > i + 1) { toWrite += "\n"; // Write a new-line if we're not making a new paragraph. } if (i > 0 && i % 100 == 0) { // Splitting into different paragraphs significantly increases selection performance. document.Blocks.Add(par); par = new Paragraph(); par.Margin = new Thickness(0); } par.Inlines.Add(toWrite); } } else { Brush keywordBrush = new SolidColorBrush(Color.FromRgb(0, 0, 150)); Brush constBrush = new SolidColorBrush(Color.FromRgb(0, 100, 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)); Brush argumentBrush = new SolidColorBrush(Color.FromRgb(80, 131, 80)); 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); } string storedStrTok = ""; 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 = storedStrTok; storedStrTok = ""; bool readingString = (tok != ""); bool escaped = 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 = ""; } if (readingString && context.isGameMaker2) { if (escaped) { escaped = false; if (line[i] == '"') { tok += line[i]; continue; } } else if (line[i] == '\\') { escaped = true; } } tok += line[i]; if (line[i] == '"') { if (readingString) { split.Add(tok); tok = ""; } readingString = !readingString; } } if (tok != "") { if (readingString) { storedStrTok = tok + "\n"; } else { split.Add(tok); } } Dictionary <string, object> usedObjects = new Dictionary <string, object>(); for (int i = 0; i < split.Count; i++) { int?val = null; string token = split[i]; if (token == "if" || token == "else" || token == "return" || token == "break" || token == "continue" || token == "while" || token == "for" || token == "repeat" || token == "with" || token == "switch" || token == "case" || token == "default" || token == "exit" || token == "var" || token == "do" || token == "until") { 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" || token == "undefined" || token == "all") { par.Inlines.Add(new Run(token) { Foreground = keywordBrush }); } else if (token.StartsWith("argument")) { par.Inlines.Add(new Run(token) { Foreground = argumentBrush }); } else if ((val = AssetTypeResolver.FindConstValue(token)) != null) { par.Inlines.Add(new Run(token) { Foreground = constBrush, FontStyle = FontStyles.Italic, ToolTip = val.ToString() }); } 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) && gettext.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) && gettextJSON.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) => { if (token.Length > 2 && token[0] == '0' && token[1] == 'x') { ev.Handled = true; return; // Hex numbers aren't objects. } UndertaleData data = (Application.Current.MainWindow as MainWindow).Data; int id; if (int.TryParse(token, out id)) { 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.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(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]); } } } } // Add used object comments. foreach (var gt in usedObjects) { par.Inlines.Add(new Run(" // " + gt.Key + " = ") { 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); } } if (par.Inlines.Count >= 250) { // Splitting into different paragraphs significantly increases selection performance. document.Blocks.Add(par); par = new Paragraph(); par.Margin = new Thickness(0); } else if (!readingString) { par.Inlines.Add(new Run("\n")); } } } } document.Blocks.Add(par); DecompiledView.Document = document; CurrentDecompiled = code; dialog.Hide(); }); }); dialog.ShowDialog(); await t; }
private async Task SaveFile(string filename) { if (Data == null || Data.UnsupportedBytecodeVersion) { return; } if (IsGMS2 == Visibility.Visible) { MessageBox.Show("This is not yet fully stable and may break. You have been warned.", "GMS2 game", MessageBoxButton.OK, MessageBoxImage.Warning); } LoaderDialog dialog = new LoaderDialog("Saving", "Saving, please wait..."); dialog.Owner = this; FilePath = filename; if (System.IO.Path.GetDirectoryName(FilePath) != System.IO.Path.GetDirectoryName(filename)) { CloseChildFiles(); } DebugDataMode debugMode = DebugDataMode.NoDebug; if (!Data.GeneralInfo.DisableDebugger) // TODO: I think the game itself can also use the .yydebug file on crash reports { DebugDataDialog debugDialog = new DebugDataDialog(); debugDialog.Owner = this; debugDialog.ShowDialog(); debugMode = debugDialog.Result; // TODO: Add an option to generate debug data for just selected scripts (to make running faster / make the full assembly not take forever to load) } Task t = Task.Run(() => { try { using (var stream = new FileStream(filename, FileMode.Create)) { UndertaleIO.Write(stream, Data); } if (debugMode != DebugDataMode.NoDebug) { Debug.WriteLine("Generating debugger data..."); UndertaleDebugData debugData = DebugDataGenerator.GenerateDebugData(Data, debugMode); using (FileStream stream = new FileStream(System.IO.Path.ChangeExtension(FilePath, ".yydebug"), FileMode.Create)) { using (UndertaleWriter writer = new UndertaleWriter(stream)) { debugData.FORM.Serialize(writer); } } } } catch (Exception e) { MessageBox.Show("An error occured while trying to save:\n" + e.Message, "Save error", MessageBoxButton.OK, MessageBoxImage.Error); } Dispatcher.Invoke(() => { dialog.Hide(); }); }); dialog.ShowDialog(); await t; }
private async Task LoadFile(string filename) { LoaderDialog dialog = new LoaderDialog("Loading", "Loading, please wait..."); dialog.Owner = this; Task t = Task.Run(() => { bool hadWarnings = false; UndertaleData data = null; try { using (var stream = new FileStream(filename, FileMode.Open)) { data = UndertaleIO.Read(stream, warning => { MessageBox.Show(warning, "Loading warning", MessageBoxButton.OK, MessageBoxImage.Warning); hadWarnings = true; }); } } catch (Exception e) { MessageBox.Show("An error occured while trying to load:\n" + e.Message, "Load error", MessageBoxButton.OK, MessageBoxImage.Error); } Dispatcher.Invoke(() => { if (data != null) { if (data.UnsupportedBytecodeVersion) { MessageBox.Show("Only bytecode versions 15 and 16 are supported for now, you are trying to load " + data.GeneralInfo.BytecodeVersion + ". A lot of code is disabled and will likely break something. Saving/exporting is disabled.", "Unsupported bytecode version", MessageBoxButton.OK, MessageBoxImage.Warning); CanSave = false; } else if (hadWarnings) { MessageBox.Show("Warnings occurred during loading. Saving has been disabled.", "Saving disabled", MessageBoxButton.OK, MessageBoxImage.Warning); CanSave = false; } else { CanSave = true; } if (data.GeneralInfo?.Major >= 2) { MessageBox.Show("Game Maker: Studio 2 game loaded! I just hacked this together quickly for the Nintendo Switch release of Undertale. Most things work, but some editors don't display all the data.", "GMS2 game loaded", MessageBoxButton.OK, MessageBoxImage.Warning); } if (System.IO.Path.GetDirectoryName(FilePath) != System.IO.Path.GetDirectoryName(filename)) { CloseChildFiles(); } this.Data = data; this.FilePath = filename; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Data")); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsGMS2")); ChangeSelection(Highlighted = new DescriptionView("Welcome to UndertaleModTool!", "Double click on the items on the left to view them!")); SelectionHistory.Clear(); } dialog.Hide(); }); }); dialog.ShowDialog(); await t; }
public DebugTraceListener(LoaderDialog loaderDialog) { this.loaderDialog = loaderDialog; }
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"); } Task t = Task.Run(() => { string decompiled = null; Exception e = null; try { decompiled = Decompiler.Decompile(code).Replace("\r\n", "\n"); } catch (Exception ex) { e = ex; } if (gettextCode != null) { UpdateGettext(gettextCode); } 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)); 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, UndertaleObject> usedObjects = new Dictionary <string, UndertaleObject>(); 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") { 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("@")) { 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 (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]); 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]]); } } } } 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)) { if (!usedObjects.ContainsKey(split[i - 1])) { usedObjects.Add(split[i - 1], (Application.Current.MainWindow as MainWindow).Data.GameObjects[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.ToString()) { Foreground = commentBrush, Cursor = Cursors.Hand }); 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; }
private async void DecompileCode(UndertaleCode code) { DecompiledEditor.IsReadOnly = true; if (code.DuplicateEntry) { DecompiledEditor.Text = "// Duplicate code entry; cannot edit here."; CurrentDecompiled = code; } else { LoaderDialog dialog = new LoaderDialog("Decompiling", "Decompiling, please wait... This can take a while on complex scripts"); dialog.Owner = Window.GetWindow(this); _ = Dispatcher.BeginInvoke(new Action(() => { if (!dialog.IsClosed) { dialog.ShowDialog(); } })); UndertaleCode gettextCode = null; if (gettext == null) { gettextCode = (Application.Current.MainWindow as MainWindow).Data.Code.ByName("gml_Script_textdata_en"); } string dataPath = Path.GetDirectoryName((Application.Current.MainWindow as MainWindow).FilePath); string gettextJsonPath = (dataPath != null) ? Path.Combine(dataPath, "lang", "lang_en.json") : null; var dataa = (Application.Current.MainWindow as MainWindow).Data; Task t = Task.Run(() => { DecompileContext context = new DecompileContext(dataa, false); string decompiled = null; Exception e = null; try { string path = Path.Combine(TempPath, code.Name.Content + ".gml"); if (!SettingsWindow.ProfileModeEnabled || !File.Exists(path)) { decompiled = Decompiler.Decompile(code, context).Replace("\r\n", "\n"); } else { decompiled = File.ReadAllText(path).Replace("\r\n", "\n"); } } catch (Exception ex) { e = ex; } if (gettextCode != null) { UpdateGettext(gettextCode); } try { if (gettextJSON == null && gettextJsonPath != null && File.Exists(gettextJsonPath)) { string err = UpdateGettextJSON(File.ReadAllText(gettextJsonPath)); if (err != null) { e = new Exception(err); } } } catch (Exception exc) { MessageBox.Show(exc.ToString()); } if (gettextJSON == null && gettextJsonPath != null && File.Exists(gettextJsonPath)) { string err = UpdateGettextJSON(File.ReadAllText(gettextJsonPath)); if (err != null) { e = new Exception(err); } } Dispatcher.Invoke(() => { if (e != null) { DecompiledEditor.Text = "/* EXCEPTION!\n " + e.ToString() + "\n*/"; } else if (decompiled != null) { DecompiledEditor.Text = decompiled; CurrentDecompiledLocals = new List <string>(); var locals = dataa.CodeLocals.ByName(code.Name.Content); if (locals != null) { foreach (var local in locals.Locals) { CurrentDecompiledLocals.Add(local.Name.Content); } } } DecompiledEditor.IsReadOnly = false; DecompiledChanged = false; CurrentDecompiled = code; dialog.Hide(); }); }); await t; dialog.Close(); } }