From dd3bb00ebda8b5fe480a2cc0d7b3c0686ae011b1 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Sun, 1 Dec 2024 11:33:49 -0700 Subject: [PATCH 1/7] Logic for setting up more of linux automagically --- MelonLoader.Installer/LinuxUtils.cs | 112 ++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 MelonLoader.Installer/LinuxUtils.cs diff --git a/MelonLoader.Installer/LinuxUtils.cs b/MelonLoader.Installer/LinuxUtils.cs new file mode 100644 index 0000000..868a2c0 --- /dev/null +++ b/MelonLoader.Installer/LinuxUtils.cs @@ -0,0 +1,112 @@ +#if LINUX +using Avalonia; +using System.Diagnostics; +using System.IO.Pipelines; +using System.Runtime.InteropServices; + +namespace MelonLoader.Installer; + +internal static partial class LinuxUtils{ + public static event InstallProgressEventHandler? Progress; + public static string DownloadUrlVCx64 = "https://aka.ms/vs/16/release/vc_redist.x64.exe"; + public static string DownloadUrlVCx86 = "https://aka.ms/vs/16/release/vc_redist.x86.exe"; + public static string TempDestination = "/tmp"; + public static bool CheckIfBinaryExists(string name){ + ProcessResult whichResult = RunCommand("which", name); + if (whichResult.ErrorLevel == 1){ + return false; + } + return true; + } + public static bool CheckIfFlatpakExists(string name){ + ProcessResult flatpakResult = RunCommand("flatpak", $"info {name}"); + if (flatpakResult.ErrorLevel == 1){ + return false; + } + return true; + } + public static bool CheckIfProtonTricksExists(){ + if(CheckIfBinaryExists("protontricks")){ + return true; + } + if(CheckIfFlatpakExists("com.github.Matoking.protontricks")){ + return true; + } + return false; + } + public static void InstallProtonDependencies(string appId){ + bool useFlatpak = false; + string command = "protontricks"; + string commandLaunch = "protontricks-launch"; + string launchArgPrefix = "--appid"; + string argPrefix = ""; + if(CheckIfFlatpakExists("com.github.Matoking.protontricks")){ + useFlatpak = true; + } + if(CheckIfBinaryExists("protontricks")){ + useFlatpak = false; + } + if(useFlatpak){ + command = "flatpak"; + commandLaunch = "flatpak"; + argPrefix = "run com.github.Matoking.protontricks "; + launchArgPrefix = "run com.github.Matoking.protontricks --command=protontricks-launch --appid"; + } + try{ + string downloadPath = $"{TempDestination}/vc_redistx86.exe"; + DownloadFile(DownloadUrlVCx86, downloadPath); + RunCommand(commandLaunch, $"{launchArgPrefix} {appId} {downloadPath}"); + File.Delete(downloadPath); + downloadPath = $"{TempDestination}/vc_redistx64.exe"; + DownloadFile(DownloadUrlVCx64, downloadPath); + RunCommand(commandLaunch, $"{launchArgPrefix} {appId} {downloadPath}"); + File.Delete(downloadPath); + } + catch{ + return; + } + RunCommand(command, $"{argPrefix}{appId} -q dotnetdesktop6"); + + } + public static void OpenSteamGameProperties(string appId){ + Process.Start($"steam://gameproperties/{appId}"); + } + public static async void DownloadFile(string url, string destination){ + var newStr = File.OpenWrite(destination); + var result = await InstallerUtils.DownloadFileAsync(url, newStr, (progress, newStatus) => Progress?.Invoke(progress, newStatus)); + if (result != null) + { + throw new Exception($"Failed to download {url}: " + result); + } + } + public static ProcessResult RunCommand(string command, string arguments){ + var escapedArgs = arguments.Replace("\"", "\\\""); + var process = new Process{ + StartInfo = new ProcessStartInfo{ + FileName = command, + Arguments = escapedArgs, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + } + }; + process.Start(); + process.WaitForExit(); + string standardError = process.StandardError.ToString() ?? ""; + string StandardOutput = process.StandardOutput.ToString() ?? ""; + return new ProcessResult(process.ExitCode, standardError, StandardOutput); + } +} + +internal class ProcessResult{ + public int ErrorLevel; + public string ErrorOutput; + public string StandardOutput; + public ProcessResult(int ErrorLevel, string ErrorOutput, string StandardOutput){ + this.ErrorLevel = ErrorLevel; + this.ErrorOutput = ErrorOutput; + this.StandardOutput = StandardOutput; + } +} + +#endif \ No newline at end of file From abf98af5e50a54723282b3045164ecbe35fe3635 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Sun, 1 Dec 2024 14:09:48 -0700 Subject: [PATCH 2/7] Automate installation of melonloader dependenices, checking for protontricks, and setting il2cpp executable --- .../GameLaunchers/EgsLauncher.cs | 2 +- .../GameLaunchers/GogLauncher.cs | 2 +- .../GameLaunchers/SteamLauncher.cs | 2 +- MelonLoader.Installer/GameManager.cs | 6 +- MelonLoader.Installer/LinuxUtils.cs | 60 ++++++++++++------- MelonLoader.Installer/MLManager.cs | 13 +++- MelonLoader.Installer/ViewModels/GameModel.cs | 3 +- MelonLoader.Installer/Views/DetailsView.axaml | 38 +++++++----- .../Views/DetailsView.axaml.cs | 30 ++++++++-- MelonLoader.Installer/Views/MainView.axaml.cs | 2 +- 10 files changed, 108 insertions(+), 50 deletions(-) diff --git a/MelonLoader.Installer/GameLaunchers/EgsLauncher.cs b/MelonLoader.Installer/GameLaunchers/EgsLauncher.cs index f9bfb9a..f98a20c 100644 --- a/MelonLoader.Installer/GameLaunchers/EgsLauncher.cs +++ b/MelonLoader.Installer/GameLaunchers/EgsLauncher.cs @@ -47,7 +47,7 @@ public override void AddGames() if (name == null) continue; - GameManager.TryAddGame(dir, name, this, null, out _); + GameManager.TryAddGame(dir, null, name, this, null, out _); } } } diff --git a/MelonLoader.Installer/GameLaunchers/GogLauncher.cs b/MelonLoader.Installer/GameLaunchers/GogLauncher.cs index 77ef37e..2c0d165 100644 --- a/MelonLoader.Installer/GameLaunchers/GogLauncher.cs +++ b/MelonLoader.Installer/GameLaunchers/GogLauncher.cs @@ -34,7 +34,7 @@ public override void AddGames() if (name == null) continue; - GameManager.TryAddGame(path, name, this, null, out _); + GameManager.TryAddGame(path, null, name, this, null, out _); } } } diff --git a/MelonLoader.Installer/GameLaunchers/SteamLauncher.cs b/MelonLoader.Installer/GameLaunchers/SteamLauncher.cs index cc3b880..3096988 100644 --- a/MelonLoader.Installer/GameLaunchers/SteamLauncher.cs +++ b/MelonLoader.Installer/GameLaunchers/SteamLauncher.cs @@ -71,7 +71,7 @@ public override void AddGames() continue; var iconPath = Path.Combine(steamPath, "appcache", "librarycache", id + "_icon.jpg"); - GameManager.TryAddGame(appDir, name, this, iconPath, out _); + GameManager.TryAddGame(appDir, id, name, this, iconPath, out _); } } } diff --git a/MelonLoader.Installer/GameManager.cs b/MelonLoader.Installer/GameManager.cs index 28d5065..9e56ca0 100644 --- a/MelonLoader.Installer/GameManager.cs +++ b/MelonLoader.Installer/GameManager.cs @@ -32,7 +32,7 @@ private static void LoadSavedGames() { foreach (var gamePath in Config.LoadGameList()) { - TryAddGame(gamePath, null, null, null, out _); + TryAddGame(gamePath, null, null, null, null, out _); } // In case it was manually edited or if any games were removed @@ -86,7 +86,7 @@ public static void RemoveGame(GameModel game) Games.Remove(game); } - public static GameModel? TryAddGame(string path, string? customName, GameLauncher? launcher, string? iconPath, [NotNullWhen(false)] out string? errorMessage) + public static GameModel? TryAddGame(string path, string? id, string? customName, GameLauncher? launcher, string? iconPath, [NotNullWhen(false)] out string? errorMessage) { if (File.Exists(path)) { @@ -169,7 +169,7 @@ public static void RemoveGame(GameModel game) var isProtected = Directory.Exists(Path.Combine(path, "EasyAntiCheat")); - var result = new GameModel(exe, customName ?? Path.GetFileNameWithoutExtension(exe), !is64, linux, launcher, icon, mlVersion, isProtected); + var result = new GameModel(exe, id, customName ?? Path.GetFileNameWithoutExtension(exe), !is64, linux, launcher, icon, mlVersion, isProtected); errorMessage = null; AddGameSorted(result); diff --git a/MelonLoader.Installer/LinuxUtils.cs b/MelonLoader.Installer/LinuxUtils.cs index 868a2c0..50a9c07 100644 --- a/MelonLoader.Installer/LinuxUtils.cs +++ b/MelonLoader.Installer/LinuxUtils.cs @@ -11,39 +11,47 @@ internal static partial class LinuxUtils{ public static string DownloadUrlVCx64 = "https://aka.ms/vs/16/release/vc_redist.x64.exe"; public static string DownloadUrlVCx86 = "https://aka.ms/vs/16/release/vc_redist.x86.exe"; public static string TempDestination = "/tmp"; - public static bool CheckIfBinaryExists(string name){ - ProcessResult whichResult = RunCommand("which", name); - if (whichResult.ErrorLevel == 1){ - return false; - } - return true; + public static async Task CheckIfBinaryExists(string name){ + ProcessResult whichResult = await RunCommand("which", name); + return !(whichResult.ErrorLevel >= 1); } - public static bool CheckIfFlatpakExists(string name){ - ProcessResult flatpakResult = RunCommand("flatpak", $"info {name}"); + public static async Task CheckIfFlatpakExists(string name){ + ProcessResult flatpakResult = await RunCommand("flatpak", $"info {name}"); if (flatpakResult.ErrorLevel == 1){ return false; } return true; } - public static bool CheckIfProtonTricksExists(){ - if(CheckIfBinaryExists("protontricks")){ + public static async Task CheckIfProtonTricksExists(){ + if(await CheckIfBinaryExists("protontricks")){ return true; } - if(CheckIfFlatpakExists("com.github.Matoking.protontricks")){ + if(await CheckIfFlatpakExists("com.github.Matoking.protontricks")){ return true; } return false; } - public static void InstallProtonDependencies(string appId){ + public static async Task InstallProtonDependencies(string? appId, InstallProgressEventHandler? onProgress){ + if(appId == null){ + //No id, probably not a steam game. + return; + } + var tasks = 3; + var currentTask = 0; + + void SetProgress(double progress, string? newStatus = null) + { + onProgress?.Invoke(currentTask / (double)tasks + progress / tasks, newStatus); + } bool useFlatpak = false; string command = "protontricks"; string commandLaunch = "protontricks-launch"; string launchArgPrefix = "--appid"; string argPrefix = ""; - if(CheckIfFlatpakExists("com.github.Matoking.protontricks")){ + if(await CheckIfFlatpakExists("com.github.Matoking.protontricks")){ useFlatpak = true; } - if(CheckIfBinaryExists("protontricks")){ + if(await CheckIfBinaryExists("protontricks")){ useFlatpak = false; } if(useFlatpak){ @@ -53,23 +61,33 @@ public static void InstallProtonDependencies(string appId){ launchArgPrefix = "run com.github.Matoking.protontricks --command=protontricks-launch --appid"; } try{ + SetProgress(0, "Install Proton dependency: vc_redistx86"); string downloadPath = $"{TempDestination}/vc_redistx86.exe"; DownloadFile(DownloadUrlVCx86, downloadPath); - RunCommand(commandLaunch, $"{launchArgPrefix} {appId} {downloadPath}"); + await RunCommand(commandLaunch, $"{launchArgPrefix} {appId} {downloadPath} /quiet"); File.Delete(downloadPath); + currentTask++; + SetProgress(0, "Install Proton dependency: vc_redistx64"); downloadPath = $"{TempDestination}/vc_redistx64.exe"; DownloadFile(DownloadUrlVCx64, downloadPath); - RunCommand(commandLaunch, $"{launchArgPrefix} {appId} {downloadPath}"); + await RunCommand(commandLaunch, $"{launchArgPrefix} {appId} {downloadPath} /quiet"); File.Delete(downloadPath); } catch{ return; } - RunCommand(command, $"{argPrefix}{appId} -q dotnetdesktop6"); - + currentTask++; + SetProgress(0, "Install Proton dependency: dotnetdesktop6"); + await RunCommand(command, $"{argPrefix}{appId} -q dotnetdesktop6"); + } + public static async Task AddExecutePerm(string path){ + await RunCommand("chmod", $"+x {path}"); } public static void OpenSteamGameProperties(string appId){ - Process.Start($"steam://gameproperties/{appId}"); + Process.Start(new ProcessStartInfo(){ + FileName = $"steam://gameproperties/{appId}", + UseShellExecute = true + }); } public static async void DownloadFile(string url, string destination){ var newStr = File.OpenWrite(destination); @@ -79,7 +97,7 @@ public static async void DownloadFile(string url, string destination){ throw new Exception($"Failed to download {url}: " + result); } } - public static ProcessResult RunCommand(string command, string arguments){ + public async static Task RunCommand(string command, string arguments){ var escapedArgs = arguments.Replace("\"", "\\\""); var process = new Process{ StartInfo = new ProcessStartInfo{ @@ -91,7 +109,7 @@ public static ProcessResult RunCommand(string command, string arguments){ } }; process.Start(); - process.WaitForExit(); + await process.WaitForExitAsync(); string standardError = process.StandardError.ToString() ?? ""; string StandardOutput = process.StandardOutput.ToString() ?? ""; return new ProcessResult(process.ExitCode, standardError, StandardOutput); diff --git a/MelonLoader.Installer/MLManager.cs b/MelonLoader.Installer/MLManager.cs index e7c9500..62f581a 100644 --- a/MelonLoader.Installer/MLManager.cs +++ b/MelonLoader.Installer/MLManager.cs @@ -11,6 +11,7 @@ namespace MelonLoader.Installer; internal static class MLManager { private static bool inited; + public static bool IsProtonTricksInstalled; internal static readonly string[] proxyNames = [ "version.dll", @@ -49,6 +50,7 @@ public static async Task Init() return true; inited = await RefreshVersions(); + IsProtonTricksInstalled = await LinuxUtils.CheckIfProtonTricksExists(); return inited; } @@ -332,7 +334,7 @@ public static void SetLocalZip(string zipPath, InstallProgressEventHandler? onPr onFinished?.Invoke(null); } - public static async Task InstallAsync(string gameDir, bool removeUserFiles, MLVersion version, bool linux, bool x86, InstallProgressEventHandler? onProgress, InstallFinishedEventHandler? onFinished) + public static async Task InstallAsync(string gameDir, string? id, bool removeUserFiles, MLVersion version, bool linux, bool x86, InstallProgressEventHandler? onProgress, InstallFinishedEventHandler? onFinished) { var downloadUrl = linux ? (!x86 ? version.DownloadUrlLinux : null) : (x86 ? version.DownloadUrlWinX86 : version.DownloadUrlWin); if (downloadUrl == null) @@ -401,6 +403,15 @@ void SetProgress(double progress, string? newStatus = null) } } + #if LINUX + + if(IsProtonTricksInstalled && !linux){ + await LinuxUtils.InstallProtonDependencies(id, onProgress); + await LinuxUtils.AddExecutePerm($"{gameDir}/MelonLoader/Dependencies/Il2CppAssemblyGenerator/Cpp2IL/Cpp2IL"); + } + + #endif + Directory.CreateDirectory(Path.Combine(gameDir, "Mods")); Directory.CreateDirectory(Path.Combine(gameDir, "Plugins")); Directory.CreateDirectory(Path.Combine(gameDir, "UserData")); diff --git a/MelonLoader.Installer/ViewModels/GameModel.cs b/MelonLoader.Installer/ViewModels/GameModel.cs index 1bfd0b3..c58e626 100644 --- a/MelonLoader.Installer/ViewModels/GameModel.cs +++ b/MelonLoader.Installer/ViewModels/GameModel.cs @@ -4,9 +4,10 @@ namespace MelonLoader.Installer.ViewModels; -public class GameModel(string path, string name, bool is32Bit, bool isLinux, GameLauncher? launcher, Bitmap? icon, SemVersion? mlVersion, bool isProtected) : ViewModelBase +public class GameModel(string path, string? id, string name, bool is32Bit, bool isLinux, GameLauncher? launcher, Bitmap? icon, SemVersion? mlVersion, bool isProtected) : ViewModelBase { public string Path => path; + public string? Id => id; public string Name => name; public bool Is32Bit => is32Bit; public bool IsLinux => isLinux; diff --git a/MelonLoader.Installer/Views/DetailsView.axaml b/MelonLoader.Installer/Views/DetailsView.axaml index 8c7cddc..375ec8f 100644 --- a/MelonLoader.Installer/Views/DetailsView.axaml +++ b/MelonLoader.Installer/Views/DetailsView.axaml @@ -58,26 +58,36 @@ VerticalAlignment="Center" Opacity="0.7" FontSize="14" /> - + How do I start MelonLoader? + + + Proton tricks is missing. Proton Dependencies will be skipped. + + Linux Launch Instructions - - In order to start MelonLoader under Wine, you'll need to export the following variable: - WINEDLLOVERRIDES="version=n,b" - On Steam, you can set the launch options to: - WINEDLLOVERRIDES="version=n,b" %command%" - - - In order to start MelonLoader, you'll need to export the following variables: - - LD_PRELOAD="libversion.so" - On Steam, you can set the launch options to: - - + + In order to start MelonLoader under Wine, you'll need to export the following variable: + WINEDLLOVERRIDES="version=n,b" + On Steam, you can set the launch options to: + WINEDLLOVERRIDES="version=n,b" %command%" + + + In order to start MelonLoader, you'll need to export the following variables: + + LD_PRELOAD="libversion.so" + On Steam, you can set the launch options to: + + + \ No newline at end of file diff --git a/MelonLoader.Installer/Views/DetailsView.axaml.cs b/MelonLoader.Installer/Views/DetailsView.axaml.cs index e3e9320..a16c19a 100644 --- a/MelonLoader.Installer/Views/DetailsView.axaml.cs +++ b/MelonLoader.Installer/Views/DetailsView.axaml.cs @@ -42,7 +42,6 @@ protected override async void OnDataContextChanged(EventArgs e) if (Model == null) return; - #if LINUX if (Model.Game.IsLinux) { @@ -51,6 +50,7 @@ protected override async void OnDataContextChanged(EventArgs e) } ShowLinuxInstructions.IsVisible = Model.Game.MLInstalled; + ShowProtonTricksWarning.IsVisible = !Model.Game.MLInstalled && !MLManager.IsProtonTricksInstalled; #endif Model.Game.PropertyChanged += PropertyChangedHandler; @@ -138,12 +138,25 @@ private void InstallHandler(object sender, RoutedEventArgs args) Model.Installing = true; ShowLinuxInstructions.IsVisible = false; - _ = MLManager.InstallAsync(Path.GetDirectoryName(Model.Game.Path)!, Model.Game.MLInstalled && !KeepFilesCheck.IsChecked!.Value, + _ = MLManager.InstallAsync(Path.GetDirectoryName(Model.Game.Path)!, Model.Game.Id, Model.Game.MLInstalled && !KeepFilesCheck.IsChecked!.Value, (MLVersion)VersionCombobox.SelectedItem!, Model.Game.IsLinux, Model.Game.Is32Bit, (progress, newStatus) => Dispatcher.UIThread.Post(() => OnInstallProgress(progress, newStatus)), (errorMessage) => Dispatcher.UIThread.Post(() => OnOperationFinished(errorMessage))); } + private void GamePropsHandler(object sender, RoutedEventArgs args){ + if (Model == null || !Model.Game.ValidateGame()) + { + MainWindow.Instance.ShowMainView(); + return; + } + #if LINUX + if(Model.Game.Id != null){ + LinuxUtils.OpenSteamGameProperties(Model.Game.Id); + } + #endif + } + private void OnInstallProgress(double progress, string? newStatus) { if (newStatus != null) @@ -163,9 +176,10 @@ private void OnOperationFinished(string? errorMessage, bool addedLocalBuild = fa Model.Game.ValidateGame(); Model.Installing = false; -#if LINUX - ShowLinuxInstructions.IsVisible = Model.Game.MLInstalled; -#endif + #if LINUX + ShowLinuxInstructions.IsVisible = Model.Game.MLInstalled; + ShowProtonTricksWarning.IsVisible = !Model.Game.MLInstalled && !MLManager.IsProtonTricksInstalled; + #endif if (errorMessage != null) { @@ -191,7 +205,11 @@ private void OnOperationFinished(string? errorMessage, bool addedLocalBuild = fa < 0 => "Downgraded" }; } - + #if LINUX + if(isInstall && Model.Game.MLInstalled){ + Model.LinuxInstructions = true; + } + #endif DialogBox.ShowNotice("SUCCESS!", $"Successfully {operationType}{((!Model.Game.MLInstalled || isInstall) ? string.Empty : " to")}\nMelonLoader v{(Model.Game.MLInstalled ? Model.Game.MLVersion : currentMLVersion)}"); } diff --git a/MelonLoader.Installer/Views/MainView.axaml.cs b/MelonLoader.Installer/Views/MainView.axaml.cs index 570326b..b3cd754 100644 --- a/MelonLoader.Installer/Views/MainView.axaml.cs +++ b/MelonLoader.Installer/Views/MainView.axaml.cs @@ -103,7 +103,7 @@ public async void AddGameManuallyHandler(object sender, RoutedEventArgs args) return; var path = files[0].Path.LocalPath; - GameManager.TryAddGame(path, null, null, null, out var error); + GameManager.TryAddGame(path, null, null, null, null, out var error); if (error != null) { DialogBox.ShowError(error); From eafaa292e17e9b53ab500863a7b1c13db9d64b98 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Sun, 1 Dec 2024 14:19:05 -0700 Subject: [PATCH 3/7] removal of il2cpp fix as it's not applicable to the installer --- MelonLoader.Installer/LinuxUtils.cs | 3 --- MelonLoader.Installer/MLManager.cs | 1 - 2 files changed, 4 deletions(-) diff --git a/MelonLoader.Installer/LinuxUtils.cs b/MelonLoader.Installer/LinuxUtils.cs index 50a9c07..e50416e 100644 --- a/MelonLoader.Installer/LinuxUtils.cs +++ b/MelonLoader.Installer/LinuxUtils.cs @@ -80,9 +80,6 @@ void SetProgress(double progress, string? newStatus = null) SetProgress(0, "Install Proton dependency: dotnetdesktop6"); await RunCommand(command, $"{argPrefix}{appId} -q dotnetdesktop6"); } - public static async Task AddExecutePerm(string path){ - await RunCommand("chmod", $"+x {path}"); - } public static void OpenSteamGameProperties(string appId){ Process.Start(new ProcessStartInfo(){ FileName = $"steam://gameproperties/{appId}", diff --git a/MelonLoader.Installer/MLManager.cs b/MelonLoader.Installer/MLManager.cs index 62f581a..e2a9966 100644 --- a/MelonLoader.Installer/MLManager.cs +++ b/MelonLoader.Installer/MLManager.cs @@ -407,7 +407,6 @@ void SetProgress(double progress, string? newStatus = null) if(IsProtonTricksInstalled && !linux){ await LinuxUtils.InstallProtonDependencies(id, onProgress); - await LinuxUtils.AddExecutePerm($"{gameDir}/MelonLoader/Dependencies/Il2CppAssemblyGenerator/Cpp2IL/Cpp2IL"); } #endif From f28298d03c124bb7062b1c3f8282f85488a04be4 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Sun, 1 Dec 2024 15:00:42 -0700 Subject: [PATCH 4/7] add flatpak usability test, reduce checks on if protontricks is installed, fix trailing quote in winedll overrides text --- MelonLoader.Installer/LinuxUtils.cs | 23 ++++++++++++++++--- MelonLoader.Installer/MLManager.cs | 6 +++-- MelonLoader.Installer/Views/DetailsView.axaml | 2 +- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/MelonLoader.Installer/LinuxUtils.cs b/MelonLoader.Installer/LinuxUtils.cs index e50416e..c1bade2 100644 --- a/MelonLoader.Installer/LinuxUtils.cs +++ b/MelonLoader.Installer/LinuxUtils.cs @@ -10,7 +10,6 @@ internal static partial class LinuxUtils{ public static event InstallProgressEventHandler? Progress; public static string DownloadUrlVCx64 = "https://aka.ms/vs/16/release/vc_redist.x64.exe"; public static string DownloadUrlVCx86 = "https://aka.ms/vs/16/release/vc_redist.x86.exe"; - public static string TempDestination = "/tmp"; public static async Task CheckIfBinaryExists(string name){ ProcessResult whichResult = await RunCommand("which", name); return !(whichResult.ErrorLevel >= 1); @@ -22,6 +21,10 @@ public static async Task CheckIfFlatpakExists(string name){ } return true; } + public static async Task CheckIfFlatpakUsable(string appId){ + ProcessResult flatpakResult = await RunCommand("flatpak", $"run com.github.Matoking.protontricks {appId} list"); + return !(flatpakResult.ErrorLevel >= 1); + } public static async Task CheckIfProtonTricksExists(){ if(await CheckIfBinaryExists("protontricks")){ return true; @@ -31,6 +34,18 @@ public static async Task CheckIfProtonTricksExists(){ } return false; } + public static async Task CheckIfCanInstallDependencies(string? appId){ + if(appId == null){ + return false; + } + if(await CheckIfBinaryExists("protontricks")){ + return true; + } + if(await CheckIfFlatpakExists("com.github.Matoking.protontricks")){ + return await CheckIfFlatpakUsable(appId); + } + return false; + } public static async Task InstallProtonDependencies(string? appId, InstallProgressEventHandler? onProgress){ if(appId == null){ //No id, probably not a steam game. @@ -48,6 +63,7 @@ void SetProgress(double progress, string? newStatus = null) string commandLaunch = "protontricks-launch"; string launchArgPrefix = "--appid"; string argPrefix = ""; + string TempDestination = "/tmp"; if(await CheckIfFlatpakExists("com.github.Matoking.protontricks")){ useFlatpak = true; } @@ -58,14 +74,15 @@ void SetProgress(double progress, string? newStatus = null) command = "flatpak"; commandLaunch = "flatpak"; argPrefix = "run com.github.Matoking.protontricks "; - launchArgPrefix = "run com.github.Matoking.protontricks --command=protontricks-launch --appid"; + launchArgPrefix = "run --command=protontricks-launch com.github.Matoking.protontricks --appid"; + TempDestination = $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}/.steam"; } try{ SetProgress(0, "Install Proton dependency: vc_redistx86"); string downloadPath = $"{TempDestination}/vc_redistx86.exe"; DownloadFile(DownloadUrlVCx86, downloadPath); await RunCommand(commandLaunch, $"{launchArgPrefix} {appId} {downloadPath} /quiet"); - File.Delete(downloadPath); + //File.Delete(downloadPath); currentTask++; SetProgress(0, "Install Proton dependency: vc_redistx64"); downloadPath = $"{TempDestination}/vc_redistx64.exe"; diff --git a/MelonLoader.Installer/MLManager.cs b/MelonLoader.Installer/MLManager.cs index e2a9966..9fb455b 100644 --- a/MelonLoader.Installer/MLManager.cs +++ b/MelonLoader.Installer/MLManager.cs @@ -11,7 +11,7 @@ namespace MelonLoader.Installer; internal static class MLManager { private static bool inited; - public static bool IsProtonTricksInstalled; + public static bool IsProtonTricksInstalled = false; internal static readonly string[] proxyNames = [ "version.dll", @@ -50,7 +50,9 @@ public static async Task Init() return true; inited = await RefreshVersions(); + #if LINUX IsProtonTricksInstalled = await LinuxUtils.CheckIfProtonTricksExists(); + #endif return inited; } @@ -405,7 +407,7 @@ void SetProgress(double progress, string? newStatus = null) #if LINUX - if(IsProtonTricksInstalled && !linux){ + if(IsProtonTricksInstalled && !linux && await LinuxUtils.CheckIfCanInstallDependencies(id)){ await LinuxUtils.InstallProtonDependencies(id, onProgress); } diff --git a/MelonLoader.Installer/Views/DetailsView.axaml b/MelonLoader.Installer/Views/DetailsView.axaml index 375ec8f..c235037 100644 --- a/MelonLoader.Installer/Views/DetailsView.axaml +++ b/MelonLoader.Installer/Views/DetailsView.axaml @@ -74,7 +74,7 @@ In order to start MelonLoader under Wine, you'll need to export the following variable: WINEDLLOVERRIDES="version=n,b" On Steam, you can set the launch options to: - WINEDLLOVERRIDES="version=n,b" %command%" + WINEDLLOVERRIDES="version=n,b" %command% In order to start MelonLoader, you'll need to export the following variables: From f3ee128ea2875f59c19f56e0e43bcd81f690dba8 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Sun, 1 Dec 2024 15:26:57 -0700 Subject: [PATCH 5/7] uncomment out file deletion, oops. --- MelonLoader.Installer/LinuxUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MelonLoader.Installer/LinuxUtils.cs b/MelonLoader.Installer/LinuxUtils.cs index c1bade2..2806818 100644 --- a/MelonLoader.Installer/LinuxUtils.cs +++ b/MelonLoader.Installer/LinuxUtils.cs @@ -82,7 +82,7 @@ void SetProgress(double progress, string? newStatus = null) string downloadPath = $"{TempDestination}/vc_redistx86.exe"; DownloadFile(DownloadUrlVCx86, downloadPath); await RunCommand(commandLaunch, $"{launchArgPrefix} {appId} {downloadPath} /quiet"); - //File.Delete(downloadPath); + File.Delete(downloadPath); currentTask++; SetProgress(0, "Install Proton dependency: vc_redistx64"); downloadPath = $"{TempDestination}/vc_redistx64.exe"; From 1af2deeba5493fafd3bd4a0db95bb5efa1bce8ba Mon Sep 17 00:00:00 2001 From: Alexandra Date: Mon, 2 Dec 2024 09:01:04 -0700 Subject: [PATCH 6/7] Strip out linux utils as the functionality was already implemented in the loader itself. Move OpenSteamGameProperties to MLManager. Remove ProtonTricks warning Clean up code formatting --- MelonLoader.Installer/LinuxUtils.cs | 144 ------------------ MelonLoader.Installer/MLManager.cs | 25 +-- MelonLoader.Installer/Views/DetailsView.axaml | 5 - .../Views/DetailsView.axaml.cs | 18 ++- 4 files changed, 25 insertions(+), 167 deletions(-) delete mode 100644 MelonLoader.Installer/LinuxUtils.cs diff --git a/MelonLoader.Installer/LinuxUtils.cs b/MelonLoader.Installer/LinuxUtils.cs deleted file mode 100644 index 2806818..0000000 --- a/MelonLoader.Installer/LinuxUtils.cs +++ /dev/null @@ -1,144 +0,0 @@ -#if LINUX -using Avalonia; -using System.Diagnostics; -using System.IO.Pipelines; -using System.Runtime.InteropServices; - -namespace MelonLoader.Installer; - -internal static partial class LinuxUtils{ - public static event InstallProgressEventHandler? Progress; - public static string DownloadUrlVCx64 = "https://aka.ms/vs/16/release/vc_redist.x64.exe"; - public static string DownloadUrlVCx86 = "https://aka.ms/vs/16/release/vc_redist.x86.exe"; - public static async Task CheckIfBinaryExists(string name){ - ProcessResult whichResult = await RunCommand("which", name); - return !(whichResult.ErrorLevel >= 1); - } - public static async Task CheckIfFlatpakExists(string name){ - ProcessResult flatpakResult = await RunCommand("flatpak", $"info {name}"); - if (flatpakResult.ErrorLevel == 1){ - return false; - } - return true; - } - public static async Task CheckIfFlatpakUsable(string appId){ - ProcessResult flatpakResult = await RunCommand("flatpak", $"run com.github.Matoking.protontricks {appId} list"); - return !(flatpakResult.ErrorLevel >= 1); - } - public static async Task CheckIfProtonTricksExists(){ - if(await CheckIfBinaryExists("protontricks")){ - return true; - } - if(await CheckIfFlatpakExists("com.github.Matoking.protontricks")){ - return true; - } - return false; - } - public static async Task CheckIfCanInstallDependencies(string? appId){ - if(appId == null){ - return false; - } - if(await CheckIfBinaryExists("protontricks")){ - return true; - } - if(await CheckIfFlatpakExists("com.github.Matoking.protontricks")){ - return await CheckIfFlatpakUsable(appId); - } - return false; - } - public static async Task InstallProtonDependencies(string? appId, InstallProgressEventHandler? onProgress){ - if(appId == null){ - //No id, probably not a steam game. - return; - } - var tasks = 3; - var currentTask = 0; - - void SetProgress(double progress, string? newStatus = null) - { - onProgress?.Invoke(currentTask / (double)tasks + progress / tasks, newStatus); - } - bool useFlatpak = false; - string command = "protontricks"; - string commandLaunch = "protontricks-launch"; - string launchArgPrefix = "--appid"; - string argPrefix = ""; - string TempDestination = "/tmp"; - if(await CheckIfFlatpakExists("com.github.Matoking.protontricks")){ - useFlatpak = true; - } - if(await CheckIfBinaryExists("protontricks")){ - useFlatpak = false; - } - if(useFlatpak){ - command = "flatpak"; - commandLaunch = "flatpak"; - argPrefix = "run com.github.Matoking.protontricks "; - launchArgPrefix = "run --command=protontricks-launch com.github.Matoking.protontricks --appid"; - TempDestination = $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}/.steam"; - } - try{ - SetProgress(0, "Install Proton dependency: vc_redistx86"); - string downloadPath = $"{TempDestination}/vc_redistx86.exe"; - DownloadFile(DownloadUrlVCx86, downloadPath); - await RunCommand(commandLaunch, $"{launchArgPrefix} {appId} {downloadPath} /quiet"); - File.Delete(downloadPath); - currentTask++; - SetProgress(0, "Install Proton dependency: vc_redistx64"); - downloadPath = $"{TempDestination}/vc_redistx64.exe"; - DownloadFile(DownloadUrlVCx64, downloadPath); - await RunCommand(commandLaunch, $"{launchArgPrefix} {appId} {downloadPath} /quiet"); - File.Delete(downloadPath); - } - catch{ - return; - } - currentTask++; - SetProgress(0, "Install Proton dependency: dotnetdesktop6"); - await RunCommand(command, $"{argPrefix}{appId} -q dotnetdesktop6"); - } - public static void OpenSteamGameProperties(string appId){ - Process.Start(new ProcessStartInfo(){ - FileName = $"steam://gameproperties/{appId}", - UseShellExecute = true - }); - } - public static async void DownloadFile(string url, string destination){ - var newStr = File.OpenWrite(destination); - var result = await InstallerUtils.DownloadFileAsync(url, newStr, (progress, newStatus) => Progress?.Invoke(progress, newStatus)); - if (result != null) - { - throw new Exception($"Failed to download {url}: " + result); - } - } - public async static Task RunCommand(string command, string arguments){ - var escapedArgs = arguments.Replace("\"", "\\\""); - var process = new Process{ - StartInfo = new ProcessStartInfo{ - FileName = command, - Arguments = escapedArgs, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - } - }; - process.Start(); - await process.WaitForExitAsync(); - string standardError = process.StandardError.ToString() ?? ""; - string StandardOutput = process.StandardOutput.ToString() ?? ""; - return new ProcessResult(process.ExitCode, standardError, StandardOutput); - } -} - -internal class ProcessResult{ - public int ErrorLevel; - public string ErrorOutput; - public string StandardOutput; - public ProcessResult(int ErrorLevel, string ErrorOutput, string StandardOutput){ - this.ErrorLevel = ErrorLevel; - this.ErrorOutput = ErrorOutput; - this.StandardOutput = StandardOutput; - } -} - -#endif \ No newline at end of file diff --git a/MelonLoader.Installer/MLManager.cs b/MelonLoader.Installer/MLManager.cs index 9fb455b..077728d 100644 --- a/MelonLoader.Installer/MLManager.cs +++ b/MelonLoader.Installer/MLManager.cs @@ -1,4 +1,5 @@ using Semver; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Nodes; @@ -11,7 +12,6 @@ namespace MelonLoader.Installer; internal static class MLManager { private static bool inited; - public static bool IsProtonTricksInstalled = false; internal static readonly string[] proxyNames = [ "version.dll", @@ -50,12 +50,21 @@ public static async Task Init() return true; inited = await RefreshVersions(); - #if LINUX - IsProtonTricksInstalled = await LinuxUtils.CheckIfProtonTricksExists(); - #endif + return inited; } + public static void OpenSteamGameProperties(string? appId){ + if(appId == null){ + return; + } + + Process.Start(new ProcessStartInfo(){ + FileName = $"steam://gameproperties/{appId}", + UseShellExecute = true + }); + } + private static Task RefreshVersions() { Versions.Clear(); @@ -405,14 +414,6 @@ void SetProgress(double progress, string? newStatus = null) } } - #if LINUX - - if(IsProtonTricksInstalled && !linux && await LinuxUtils.CheckIfCanInstallDependencies(id)){ - await LinuxUtils.InstallProtonDependencies(id, onProgress); - } - - #endif - Directory.CreateDirectory(Path.Combine(gameDir, "Mods")); Directory.CreateDirectory(Path.Combine(gameDir, "Plugins")); Directory.CreateDirectory(Path.Combine(gameDir, "UserData")); diff --git a/MelonLoader.Installer/Views/DetailsView.axaml b/MelonLoader.Installer/Views/DetailsView.axaml index c235037..79ff644 100644 --- a/MelonLoader.Installer/Views/DetailsView.axaml +++ b/MelonLoader.Installer/Views/DetailsView.axaml @@ -62,11 +62,6 @@ How do I start MelonLoader? - - - Proton tricks is missing. Proton Dependencies will be skipped. - - Linux Launch Instructions diff --git a/MelonLoader.Installer/Views/DetailsView.axaml.cs b/MelonLoader.Installer/Views/DetailsView.axaml.cs index a16c19a..111f673 100644 --- a/MelonLoader.Installer/Views/DetailsView.axaml.cs +++ b/MelonLoader.Installer/Views/DetailsView.axaml.cs @@ -42,7 +42,8 @@ protected override async void OnDataContextChanged(EventArgs e) if (Model == null) return; -#if LINUX + + #if LINUX if (Model.Game.IsLinux) { LdLibPathVar.Text = $"LD_LIBRARY_PATH=\"{Model.Game.Dir}:$LD_LIBRARY_PATH\""; @@ -50,8 +51,7 @@ protected override async void OnDataContextChanged(EventArgs e) } ShowLinuxInstructions.IsVisible = Model.Game.MLInstalled; - ShowProtonTricksWarning.IsVisible = !Model.Game.MLInstalled && !MLManager.IsProtonTricksInstalled; -#endif + #endif Model.Game.PropertyChanged += PropertyChangedHandler; @@ -144,17 +144,21 @@ private void InstallHandler(object sender, RoutedEventArgs args) (errorMessage) => Dispatcher.UIThread.Post(() => OnOperationFinished(errorMessage))); } - private void GamePropsHandler(object sender, RoutedEventArgs args){ + private void GamePropsHandler(object sender, RoutedEventArgs args) + { if (Model == null || !Model.Game.ValidateGame()) { MainWindow.Instance.ShowMainView(); return; } + #if LINUX if(Model.Game.Id != null){ - LinuxUtils.OpenSteamGameProperties(Model.Game.Id); + MLManager.OpenSteamGameProperties(Model.Game.Id); } + #endif + } private void OnInstallProgress(double progress, string? newStatus) @@ -178,7 +182,6 @@ private void OnOperationFinished(string? errorMessage, bool addedLocalBuild = fa #if LINUX ShowLinuxInstructions.IsVisible = Model.Game.MLInstalled; - ShowProtonTricksWarning.IsVisible = !Model.Game.MLInstalled && !MLManager.IsProtonTricksInstalled; #endif if (errorMessage != null) @@ -205,11 +208,14 @@ private void OnOperationFinished(string? errorMessage, bool addedLocalBuild = fa < 0 => "Downgraded" }; } + #if LINUX if(isInstall && Model.Game.MLInstalled){ Model.LinuxInstructions = true; } + #endif + DialogBox.ShowNotice("SUCCESS!", $"Successfully {operationType}{((!Model.Game.MLInstalled || isInstall) ? string.Empty : " to")}\nMelonLoader v{(Model.Game.MLInstalled ? Model.Game.MLVersion : currentMLVersion)}"); } From 38edce4620655fddbb49263de7eaa10b0fd23357 Mon Sep 17 00:00:00 2001 From: Alexandra Date: Mon, 2 Dec 2024 09:09:03 -0700 Subject: [PATCH 7/7] Remove another if macro. Couple more code style tweaks. --- MelonLoader.Installer/MLManager.cs | 3 ++- MelonLoader.Installer/Views/DetailsView.axaml.cs | 8 +++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/MelonLoader.Installer/MLManager.cs b/MelonLoader.Installer/MLManager.cs index 077728d..ab0f9d3 100644 --- a/MelonLoader.Installer/MLManager.cs +++ b/MelonLoader.Installer/MLManager.cs @@ -54,7 +54,8 @@ public static async Task Init() return inited; } - public static void OpenSteamGameProperties(string? appId){ + public static void OpenSteamGameProperties(string? appId) + { if(appId == null){ return; } diff --git a/MelonLoader.Installer/Views/DetailsView.axaml.cs b/MelonLoader.Installer/Views/DetailsView.axaml.cs index 111f673..8c674a9 100644 --- a/MelonLoader.Installer/Views/DetailsView.axaml.cs +++ b/MelonLoader.Installer/Views/DetailsView.axaml.cs @@ -151,14 +151,12 @@ private void GamePropsHandler(object sender, RoutedEventArgs args) MainWindow.Instance.ShowMainView(); return; } - - #if LINUX - if(Model.Game.Id != null){ + + if(Model.Game.Id != null) + { MLManager.OpenSteamGameProperties(Model.Game.Id); } - #endif - } private void OnInstallProgress(double progress, string? newStatus)