Skip to content
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
67b59ce
Initial Macos Support
thesupersonic16 Mar 5, 2025
8c956b4
Initial Macos Support
to-fuu Mar 6, 2025
6f941d9
Add building instructions
to-fuu Mar 6, 2025
409d8a0
Make self contained
to-fuu Mar 7, 2025
70d5b0b
Remove log
to-fuu Mar 7, 2025
6aee6fe
add build command
to-fuu Mar 7, 2025
2a4b84f
remove Dotnet.Bundle dependencies
to-fuu Mar 7, 2025
db07d96
Update icon
to-fuu Mar 7, 2025
e0ed516
Disable detecting UnleashedRecomp
to-fuu Mar 7, 2025
f1e7c13
Update build docs
to-fuu Mar 7, 2025
b8b3adb
Fix menu bar showing Avalonia Application
to-fuu Mar 7, 2025
a2ae2a1
Support URI Schemes
to-fuu Mar 7, 2025
73e5474
Support URI Schemes
to-fuu Mar 7, 2025
2993c29
Fix build command
to-fuu Mar 8, 2025
e758958
change bash to zsh
to-fuu Mar 8, 2025
1d2f7ea
change bash to zsh
to-fuu Mar 8, 2025
9fab194
update the build command
to-fuu Mar 8, 2025
e4bc8ad
change build command ro release
to-fuu Mar 8, 2025
8459403
Merge remote-tracking branch 'origin' into macos-support
to-fuu Mar 20, 2025
466a041
ignore output and idea folder
to-fuu Mar 20, 2025
b88eacd
move .icns file to macos folder
to-fuu Mar 20, 2025
f11c76a
move info.plist out
to-fuu Mar 20, 2025
56f7431
update locating on macos
to-fuu Mar 20, 2025
b6b158e
use the game's ID
to-fuu Mar 20, 2025
9bb00f0
change to macOS
to-fuu Mar 20, 2025
37f9d2d
update docs
to-fuu Mar 20, 2025
bc7f08a
update output folder
to-fuu Mar 20, 2025
dbe4881
fix rebase
to-fuu Mar 20, 2025
a3b7422
remove version requirement
to-fuu Mar 20, 2025
1b08a77
improve build docs
to-fuu Mar 20, 2025
0a3be97
remove version from docs
to-fuu Mar 20, 2025
25decd2
remove empty space
to-fuu Mar 20, 2025
964a1d7
use Executable for root
to-fuu Mar 20, 2025
c456b48
remove empty line
to-fuu Mar 20, 2025
32b4237
update build release
to-fuu Mar 20, 2025
83ace70
revert changes
to-fuu Mar 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/build-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ on:
env:
PROJECT_PATH: ./Source/HedgeModManager.UI/HedgeModManager.UI.csproj
FLATPAK_ID: io.github.hedge_dev.hedgemodmanager
MACOS_ID: com.hedge_dev.hedgemodmanager
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why this is using com.hedge_dev.hedgemodmanager? Is io.github.hedge_dev.hedgemodmanager or com.github.hedge_dev.hedgemodmanager usable here? hedge-dev does not own/control hedge_dev.com.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ID can be anything on macOS as long as the code is consistent. I've seen com, org, and io in Mac app IDs before. I would make it the same as the Flatpak ID for consistency.

MACOS_BUILD_SCRIPT: ./macos/generate-bundle.bash
GENERATOR_URL: https://raw.githubusercontent.com/flatpak/flatpak-builder-tools/refs/heads/master/dotnet/flatpak-dotnet-generator.py
DOTNET_VERSION: 8
DOTNET_CLI_HOME: /tmp/.dotnet
Expand Down Expand Up @@ -82,12 +84,19 @@ jobs:

- name: Build Flatpak Bundle
run: flatpak build-bundle repo ./flatpak/${{env.FLATPAK_ID}}.flatpak ${{env.FLATPAK_ID}}

- name: osx-arm64 Build
run: dotnet publish -p:PublishProfile=osx-arm64 -c Release -p:AssemblyVersion=${{ github.event.inputs.version }} -p:FileVersion=${{ github.event.inputs.version }} -o ./output/osx-arm64 ${{env.PROJECT_PATH}} -p:UseAppHost=true

- name: Build osx-arm64 Bundle
run: /bin/bash ${{env.MACOS_BUILD_SCRIPT}} ${{ github.event.inputs.version }} ${{ env.MACOS_ID }}

- name: Prepare Release
run: |
mkdir -p ./release
mv ./output/win-x64/HedgeModManager.UI.exe ./release/HedgeModManager.exe
mv ./flatpak/${{env.FLATPAK_ID}}.flatpak ./release/${{env.FLATPAK_ID}}.flatpak
mv ./output/osx-arm64/HedgeModManager.app ./release/HedgeModManager.app
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can bundles support multi-arch? Like HedgeModManager.app being runnable on x86_64 and arm64 systems. If possible, might be worth adding unless it inflates the file size too much.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

macOS bundles have supported multi-arch since the PowerPC -> Intel transition; the bundle doesn't have to be exclusively arm64. Other native-built projects I've seen (to give an example, ioquake3) separately build the x86_64 and arm64 executables and any dependencies, then combine them using the lipo command to put in the bundle. If this build process produces a standard Mach-O executable, then I think that's the best way to go about it.


- name: Create Release
uses: softprops/[email protected]
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,14 @@ bld/
bin/
[Bb]in/
[Oo]bj/
output/

# Visual Studio 2015 cache/options directory
.vs/

Source/.idea


# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
Expand Down Expand Up @@ -215,3 +219,4 @@ _Pvt_Extensions/
ModelManifest.xml

Ignored/
.DS_Store
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ A mod manager for Hedgehog Engine games on PC.
- [Installation](#installation)
- [Windows](#windows)
- [Linux/Steam Deck](#linuxsteam-deck)
- [Building](#building)
- [Frequently Asked Questions](#frequently-asked-questions)
- [Bug/Issue Reporting](#bugissue-reporting)
- [Contribute](#contribute)
Expand Down Expand Up @@ -62,6 +63,12 @@ Flatpak bundles are also available from the [releases page](https://github.com/h
>
> For Heroic, it must be a flatpak install within the home directory.

> [!NOTE]
> Currently Hedge Mod Manager is not officially available to be downloaded from any package managers.

### Building
[Check out the building instructions here](/docs/BUILDING.md).

### Frequently Asked Questions
- Where can I install mods?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
<ProjectReference Include="..\HedgeModManager\HedgeModManager.csproj" />
</ItemGroup>

</Project>
</Project>
3 changes: 2 additions & 1 deletion Source/HedgeModManager.UI/App.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
xmlns:local="using:HedgeModManager.UI"
xmlns:themes="using:HedgeModManager.UI.Themes"
xmlns:materialIcons="using:Material.Icons.Avalonia"
RequestedThemeVariant="{x:Static themes:Themes.Darker}">
RequestedThemeVariant="{x:Static themes:Themes.Darker}"
Name="Hedge Mod Manager">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->

<Application.Resources>
Expand Down
2 changes: 1 addition & 1 deletion Source/HedgeModManager.UI/HedgeModManager.UI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>
</Project>
23 changes: 23 additions & 0 deletions Source/HedgeModManager.UI/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Runtime.Versioning;
using Avalonia.Controls.ApplicationLifetimes;

#if !DEBUG
using System.Diagnostics;
Expand Down Expand Up @@ -176,6 +177,28 @@ static void installToRegistery(string schema, string args)
}
}

[SupportedOSPlatform("macos")]
public static void ListenForUriSchemeMac(Action<List<ICliCommand>> callback)
{
// Use Avalonia IActivatableLifetime to listen for OpenUri events on macOS
if (Application.Current?.TryGetFeature(typeof(IActivatableLifetime)) is { } activatableLifetime)
{
((IActivatableLifetime)activatableLifetime).Activated += async (s, a) =>
{
if (a is ProtocolActivatedEventArgs protocolArgs && protocolArgs.Kind == ActivationKind.OpenUri)
{
var uri = protocolArgs.Uri.ToString();
var args = CommandLine.ParseArguments(["--schema", uri]);
var (_, commands) = CommandLine.ExecuteArguments(args);
if (commands.Count > 0)
{
callback(commands);
}
}
};
}
}

public static string GetFormattedAppVersion()
{
string unstableType = "beta";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project>
<PropertyGroup>
<RuntimeIdentifiers>osx-arm64</RuntimeIdentifiers>
<PublishSingleFile>true</PublishSingleFile>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<SelfContained>true</SelfContained>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project>
<PropertyGroup>
<RuntimeIdentifiers>osx-x64</RuntimeIdentifiers>
<PublishSingleFile>true</PublishSingleFile>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<SelfContained>false</SelfContained>
</PropertyGroup>
</Project>
23 changes: 17 additions & 6 deletions Source/HedgeModManager.UI/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public partial class MainWindowViewModel : ViewModelBase
[ObservableProperty] private string _lastLog = "";
[ObservableProperty] private string _message = "";
[ObservableProperty] private TabInfo? _currentTabInfo;
[ObservableProperty] private TabInfo[] _tabInfos =
[ObservableProperty] private TabInfo[] _tabInfos =
[new ("Loading"), new("Setup"), new("Mods"), new("Codes"), new("Settings"), new("About"), new("Test")];
[ObservableProperty] private ObservableCollection<Modal> _modals = [];
[ObservableProperty] private ObservableCollection<IMod> _mods = [];
Expand Down Expand Up @@ -115,7 +115,7 @@ public async Task CheckForManagerUpdatesAsync()
.OnRun(async (d, c) =>
{
d.CreateProgress().ReportMax(-1);

var (update, status) = await Updater.CheckForUpdates();
if (update == null)
{
Expand Down Expand Up @@ -345,7 +345,7 @@ await CreateSimpleDownload("Download.Text.InstallModLoader", "Failed to install
var gameGeneric = GetModdableGameGeneric();
if (gameGeneric == null || gameGeneric.ModLoader == null)
return;

if (install == true)
_ = await gameGeneric.ModLoader.InstallAsync();
else
Expand Down Expand Up @@ -393,7 +393,7 @@ public async Task SwitchProfileAsync()

IsBusy = true;

await CreateSimpleDownload("Download.Text.SwitchProfileSave", "Failed to switch profiles",
await CreateSimpleDownload("Download.Text.SwitchProfileSave", "Failed to switch profiles",
async (d, p, c) =>
{
var backupProgress = d.CreateProgress();
Expand Down Expand Up @@ -462,7 +462,18 @@ public async Task SaveAsync(bool setBusy = true)

await SelectedGame.Game.ModDatabase.Save();
if (SelectedGame.Game.ModLoaderConfiguration is ModLoaderConfiguration config)
await config.Save(Path.Combine(SelectedGame.Game.Root, "cpkredir.ini"));
{
// For macos, save under the user's Application Support directory.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*macOS

if (OperatingSystem.IsMacOS())
{
var appSupportDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
await config.Save(Path.Combine( appSupportDir,SelectedGame.Game.Executable, "cpkredir.ini"));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await config.Save(Path.Combine(appSupportDir, SelectedGame.Game.Executable, "cpkredir.ini"));

}
else
{
await config.Save(Path.Combine(SelectedGame.Game.Root, "cpkredir.ini"));
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whats going on here? cpkredir.ini must be saved in the game's root. If the file must be next to the game executable, then make sure to set Root to the correct path in the locator.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

macos executables usually live within the Applications folder (or anywhere you place them) however other files will live in the Application Support folder.
For the mods to load, cpkredir.ini must live inside Application Support, inside the game's folder.
The folder name matches the bundle name.
Previously it was UnleashedRecomp.app. Apparently it was changed to Unleashed Recompiled.app
I will double check and update the values accordingly

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure the root is set to the directory where the ini is located, this needs to be done in the locator. Since this is different from the app install, change the root directory existance check to check if the .app exist.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change should not exist, please remove it, and later when the Unleashed Recompield macOS code gets approved you can then go fix the pathing issues in the locator.

While naming can be confusing, Root on non-Windows games is where the mod configuration gets saved.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure the root is set to the directory where the ini is located, this needs to be done in the locator. Since this is different from the app install, change the root directory existance check to check if the .app exist.

Previously I believe the root was used in the run command so by default I set as thr Applications dir. now that that was changed I agree it makes more sense to change this to where the .ini lives aka in Application Support

}
catch (UnauthorizedAccessException e)
{
Expand Down Expand Up @@ -898,7 +909,7 @@ public async Task StartServerAsync()
ServerStatus = 1;
while (ServerStatus == 1)
{
using var server = new NamedPipeServerStream(Program.PipeName, PipeDirection.In,
using var server = new NamedPipeServerStream(Program.PipeName, PipeDirection.In,
NamedPipeServerStream.MaxAllowedServerInstances,
PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
Logger.Debug("Waiting for connection");
Expand Down
7 changes: 6 additions & 1 deletion Source/HedgeModManager.UI/Views/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using HedgeModManager.Foundation;
using Avalonia;


namespace HedgeModManager.UI.Views;

public partial class MainWindow : Window
Expand Down Expand Up @@ -40,7 +41,7 @@ public void LoadGames()
// Select the last selected game or first game
ViewModel.SelectedGame = ViewModel.Games
.FirstOrDefault(x => x != null && Path.Combine(x.Game.Root, x.Game.Executable ?? "")
== ViewModel.Config.LastSelectedPath, ViewModel.Games.FirstOrDefault());
== ViewModel.Config.LastSelectedPath, ViewModel.Games.FirstOrDefault());
}

// Set to true until we create a setup
Expand Down Expand Up @@ -70,6 +71,8 @@ private void Window_Loaded(object? sender, RoutedEventArgs e)

Logger.Information($"Loading URI handlers...");
Program.InstallURIHandler();
if (OperatingSystem.IsMacOS())
Program.ListenForUriSchemeMac((commands) => _ = ViewModel.ProcessCommandsAsync(commands));

LoadGames();

Expand Down Expand Up @@ -148,6 +151,7 @@ private void OnKeyDown(object? sender, KeyEventArgs e)
});
}
}

break;
case Key.F11:
toggleFullscreen = true;
Expand Down Expand Up @@ -207,6 +211,7 @@ private void OnKeyDown(object? sender, KeyEventArgs e)
if (button != Buttons.None)
_ = Dispatcher.UIThread.Invoke(async () => await ViewModel.OnInputDownAsync(button));
}

break;
}

Expand Down
2 changes: 1 addition & 1 deletion Source/HedgeModManager/HedgeModManager.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>

</Project>
33 changes: 29 additions & 4 deletions Source/HedgeModManager/ModdableGameLocator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace HedgeModManager;

using Foundation;
using HedgeModManager.Epic;
using HedgeModManager.Properties;
Expand All @@ -17,7 +18,7 @@ public class ModdableGameLocator
ID = "SonicGenerations",
ModLoaderName = "HE1ModLoader",
ModLoaderFileName = "d3d9.dll",
ModLoaderIncompatibleFileNames = [ "dinput8.dll" ],
ModLoaderIncompatibleFileNames = ["dinput8.dll"],
ModLoaderDownloadURL = Resources.HE1MLDownloadURL,
Is64Bit = false,
PlatformInfos = new()
Expand All @@ -30,7 +31,7 @@ public class ModdableGameLocator
ID = "SonicLostWorld",
ModLoaderName = "HE1ModLoader",
ModLoaderFileName = "d3d9.dll",
ModLoaderIncompatibleFileNames = [ "dinput8.dll" ],
ModLoaderIncompatibleFileNames = ["dinput8.dll"],
ModLoaderDownloadURL = Resources.HE1MLDownloadURL,
Is64Bit = false,
PlatformInfos = new() { { "Steam", [new ("329440", "slw.exe")] } }
Expand All @@ -40,7 +41,7 @@ public class ModdableGameLocator
ID = "SonicForces",
ModLoaderName = "HE2ModLoader",
ModLoaderFileName = "d3d11.dll",
ModLoaderIncompatibleFileNames = [ "dinput8.dll" ],
ModLoaderIncompatibleFileNames = ["dinput8.dll"],
ModLoaderDownloadURL = Resources.HE2MLDownloadURL,
PlatformInfos = new() { { "Steam", [new ("637100", Path.Combine("build", "main", "projects", "exec", "Sonic Forces.exe"))] } }
},
Expand Down Expand Up @@ -132,7 +133,8 @@ public class ModdableGameLocator
{
{ "Windows", [new(string.Empty, "SOFTWARE\\UnleashedRecomp")] },
{ "Flatpak", [new("io.github.hedge_dev.unleashedrecomp", "unleashedrecomp")] },
{ "Desktop", [new("io.github.hedge_dev.unleashedrecomp", "unleashedrecomp"), new("UnleashedRecomp", "unleashedrecomp")] }
{ "Desktop", [new("io.github.hedge_dev.unleashedrecomp", "unleashedrecomp"), new("UnleashedRecomp", "unleashedrecomp")] },
{ "macOS", [new("io.github.hedge_dev.unleashedrecomp", "unleashedrecomp"), new("UnleashedRecomp", "unleashedrecomp")] }
}
}
];
Expand Down Expand Up @@ -390,6 +392,29 @@ public static List<IModdableGame> LocateGames()
}
}
}
if (OperatingSystem.IsMacOS() && gameInfo.PlatformInfos.TryGetValue("macOS", out var macOsInfo))
{
foreach (var entry in macOsInfo)
{
string root = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
"Library", "Application Support", entry.ID);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the most direct way to access the "Application Support" directory? And is this with the ID the path the game will access the cpkredir.ini file?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the Executable makes more sense so I'm changing it to that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will need re-reviewing when a Unleashed Recompiled macOS port gets approved. The decision on the path is clearly not final.

if (Directory.Exists(root))
{
var gameSimple = new GameSimple(
"macOS", entry.ID, gameInfo.ID,
root, Path.GetFileName(entry.Executable), "macOS",
null, $"open -a {entry.Executable}", null);
var game = new ModdableGameGeneric(gameSimple)
{
SupportsDirectLaunch = true,
SupportsLauncher = false,
Is64Bit = gameInfo.Is64Bit
};
game.ModDatabase.SupportsCodeCompilation = gameInfo.SupportsCodes;
games.Add(game);
}
}
}
}

return games;
Expand Down
26 changes: 26 additions & 0 deletions docs/BUILDING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Building from source

## 1. Clone the repository
```zsh
git clone https://github.com/hedge-dev/HedgeModManager.git
```

## 2. Build the project

### Macos

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Macos -> macOS


1. Navigate to the root of the project
2. Run the build command
```zsh
dotnet publish -p:PublishProfile=osx-arm64 -c Release -p:AssemblyVersion=8.0.0 -p:FileVersion=8.0.0 -o ./output/macos/osx-arm64 ./Source/HedgeModManager.UI/HedgeModManager.UI.csproj -p:UseAppHost=true
```
Navigate to `/macos`
```zsh
cd macos
```
Run the following command to create the app bundle.
```zsh
/bin/bash generate-bundle.bash 8.0.4
```
3. Navigate to `/output/macos`
4. Run `HedgeModManager.app`
Binary file added macos/AppIcon.icns
Binary file not shown.
Loading