Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
More changes
Browse files Browse the repository at this point in the history
thesupersonic16 committed Jan 16, 2025
1 parent 5d82d07 commit ac18ef9
Showing 15 changed files with 301 additions and 84 deletions.
29 changes: 16 additions & 13 deletions Source/HedgeModManager.UI/Controls/MainWindow/Test.axaml
Original file line number Diff line number Diff line change
@@ -7,19 +7,19 @@
mc:Ignorable="d" d:DesignWidth="920" d:DesignHeight="390"
x:Class="HedgeModManager.UI.Controls.MainWindow.Test"
x:DataType="vm:MainWindowViewModel"
Loaded="OnLoaded"
Loaded="OnLoaded" Unloaded="OnUnloaded"
Background="{DynamicResource BackgroundL0Brush}">
<Grid>
<StackPanel Margin="16,16,0,0" HorizontalAlignment="Left">
<Grid ColumnDefinitions="Auto,*">
<StackPanel Grid.Column="0" Margin="16,16,0,0">
<TextBlock Text="Config" Margin="0,8,0,0" />
<StackPanel Spacing="4" Orientation="Horizontal">
<Button Click="SaveConfig_Click">Save</Button>
<Button Click="LoadConfig_Click">Load</Button>
<Button Click="ResetConfig_Click">Reset</Button>
</StackPanel>
<StackPanel x:Name="ConfigProps">
</StackPanel>

<StackPanel x:Name="ConfigProps" />

<TextBlock Text="Game" Margin="0,8,0,0" />
<StackPanel Spacing="4" Orientation="Horizontal">
<Button Click="SaveGame_Click">Save</Button>
@@ -64,11 +64,11 @@
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<Grid Margin="0,16,0,0"
<Grid Grid.Column="1"
Margin="16,16,0,0"
RowDefinitions="Auto,*"
HorizontalAlignment="Right">
<TextBlock Grid.Row="0" Margin="0,0,0,8"
Text="Log"
HorizontalAlignment="Stretch">
<TextBlock Grid.Row="0" Text="Log"
VerticalAlignment="Center" />
<StackPanel Grid.Row="0" Margin="0,0,8,0"
Spacing="4"
@@ -77,10 +77,13 @@
<Button Click="ExportLog_Click">Export</Button>
<Button Click="ClearLog_Click">Clear</Button>
</StackPanel>
<Border Grid.Row="1" Width="800" VerticalAlignment="Stretch"
BorderThickness="1" Padding="8"
<Border Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderThickness="1"
Margin="0,8,8,8" Padding="8"
BorderBrush="{DynamicResource BorderBrush}">
<ScrollViewer>
<ScrollViewer x:Name="LogScrollViewer">
<ItemsControl ItemsSource="{Binding LoggerInstance.Logs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
18 changes: 18 additions & 0 deletions Source/HedgeModManager.UI/Controls/MainWindow/Test.axaml.cs
Original file line number Diff line number Diff line change
@@ -5,9 +5,11 @@
using Avalonia.Layout;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
using Avalonia.Threading;
using HedgeModManager.UI.Controls.Modals;
using HedgeModManager.UI.Models;
using HedgeModManager.UI.ViewModels;
using System.Collections.Specialized;
using System.Diagnostics;

namespace HedgeModManager.UI.Controls.MainWindow;
@@ -25,6 +27,9 @@ private void OnLoaded(object? sender, RoutedEventArgs e)
if (viewModel == null)
return;

if (viewModel.LoggerInstance != null)
viewModel.LoggerInstance.Logs.CollectionChanged += OnLogCollectionChanged;

// Add buttons
if (viewModel.CurrentTabInfo != null)
{
@@ -114,6 +119,19 @@ void createStringEditor(string name)
}
}

private void OnUnloaded(object? sender, RoutedEventArgs e)
{
if (DataContext is not MainWindowViewModel viewModel || viewModel.LoggerInstance == null)
throw new Exception("View model or logger is missing. It is not safe to continue.");

viewModel.LoggerInstance.Logs.CollectionChanged += OnLogCollectionChanged;
}

private void OnLogCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
Dispatcher.UIThread.Invoke(LogScrollViewer.ScrollToEnd);
}

private async void SaveConfig_Click(object? sender, RoutedEventArgs e)
{
await (DataContext as MainWindowViewModel)!.Config.SaveAsync();
22 changes: 22 additions & 0 deletions Source/HedgeModManager.UI/Controls/Settings/Settings.axaml
Original file line number Diff line number Diff line change
@@ -78,6 +78,28 @@
<ToggleSwitch Margin="0,0,10,0" IsChecked="{Binding ViewModel.EnableLauncher, RelativeSource={RelativeSource AncestorType=cs:Settings}}" />
</cs:SettingsEntry.Data>
</cs:SettingsEntry>
<cs:SettingsEntry Icon="Atom"
IsVisible="{Binding ViewModel.SupportsProton, RelativeSource={RelativeSource AncestorType=cs:Settings}}"
Title="{DynamicResource Settings.Title.Proton}"
Description="{DynamicResource Settings.Description.Proton}">
<cs:SettingsEntry.Data>
<StackPanel Orientation="Horizontal" Spacing="10">
<cb:Button MinWidth="105"
Text="{DynamicResource Settings.Button.ClearPrefix}"
VerticalContentAlignment="Center"
IsEnabled="{Binding IsBusy, Converter={StaticResource InvertedBoolConverter}}" />
<cb:Button MinWidth="105"
Text="{DynamicResource Settings.Button.ReinstallRuntime}"
VerticalContentAlignment="Center"
IsEnabled="{Binding IsBusy, Converter={StaticResource InvertedBoolConverter}}"
Click="OnPrefixReinstallClick" />
<cb:Button MinWidth="105"
Text="{DynamicResource Settings.Button.OpenPrefixDir}"
VerticalContentAlignment="Center"
Click="OnPrefixOpenClick" />
</StackPanel>
</cs:SettingsEntry.Data>
</cs:SettingsEntry>
</StackPanel>

<!-- Hedge Mod Manager -->
48 changes: 43 additions & 5 deletions Source/HedgeModManager.UI/Controls/Settings/Settings.axaml.cs
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Platform.Storage;
using HedgeModManager.CoreLib;
using HedgeModManager.UI.Controls.Modals;
using HedgeModManager.UI.Languages;
using HedgeModManager.UI.Models;
@@ -142,18 +143,55 @@ private void OnGameDirClick(object? sender, RoutedEventArgs e)

private async void OnInstallMLClick(object? sender, RoutedEventArgs e)
{
if (_isInstalling)
return;
_isInstalling = true;
if (DataContext is not MainWindowViewModel mainViewModel
|| mainViewModel.SelectedGame is not UIGame uiGame)
return;

await uiGame.Game.InstallModLoaderAsync();
if (_isInstalling)
return;
_isInstalling = true;
await mainViewModel.InstallModLoader();
ViewModel.Update();
_isInstalling = false;
}


private async void OnPrefixReinstallClick(object? sender, RoutedEventArgs e)
{
if (DataContext is not MainWindowViewModel mainViewModel)
return;

mainViewModel.IsBusy = true;
await MainWindowViewModel.CreateSimpleDownload("Download.Text.InstallRuntime", "Failed to install runtime",
async (d, p, c) =>
{
if (mainViewModel.SelectedGame == null)
return;

await LinuxCompatibility.InstallRuntimeToPrefix(
mainViewModel.SelectedGame.Game.PrefixRoot);
}).OnFinally((d) =>
{
mainViewModel.IsBusy = false;
return Task.CompletedTask;
}).RunAsync(mainViewModel);
}

private void OnPrefixOpenClick(object? sender, RoutedEventArgs e)
{
if (DataContext is not MainWindowViewModel mainViewModel)
return;

if (mainViewModel.SelectedGame == null)
return;

Process.Start(new ProcessStartInfo
{
FileName = mainViewModel.SelectedGame.Game.PrefixRoot ?? string.Empty,
UseShellExecute = true
});

}

private async void OnCheckManagerUpdatesClick(object? sender, RoutedEventArgs e)
{
ViewModel.CheckManagerUpdatesText = "Settings.Button.CheckingUpdates";
15 changes: 12 additions & 3 deletions Source/HedgeModManager.UI/Languages/en-AU.axaml
Original file line number Diff line number Diff line change
@@ -49,6 +49,8 @@
<system:String x:Key="Download.Text.DownloadMod" >Downloading {0}...</system:String>
<system:String x:Key="Download.Text.InstallMod" >Installing {0}...</system:String>
<system:String x:Key="Download.Text.InstallModMultiple">Installing {0} mods...</system:String>
<system:String x:Key="Download.Text.InstallModLoader" >Installing mod loader...</system:String>
<system:String x:Key="Download.Text.InstallRuntime" >Installing runtime...</system:String>
<system:String x:Key="Download.Text.UpdateManager0" >Downloading updates...</system:String>
<system:String x:Key="Download.Text.UpdateManager1" >Extracting update...</system:String>
<system:String x:Key="Theme.Name.Dark" >Cool Dark</system:String>
@@ -73,27 +75,32 @@
<system:String x:Key="Settings.Button.ChangeModsDir" >Change</system:String>
<system:String x:Key="Settings.Button.CheckUpdates" >Check</system:String>
<system:String x:Key="Settings.Button.CheckingUpdates" >Checking...</system:String>
<system:String x:Key="Settings.Button.ClearPrefix" >Delete</system:String>
<system:String x:Key="Settings.Button.InstallML" >Install</system:String>
<system:String x:Key="Settings.Button.ReinstallRuntime" >Reinstall</system:String>
<system:String x:Key="Settings.Button.UninstallML" >Uninstall</system:String>
<system:String x:Key="Settings.Button.OpenGameDir" >Open Game Directory</system:String>
<system:String x:Key="Settings.Button.OpenModsDir" >Open</system:String>
<system:String x:Key="Settings.Button.OpenPrefixDir" >Open</system:String>
<system:String xml:space="preserve" x:Key="Settings.Header.GameML">Game &amp; Mod Loader</system:String>
<system:String x:Key="Settings.Header.Manager" >Hedge Mod Manager</system:String>
<system:String x:Key="Settings.Header.Text" >Settings</system:String>
<system:String x:Key="Settings.Title.Profile" >Profile</system:String>
<system:String x:Key="Settings.Description.Profile" >Quickly change the enabled mods using user-defined presets.</system:String>
<system:String x:Key="Settings.Description.Proton" >Tools for managing the Windows compatibility layer.</system:String>
<system:String x:Key="Settings.Description.ModLoader" >Enable and install to load mods.</system:String>
<system:String x:Key="Settings.Description.DebugConsole" >Enable to display mod loader debug messages.</system:String>
<system:String x:Key="Settings.Description.Launcher" >Launch the game using the associated game launcher.</system:String>
<system:String x:Key="Settings.Description.UpdateCodes" >Automatically check for code update on startup and game change.</system:String>
<system:String x:Key="Settings.Description.UpdateManager" >Automatically check for Hedge Mod Manager updates on startup.</system:String>
<system:String x:Key="Settings.Description.UpdateML" >Automatically check for mod loader updates on startup and game change.</system:String>
<system:String x:Key="Settings.Description.UpdateMod" >Automatically check for mod updates on startup and game change.</system:String>
<system:String x:Key="Settings.Title.ModsDir" >Mods Directory</system:String>
<system:String x:Key="Settings.Title.ModLoader" >Mod Loader</system:String>
<system:String x:Key="Settings.Title.DebugConsole" >Enable Debug Console</system:String>
<system:String x:Key="Settings.Title.Language" >Language</system:String>
<system:String x:Key="Settings.Title.Launcher" >Use Game Launcher</system:String>
<system:String x:Key="Settings.Title.ModsDir" >Mods Directory</system:String>
<system:String x:Key="Settings.Title.ModLoader" >Mod Loader</system:String>
<system:String x:Key="Settings.Title.Proton" >Proton</system:String>
<system:String x:Key="Settings.Title.Theme" >Theme</system:String>
<system:String x:Key="Settings.Title.UpdateCodes" >Check for code updates</system:String>
<system:String x:Key="Settings.Title.UpdateManager" >Check for Hedge Mod Manager updates</system:String>
@@ -126,17 +133,19 @@
<system:String x:Key="Modal.Title.SelectMods" >Select Mods Directory...</system:String>
<system:String x:Key="Modal.Title.SelectModsFailed" >Invalid Mods Directory</system:String>
<system:String x:Key="Modal.Title.UpdateError" >Update Error</system:String>
<system:String x:Key="Modal.Title.UpdateMod" >Update Available</system:String>
<system:String x:Key="Modal.Title.UpdateManager" >Hedge Mod Manager Update</system:String>
<system:String x:Key="Modal.Title.RunError" >Launch Error</system:String>
<system:String x:Key="Modal.Title.ConfigureMod" >Configure {0}</system:String>
<system:String xml:space="preserve" x:Key="Modal.Message.DeleteMod">Are you sure you want to delete &quot;{0}&quot;?&#x0a;This action cannot be undone!</system:String>
<system:String xml:space="preserve" x:Key="Modal.Message.DownloadError">Failed to download mod.&quot; Please try again later.</system:String>
<system:String xml:space="preserve" x:Key="Modal.Message.PkgUpdate">Please update Hedge Mod Manager using your system's package manager.</system:String>
<system:String xml:space="preserve" x:Key="Modal.Message.GameLoadError">An error occurred when Hedge Mod Manager was trying&#x0a;to load mod data.</system:String>
<system:String xml:space="preserve" x:Key="Modal.Message.GameMissingError">Hedge Mod Manager is unable to install &quot;{0}&quot;&#x0a;due to {1} not being found.</system:String>
<system:String xml:space="preserve" x:Key="Modal.Message.GameNoAccess">Hedge Mod Manager does not have write&#x0a;access to the game files.&#x0a;&#x0a;Please make sure the game files are writable.</system:String>
<system:String xml:space="preserve" x:Key="Modal.Message.InstallError">Failed to install mod. Check log for exception.</system:String>
<system:String xml:space="preserve" x:Key="Modal.Message.PkgUpdate">Please update Hedge Mod Manager using your system's package manager.</system:String>
<system:String xml:space="preserve" x:Key="Modal.Message.SelectModsError">Hedge Mod Manager does not have permissions to the selected directory.&#x0a;&#x0a;Please select another directory.</system:String>
<system:String xml:space="preserve" x:Key="Modal.Message.UpdateMod">There is an update available for {0}.&#x0a;&#x0a;Would you like to update the mod?</system:String>
<system:String xml:space="preserve" x:Key="Modal.Message.UnknownSaveError">Hedge Mod Manager was unable to save&#x0a;due to an unknown error.</system:String>
<system:String xml:space="preserve" x:Key="Modal.Message.UnknownUpdateError">An error occurred when trying to prepare&#x0a;for updating.&#x0a;&#x0a;Please try again later.</system:String>
<system:String xml:space="preserve" x:Key="Modal.Message.UnknownRunError">Hedge Mod Manager was unable to launch&#x0a;the game due to an unknown error.</system:String>
14 changes: 11 additions & 3 deletions Source/HedgeModManager.UI/Logger.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using HedgeModManager.Foundation;
using System.Collections.ObjectModel;
using System.Text;
@@ -8,6 +9,7 @@ namespace HedgeModManager.UI;
public partial class UILogger : ObservableObject, ILogger
{
[ObservableProperty] private ObservableCollection<Log> _logs = [];
private List<Log> _backupLogs = [];

public UILogger()
{
@@ -16,7 +18,12 @@ public UILogger()

public void WriteLine(LogType type, string message)
{
Logs.Add(new Log(type, message));
var log = new Log(type, message);
_backupLogs.Add(log);
Dispatcher.UIThread.Invoke(() =>
{
Logs.Add(new Log(type, message));
});
}

public static UILogger GetInstance()
@@ -27,11 +34,12 @@ public static UILogger GetInstance()
public static void Clear()
{
GetInstance().Logs.Clear();
GetInstance()._backupLogs.Clear();
}

public static string Export()
{
return GetInstance().Logs
return GetInstance()._backupLogs
.Aggregate(new StringBuilder(), (sb, log) => sb.AppendLine(log.ToString()))
.ToString();
}
8 changes: 2 additions & 6 deletions Source/HedgeModManager.UI/Program.cs
Original file line number Diff line number Diff line change
@@ -75,14 +75,10 @@ public static void Main(string[] args)
sb.AppendLine("-- Logger Dump End --");
sb.AppendLine();

var thrownException = (Exception)e.ExceptionObject;
var thrownException = e.ExceptionObject as Exception;

sb.AppendLine("Unhandled Exception:");
sb.AppendLine(thrownException.ToString());
sb.AppendLine();

sb.AppendLine("Unhandled Inner Exception:");
sb.AppendLine(thrownException.InnerException?.ToString() ?? "None");
sb.AppendLine(thrownException?.ToString() ?? "NULL EXCEPTION");
sb.AppendLine();

string text = sb.ToString();
20 changes: 14 additions & 6 deletions Source/HedgeModManager.UI/Updater.cs
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ public class Updater
public static string GetUpdatePackageName()
{
if (OperatingSystem.IsWindows())
return $"HedgeModManager-win-x64.zip";
return $"HedgeModManager.exe";
else if (OperatingSystem.IsLinux())
return $"HedgeModManager-linux-x64.zip";
else
@@ -33,7 +33,9 @@ public static string GetUpdatePackageName()
if (app == null)
{
Logger.Error("Failed to check for updates");
return (null, UpdateCheckStatus.Error);
// TODO: Swap these nearing release
//return (null, UpdateCheckStatus.Error);
return (null, UpdateCheckStatus.NoUpdate);
}
Logger.Debug("Got Flathub data");
Logger.Debug($"Current Version: {Program.ApplicationVersion}");
@@ -46,7 +48,8 @@ public static string GetUpdatePackageName()
{
Version = app.CurrentReleaseVersion!,
Title = app.Name,
Description = app.CurrentReleaseDescription
Description = app.CurrentReleaseDescription,
IsSingleExecutable = false
}, UpdateCheckStatus.UpdateAvailable);
}

@@ -60,7 +63,9 @@ public static string GetUpdatePackageName()
if (release == null)
{
Logger.Error("Failed to check for updates");
return (null, UpdateCheckStatus.Error);
// TODO: Swap these nearing release
//return (null, UpdateCheckStatus.Error);
return (null, UpdateCheckStatus.NoUpdate);
}

Logger.Debug($"Current Version: {Program.ApplicationVersion}");
@@ -85,7 +90,8 @@ public static string GetUpdatePackageName()
Version = release.TagName,
Title = release.Name,
Description = release.Body,
DownloadURI = asset.BrowserDownloadURL
DownloadURI = asset.BrowserDownloadURL,
IsSingleExecutable = OperatingSystem.IsWindows()
}, UpdateCheckStatus.UpdateAvailable);
}
else
@@ -104,7 +110,8 @@ public static async Task BeginUpdate(Update update, MainWindowViewModel? mainVie
return;
}
string tempPath = Paths.GetTempPath();
string packageFilePath = Path.Combine(tempPath, "update.zip");
string packageFileName = update.IsSingleExecutable ? "update.exe" : "update.zip";
string packageFilePath = Path.Combine(tempPath, packageFileName);

await new Download(Localize("Download.Text.UpdateManager0"), true)
.OnRun(async (d, c) =>
@@ -233,6 +240,7 @@ public class Update
public string Title { get; set; } = "Update Title";
public string? Description { get; set; }
public Uri? DownloadURI { get; set; }
public bool IsSingleExecutable { get; set; }
}

public class FlathubApp
59 changes: 44 additions & 15 deletions Source/HedgeModManager.UI/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
@@ -121,7 +121,7 @@ public async Task CheckForManagerUpdates()
}
d.Destroy();

string message = $"update found:\n{update.Title} - {update.Version}";
string message = $"Update found:\n{update.Title} - {update.Version}";
Logger.Information(message);
var messageBox = new MessageBoxModal("Modal.Title.UpdateManager", message);
messageBox.AddButton("Common.Button.Cancel", (s, e) => messageBox.Close());
@@ -200,16 +200,20 @@ public async Task CheckForAllModUpdates()
Logger.Debug($" Latest: {info.Version}");
if (promptUpdate)
{
var messageBox = new MessageBoxModal("Modal.Title.UpdateMod", "Modal.Message.UpdateMod");
var messageBox = new MessageBoxModal("Modal.Title.UpdateMod", Localize("Modal.Message.UpdateMod", mod.Title));
messageBox.AddButton("Common.Button.Cancel", (s, e) => messageBox.Close());
messageBox.AddButton("Common.Button.Update", async (s, e) =>
{
Modals.Where(x => x.Control is ModInfoModal).ToList().ForEach(x => x.Close());
Logger.Information($"Update clicked for {mod.Title}");
messageBox.Close();
await mod.Updater.PerformUpdateAsync(c);
Modals.Where(x => x.Control is ModInfoModal).ToList().ForEach(x => x.Close());
await Dispatcher.UIThread.InvokeAsync(RefreshGame);
// TODO: Look into threading issues
await Dispatcher.UIThread.InvokeAsync(async () =>
{
Modals.Where(x => x.Control is ModInfoModal).ToList().ForEach(x => x.Close());
Logger.Information($"Update clicked for {mod.Title}");
messageBox.Close();
await mod.Updater.PerformUpdateAsync(c);
Modals.Where(x => x.Control is ModInfoModal).ToList().ForEach(x => x.Close());
RefreshGame();
});
});
messageBox.Open(this);
}
@@ -247,7 +251,7 @@ public async Task LoadGame()
if (game.ModDatabase is ModDatabaseGeneric modsDB)
Profiles = new(await LoadProfiles(game) ?? []);
SelectedProfile = Profiles.FirstOrDefault() ?? ModProfile.Default;
Logger.Debug($"Loaded {Profiles.Count} profiles");
Logger.Debug($"Loaded {Profiles.Count} profile(s)");

Config.LastSelectedPath = Path.Combine(game.Root, game.Executable ?? "");

@@ -261,6 +265,34 @@ public async Task LoadGame()
}
}

public async Task InstallModLoader(bool? install = null)
{
IsBusy = true;
await CreateSimpleDownload("Download.Text.InstallModLoader", "Failed to install modloader",
async (d, p, c) =>
{
if (SelectedGame == null)
return;
if (install != null)
{
var gameGeneric = GetModdableGameGeneric();
if (gameGeneric == null || gameGeneric.ModLoader == null)
return;

if (install == true)
_ = await gameGeneric.ModLoader.InstallAsync();
else
_ = await gameGeneric.ModLoader.UninstallAsync();
}
else
{
_ = await SelectedGame.Game.InstallModLoaderAsync();
}
IsBusy = false;
Dispatcher.UIThread.Invoke(RefreshUI);
}).RunAsync(this);
}

// TODO: Implement mod config switching
public Task LoadProfile()
{
@@ -300,6 +332,7 @@ public async Task Save(bool setBusy = true)
{
try
{
// TODO: Resolve mod dependencies
await SelectedGame.Game.ModDatabase.Save();
if (SelectedGame.Game.ModLoaderConfiguration is ModLoaderConfiguration config)
await config.Save(Path.Combine(SelectedGame.Game.Root, "cpkredir.ini"));
@@ -319,8 +352,7 @@ public async Task Save(bool setBusy = true)
}
catch (Exception e)
{
OpenErrorMessage("Modal.Title.SaveError", "Modal.Message.UnknownSaveError",
"Failed to save", e);
OpenErrorMessage("Modal.Title.SaveError", "Modal.Message.UnknownSaveError", "Failed to save", e);
}
if (setBusy)
IsBusy = false;
@@ -333,7 +365,6 @@ public async Task RunGame()
try
{
await SelectedGame.Game.Run(null, true);
// Add delay
await Task.Delay(5000);
}
catch (Exception e)
@@ -361,8 +392,7 @@ public void StartSetup()

public async Task UpdateCodes(bool force, bool append)
{
if (SelectedGame != null &&
SelectedGame.Game is ModdableGameGeneric gameGeneric &&
if (SelectedGame != null && SelectedGame.Game is ModdableGameGeneric gameGeneric &&
gameGeneric.ModLoaderConfiguration is ModLoaderConfiguration config)
{
string modsRoot = PathEx.GetDirectoryName(config.DatabasePath).ToString();
@@ -512,7 +542,6 @@ public async Task InstallMod(Visual visual, UIGame? game)
InstallMod(file.Name, Utils.ConvertToPath(file.Path), game);
}


public void InstallMod(string name, string path, UIGame? game)
{
game ??= SelectedGame;
Original file line number Diff line number Diff line change
@@ -115,6 +115,8 @@ public Theme SelectedTheme
}
}

public bool SupportsProton => Game?.NativeOS == "Windows" && OperatingSystem.IsLinux() && !string.IsNullOrEmpty(Game.PrefixRoot);

public SettingsViewModel()
{
UpdateThemes();
@@ -130,6 +132,7 @@ public void Update()
{
OnPropertyChanged(nameof(InstallModLoaderText));
OnPropertyChanged(nameof(SelectedProfile));
OnPropertyChanged(nameof(SupportsProton));
}

protected override void OnPropertyChanged(PropertyChangedEventArgs e)
@@ -142,6 +145,7 @@ protected override void OnPropertyChanged(PropertyChangedEventArgs e)
OnPropertyChanged(nameof(EnableDebugConsole));
OnPropertyChanged(nameof(HasModLoader));
OnPropertyChanged(nameof(SupportsMultipleLaunchMethods));
OnPropertyChanged(nameof(SupportsProton));
}
if (e.PropertyName == nameof(MainViewModel))
OnPropertyChanged(nameof(SelectedLanguage));
29 changes: 28 additions & 1 deletion Source/HedgeModManager/LinuxCompatibility.cs
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
using HedgeModManager.Steam;
using System.IO;
using System.IO.Compression;
using System.Text;

public class LinuxCompatibility
{
@@ -55,8 +56,10 @@ await Task.Run(() =>
}
Logger.Debug($"Extracted .NET runtime");
});

await stream.DisposeAsync();

await AddDotnetRegPatch(path);

return true;
}

@@ -83,6 +86,30 @@ public static async Task<bool> AddDllOverride(string? path, string name)
return true;
}

public static async Task<bool> AddDotnetRegPatch(string? path)
{
Logger.Debug($"Adding .NET Framework registry patch to {path}");
if (string.IsNullOrEmpty(path) || !Directory.Exists(path))
{
Logger.Debug($"Prefix is missing!");
return false;
}

string reg = Path.Combine(path!, "system.reg");

if (!Path.Exists(reg))
{
Logger.Debug($"Prefix is not initialised!");
return false;
}

string regPatch = Encoding.Unicode.GetString(Resources.dotnetReg);

await File.AppendAllTextAsync(reg, regPatch);
Logger.Debug($"File written");
return true;
}

public static bool IsPrefixValid(string? path)
{
if (path == null)
10 changes: 10 additions & 0 deletions Source/HedgeModManager/Properties/Resources.Designer.cs
4 changes: 4 additions & 0 deletions Source/HedgeModManager/Properties/Resources.resx
Original file line number Diff line number Diff line change
@@ -135,4 +135,8 @@
<data name="CommunityCodesURL" xml:space="preserve">
<value>https://raw.githubusercontent.com/hedge-dev/HMMCodes/refs/heads/build/</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="dotnetReg" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\dotnet.reg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>
Binary file added Source/HedgeModManager/Resources/dotnet.reg
Binary file not shown.
105 changes: 73 additions & 32 deletions Source/HedgeModManager/Updates/UpdateSourceGMI.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace HedgeModManager.Updates;
using Foundation;
using HedgeModManager.Text;
using System.Threading;

public class UpdateSourceGMI<TMod> : IUpdateSource where TMod : IMod
{
@@ -10,6 +11,7 @@ public class UpdateSourceGMI<TMod> : IUpdateSource where TMod : IMod
public string Host => Url.Host;
public ModUpdateClient Client { get; set; }
public TMod Mod { get; set; }
public SemaphoreSlim Semaphore = new(3);

public UpdateSourceGMI(TMod mod, Uri url)
{
@@ -58,45 +60,84 @@ public async Task PerformUpdateAsync(CancellationToken cancellationToken)
string type = line[..line.IndexOf(' ')];
string path = line[(line.IndexOf(' ') + 1)..];

try
await Semaphore.WaitAsync(cancellationToken);
_ = Task.Run(async () =>
{
switch (type)
int attemptsLeft = 3;
while (attemptsLeft > 0)
{
case "add":
var data = await Client.GetByteArrayAsync(path, cancellationToken);
string filePath = Path.Combine(Mod.Root, path);
Directory.CreateDirectory(Path.GetDirectoryName(filePath)!);
File.WriteAllBytes(filePath, data);
try
{
await ProcessCommand(type, path, cancellationToken);
break;
case "mkdir":
Directory.CreateDirectory(Path.Combine(Mod.Root, path));
break;
case "pause":
if (int.TryParse(line, out int timeout))
}
catch (Exception ex)
{
attemptsLeft--;
Logger.Error(ex);
if (attemptsLeft == 0)
Logger.Error("Command process failed 3 times, skipping!");
else
{
Logger.Information($"Pausing for {timeout} miliseconds...");
await Task.Delay(timeout, cancellationToken);
Logger.Error($"Command process failed, retrying ({attemptsLeft} attempts left)");
await Task.Delay(1000, cancellationToken);
}
break;
case "print":
Logger.Information($"[print] {path}");
break;
case "delete":
if (File.Exists(path))
File.Delete(path);
else if (Directory.Exists(path))
Directory.Delete(path, true);
break;
default:
Logger.Error($"Unknown command type: {type}");
}
if (cancellationToken.IsCancellationRequested)
break;
}
}
catch (Exception ex)
{
Logger.Error(ex);
Logger.Error($"Failed to process GMI command: {line}");
}
Semaphore.Release();
}, cancellationToken);
}
}

public async Task ProcessCommand(string type, string path, CancellationToken? c)
{
CancellationToken cancellationToken = c ?? CancellationToken.None;
switch (type)
{
case "add":
Logger.Information($"Downloading {path}...");
var data = await Client.GetByteArrayAsync(path, cancellationToken);
if (cancellationToken.IsCancellationRequested)
return;
string filePath = Path.Combine(Mod.Root, path);
Directory.CreateDirectory(Path.GetDirectoryName(filePath)!);
File.WriteAllBytes(filePath, data);
break;
case "mkdir":
Logger.Debug($"Creating directory {path}...");
Directory.CreateDirectory(Path.Combine(Mod.Root, path));
break;
case "pause":
if (int.TryParse(path, out int timeout))
{
Logger.Information($"Pausing for {timeout} miliseconds...");
await Task.Delay(timeout, cancellationToken);
}
else
{
Logger.Debug($"Could not parse timeout for \"{path}\"");
}
break;
case "print":
Logger.Information($"[{Mod.Title}] {path}");
break;
case "delete":
if (File.Exists(path))
{
Logger.Debug($"Deleting file \"{path}\"");
File.Delete(path);
}
else if (Directory.Exists(path))
{
Logger.Debug($"Deleting directory \"{path}\"");
Directory.Delete(path, true);
}
break;
default:
Logger.Error($"Unknown command type: {type}");
break;
}
}
}

0 comments on commit ac18ef9

Please sign in to comment.