diff --git a/.github/workflows/build-final.yml b/.github/workflows/build-final.yml
new file mode 100644
index 0000000..ce1b32e
--- /dev/null
+++ b/.github/workflows/build-final.yml
@@ -0,0 +1,27 @@
+name: Build Installer Final
+
+run-name: Final Build
+
+on:
+ push:
+ branches: [ master ]
+
+jobs:
+ build:
+ name: Build Installer
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup dotnet
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 9.0.x
+
+ - name: Build
+ run: dotnet publish
+
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: MelonLoader.Installer
+ path: Output/win-x64/
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..4604855
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,33 @@
+name: Build Installer
+
+run-name: 4.0.0-ci.${{ github.run_number }} | ${{ github.event_name != 'workflow_dispatch' && (github.event.head_commit.message || format('`[PR]` {0}', github.event.pull_request.title)) || 'Manual Build' }}
+
+env:
+ DEVVERSION: "4.0.0"
+
+on:
+ push:
+ branches: [ development ]
+ pull_request:
+ branches: [ development ]
+ workflow_dispatch:
+
+jobs:
+ build:
+ name: Build Installer
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup dotnet
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 9.0.x
+
+ - name: Build
+ run: dotnet publish -p:Version="${{ env.DEVVERSION }}.${{ github.run_number }}"
+
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: MelonLoader.Installer
+ path: Output/win-x64/
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index ee4489e..6858d78 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,6 +1,8 @@
-
- enable
- 11.2.1
-
-
+
+ 4.0.0
+
+ enable
+ 11.2.1
+
+
\ No newline at end of file
diff --git a/MelonLoader.Installer/App.axaml.cs b/MelonLoader.Installer/App.axaml.cs
index 4177f3a..b5fd626 100644
--- a/MelonLoader.Installer/App.axaml.cs
+++ b/MelonLoader.Installer/App.axaml.cs
@@ -9,7 +9,7 @@
namespace MelonLoader.Installer;
-public partial class App : Application
+public class App : Application
{
public override void Initialize()
{
diff --git a/MelonLoader.Installer/InstallerUtils.cs b/MelonLoader.Installer/InstallerUtils.cs
index 050e437..ef8cf31 100644
--- a/MelonLoader.Installer/InstallerUtils.cs
+++ b/MelonLoader.Installer/InstallerUtils.cs
@@ -9,7 +9,7 @@ public static class InstallerUtils
static InstallerUtils()
{
Http = new();
- Http.DefaultRequestHeaders.Add("User-Agent", $"MelonLoader Installer v{Program.Version.ToString(3)}");
+ Http.DefaultRequestHeaders.Add("User-Agent", $"MelonLoader Installer v{Program.VersionName}");
}
public static async Task DownloadFileAsync(string url, Stream destination, InstallProgressEventHandler? onProgress)
diff --git a/MelonLoader.Installer/MLManager.cs b/MelonLoader.Installer/MLManager.cs
index b9a7a4c..8a7fa6e 100644
--- a/MelonLoader.Installer/MLManager.cs
+++ b/MelonLoader.Installer/MLManager.cs
@@ -81,7 +81,7 @@ private static async Task GetVersionsAsync(List versions)
try
{
- resp = await InstallerUtils.Http.GetAsync(run!["artifacts_url"]!.ToString()).ConfigureAwait(false);
+ resp = await InstallerUtils.Http.GetAsync(run["artifacts_url"]!.ToString()).ConfigureAwait(false);
}
catch
{
@@ -387,7 +387,7 @@ void SetProgress(double progress, string? newStatus = null)
onProgress?.Invoke(currentTask / (double)tasks + progress / tasks, newStatus);
}
- SetProgress(0, "Downloading MelonLoader " + version.ToString());
+ SetProgress(0, "Downloading MelonLoader " + version);
using var bufferStr = new MemoryStream();
var result = await InstallerUtils.DownloadFileAsync(downloadUrl, bufferStr, SetProgress);
@@ -400,7 +400,7 @@ void SetProgress(double progress, string? newStatus = null)
currentTask++;
- SetProgress(0, "Installing " + version.ToString());
+ SetProgress(0, "Installing " + version);
var extRes = InstallerUtils.Extract(bufferStr, gameDir, SetProgress);
if (extRes != null)
diff --git a/MelonLoader.Installer/MelonLoader.Installer.csproj b/MelonLoader.Installer/MelonLoader.Installer.csproj
index e7adc02..66f5bb7 100644
--- a/MelonLoader.Installer/MelonLoader.Installer.csproj
+++ b/MelonLoader.Installer/MelonLoader.Installer.csproj
@@ -10,7 +10,7 @@
latesttrueAssets/icon.ico
- ../Output/win-x64
+ ../Output/$(RuntimeIdentifier)truetrueembedded
@@ -21,8 +21,6 @@
Lava Gangdiscord.gg/2Wn3N2P
-
- 4.0.0
diff --git a/MelonLoader.Installer/Program.cs b/MelonLoader.Installer/Program.cs
index 9984a52..c2697b9 100644
--- a/MelonLoader.Installer/Program.cs
+++ b/MelonLoader.Installer/Program.cs
@@ -9,7 +9,10 @@ internal static class Program
public static event Action? Exiting;
- public static Version Version { get; private set; } = typeof(Program).Assembly.GetName().Version!;
+ public static Version Version { get; } = typeof(Program).Assembly.GetName().Version!;
+
+ public static string VersionName { get; } =
+ $"v{Version.Major}.{Version.Minor}.{Version.Build}{(Version.Revision > 0 ? $"-ci.{Version.Revision}" : string.Empty)}";
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
@@ -69,11 +72,8 @@ private static bool CheckProcessLock()
var procId = BitConverter.ToInt32(procIdRaw);
var proc = Process.GetProcessById(procId);
- if (proc != null)
- {
- GrabAttention(proc);
- return false;
- }
+ GrabAttention(proc);
+ return false;
}
catch { return false; }
}
diff --git a/MelonLoader.Installer/Updater.cs b/MelonLoader.Installer/Updater.cs
index 3c6f40f..c44653b 100644
--- a/MelonLoader.Installer/Updater.cs
+++ b/MelonLoader.Installer/Updater.cs
@@ -13,6 +13,10 @@ public static class Updater
public static bool UpdateIfPossible()
{
+ // Don't auto-update on CI builds
+ if (Program.Version.Revision > 0)
+ return false;
+
var downloadUrl = CheckForUpdateAsync().GetAwaiter().GetResult();
if (downloadUrl == null)
return false;
@@ -87,11 +91,7 @@ private static async Task UpdateAsync(string downloadUrl)
}
}
- if (Process.Start(newPath, ["-handleupdate", Environment.ProcessPath!, Environment.ProcessId.ToString()]) == null)
- {
- Finish("Failed to start the new installer.");
- return;
- }
+ Process.Start(newPath, ["-handleupdate", Environment.ProcessPath!, Environment.ProcessId.ToString()]);
Finish(null);
}
@@ -101,8 +101,8 @@ public static bool CheckLegacyUpdate()
if (!Environment.ProcessPath!.EndsWith(".tmp.exe", StringComparison.OrdinalIgnoreCase))
return false;
- var dir = Path.GetDirectoryName(Environment.ProcessPath!)!;
- var name = Path.GetFileNameWithoutExtension(Environment.ProcessPath!);
+ var dir = Path.GetDirectoryName(Environment.ProcessPath)!;
+ var name = Path.GetFileNameWithoutExtension(Environment.ProcessPath);
name = name.Remove(name.Length - 4) + ".exe";
var final = Path.Combine(dir, name);
diff --git a/MelonLoader.Installer/ViewModels/DetailsViewModel.cs b/MelonLoader.Installer/ViewModels/DetailsViewModel.cs
index be2cc71..0520f4b 100644
--- a/MelonLoader.Installer/ViewModels/DetailsViewModel.cs
+++ b/MelonLoader.Installer/ViewModels/DetailsViewModel.cs
@@ -3,7 +3,6 @@
public class DetailsViewModel(GameModel game) : ViewModelBase
{
private bool _installing;
- private bool _confirmation;
private bool _offline;
public GameModel Game => game;
@@ -14,34 +13,21 @@ public bool Installing
set
{
_installing = value;
- OnPropertyChanged(nameof(Installing));
- OnPropertyChanged(nameof(CanInstall));
+ OnPropertyChanged();
OnPropertyChanged(nameof(EnableSettings));
}
}
- public bool Confirmation
- {
- get => _confirmation;
- set
- {
- _confirmation = value;
- OnPropertyChanged(nameof(Confirmation));
- OnPropertyChanged(nameof(CanInstall));
- }
- }
-
public bool Offline
{
get => _offline;
set
{
_offline = value;
- OnPropertyChanged(nameof(Confirmation));
+ OnPropertyChanged();
OnPropertyChanged(nameof(EnableSettings));
}
}
- public bool CanInstall => !Installing && !Confirmation;
public bool EnableSettings => !Offline && !Installing;
}
diff --git a/MelonLoader.Installer/ViewModels/DialogBoxModel.cs b/MelonLoader.Installer/ViewModels/DialogBoxModel.cs
new file mode 100644
index 0000000..897c853
--- /dev/null
+++ b/MelonLoader.Installer/ViewModels/DialogBoxModel.cs
@@ -0,0 +1,10 @@
+namespace MelonLoader.Installer.ViewModels;
+
+public class DialogBoxModel : ViewModelBase
+{
+ public required string Message { get; init; }
+ public string ConfirmText { get; init; } = "YES";
+ public string CancelText { get; init; } = "NO";
+ public bool IsError { get; init; }
+ public bool IsConfirmation { get; init; }
+}
diff --git a/MelonLoader.Installer/ViewModels/GameModel.cs b/MelonLoader.Installer/ViewModels/GameModel.cs
index 56633f5..f0b60be 100644
--- a/MelonLoader.Installer/ViewModels/GameModel.cs
+++ b/MelonLoader.Installer/ViewModels/GameModel.cs
@@ -22,7 +22,7 @@ public SemVersion? MLVersion
set
{
mlVersion = value;
- OnPropertyChanged(nameof(MLVersion));
+ OnPropertyChanged();
OnPropertyChanged(nameof(MLVersionText));
OnPropertyChanged(nameof(MLStatusText));
OnPropertyChanged(nameof(MLInstalled));
diff --git a/MelonLoader.Installer/ViewModels/MainViewModel.cs b/MelonLoader.Installer/ViewModels/MainViewModel.cs
index 8859821..35c81a4 100644
--- a/MelonLoader.Installer/ViewModels/MainViewModel.cs
+++ b/MelonLoader.Installer/ViewModels/MainViewModel.cs
@@ -2,7 +2,7 @@
namespace MelonLoader.Installer.ViewModels;
-public partial class MainViewModel : ViewModelBase
+public class MainViewModel : ViewModelBase
{
private static bool _ready;
@@ -12,11 +12,11 @@ public bool Ready
set
{
_ready = value;
- OnPropertyChanged(nameof(Ready));
+ OnPropertyChanged();
}
}
public ObservableCollection Games => GameManager.Games;
- public string Version => 'v' + Program.Version.ToString(3);
+ public string Version => Program.VersionName;
}
diff --git a/MelonLoader.Installer/Views/DetailsView.axaml b/MelonLoader.Installer/Views/DetailsView.axaml
index bfe2440..1d799e3 100644
--- a/MelonLoader.Installer/Views/DetailsView.axaml
+++ b/MelonLoader.Installer/Views/DetailsView.axaml
@@ -3,8 +3,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:MelonLoader.Installer.ViewModels"
- xmlns:views="clr-namespace:MelonLoader.Installer.Views"
- xmlns:root="clr-namespace:MelonLoader.Installer"
mc:Ignorable="d" d:DesignWidth="450" d:DesignHeight="650"
x:Class="MelonLoader.Installer.Views.DetailsView"
x:DataType="vm:DetailsViewModel">
@@ -42,7 +40,7 @@
IsVisible="{Binding Game.MLInstalled}" IsChecked="True">Keep mods and settings
-
+
-
+
diff --git a/MelonLoader.Installer/Views/DetailsView.axaml.cs b/MelonLoader.Installer/Views/DetailsView.axaml.cs
index 670a249..0daba6c 100644
--- a/MelonLoader.Installer/Views/DetailsView.axaml.cs
+++ b/MelonLoader.Installer/Views/DetailsView.axaml.cs
@@ -49,7 +49,7 @@ protected override void OnDataContextChanged(EventArgs e)
if (!MLManager.Init())
{
Model.Offline = true;
- ErrorBox.Open("Failed to fetch MelonLoader releases. Ensure you're online.");
+ DialogBox.ShowError("Failed to fetch MelonLoader releases. Ensure you're online.");
}
}
@@ -89,8 +89,6 @@ public void UpdateVersionInfo()
if (Model == null || VersionCombobox.SelectedItem == null)
return;
- Model.Confirmation = false;
-
MelonIcon.Opacity = Model.Game.MLInstalled ? 1 : 0.3;
if (Model.Game.MLVersion == null)
@@ -139,18 +137,20 @@ private void OnInstallFinished(string? errorMessage)
if (Model == null)
return;
+ var wasReinstall = Model.Game.MLInstalled;
Model.Game.ValidateGame();
Model.Installing = false;
+ NightlyCheck.IsEnabled = true;
+ VersionCombobox.IsEnabled = true;
if (errorMessage != null)
{
- ErrorBox.Open(errorMessage);
+ DialogBox.ShowError(errorMessage);
return;
}
- InstallStatus.Text = "Done!";
- Model.Confirmation = true;
+ DialogBox.ShowNotice("Success!", $"{(wasReinstall ? "Reinstall" : "Install")} was Successful!");
}
private void OpenDirHandler(object sender, RoutedEventArgs args)
@@ -174,10 +174,13 @@ private void UninstallHandler(object sender, RoutedEventArgs args)
if (!MLManager.Uninstall(Path.GetDirectoryName(Model.Game.Path)!, !KeepFilesCheck.IsChecked!.Value, out var error))
{
- ErrorBox.Open(error);
+ DialogBox.ShowError(error);
+ Model.Game.ValidateGame();
+ return;
}
Model.Game.ValidateGame();
+ DialogBox.ShowNotice("Success!", "Uninstall was Successful!");
}
private async void SelectZipHandler(object sender, TappedEventArgs args)
@@ -215,7 +218,7 @@ private async void SelectZipHandler(object sender, TappedEventArgs args)
var ver = MLManager.Versions[0];
if ((Model.Game.Is32Bit ? ver.DownloadX86Url : ver.DownloadUrl) == null)
{
- ErrorBox.Open($"The selected version does not support the architechture of the current game: {(Model.Game.Is32Bit ? "x86" : "x64")}");
+ DialogBox.ShowError($"The selected version does not support the architechture of the current game: {(Model.Game.Is32Bit ? "x86" : "x64")}");
}
}
diff --git a/MelonLoader.Installer/Views/DialogBox.axaml b/MelonLoader.Installer/Views/DialogBox.axaml
new file mode 100644
index 0000000..8640116
--- /dev/null
+++ b/MelonLoader.Installer/Views/DialogBox.axaml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MelonLoader.Installer/Views/DialogBox.axaml.cs b/MelonLoader.Installer/Views/DialogBox.axaml.cs
new file mode 100644
index 0000000..02a1a75
--- /dev/null
+++ b/MelonLoader.Installer/Views/DialogBox.axaml.cs
@@ -0,0 +1,108 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using MelonLoader.Installer.ViewModels;
+
+namespace MelonLoader.Installer.Views;
+
+public partial class DialogBox : Window
+{
+ private Action? OnConfirm;
+ private Action? OnCancel;
+
+ public DialogBox()
+ => InitializeComponent();
+
+ public static void ShowError(string message)
+ {
+ new DialogBox
+ {
+ Title = "Error",
+ DataContext = new DialogBoxModel
+ {
+ Message = message,
+ IsError = true
+ }
+ }.Open();
+ }
+
+ public static void ShowNotice(string message)
+ => ShowNotice("Notice", message);
+
+ public static void ShowNotice(string title, string message)
+ {
+ new DialogBox
+ {
+ Title = title,
+ DataContext = new DialogBoxModel
+ {
+ Message = message
+ }
+ }.Open();
+ }
+
+ public static void ShowConfirmation(
+ string message,
+ Action? onConfirm = null,
+ Action? onCancel = null,
+ string confirmText = "YES",
+ string cancelText = "NO")
+ => ShowConfirmation("CONFIRMATION",
+ message,
+ onConfirm,
+ onCancel,
+ confirmText,
+ cancelText);
+
+ public static void ShowConfirmation(
+ string title,
+ string message,
+ Action? onConfirm = null,
+ Action? onCancel = null,
+ string confirmText = "YES",
+ string cancelText = "NO")
+ {
+ new DialogBox
+ {
+ Title = title,
+ DataContext = new DialogBoxModel
+ {
+ Message = message,
+ IsConfirmation = true,
+ ConfirmText = confirmText,
+ CancelText = cancelText
+ },
+ OnConfirm = onConfirm,
+ OnCancel = onCancel
+ }.Open();
+ }
+
+ private void Open()
+ {
+ BringToFront();
+
+ if (MainWindow.Instance.IsVisible)
+ ShowDialog(MainWindow.Instance);
+ else
+ Show();
+ }
+
+ private void BringToFront()
+ {
+ Topmost = true;
+ Topmost = false;
+ Program.GrabAttention();
+ Focus();
+ }
+
+ private void ConfirmHandler(object sender, RoutedEventArgs args)
+ {
+ Close();
+ OnConfirm?.Invoke();
+ }
+
+ private void CancelHandler(object sender, RoutedEventArgs args)
+ {
+ Close();
+ OnCancel?.Invoke();
+ }
+}
\ No newline at end of file
diff --git a/MelonLoader.Installer/Views/ErrorBox.axaml b/MelonLoader.Installer/Views/ErrorBox.axaml
deleted file mode 100644
index bb455d6..0000000
--- a/MelonLoader.Installer/Views/ErrorBox.axaml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/MelonLoader.Installer/Views/ErrorBox.axaml.cs b/MelonLoader.Installer/Views/ErrorBox.axaml.cs
deleted file mode 100644
index 7e3812f..0000000
--- a/MelonLoader.Installer/Views/ErrorBox.axaml.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using Avalonia.Controls;
-using Avalonia.Interactivity;
-
-namespace MelonLoader.Installer.Views;
-
-public partial class ErrorBox : Window
-{
- public ErrorBox()
- {
- InitializeComponent();
- }
-
- public static void Open(string message)
- {
- var box = new ErrorBox();
- box.Message.Text = message;
- box.Topmost = true;
- box.Topmost = false;
-
- Program.GrabAttention();
- box.Focus();
-
- if (MainWindow.Instance.IsVisible)
- box.ShowDialog(MainWindow.Instance);
- else
- box.Show();
- }
-
- private void OkHandler(object sender, RoutedEventArgs args)
- {
- Close();
- }
-}
\ No newline at end of file
diff --git a/MelonLoader.Installer/Views/MainView.axaml b/MelonLoader.Installer/Views/MainView.axaml
index 3fabd96..b60b660 100644
--- a/MelonLoader.Installer/Views/MainView.axaml
+++ b/MelonLoader.Installer/Views/MainView.axaml
@@ -15,7 +15,7 @@
-
diff --git a/MelonLoader.Installer/Views/MainView.axaml.cs b/MelonLoader.Installer/Views/MainView.axaml.cs
index c42f039..141a6cf 100644
--- a/MelonLoader.Installer/Views/MainView.axaml.cs
+++ b/MelonLoader.Installer/Views/MainView.axaml.cs
@@ -69,7 +69,7 @@ public async void AddGameManuallyHandler(object sender, RoutedEventArgs args)
GameManager.TryAddGame(path, null, null, null, out var error);
if (error != null)
{
- ErrorBox.Open(error);
+ DialogBox.ShowError(error);
return;
}
diff --git a/MelonLoader.Installer/Views/MainWindow.axaml b/MelonLoader.Installer/Views/MainWindow.axaml
index 0b261a4..3bb21a8 100644
--- a/MelonLoader.Installer/Views/MainWindow.axaml
+++ b/MelonLoader.Installer/Views/MainWindow.axaml
@@ -1,9 +1,7 @@
diff --git a/MelonLoader.Installer/Views/UpdaterView.axaml.cs b/MelonLoader.Installer/Views/UpdaterView.axaml.cs
index debc750..691604c 100644
--- a/MelonLoader.Installer/Views/UpdaterView.axaml.cs
+++ b/MelonLoader.Installer/Views/UpdaterView.axaml.cs
@@ -9,7 +9,7 @@ public UpdaterView()
{
InitializeComponent();
- Updater.Progress += (progress, newStatus) => Dispatcher.UIThread.Post(() => OnProgress(progress));
+ Updater.Progress += (progress, _) => Dispatcher.UIThread.Post(() => OnProgress(progress));
}
private void OnProgress(double progress)
diff --git a/README.md b/README.md
index ab6ef45..aaab007 100644
--- a/README.md
+++ b/README.md
@@ -4,14 +4,16 @@
# MelonLoader Installer
-This is the Official Dedicated Installer for [MelonLoader](https://github.com/LavaGang/MelonLoader).
+
+
+The Official Dedicated Installer for [MelonLoader](https://github.com/LavaGang/MelonLoader).
## Report an issue
### Join our official Discord server
## How does it work?
-The installer should automatically list installed Unity games found in your Steam library.
+The installer should automatically list installed Unity games on your machine.
If the game you're trying to mod isn't listed, you can locate it manually by clicking on the `Add Game Manually` button.
@@ -22,9 +24,22 @@ To install MelonLoader, click on the game you wish to mod. Next, select your pre
Nightly builds are bleeding-edge builds, generated right after any changes are made to the project. They can be enabled with the switch under the version selection menu.
-## Compilation
-The project can be compiled using the .NET 8.0 compiler.
-To create a single-file build, use the `dotnet publish` command in the repository's root directory.
+## Contribution
+
+Contributions to this project are welcome! If you wish to contribute, please adhere to the following guidelines:
+
+### Requirements:
+Ensure you have the .NET 9.0 SDK installed on your machine. This is required to build the project.
+
+### Building the Project:
+You can build the project locally using the following command:
+```bash
+dotnet publish
+```
+
+### Pull Requests:
+All pull requests should be made towards the `development` branch.
+The master branch contains the source code for the stable release and is protected to ensure stability.
## LICENSING & CREDITS:
MelonLoader.Installer is licensed under the Apache License, Version 2.0. See [LICENSE](https://github.com/LavaGang/MelonLoader.Installer/blob/master/LICENSE.md) for the full License.