From db7cc2e25d58af6b0025b374aafe3d390e535de6 Mon Sep 17 00:00:00 2001 From: nhaar Date: Wed, 18 Oct 2023 09:31:49 -0300 Subject: [PATCH 1/8] add relative path solution to script's #load --- UndertaleModTool/MainWindow.xaml.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index 4f0a29f87..c11571799 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -1,4 +1,4 @@ -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting.Hosting; @@ -2580,6 +2580,18 @@ private async Task RunScriptNow(string path) string scriptText = $"#line 1 \"{path}\"\n" + File.ReadAllText(path); Debug.WriteLine(path); + // since attempting to load scripts with #load in a file will lead to it using the UTMT path, + // we can circumvent this by hardcoding the absolute path to the script directory + // in all instances of #load with a relative path + var scriptDir = Path.GetDirectoryName(path); + var matches = Regex.Matches(scriptText, @"(?<=^#load\s+"").*\.csx(?=""\s*$)", RegexOptions.Multiline); + foreach (Match match in matches) + { + if (Path.IsPathRooted(match.Value)) + continue; + scriptText = scriptText.Replace(match.Value, Path.Combine(scriptDir, match.Value)); + } + Dispatcher.Invoke(() => CommandBox.Text = "Running " + Path.GetFileName(path) + " ..."); try { From 3a7deac497bcdebccc97b84c9edef66610a649d6 Mon Sep 17 00:00:00 2001 From: nhaar Date: Tue, 12 Dec 2023 08:54:11 -0300 Subject: [PATCH 2/8] handle exceptions across multiple loaded files --- UndertaleModTool/MainWindow.xaml.cs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index c11571799..ea7b03127 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -1,4 +1,4 @@ -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting.Hosting; @@ -2469,7 +2469,8 @@ public void OpenCodeFile(string name, CodeEditorMode editorDecompile, bool inNew public string ProcessException(in Exception exc, in string scriptText) { - List excLineNums = new(); + // each member is a (file name, line number) pair for all lines in the stack trace + List> loadedScriptLineNums = new(); string excText = string.Empty; List traceLines = new(); Dictionary exTypesDict = null; @@ -2505,12 +2506,13 @@ public string ProcessException(in Exception exc, in string scriptText) { if (traceLine.TrimStart()[..13] == "at Submission") // only stack trace lines from the script { + // matches full path of the script file + string sourceFile = Regex.Match(traceLine, @"(?<=in ).*\.csx(?=:line \d+)").Value; int linePos = traceLine.IndexOf(":line ") + 6; // ":line ".Length = 6 if (linePos != (-1 + 6)) { int lineNum = Convert.ToInt32(traceLine[linePos..]); - if (!excLineNums.Contains(lineNum)) - excLineNums.Add(lineNum); + loadedScriptLineNums.Add(new Tuple(sourceFile, lineNum)); } } } @@ -2526,10 +2528,22 @@ public string ProcessException(in Exception exc, in string scriptText) return $"An error occurred while processing the exception text.\nError message - \"{e.Message}\"\nThe unprocessed text is below.\n\n" + excString; } - if (excLineNums.Count > 0) //if line number(s) is found + if (loadedScriptLineNums.Count > 0) //if line number(s) is found { + // read the code for the files to know what the code line associated with the stack trace is + Dictionary> scriptsCode = new(); + foreach (Tuple scriptLineNum in loadedScriptLineNums) + { + string scriptPath = scriptLineNum.Item1; + if (!scriptsCode.ContainsKey(scriptPath)) + { + string scriptCode = File.ReadAllText(scriptPath); + scriptsCode.Add(scriptPath, scriptCode.Split('\n').ToList()); + } + } + string[] scriptLines = scriptText.Split('\n'); - string excLines = string.Join('\n', excLineNums.Select(n => $"Line {n}: {scriptLines[n].TrimStart(new char[] { '\t', ' ' })}")); + string excLines = string.Join('\n', loadedScriptLineNums.Select(pair => $"Line {pair.Item2} in script {pair.Item1}: {scriptsCode[pair.Item1][pair.Item2 - 1]}")); // - 1 because line numbers start from 1 if (exTypesDict is not null) { string exTypesStr = string.Join(",\n", exTypesDict.Select(x => $"{x.Key}{((x.Value > 1) ? " (x" + x.Value + ")" : string.Empty)}")); From 29e13bfde8723b6f5d170889ccf72f55a207561d Mon Sep 17 00:00:00 2001 From: nhaar Date: Tue, 12 Dec 2023 09:03:26 -0300 Subject: [PATCH 3/8] remove unnecessary argument --- UndertaleModTool/MainWindow.xaml.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index ea7b03127..a47f8bfce 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -2467,7 +2467,7 @@ public void OpenCodeFile(string name, CodeEditorMode editorDecompile, bool inNew } } - public string ProcessException(in Exception exc, in string scriptText) + public string ProcessException(in Exception exc) { // each member is a (file name, line number) pair for all lines in the stack trace List> loadedScriptLineNums = new(); @@ -2542,7 +2542,6 @@ public string ProcessException(in Exception exc, in string scriptText) } } - string[] scriptLines = scriptText.Split('\n'); string excLines = string.Join('\n', loadedScriptLineNums.Select(pair => $"Line {pair.Item2} in script {pair.Item1}: {scriptsCode[pair.Item1][pair.Item2 - 1]}")); // - 1 because line numbers start from 1 if (exTypesDict is not null) { @@ -2641,7 +2640,7 @@ private async Task RunScriptNow(string path) string excString = string.Empty; if (!isScriptException) - excString = ProcessException(in exc, in scriptText); + excString = ProcessException(in exc); await StopProgressBarUpdater(); From afff3badfddcebfdd179ebd7e6f0906dfdfee1c4 Mon Sep 17 00:00:00 2001 From: nhaar <38634785+nhaar@users.noreply.github.com> Date: Fri, 12 Jul 2024 18:44:10 -0300 Subject: [PATCH 4/8] add safe check for unexistent source file Co-authored-by: Vladislav Stepanov --- UndertaleModTool/MainWindow.xaml.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index a47f8bfce..761587b2a 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -2508,6 +2508,8 @@ public string ProcessException(in Exception exc) { // matches full path of the script file string sourceFile = Regex.Match(traceLine, @"(?<=in ).*\.csx(?=:line \d+)").Value; + if (!File.Exists(sourceFile)) + continue; int linePos = traceLine.IndexOf(":line ") + 6; // ":line ".Length = 6 if (linePos != (-1 + 6)) { From e2895851bd0ed01b5b358304ec1107e50d5c51f0 Mon Sep 17 00:00:00 2001 From: nhaar <38634785+nhaar@users.noreply.github.com> Date: Fri, 12 Jul 2024 18:45:10 -0300 Subject: [PATCH 5/8] add error catching if the source file can't be read Co-authored-by: Vladislav Stepanov --- UndertaleModTool/MainWindow.xaml.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index 761587b2a..da5231589 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -2539,7 +2539,15 @@ public string ProcessException(in Exception exc) string scriptPath = scriptLineNum.Item1; if (!scriptsCode.ContainsKey(scriptPath)) { - string scriptCode = File.ReadAllText(scriptPath); + string scriptCode = null; + try + { + scriptCode = File.ReadAllText(scriptPath); + } + catch (Exception e) + { + return $"An error occurred while processing the exception text.\nError message - \"{e.Message}\"\nThe unprocessed text is below.\n\n" + excString; + } scriptsCode.Add(scriptPath, scriptCode.Split('\n').ToList()); } } From 71aeb33ae6e1094ec2a512bf57a791894ef2efef Mon Sep 17 00:00:00 2001 From: nhaar <38634785+nhaar@users.noreply.github.com> Date: Fri, 12 Jul 2024 18:46:01 -0300 Subject: [PATCH 6/8] make error messages display only script name, not directory Co-authored-by: Vladislav Stepanov --- UndertaleModTool/MainWindow.xaml.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index da5231589..e92a7e190 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -2552,7 +2552,11 @@ public string ProcessException(in Exception exc) } } - string excLines = string.Join('\n', loadedScriptLineNums.Select(pair => $"Line {pair.Item2} in script {pair.Item1}: {scriptsCode[pair.Item1][pair.Item2 - 1]}")); // - 1 because line numbers start from 1 + string excLines = string.Join('\n', loadedScriptLineNums.Select(pair => + { + string scriptName = pair.Item1.Split(Path.DirectorySeparatorChar)[^1]; + return $"Line {pair.Item2} in script {scriptName}: {scriptsCode[pair.Item1][pair.Item2 - 1]}"; // - 1 because line numbers start from 1 + })); if (exTypesDict is not null) { string exTypesStr = string.Join(",\n", exTypesDict.Select(x => $"{x.Key}{((x.Value > 1) ? " (x" + x.Value + ")" : string.Empty)}")); From fd5c0e6407eb1749034ad9884afc0f9fa9994813 Mon Sep 17 00:00:00 2001 From: nhaar <38634785+nhaar@users.noreply.github.com> Date: Sun, 14 Jul 2024 17:50:54 -0300 Subject: [PATCH 7/8] fix compilation issue in exception check --- UndertaleModTool/MainWindow.xaml.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index e92a7e190..9d3993d34 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -2546,6 +2546,8 @@ public string ProcessException(in Exception exc) } catch (Exception e) { + string excString = exc.ToString(); + return $"An error occurred while processing the exception text.\nError message - \"{e.Message}\"\nThe unprocessed text is below.\n\n" + excString; } scriptsCode.Add(scriptPath, scriptCode.Split('\n').ToList()); From aaf7e71992b5e3110fa0dfa86a240929a766cdcf Mon Sep 17 00:00:00 2001 From: nhaar <38634785+nhaar@users.noreply.github.com> Date: Mon, 15 Jul 2024 18:39:08 -0300 Subject: [PATCH 8/8] add the load fix to the CLI --- UndertaleModCli/Program.cs | 3 ++- UndertaleModLib/Util/ScriptUtil.cs | 37 +++++++++++++++++++++++++++++ UndertaleModTool/MainWindow.xaml.cs | 15 +----------- 3 files changed, 40 insertions(+), 15 deletions(-) create mode 100644 UndertaleModLib/Util/ScriptUtil.cs diff --git a/UndertaleModCli/Program.cs b/UndertaleModCli/Program.cs index f8261801d..a6b56307d 100644 --- a/UndertaleModCli/Program.cs +++ b/UndertaleModCli/Program.cs @@ -721,7 +721,8 @@ private void RunCSharpCode(string code, string scriptFile = null) try { - CSharpScript.EvaluateAsync(code, CliScriptOptions, this, typeof(IScriptInterface)).GetAwaiter().GetResult(); + var processedCode = ScriptUtil.ProcessScriptText(code, scriptFile); + CSharpScript.EvaluateAsync(processedCode, CliScriptOptions, this, typeof(IScriptInterface)).GetAwaiter().GetResult(); ScriptExecutionSuccess = true; ScriptErrorMessage = ""; } diff --git a/UndertaleModLib/Util/ScriptUtil.cs b/UndertaleModLib/Util/ScriptUtil.cs new file mode 100644 index 000000000..58ecd4e40 --- /dev/null +++ b/UndertaleModLib/Util/ScriptUtil.cs @@ -0,0 +1,37 @@ +using System.IO; +using System.Text.RegularExpressions; + +namespace UndertaleModLib.Util +{ + /// + /// Utils related to running scripts + /// + public static class ScriptUtil + { + /// + /// Processes a script text so that it can use relative `#load` preprocessing + /// + /// The script code + /// The path to the script + /// + public static string ProcessScriptText(string code, string path) + { + var output = code; + // since attempting to load scripts with #load in a file will lead to it using the UTMT path, + // we can circumvent this by hardcoding the absolute path to the script directory + // in all instances of #load with a relative path + var scriptDir = Path.GetDirectoryName(path); + var matches = Regex.Matches(code, @"(?<=^#load\s+"").*\.csx(?=""\s*$)", RegexOptions.Multiline); + foreach (Match match in matches) + { + if (Path.IsPathRooted(match.Value)) + { + continue; + } + output = output.Replace(match.Value, Path.Combine(scriptDir, match.Value)); + } + + return output; + } + } +} \ No newline at end of file diff --git a/UndertaleModTool/MainWindow.xaml.cs b/UndertaleModTool/MainWindow.xaml.cs index 9d3993d34..113232875 100644 --- a/UndertaleModTool/MainWindow.xaml.cs +++ b/UndertaleModTool/MainWindow.xaml.cs @@ -2606,20 +2606,7 @@ public async Task RunScript(string path) private async Task RunScriptNow(string path) { - string scriptText = $"#line 1 \"{path}\"\n" + File.ReadAllText(path); - Debug.WriteLine(path); - - // since attempting to load scripts with #load in a file will lead to it using the UTMT path, - // we can circumvent this by hardcoding the absolute path to the script directory - // in all instances of #load with a relative path - var scriptDir = Path.GetDirectoryName(path); - var matches = Regex.Matches(scriptText, @"(?<=^#load\s+"").*\.csx(?=""\s*$)", RegexOptions.Multiline); - foreach (Match match in matches) - { - if (Path.IsPathRooted(match.Value)) - continue; - scriptText = scriptText.Replace(match.Value, Path.Combine(scriptDir, match.Value)); - } + string scriptText = ScriptUtil.ProcessScriptText($"#line 1 \"{path}\"\n" + File.ReadAllText(path), path); Dispatcher.Invoke(() => CommandBox.Text = "Running " + Path.GetFileName(path) + " ..."); try