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; }