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 @@ latest true Assets/icon.ico - ../Output/win-x64 + ../Output/$(RuntimeIdentifier) true true embedded @@ -21,8 +21,6 @@ Lava Gang discord.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/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.