diff --git a/MelonLoader.Installer/Architecture.cs b/MelonLoader.Installer/Architecture.cs new file mode 100644 index 0000000..8936376 --- /dev/null +++ b/MelonLoader.Installer/Architecture.cs @@ -0,0 +1,18 @@ +using System.ComponentModel; + +namespace MelonLoader.Installer; + +/// +/// Supported architectures by MelonLoader. +/// +public enum Architecture +{ + [Description("unknown")] + Unknown, + [Description("win-x86")] + WindowsX86, + [Description("win-x64")] + WindowsX64, + [Description("linux-x64")] + LinuxX64, +} diff --git a/MelonLoader.Installer/GameManager.cs b/MelonLoader.Installer/GameManager.cs index 28d5065..add8f05 100644 --- a/MelonLoader.Installer/GameManager.cs +++ b/MelonLoader.Installer/GameManager.cs @@ -100,7 +100,7 @@ public static void RemoveGame(GameModel game) path = Path.GetFullPath(path); - var linux = false; + var arch = Architecture.Unknown; var rawDataDirs = Directory.GetDirectories(path, "*_Data"); var dataDirs = rawDataDirs.Where(x => File.Exists(x[..^5] + ".exe")).ToArray(); @@ -109,7 +109,7 @@ public static void RemoveGame(GameModel game) dataDirs = rawDataDirs.Where(x => File.Exists(x[..^5] + ".x86_64")).ToArray(); if (dataDirs.Length != 0) { - linux = true; + arch = Architecture.LinuxX64; } else { @@ -124,7 +124,7 @@ public static void RemoveGame(GameModel game) return null; } - var exe = dataDirs[0][..^5] + (linux ? ".x86_64" : ".exe"); + var exe = dataDirs[0][..^5] + (arch == Architecture.LinuxX64 ? ".x86_64" : ".exe"); if (Games.Any(x => x.Path.Equals(exe, StringComparison.OrdinalIgnoreCase))) { @@ -132,13 +132,12 @@ public static void RemoveGame(GameModel game) return null; } - var is64 = true; - if (!linux) + if (arch == Architecture.Unknown) { try { using var pe = new PEReader(File.OpenRead(exe)); - is64 = pe.PEHeaders.CoffHeader.Machine == Machine.Amd64; + arch = pe.PEHeaders.CoffHeader.Machine == Machine.Amd64 ? Architecture.WindowsX64 : Architecture.WindowsX86; } catch { @@ -147,8 +146,8 @@ public static void RemoveGame(GameModel game) } } - var mlVersion = MLVersion.GetMelonLoaderVersion(path, out var ml86, out var mlLinux); - if (mlVersion != null && (is64 == ml86 || linux != mlLinux)) + var mlVersion = MLVersion.GetMelonLoaderVersion(path, out var mlArch); + if (mlVersion != null && (mlArch != arch)) mlVersion = null; Bitmap? icon = null; @@ -163,13 +162,13 @@ public static void RemoveGame(GameModel game) } #if WINDOWS - if (!linux) + if (arch != Architecture.LinuxX64) icon ??= IconExtractor.GetExeIcon(exe); #endif 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, customName ?? Path.GetFileNameWithoutExtension(exe), arch, launcher, icon, mlVersion, isProtected); errorMessage = null; AddGameSorted(result); diff --git a/MelonLoader.Installer/MLManager.cs b/MelonLoader.Installer/MLManager.cs index e7c9500..59efd08 100644 --- a/MelonLoader.Installer/MLManager.cs +++ b/MelonLoader.Installer/MLManager.cs @@ -1,4 +1,5 @@ -using Semver; +using MelonLoader.Installer.Utils; +using Semver; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Nodes; @@ -310,7 +311,7 @@ public static void SetLocalZip(string zipPath, InstallProgressEventHandler? onPr return; } - var mlVer = MLVersion.GetMelonLoaderVersion(Config.LocalZipCache, out var x86, out var linux); + var mlVer = MLVersion.GetMelonLoaderVersion(Config.LocalZipCache, out var arch); if (mlVer == null) { onFinished?.Invoke("The selected zip archive does not contain a valid MelonLoader build."); @@ -320,9 +321,9 @@ public static void SetLocalZip(string zipPath, InstallProgressEventHandler? onPr var version = new MLVersion() { Version = mlVer, - DownloadUrlWin = !linux ? (!x86 ? Config.LocalZipCache : null) : null, - DownloadUrlWinX86 = !linux ? (x86 ? Config.LocalZipCache : null) : null, - DownloadUrlLinux = linux ? Config.LocalZipCache : null, + DownloadUrlWin = arch == Architecture.WindowsX64 ? Config.LocalZipCache : null, + DownloadUrlWinX86 = arch == Architecture.WindowsX86 ? Config.LocalZipCache : null, + DownloadUrlLinux = arch == Architecture.LinuxX64 ? Config.LocalZipCache : null, IsLocalPath = true }; @@ -332,12 +333,19 @@ 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, bool removeUserFiles, MLVersion version, Architecture arch, InstallProgressEventHandler? onProgress, InstallFinishedEventHandler? onFinished) { - var downloadUrl = linux ? (!x86 ? version.DownloadUrlLinux : null) : (x86 ? version.DownloadUrlWinX86 : version.DownloadUrlWin); + var downloadUrl = arch switch + { + Architecture.LinuxX64 => version.DownloadUrlLinux, + Architecture.WindowsX64 => version.DownloadUrlWin, + Architecture.WindowsX86 => version.DownloadUrlWinX86, + _ => null + }; + if (downloadUrl == null) { - onFinished?.Invoke($"The selected version does not support the selected architecture: {(linux ? "linux" : "win")}-{(x86 ? "x86" : "x64")}"); + onFinished?.Invoke($"The selected version does not support the selected architecture: {arch.GetDescription()}"); return; } diff --git a/MelonLoader.Installer/MLVersion.cs b/MelonLoader.Installer/MLVersion.cs index f6e9900..db018bd 100644 --- a/MelonLoader.Installer/MLVersion.cs +++ b/MelonLoader.Installer/MLVersion.cs @@ -12,10 +12,9 @@ public class MLVersion public required SemVersion Version { get; init; } public bool IsLocalPath { get; init; } - public static SemVersion? GetMelonLoaderVersion(string gameDir, out bool x86, out bool linux) + public static SemVersion? GetMelonLoaderVersion(string gameDir, out Architecture architecture) { - x86 = false; - linux = false; + architecture = Architecture.Unknown; var mlDir = Path.Combine(gameDir, "MelonLoader"); if (!Directory.Exists(mlDir)) @@ -57,17 +56,17 @@ public class MLVersion return null; proxyPath = Path.Combine(gameDir, proxyPath); - - linux = proxyPath.EndsWith(".so"); - - if (linux) + if (proxyPath.EndsWith(".so")) + { + architecture = Architecture.LinuxX64; return version; + } try { using var proxyStr = File.OpenRead(proxyPath); var pe = new PEReader(proxyStr); - x86 = pe.PEHeaders.CoffHeader.Machine != Machine.Amd64; + architecture = pe.PEHeaders.CoffHeader.Machine == Machine.Amd64 ? Architecture.WindowsX64 : Architecture.WindowsX86; return version; } catch diff --git a/MelonLoader.Installer/Utils/EnumHelper.cs b/MelonLoader.Installer/Utils/EnumHelper.cs new file mode 100644 index 0000000..7d1e38c --- /dev/null +++ b/MelonLoader.Installer/Utils/EnumHelper.cs @@ -0,0 +1,28 @@ +using System.ComponentModel; + +namespace MelonLoader.Installer.Utils; + +/// +/// Class for converting Description annotations into string +/// +public static class EnumHelper +{ + public static string? GetDescription(this T enumValue) + where T : struct, IConvertible + { + if (!typeof(T).IsEnum) + return null; + + var description = enumValue.ToString(); + var fieldInfo = enumValue.GetType().GetField(enumValue.ToString()); + + if (fieldInfo == null) + return description; + + var attrs = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), true); + if (attrs.Length > 0) + description = ((DescriptionAttribute)attrs[0]).Description; + + return description; + } +} diff --git a/MelonLoader.Installer/ViewModels/GameModel.cs b/MelonLoader.Installer/ViewModels/GameModel.cs index 1bfd0b3..d34d3c1 100644 --- a/MelonLoader.Installer/ViewModels/GameModel.cs +++ b/MelonLoader.Installer/ViewModels/GameModel.cs @@ -4,12 +4,12 @@ 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 name, Architecture architecture, GameLauncher? launcher, Bitmap? icon, SemVersion? mlVersion, bool isProtected) : ViewModelBase { public string Path => path; public string Name => name; - public bool Is32Bit => is32Bit; - public bool IsLinux => isLinux; + public Architecture Arch => architecture; + public bool IsLinux => architecture == Architecture.LinuxX64; public GameLauncher? Launcher => launcher; public Bitmap? Icon => icon; public string? MLVersionText => mlVersion != null ? 'v' + mlVersion.ToString() : null; @@ -47,8 +47,8 @@ public bool ValidateGame() return false; } - var newMlVersion = Installer.MLVersion.GetMelonLoaderVersion(Dir, out var ml86, out var mlLinux); - if (newMlVersion != null && (ml86 != Is32Bit || mlLinux != IsLinux)) + var newMlVersion = Installer.MLVersion.GetMelonLoaderVersion(Dir, out var arch); + if (newMlVersion != null && arch != Arch) newMlVersion = null; if (newMlVersion == MLVersion) diff --git a/MelonLoader.Installer/Views/DetailsView.axaml.cs b/MelonLoader.Installer/Views/DetailsView.axaml.cs index e3e9320..086c0e8 100644 --- a/MelonLoader.Installer/Views/DetailsView.axaml.cs +++ b/MelonLoader.Installer/Views/DetailsView.axaml.cs @@ -3,6 +3,7 @@ using Avalonia.Interactivity; using Avalonia.Platform.Storage; using Avalonia.Threading; +using MelonLoader.Installer.Utils; using MelonLoader.Installer.ViewModels; using System.ComponentModel; using System.Runtime.InteropServices; @@ -44,7 +45,7 @@ protected override async void OnDataContextChanged(EventArgs e) return; #if LINUX - if (Model.Game.IsLinux) + if (Model.Game.Arch == Architecture.LinuxX64) { LdLibPathVar.Text = $"LD_LIBRARY_PATH=\"{Model.Game.Dir}:$LD_LIBRARY_PATH\""; SteamLaunchOptions.Text = $"{LdLibPathVar.Text} {LdPreloadVar.Text} %command%"; @@ -74,7 +75,13 @@ public void UpdateVersionList() if (Model == null) return; - var en = MLManager.Versions.Where(x => (Model.Game.IsLinux ? x.DownloadUrlLinux : (Model.Game.Is32Bit ? x.DownloadUrlWinX86 : x.DownloadUrlWin)) != null); + var en = MLManager.Versions.Where(x => + Model.Game.Arch switch + { + Architecture.LinuxX64 => x.DownloadUrlLinux, + Architecture.WindowsX86 => x.DownloadUrlWinX86, + _ => x.DownloadUrlWin + } != null); if (NightlyCheck.IsChecked != true) en = en.Where(x => !x.Version.IsPrerelease || x.IsLocalPath); @@ -139,7 +146,7 @@ private void InstallHandler(object sender, RoutedEventArgs args) ShowLinuxInstructions.IsVisible = false; _ = MLManager.InstallAsync(Path.GetDirectoryName(Model.Game.Path)!, Model.Game.MLInstalled && !KeepFilesCheck.IsChecked!.Value, - (MLVersion)VersionCombobox.SelectedItem!, Model.Game.IsLinux, Model.Game.Is32Bit, + (MLVersion)VersionCombobox.SelectedItem!, Model.Game.Arch, (progress, newStatus) => Dispatcher.UIThread.Post(() => OnInstallProgress(progress, newStatus)), (errorMessage) => Dispatcher.UIThread.Post(() => OnOperationFinished(errorMessage))); } @@ -253,9 +260,16 @@ private async void SelectZipHandler(object sender, TappedEventArgs args) if (errorMessage == null) { var ver = MLManager.Versions[0]; - if ((Model.Game.IsLinux ? ver.DownloadUrlLinux : (Model.Game.Is32Bit ? ver.DownloadUrlWinX86 : ver.DownloadUrlWin)) == null) + var downloadUrl = Model.Game.Arch switch + { + Architecture.LinuxX64 => ver.DownloadUrlLinux, + Architecture.WindowsX64 => ver.DownloadUrlWin, + Architecture.WindowsX86 => ver.DownloadUrlWinX86, + _ => null + }; + if (downloadUrl == null) { - DialogBox.ShowError($"The selected version does not support the architechture of the current game: {(Model.Game.IsLinux ? "linux" : "win")}-{(Model.Game.Is32Bit ? "x86" : "x64")}"); + DialogBox.ShowError($"The selected version does not support the architecture of the current game: {Model.Game.Arch.GetDescription()}"); } } diff --git a/MelonLoader.Installer/Views/GameControl.axaml.cs b/MelonLoader.Installer/Views/GameControl.axaml.cs index 6daa76e..554c471 100644 --- a/MelonLoader.Installer/Views/GameControl.axaml.cs +++ b/MelonLoader.Installer/Views/GameControl.axaml.cs @@ -29,7 +29,7 @@ protected override void OnDataContextChanged(EventArgs e) var showWine = #if LINUX - !Model.IsLinux; + Model.Arch != Architecture.LinuxX64; #else false; #endif