Skip to content

Add support for macOS #34

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ jobs:
rid: linux-x64
- os: windows
rid: win-x64
- os: macos
rid: macos-universal

runs-on: ${{ matrix.os }}-latest
steps:
Expand All @@ -23,12 +25,21 @@ jobs:
- name: Restore
run: dotnet restore
- name: Build
if: runner.os != 'macOS'
run: dotnet build -c Release --no-restore
- name: Test
if: runner.os != 'macOS'
run: dotnet test --no-restore
- name: Publish
working-directory: src/openlauncher
if: runner.os != 'macOS'
run: dotnet publish -c Release -r ${{ matrix.rid }} --self-contained
- name: Build (macOS)
if: runner.os == 'macOS'
run: ./build-mac.sh
- name: Package (macOS)
if: runner.os == 'macOS'
run: ./package-mac.sh
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
Expand Down Expand Up @@ -108,4 +119,5 @@ jobs:
artifacts/openlauncher*.exe
artifacts/openlauncher*.rpm
artifacts/openlauncher*.deb
artifacts/OpenLauncher*.zip

37 changes: 37 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,40 @@ FodyWeavers.xsd

# JetBrains Rider
*.sln.iml
# Created by https://www.toptal.com/developers/gitignore/api/macos
# Edit at https://www.toptal.com/developers/gitignore?templates=macos

### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

### macOS Patch ###
# iCloud generated files
*.icloud

# End of https://www.toptal.com/developers/gitignore/api/macos

Expand Down
41 changes: 41 additions & 0 deletions build-mac.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash
#Modified from Avalonia's documentation: https://docs.avaloniaui.net/docs/0.10.x/distribution-publishing/macos

set -e
trap 'echo "Error packaging app"; exit 1' ERR

PROJECT_NAME="openlauncher"
OUTPUT_DIR="./src/openlauncher/bin/Release/net8.0"
FINAL_OUTPUT_DIR="./src/openlauncher/bin/Release/net8.0/macos-universal"

# Function to build for a specific architecture
build_for_arch() {
local arch=$1
echo "Building for $arch..."
dotnet publish -r osx-$arch -c Release
}

# Build for both architectures
build_for_arch "x64"
build_for_arch "arm64"

# Create the final output directory
mkdir -p "$FINAL_OUTPUT_DIR"

ARM_OUTPUT="$OUTPUT_DIR/osx-arm64/publish"
X64_OUTPUT="$OUTPUT_DIR/osx-x64/publish"

#Copy libraries into final output dir
for FILE in "$ARM_OUTPUT"/*; do
BASENAME=$(basename "$FILE")
if [ -f "$FILE" ] && [[ "$BASENAME" != "openlauncher" ]]; then
cp "$FILE" "$FINAL_OUTPUT_DIR"/.
fi
done

# Create universal binary
echo "Creating universal binary..."
lipo -create \
"$OUTPUT_DIR/osx-x64/publish/$PROJECT_NAME" \
"$OUTPUT_DIR/osx-arm64/publish/$PROJECT_NAME" \
-output "$FINAL_OUTPUT_DIR/$PROJECT_NAME"
26 changes: 26 additions & 0 deletions info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIconFile</key>
<string>AppIcon.icns</string>
<key>CFBundleIdentifier</key>
<string>io.openrct2.openlauncher</string>
<key>CFBundleName</key>
<string>OpenLauncher</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<key>LSMinimumSystemVersion</key>
<string>10.15</string>
<key>CFBundleExecutable</key>
<string>openlauncher</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>APP_VERSION_NUMBER</string>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</plist>
48 changes: 48 additions & 0 deletions package-mac.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/bin/bash
#Modified from Avalonia's documentation: https://docs.avaloniaui.net/docs/0.10.x/distribution-publishing/macos
set -e
trap 'echo "Error packaging app"; exit 1' ERR

APP_NAME="./src/openlauncher/bin/Release/net8.0/macos-universal/OpenLauncher.app"
PUBLISH_OUTPUT_DIRECTORY="./src/openlauncher/bin/Release/net8.0/macos-universal/."

INFO_PLIST="./info.plist"

ICON_FILE="./src/openlauncher/resources/logo-mac.icns"

if [ -d "$APP_NAME" ]
then
rm -rf "$APP_NAME"
fi

echo "Creating OpenLauncher.app"

rm -r -f "$APP_NAME"
mkdir "$APP_NAME"

mkdir "$APP_NAME/Contents"
mkdir "$APP_NAME/Contents/MacOS"
mkdir "$APP_NAME/Contents/Resources"

cp "$INFO_PLIST" "$APP_NAME/Contents/Info.plist"
cp "$ICON_FILE" "$APP_NAME/Contents/Resources/AppIcon.icns"
for FILE in "$PUBLISH_OUTPUT_DIRECTORY"/*; do
if [ -f "$FILE" ]; then
cp -a "$FILE" "$APP_NAME/Contents/MacOS/."
fi
done

#Get version number from csproj
VERSION_NUMBER=$(sed -n 's/.*<AssemblyVersion>\(.*\)<\/AssemblyVersion>.*/\1/p' ./src/openlauncher/openlauncher.csproj)
#Replace placeholder version with real version number
sed -i -e "s/APP_VERSION_NUMBER/$VERSION_NUMBER/" "$APP_NAME/Contents/Info.plist"
#For whatever reason, sed on macOS creates a backup file when replacing text, so we need to remove it
rm "$APP_NAME/Contents/Info.plist-e"

codesign --sign - --force --deep "./src/openlauncher/bin/Release/net8.0/macos-universal/OpenLauncher.app"

mkdir "$PUBLISH_OUTPUT_DIRECTORY/publish"

echo "Zipping OpenLauncher.app..."

ditto -c -k --keepParent "./src/openlauncher/bin/Release/net8.0/macos-universal/OpenLauncher.app" "./src/openlauncher/bin/Release/net8.0/macos-universal/publish/OpenLauncher.zip"
25 changes: 22 additions & 3 deletions src/IntelOrca.OpenLauncher.Core/InstallService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,15 @@ public string ExecutablePath
{
get
{
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var binaryName = isWindows ? $"{_game.BinaryName}.exe" : _game.BinaryName;
string binaryName;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
binaryName = $"{_game.BinaryName}.exe";
} else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
// We need to use Name and not BinaryName since BinaryName isn't capitalized
binaryName = $"{_game.Name}.app/Contents/MacOS/{_game.Name}";
} else {
binaryName = _game.BinaryName;
}
return Path.Combine(_game.BinPath, binaryName);
}
}
Expand Down Expand Up @@ -157,7 +164,11 @@ private void ExtractArchive(Shell shell, Uri uri, string archivePath, string out
{
if (uri.LocalPath.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
{
ZipFile.ExtractToDirectory(archivePath, outDirectory, overwriteFiles: true);
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
ExtractArchiveMac(archivePath, outDirectory);
} else {
ZipFile.ExtractToDirectory(archivePath, outDirectory, overwriteFiles: true);
}
}
else if (uri.LocalPath.EndsWith(".AppImage", StringComparison.OrdinalIgnoreCase))
{
Expand Down Expand Up @@ -191,5 +202,13 @@ private void ExtractArchive(Shell shell, Uri uri, string archivePath, string out
throw new Exception("Unknown file format to extract.");
}
}

private void ExtractArchiveMac(string archivePath, string outDirectory) {
var dittoProcess = new Process();
var args = $"-k -x \"{archivePath}\" \"{outDirectory}\"";
dittoProcess.StartInfo = new ProcessStartInfo("/usr/bin/ditto", args);
dittoProcess.Start();
dittoProcess.WaitForExit();
}
}
}
1 change: 1 addition & 0 deletions src/openlauncher/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Threading;
using IntelOrca.OpenLauncher.Core;
using StringResources = openlauncher.Properties.Resources;
Expand Down