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