diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml
index 0f93882..e6a8a0f 100644
--- a/.github/workflows/build-release.yml
+++ b/.github/workflows/build-release.yml
@@ -19,6 +19,7 @@ on:
env:
PROJECT_PATH: ./Source/HedgeModManager.UI/HedgeModManager.UI.csproj
FLATPAK_ID: io.github.hedge_dev.hedgemodmanager
+ 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
@@ -82,12 +83,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/macos/osx-arm64 ${{env.PROJECT_PATH}} -p:UseAppHost=true
+
+ - name: Build osx-arm64 Bundle
+ run: /bin/bash ${{env.MACOS_BUILD_SCRIPT}} ${{ github.event.inputs.version }}
- 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/macos/HedgeModManager.app ./release/HedgeModManager.app
- name: Create Release
uses: softprops/action-gh-release@v2.2.1
diff --git a/.gitignore b/.gitignore
index f740d3a..a221143 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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.*
@@ -215,3 +219,4 @@ _Pvt_Extensions/
ModelManifest.xml
Ignored/
+.DS_Store
\ No newline at end of file
diff --git a/README.md b/README.md
index 1c06756..92db837 100644
--- a/README.md
+++ b/README.md
@@ -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)
@@ -62,6 +63,9 @@ 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.
+### Building
+For building from source, please check the [building instructions](/docs/BUILDING.md) page.
+
### Frequently Asked Questions
- Where can I install mods?
diff --git a/Source/HedgeModManager.Console/HedgeModManager.Console.csproj b/Source/HedgeModManager.Console/HedgeModManager.Console.csproj
index 6acb2e7..040f275 100644
--- a/Source/HedgeModManager.Console/HedgeModManager.Console.csproj
+++ b/Source/HedgeModManager.Console/HedgeModManager.Console.csproj
@@ -12,4 +12,4 @@
-
+
\ No newline at end of file
diff --git a/Source/HedgeModManager.UI/App.axaml b/Source/HedgeModManager.UI/App.axaml
index 392e294..476c95f 100644
--- a/Source/HedgeModManager.UI/App.axaml
+++ b/Source/HedgeModManager.UI/App.axaml
@@ -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">
diff --git a/Source/HedgeModManager.UI/HedgeModManager.UI.csproj b/Source/HedgeModManager.UI/HedgeModManager.UI.csproj
index be08e2f..6032e8e 100644
--- a/Source/HedgeModManager.UI/HedgeModManager.UI.csproj
+++ b/Source/HedgeModManager.UI/HedgeModManager.UI.csproj
@@ -60,4 +60,4 @@
Resources.Designer.cs
-
+
\ No newline at end of file
diff --git a/Source/HedgeModManager.UI/Program.cs b/Source/HedgeModManager.UI/Program.cs
index 27ce37e..f23aa2a 100644
--- a/Source/HedgeModManager.UI/Program.cs
+++ b/Source/HedgeModManager.UI/Program.cs
@@ -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;
@@ -176,6 +177,28 @@ static void installToRegistery(string schema, string args)
}
}
+ [SupportedOSPlatform("macos")]
+ public static void ListenForUriSchemeMac(Action> 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";
diff --git a/Source/HedgeModManager.UI/Properties/PublishProfiles/osx-arm64.pubxml b/Source/HedgeModManager.UI/Properties/PublishProfiles/osx-arm64.pubxml
new file mode 100644
index 0000000..097e12a
--- /dev/null
+++ b/Source/HedgeModManager.UI/Properties/PublishProfiles/osx-arm64.pubxml
@@ -0,0 +1,8 @@
+
+
+ osx-arm64
+ true
+ true
+ true
+
+
diff --git a/Source/HedgeModManager.UI/Properties/PublishProfiles/osx-x64.pubxml b/Source/HedgeModManager.UI/Properties/PublishProfiles/osx-x64.pubxml
new file mode 100644
index 0000000..ca16eae
--- /dev/null
+++ b/Source/HedgeModManager.UI/Properties/PublishProfiles/osx-x64.pubxml
@@ -0,0 +1,8 @@
+
+
+ osx-x64
+ true
+ true
+ false
+
+
diff --git a/Source/HedgeModManager.UI/ViewModels/MainWindowViewModel.cs b/Source/HedgeModManager.UI/ViewModels/MainWindowViewModel.cs
index 652a2e5..b12c8f9 100644
--- a/Source/HedgeModManager.UI/ViewModels/MainWindowViewModel.cs
+++ b/Source/HedgeModManager.UI/ViewModels/MainWindowViewModel.cs
@@ -945,4 +945,4 @@ protected override void OnPropertyChanged(PropertyChangedEventArgs e)
OnPropertyChanged(nameof(IsFullscreen));
base.OnPropertyChanged(e);
}
-}
+}
\ No newline at end of file
diff --git a/Source/HedgeModManager.UI/Views/MainWindow.axaml.cs b/Source/HedgeModManager.UI/Views/MainWindow.axaml.cs
index 6e4ba1c..a91fb70 100644
--- a/Source/HedgeModManager.UI/Views/MainWindow.axaml.cs
+++ b/Source/HedgeModManager.UI/Views/MainWindow.axaml.cs
@@ -40,7 +40,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
@@ -70,6 +70,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();
@@ -148,6 +150,7 @@ private void OnKeyDown(object? sender, KeyEventArgs e)
});
}
}
+
break;
case Key.F11:
toggleFullscreen = true;
@@ -207,6 +210,7 @@ private void OnKeyDown(object? sender, KeyEventArgs e)
if (button != Buttons.None)
_ = Dispatcher.UIThread.Invoke(async () => await ViewModel.OnInputDownAsync(button));
}
+
break;
}
diff --git a/Source/HedgeModManager/ModdableGameLocator.cs b/Source/HedgeModManager/ModdableGameLocator.cs
index dd2ce3f..4d79811 100644
--- a/Source/HedgeModManager/ModdableGameLocator.cs
+++ b/Source/HedgeModManager/ModdableGameLocator.cs
@@ -1,4 +1,5 @@
namespace HedgeModManager;
+
using Foundation;
using HedgeModManager.Epic;
using HedgeModManager.Properties;
@@ -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()
@@ -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")] } }
@@ -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"))] } }
},
@@ -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")] }
}
}
];
@@ -390,6 +392,27 @@ public static List 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.Executable);
+ 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)
+ {
+ Is64Bit = gameInfo.Is64Bit
+ };
+ game.ModDatabase.SupportsCodeCompilation = gameInfo.SupportsCodes;
+ games.Add(game);
+ }
+ }
+ }
}
return games;
diff --git a/docs/BUILDING.md b/docs/BUILDING.md
new file mode 100644
index 0000000..576be7e
--- /dev/null
+++ b/docs/BUILDING.md
@@ -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
+
+1. Navigate to the root of the project
+2. Run the build command
+```zsh
+dotnet publish -p:PublishProfile=osx-arm64 -c Release -o ./output/macos/osx-arm64 ./Source/HedgeModManager.UI/HedgeModManager.UI.csproj -p:UseAppHost=true
+```
+3. Navigate to `macos`
+```zsh
+cd macos
+```
+4. Run the following command to create the app bundle.
+```zsh
+/bin/bash generate-bundle.bash
+```
+5. Navigate to `../output/macos`
+6. Run `HedgeModManager.app`
diff --git a/macos/AppIcon.icns b/macos/AppIcon.icns
new file mode 100644
index 0000000..e85f5d7
Binary files /dev/null and b/macos/AppIcon.icns differ
diff --git a/macos/Info.plist b/macos/Info.plist
new file mode 100644
index 0000000..9fce9e4
--- /dev/null
+++ b/macos/Info.plist
@@ -0,0 +1,43 @@
+
+
+
+
+ CFBundleURLTypes
+
+
+ CFBundleURLSchemes
+
+ hedgemmswa
+ hedgemmgens
+ hedgemmlw
+ hedgemmforces
+ hedgemmtenpex
+ hedgemmmusashi
+ hedgemmrainbow
+ hedgemmhite
+ hedgemmrangers
+ hedgemmmillersonic
+ hedgemmmillershadow
+
+
+
+ CFBundleIconFile
+ AppIcon.icns
+ CFBundleIdentifier
+ io.github.hedge_dev.hedgemodmanager
+ CFBundleName
+ HedgeModManager
+ CFBundleVersion1.0.0
+ LSMinimumSystemVersion
+ 12.0
+ CFBundleExecutable
+ HedgeModManager.UI
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString1.0.0
+ NSHighResolutionCapable
+
+
+
\ No newline at end of file
diff --git a/macos/generate-bundle.bash b/macos/generate-bundle.bash
new file mode 100644
index 0000000..c8ad69d
--- /dev/null
+++ b/macos/generate-bundle.bash
@@ -0,0 +1,31 @@
+#!/bin/bash
+VERSION=${1:-"1.0.0"}
+
+APP_NAME="../output/macos/HedgeModManager.app"
+PLIST_PATH="./Info.plist"
+PUBLISH_OUTPUT_DIRECTORY="../output/macos/osx-arm64/."
+
+ICON_FILE="./AppIcon.icns"
+
+if [ -d "$APP_NAME" ]
+then
+ rm -rf "$APP_NAME"
+fi
+
+mkdir "$APP_NAME"
+
+mkdir "$APP_NAME/Contents"
+mkdir "$APP_NAME/Contents/MacOS"
+mkdir "$APP_NAME/Contents/Resources"
+
+cp "$ICON_FILE" "$APP_NAME/Contents/Resources/AppIcon.icns"
+cp -a "$PUBLISH_OUTPUT_DIRECTORY" "$APP_NAME/Contents/MacOS"
+cp -a "$PLIST_PATH" "$APP_NAME/Contents/Info.plist"
+
+PLIST_PATH="$APP_NAME/Contents/Info.plist"
+
+# Update CFBundleShortVersionString using sed
+sed -i '' -e "s/CFBundleShortVersionString<\/key>\s*[^<]*<\/string>/CFBundleShortVersionString<\/key>${VERSION}<\/string>/g" "$PLIST_PATH"
+
+# Update CFBundleVersion using sed
+sed -i '' -e "s/CFBundleVersion<\/key>\s*[^<]*<\/string>/CFBundleVersion<\/key>${VERSION}<\/string>/g" "$PLIST_PATH"