Skip to content

Commit

Permalink
Introduce Architecture enum for supported aches
Browse files Browse the repository at this point in the history
Currently MelonLoader supports Linux x64, Windows x64 and Windows x86. Enum Architecture simplifies logic on arch detection.
  • Loading branch information
winterheart committed Dec 15, 2024
1 parent df7a45b commit aa5904d
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 37 deletions.
18 changes: 18 additions & 0 deletions MelonLoader.Installer/Architecture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.ComponentModel;

namespace MelonLoader.Installer;

/// <summary>
/// Supported architectures by MelonLoader.
/// </summary>
public enum Architecture
{
[Description("unknown")]
Unknown,
[Description("win-x86")]
WindowsX86,
[Description("win-x64")]
WindowsX64,
[Description("linux-x64")]
LinuxX64,
}
19 changes: 9 additions & 10 deletions MelonLoader.Installer/GameManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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
{
Expand All @@ -124,21 +124,20 @@ 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)))
{
errorMessage = "Game is already listed.";
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
{
Expand All @@ -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;
Expand All @@ -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);
Expand Down
24 changes: 16 additions & 8 deletions MelonLoader.Installer/MLManager.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Semver;
using MelonLoader.Installer.Utils;
using Semver;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Nodes;

Expand Down Expand Up @@ -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.");
Expand All @@ -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
};

Expand All @@ -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;
}

Expand Down
15 changes: 7 additions & 8 deletions MelonLoader.Installer/MLVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand Down
28 changes: 28 additions & 0 deletions MelonLoader.Installer/Utils/EnumHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.ComponentModel;

namespace MelonLoader.Installer.Utils;

/// <summary>
/// Class for converting Description annotations into string
/// </summary>
public static class EnumHelper
{
public static string? GetDescription<T>(this T enumValue)
where T : struct, IConvertible
{
if (!typeof(T).IsEnum)
return null;

var description = enumValue.ToString();
var fieldInfo = enumValue.GetType().GetField(enumValue.ToString());

Check warning on line 17 in MelonLoader.Installer/Utils/EnumHelper.cs

View workflow job for this annotation

GitHub Actions / Build Installer

Possible null reference argument for parameter 'name' in 'FieldInfo? Type.GetField(string name)'.

Check warning on line 17 in MelonLoader.Installer/Utils/EnumHelper.cs

View workflow job for this annotation

GitHub Actions / Build Installer

Possible null reference argument for parameter 'name' in 'FieldInfo? Type.GetField(string name)'.

if (fieldInfo == null)
return description;

var attrs = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), true);
if (attrs.Length > 0)
description = ((DescriptionAttribute)attrs[0]).Description;

return description;
}
}
10 changes: 5 additions & 5 deletions MelonLoader.Installer/ViewModels/GameModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
24 changes: 19 additions & 5 deletions MelonLoader.Installer/Views/DetailsView.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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%";
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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)));
}
Expand Down Expand Up @@ -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()}");
}
}

Expand Down
2 changes: 1 addition & 1 deletion MelonLoader.Installer/Views/GameControl.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ protected override void OnDataContextChanged(EventArgs e)

var showWine =
#if LINUX
!Model.IsLinux;
Model.Arch != Architecture.LinuxX64;
#else
false;
#endif
Expand Down

0 comments on commit aa5904d

Please sign in to comment.