From 5537fc87007daa9265e6695d0078f1acb306a314 Mon Sep 17 00:00:00 2001 From: CRefice Date: Sun, 7 Sep 2025 14:44:07 +0200 Subject: [PATCH 1/2] Add InstallTester command-line app This can be used to run the installer in batch-mode over many different versions of DT and UT at once --- SpaghettiCh2/InstallTester/App.config | 6 + .../InstallTester/InstallTester.csproj | 39 ++++ .../SpaghettiCh2/Models/ScriptContext.cs | 4 +- .../ViewModels/InstallationViewModel.cs | 20 +- .../ViewModels/PageViewModelBase.cs | 2 +- SpaghettiCh2/USPInstallTester/Program.cs | 188 ++++++++++++++++++ .../USPInstallTester/USPInstallTester.csproj | 14 ++ SpaghettiCh2/USPInstaller.sln | 10 + 8 files changed, 275 insertions(+), 8 deletions(-) create mode 100644 SpaghettiCh2/InstallTester/App.config create mode 100644 SpaghettiCh2/InstallTester/InstallTester.csproj create mode 100644 SpaghettiCh2/USPInstallTester/Program.cs create mode 100644 SpaghettiCh2/USPInstallTester/USPInstallTester.csproj diff --git a/SpaghettiCh2/InstallTester/App.config b/SpaghettiCh2/InstallTester/App.config new file mode 100644 index 0000000..56efbc7 --- /dev/null +++ b/SpaghettiCh2/InstallTester/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/SpaghettiCh2/InstallTester/InstallTester.csproj b/SpaghettiCh2/InstallTester/InstallTester.csproj new file mode 100644 index 0000000..82ee32a --- /dev/null +++ b/SpaghettiCh2/InstallTester/InstallTester.csproj @@ -0,0 +1,39 @@ + + + + + Debug + AnyCPU + {9F48A343-5238-4B71-8F2E-6083FFF2DB6E} + Exe + InstallTester + InstallTester + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + \ No newline at end of file diff --git a/SpaghettiCh2/SpaghettiCh2/Models/ScriptContext.cs b/SpaghettiCh2/SpaghettiCh2/Models/ScriptContext.cs index 157fea1..c5ecb46 100644 --- a/SpaghettiCh2/SpaghettiCh2/Models/ScriptContext.cs +++ b/SpaghettiCh2/SpaghettiCh2/Models/ScriptContext.cs @@ -26,7 +26,7 @@ public void ScriptMessage(string message) public void ScriptError(string error, string title = "Error", bool SetConsoleText = true) { - model.Log(title + ": " + error); + model.LogError(title + ": " + error); } public void UpdateProgressBar(string message, string status, double progressValue, double maxValue) @@ -45,7 +45,7 @@ public void HideProgressBar() public bool ScriptQuestion(string message) { - return MessageBoxViewModel.Show(message, "Avviso", true).Result; + return model.AskUserQuestion(message); } public void UpdateProgressValue(double progressValue) diff --git a/SpaghettiCh2/SpaghettiCh2/ViewModels/InstallationViewModel.cs b/SpaghettiCh2/SpaghettiCh2/ViewModels/InstallationViewModel.cs index 4f2ef1f..e73b052 100644 --- a/SpaghettiCh2/SpaghettiCh2/ViewModels/InstallationViewModel.cs +++ b/SpaghettiCh2/SpaghettiCh2/ViewModels/InstallationViewModel.cs @@ -11,7 +11,7 @@ namespace USPInstaller.ViewModels { - partial class InstallationViewModel : PageViewModelBase + public partial class InstallationViewModel : PageViewModelBase { // HACK: Avalonia wants a public empty constructor so that we can use the designer // to see how the viewmodel looks at design-time. Not to be used in actual code. @@ -38,11 +38,21 @@ public void UpdateProgress(string message, string subMessage, double value, doub ProgressValue = value / maxValue * 100; } - public void Log(string s) + public virtual void Log(string s) { log.AppendLine(s); } + public virtual void LogError(string s) + { + log.AppendLine(s); + } + + public virtual bool AskUserQuestion(string message) + { + return MessageBoxViewModel.Show(message, "Avviso", true).Result; + } + public event Action? InstallationError; public event Action? InstallationSuccess; @@ -131,9 +141,9 @@ private async Task DumpLog(string log) } } return null; - } + } - private async Task InstallUndertale(string assetPath, string exePath) + public async Task InstallUndertale(string assetPath, string exePath) { string scriptPath = Path.Join(assetPath, "Undertale", "installer.csx"); @@ -141,7 +151,7 @@ private async Task InstallUndertale(string assetPath, string exePath) await RunScriptOn(scriptPath, GetDataFileName(exePath)!); } - private async Task InstallDeltarune(string assetPath, string exePath) + public async Task InstallDeltarune(string assetPath, string exePath) { string scriptsPath = Path.Join(assetPath, "Deltarune", "InstallScripts"); string dataPath = GetDataFileName(exePath)! ?? throw new FileNotFoundException("Non trovo il file di dati del gioco", exePath); diff --git a/SpaghettiCh2/SpaghettiCh2/ViewModels/PageViewModelBase.cs b/SpaghettiCh2/SpaghettiCh2/ViewModels/PageViewModelBase.cs index 0ec70ee..3bf543f 100644 --- a/SpaghettiCh2/SpaghettiCh2/ViewModels/PageViewModelBase.cs +++ b/SpaghettiCh2/SpaghettiCh2/ViewModels/PageViewModelBase.cs @@ -2,7 +2,7 @@ namespace USPInstaller.ViewModels { - class PageViewModelBase : ObservableObject + public class PageViewModelBase : ObservableObject { } } diff --git a/SpaghettiCh2/USPInstallTester/Program.cs b/SpaghettiCh2/USPInstallTester/Program.cs new file mode 100644 index 0000000..f8757b2 --- /dev/null +++ b/SpaghettiCh2/USPInstallTester/Program.cs @@ -0,0 +1,188 @@ + +using System.ComponentModel; +using USPInstaller.Models; +using USPInstaller.ViewModels; + +class TestInstallViewModel : InstallationViewModel +{ + public TestInstallViewModel() : base() + { + PropertyChanged += OnPropertyChanged; + } + + private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == "OverallProgressMessage") + { + Console.WriteLine(OverallProgressMessage); + } + } + + public override bool AskUserQuestion(string message) + { + // Always assume user selected "yes" to questions + return true; + } + + public override void LogError(string s) + { + // Treat any error log as a hard error + throw new Exception("Installation script logged an error: rs" + s); + } +} + +class Program +{ + static string? FindApplicationWithName(string directory, string name) + { + string attempt = Path.Join(directory, name + ".exe"); + if (File.Exists(attempt)) + { + return attempt; + } + attempt = Path.Join(directory, name + ".app"); + if (Directory.Exists(attempt)) + { + return attempt; + } + attempt = Path.Join(directory, "run.sh"); + if (File.Exists(attempt)) + { + return attempt; + } + return null; + } + + static async Task<(string, string)> GetConfig() + { + string configPath = Path.Join(AppContext.BaseDirectory, "config.txt"); + if (File.Exists(configPath)) + { + // Read two lines from configPath + string[] lines = await File.ReadAllLinesAsync(configPath); + if (lines.Length >= 2) + { + Console.WriteLine("Read configuration from: " + configPath); + Console.WriteLine("Assets path: " + lines[0]); + Console.WriteLine("Games root path: " + lines[1]); + return (lines[0], lines[1]); + } + } + string? assetPath = null; + while (assetPath == null) + { + Console.Write("Asset path: "); + assetPath = Console.ReadLine(); + } + + string? rootDir = null; + while (rootDir == null) + { + Console.Write("Games root directory: "); + rootDir = Console.ReadLine(); + } + await File.WriteAllLinesAsync(configPath, [assetPath, rootDir]); + Console.WriteLine("Wrote configuration to: " + configPath); + return (assetPath, rootDir); + } + + // See https://learn.microsoft.com/en-us/dotnet/standard/io/how-to-copy-directories + static async Task CopyDirectory(string sourceDir, string destinationDir, bool recursive) + { + // Get information about the source directory + var dir = new DirectoryInfo(sourceDir); + + // Check if the source directory exists + if (!dir.Exists) + throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}"); + + // Cache directories before we start copying + DirectoryInfo[] dirs = dir.GetDirectories(); + + // Create the destination directory + Directory.CreateDirectory(destinationDir); + + // Get the files in the source directory and copy to the destination directory + foreach (FileInfo file in dir.GetFiles()) + { + string targetFilePath = Path.Combine(destinationDir, file.Name); + file.CopyTo(targetFilePath); + } + + // If recursive and copying subdirectories, recursively call this method + if (recursive) + { + foreach (DirectoryInfo subDir in dirs) + { + string newDestinationDir = Path.Combine(destinationDir, subDir.Name); + await CopyDirectory(subDir.FullName, newDestinationDir, true); + } + } + } + + static async Task CopyToFolder(string directory, string tempPath) + { + if (Directory.Exists(tempPath)) + Directory.Delete(tempPath, true); + await CopyDirectory(directory, tempPath, true); + } + + static async Task Main(string[] args) + { + (string assetPath, string rootDir) = await GetConfig(); + + var patchedRootDir = Path.Combine(rootDir, "Patched"); + Directory.CreateDirectory(patchedRootDir); + + InstallationViewModel installer = new TestInstallViewModel(); + + bool failed = false; + foreach (string subDir in Directory.GetDirectories(rootDir)) + { + try + { + string fileName = Path.GetFileName(subDir); + string patchedCopyDir = Path.Combine(patchedRootDir, fileName); + + string? undertaleExe = FindApplicationWithName(subDir, "UNDERTALE"); + if (undertaleExe != null) + { + await CopyToFolder(subDir, patchedCopyDir); + Console.WriteLine($"\nInstalling Undertale patch on {fileName}..."); + await installer.InstallUndertale(assetPath, FindApplicationWithName(patchedCopyDir, "UNDERTALE")!); + continue; + } + string? deltaruneExe = FindApplicationWithName(subDir, "DELTARUNE"); + if (deltaruneExe != null) + { + await CopyToFolder(subDir, patchedCopyDir); + Console.WriteLine($"\nInstalling Deltarune patch on {fileName}..."); + await installer.InstallDeltarune(assetPath, FindApplicationWithName(patchedCopyDir, "DELTARUNE")!); + } + } + catch (Exception e) + { + Console.WriteLine($"Installation to {Path.GetFileName(subDir)} failed:"); + Console.WriteLine(e.Message); + Console.WriteLine(e.StackTrace); + failed = true; + } + } + + if (!failed) + { + if (!Console.IsOutputRedirected) + { + Console.WriteLine("\nInstallation completed succesfully :D\nPress any key to exit..."); + Console.ReadKey(); + } + return 0; + } + if (!Console.IsOutputRedirected) + { + Console.WriteLine("\nThere were some errors during installation :(\nPress any key to exit"); + Console.ReadKey(); + } + return 10; + } +} \ No newline at end of file diff --git a/SpaghettiCh2/USPInstallTester/USPInstallTester.csproj b/SpaghettiCh2/USPInstallTester/USPInstallTester.csproj new file mode 100644 index 0000000..bfbcc01 --- /dev/null +++ b/SpaghettiCh2/USPInstallTester/USPInstallTester.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/SpaghettiCh2/USPInstaller.sln b/SpaghettiCh2/USPInstaller.sln index f25bef3..619cede 100644 --- a/SpaghettiCh2/USPInstaller.sln +++ b/SpaghettiCh2/USPInstaller.sln @@ -18,6 +18,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Underanalyzer", "UndertaleM EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UndertaleModLib", "UndertaleModTool\UndertaleModLib\UndertaleModLib.csproj", "{3E1FBADF-4C64-D01C-7F2D-01009E3B0CBB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "USPInstallTester", "USPInstallTester\USPInstallTester.csproj", "{479C5045-B40D-4978-95EA-E16C9AAFCD94}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -50,6 +52,14 @@ Global {3E1FBADF-4C64-D01C-7F2D-01009E3B0CBB}.Release|Any CPU.Build.0 = Release|Any CPU {3E1FBADF-4C64-D01C-7F2D-01009E3B0CBB}.Release|x64.ActiveCfg = Release|x64 {3E1FBADF-4C64-D01C-7F2D-01009E3B0CBB}.Release|x64.Build.0 = Release|x64 + {479C5045-B40D-4978-95EA-E16C9AAFCD94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {479C5045-B40D-4978-95EA-E16C9AAFCD94}.Debug|Any CPU.Build.0 = Debug|Any CPU + {479C5045-B40D-4978-95EA-E16C9AAFCD94}.Debug|x64.ActiveCfg = Debug|Any CPU + {479C5045-B40D-4978-95EA-E16C9AAFCD94}.Debug|x64.Build.0 = Debug|Any CPU + {479C5045-B40D-4978-95EA-E16C9AAFCD94}.Release|Any CPU.ActiveCfg = Release|Any CPU + {479C5045-B40D-4978-95EA-E16C9AAFCD94}.Release|Any CPU.Build.0 = Release|Any CPU + {479C5045-B40D-4978-95EA-E16C9AAFCD94}.Release|x64.ActiveCfg = Release|Any CPU + {479C5045-B40D-4978-95EA-E16C9AAFCD94}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 4c0fed1efa7cdc06a04058ed4694d8662be8ec98 Mon Sep 17 00:00:00 2001 From: CRefice Date: Sun, 7 Sep 2025 14:58:04 +0200 Subject: [PATCH 2/2] Add release profile and remove I18N folders from build --- .../PublishProfiles/FolderProfile.pubxml | 13 +++++++++++ .../USPInstallTester/USPInstallTester.csproj | 22 ++++++++++--------- 2 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 SpaghettiCh2/USPInstallTester/Properties/PublishProfiles/FolderProfile.pubxml diff --git a/SpaghettiCh2/USPInstallTester/Properties/PublishProfiles/FolderProfile.pubxml b/SpaghettiCh2/USPInstallTester/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 0000000..3f4a83a --- /dev/null +++ b/SpaghettiCh2/USPInstallTester/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,13 @@ + + + + + Release + Any CPU + bin\Release\net8.0\publish\ + FileSystem + <_TargetId>Folder + net8.0 + false + + \ No newline at end of file diff --git a/SpaghettiCh2/USPInstallTester/USPInstallTester.csproj b/SpaghettiCh2/USPInstallTester/USPInstallTester.csproj index bfbcc01..707e631 100644 --- a/SpaghettiCh2/USPInstallTester/USPInstallTester.csproj +++ b/SpaghettiCh2/USPInstallTester/USPInstallTester.csproj @@ -1,14 +1,16 @@  - - Exe - net8.0 - enable - enable - - - - - + + Exe + net8.0 + enable + enable + + + + + + en-US;it-IT +