Package macOS releases as a universal binary.
* Minimum macOS version is raised to 10.11. * App bundles ship 3 versions of the runtime and engine binaries, and a fat launcher that selects the appropriate runtime/apphost. * Mono is used for macOS 10.11 - 10.14, or if OPENRA_PREFER_MONO environment variable has been set.
This commit is contained in:
42
.github/workflows/packaging.yml
vendored
42
.github/workflows/packaging.yml
vendored
@@ -65,8 +65,8 @@ jobs:
|
||||
file_glob: true
|
||||
file: build/linux/*
|
||||
|
||||
macos-net:
|
||||
name: macOS .NET
|
||||
macos:
|
||||
name: macOS Disk Image
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
@@ -89,43 +89,7 @@ jobs:
|
||||
MACOS_DEVELOPER_PASSWORD: ${{ secrets.MACOS_DEVELOPER_PASSWORD }}
|
||||
run: |
|
||||
mkdir -p build/macos
|
||||
./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos" "standard" "build.dmg"
|
||||
|
||||
- name: Upload Package
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/macos/*
|
||||
|
||||
macos-mono:
|
||||
name: macOS Mono
|
||||
runs-on: macos-11
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package Disk Image
|
||||
env:
|
||||
MACOS_DEVELOPER_IDENTITY: ${{ secrets.MACOS_DEVELOPER_IDENTITY }}
|
||||
MACOS_DEVELOPER_CERTIFICATE_BASE64: ${{ secrets.MACOS_DEVELOPER_CERTIFICATE_BASE64 }}
|
||||
MACOS_DEVELOPER_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_DEVELOPER_CERTIFICATE_PASSWORD }}
|
||||
MACOS_DEVELOPER_USERNAME: ${{ secrets.MACOS_DEVELOPER_USERNAME }}
|
||||
MACOS_DEVELOPER_PASSWORD: ${{ secrets.MACOS_DEVELOPER_PASSWORD }}
|
||||
run: |
|
||||
mkdir -p build/macos
|
||||
./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos" "mono" "build-mono.dmg"
|
||||
./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos"
|
||||
|
||||
- name: Upload Package
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
|
||||
48
packaging/macos/apphost-mono.c
Normal file
48
packaging/macos/apphost-mono.c
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
|
||||
//
|
||||
// A custom apphost is required (instead of just invoking `mono OpenRA.dll ...` directly)
|
||||
// because macOS will only properly associate dock icons and tooltips to windows that are
|
||||
// created by a process in the Contents/MacOS directory (not subdirectories).
|
||||
//
|
||||
// Based on https://github.com/mono/monodevelop/blob/main/main/build/MacOSX/monostub.mm
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
typedef int (* mono_main)(int argc, char **argv);
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
// TODO: This snippet increasing the open file limit was copied from
|
||||
// the monodevelop launcher stub. It may not be needed for OpenRA.
|
||||
struct rlimit limit;
|
||||
if (getrlimit(RLIMIT_NOFILE, &limit) == 0 && limit.rlim_cur < 1024)
|
||||
{
|
||||
limit.rlim_cur = limit.rlim_max < 1024 ? limit.rlim_max : 1024;
|
||||
setrlimit(RLIMIT_NOFILE, &limit);
|
||||
}
|
||||
|
||||
void *libmono = dlopen(argv[1], RTLD_LAZY);
|
||||
if (libmono == NULL)
|
||||
{
|
||||
fprintf(stderr, "Failed to load libmonosgen-2.0.dylib: %s\n", dlerror());
|
||||
return 1;
|
||||
}
|
||||
|
||||
mono_main _mono_main = (mono_main)dlsym(libmono, "mono_main");
|
||||
if (!_mono_main)
|
||||
{
|
||||
fprintf(stderr, "Could not load mono_main(): %s\n", dlerror());
|
||||
return 1;
|
||||
}
|
||||
|
||||
return _mono_main(argc - 1, &argv[1]);
|
||||
}
|
||||
84
packaging/macos/apphost.c
Normal file
84
packaging/macos/apphost.c
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
|
||||
//
|
||||
// A custom apphost is required (instead of just invoking <arch-dir>/OpenRA directly)
|
||||
// because macOS will only properly associate dock icons and tooltips to windows that are
|
||||
// created by a process in the Contents/MacOS directory (not subdirectories).
|
||||
//
|
||||
// .NET 6 does not support universal binaries, and the apphost that is created when
|
||||
// publishing requires the runtime files to exist in the same directory as the launcher.
|
||||
//
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <libgen.h>
|
||||
#include <stdio.h>
|
||||
|
||||
typedef void* hostfxr_handle;
|
||||
struct hostfxr_initialize_parameters
|
||||
{
|
||||
size_t size;
|
||||
char *host_path;
|
||||
char *dotnet_root;
|
||||
};
|
||||
|
||||
typedef int32_t(*hostfxr_initialize_for_dotnet_command_line_fn)(
|
||||
int argc,
|
||||
char **argv,
|
||||
struct hostfxr_initialize_parameters *parameters,
|
||||
hostfxr_handle *host_context_handle);
|
||||
|
||||
typedef int32_t(*hostfxr_run_app_fn)(const hostfxr_handle host_context_handle);
|
||||
typedef int32_t(*hostfxr_close_fn)(const hostfxr_handle host_context_handle);
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
void *lib = dlopen(argv[1], RTLD_LAZY);
|
||||
if (lib == NULL)
|
||||
{
|
||||
fprintf(stderr, "Failed to load %s: %s\n", argv[1], dlerror());
|
||||
return 1;
|
||||
}
|
||||
|
||||
hostfxr_initialize_for_dotnet_command_line_fn hostfxr_initialize_for_dotnet_command_line = (hostfxr_initialize_for_dotnet_command_line_fn)dlsym(lib, "hostfxr_initialize_for_dotnet_command_line");
|
||||
if (!hostfxr_initialize_for_dotnet_command_line)
|
||||
{
|
||||
fprintf(stderr, "Could not load hostfxr_initialize_for_dotnet_command_line(): %s\n", dlerror());
|
||||
return 1;
|
||||
}
|
||||
|
||||
hostfxr_run_app_fn hostfxr_run_app = (hostfxr_run_app_fn)dlsym(lib, "hostfxr_run_app");
|
||||
if (!hostfxr_run_app)
|
||||
{
|
||||
fprintf(stderr, "Could not load hostfxr_run_app(): %s\n", dlerror());
|
||||
return 1;
|
||||
}
|
||||
|
||||
hostfxr_close_fn hostfxr_close = (hostfxr_close_fn)dlsym(lib, "hostfxr_close");
|
||||
if (!hostfxr_close)
|
||||
{
|
||||
fprintf(stderr, "Could not load hostfxr_close(): %s\n", dlerror());
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct hostfxr_initialize_parameters params;
|
||||
params.size = sizeof(params);
|
||||
params.host_path = argv[0];
|
||||
params.dotnet_root = dirname(argv[1]);
|
||||
|
||||
hostfxr_handle host_context_handle;
|
||||
hostfxr_initialize_for_dotnet_command_line(
|
||||
argc - 2,
|
||||
&argv[2],
|
||||
¶ms,
|
||||
&host_context_handle);
|
||||
|
||||
hostfxr_run_app(host_context_handle);
|
||||
|
||||
return hostfxr_close(host_context_handle);
|
||||
}
|
||||
@@ -21,8 +21,10 @@ if [[ "${OSTYPE}" != "darwin"* ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ $# -ne "4" ]; then
|
||||
echo "Usage: $(basename "$0") tag outputdir platform dmg"
|
||||
command -v clang >/dev/null 2>&1 || { echo >&2 "macOS packaging requires clang."; exit 1; }
|
||||
|
||||
if [ $# -ne "2" ]; then
|
||||
echo "Usage: $(basename "$0") tag outputdir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -45,6 +47,7 @@ fi
|
||||
|
||||
TAG="${1}"
|
||||
OUTPUTDIR="${2}"
|
||||
|
||||
SRCDIR="$(pwd)/../.."
|
||||
BUILTDIR="$(pwd)/build"
|
||||
ARTWORK_DIR="$(pwd)/../artwork/"
|
||||
@@ -55,15 +58,13 @@ modify_plist() {
|
||||
|
||||
# Copies the game files and sets metadata
|
||||
build_app() {
|
||||
PLATFORM="${1}"
|
||||
TEMPLATE_DIR="${2}"
|
||||
LAUNCHER_DIR="${3}"
|
||||
MOD_ID="${4}"
|
||||
MOD_NAME="${5}"
|
||||
DISCORD_APPID="${6}"
|
||||
TEMPLATE_DIR="${1}"
|
||||
LAUNCHER_DIR="${2}"
|
||||
MOD_ID="${3}"
|
||||
MOD_NAME="${4}"
|
||||
DISCORD_APPID="${5}"
|
||||
|
||||
LAUNCHER_CONTENTS_DIR="${LAUNCHER_DIR}/Contents"
|
||||
LAUNCHER_ASSEMBLY_DIR="${LAUNCHER_CONTENTS_DIR}/MacOS"
|
||||
LAUNCHER_RESOURCES_DIR="${LAUNCHER_CONTENTS_DIR}/Resources"
|
||||
|
||||
cp -r "${TEMPLATE_DIR}" "${LAUNCHER_DIR}"
|
||||
@@ -74,12 +75,10 @@ build_app() {
|
||||
fi
|
||||
|
||||
# Install engine and mod files
|
||||
RUNTIME="net6"
|
||||
if [ "${PLATFORM}" = "mono" ]; then
|
||||
RUNTIME="mono"
|
||||
fi
|
||||
install_assemblies "${SRCDIR}" "${LAUNCHER_CONTENTS_DIR}/MacOS/x86_64" "osx-x64" "net6" "True" "True" "${IS_D2K}"
|
||||
install_assemblies "${SRCDIR}" "${LAUNCHER_CONTENTS_DIR}/MacOS/arm64" "osx-arm64" "net6" "True" "True" "${IS_D2K}"
|
||||
install_assemblies "${SRCDIR}" "${LAUNCHER_CONTENTS_DIR}/MacOS/mono" "osx-x64" "mono" "True" "True" "${IS_D2K}"
|
||||
|
||||
install_assemblies "${SRCDIR}" "${LAUNCHER_ASSEMBLY_DIR}" "osx-x64" "${RUNTIME}" "True" "True" "${IS_D2K}"
|
||||
install_data "${SRCDIR}" "${LAUNCHER_RESOURCES_DIR}" "${MOD_ID}"
|
||||
set_engine_version "${TAG}" "${LAUNCHER_RESOURCES_DIR}"
|
||||
set_mod_version "${TAG}" "${LAUNCHER_RESOURCES_DIR}/mods/${MOD_ID}/mod.yaml" "${LAUNCHER_RESOURCES_DIR}/mods/modcontent/mod.yaml"
|
||||
@@ -110,82 +109,101 @@ build_app() {
|
||||
fi
|
||||
}
|
||||
|
||||
build_platform() {
|
||||
PLATFORM="${1}"
|
||||
DMG_PATH="${2}"
|
||||
echo "Building launchers (${PLATFORM})"
|
||||
echo "Building launchers"
|
||||
|
||||
# Prepare generic template for the mods to duplicate and customize
|
||||
TEMPLATE_DIR="${BUILTDIR}/template.app"
|
||||
mkdir -p "${TEMPLATE_DIR}/Contents/Resources"
|
||||
mkdir -p "${TEMPLATE_DIR}/Contents/MacOS"
|
||||
echo "APPL????" > "${TEMPLATE_DIR}/Contents/PkgInfo"
|
||||
cp Info.plist.in "${TEMPLATE_DIR}/Contents/Info.plist"
|
||||
modify_plist "{DEV_VERSION}" "${TAG}" "${TEMPLATE_DIR}/Contents/Info.plist"
|
||||
modify_plist "{FAQ_URL}" "http://wiki.openra.net/FAQ" "${TEMPLATE_DIR}/Contents/Info.plist"
|
||||
# Prepare generic template for the mods to duplicate and customize
|
||||
TEMPLATE_DIR="${BUILTDIR}/template.app"
|
||||
mkdir -p "${TEMPLATE_DIR}/Contents/Resources"
|
||||
mkdir -p "${TEMPLATE_DIR}/Contents/MacOS/mono"
|
||||
mkdir -p "${TEMPLATE_DIR}/Contents/MacOS/x86_64"
|
||||
mkdir -p "${TEMPLATE_DIR}/Contents/MacOS/arm64"
|
||||
|
||||
if [ "${PLATFORM}" = "mono" ]; then
|
||||
modify_plist "{MINIMUM_SYSTEM_VERSION}" "10.9" "${TEMPLATE_DIR}/Contents/Info.plist"
|
||||
clang -m64 launcher-mono.m -o "${TEMPLATE_DIR}/Contents/MacOS/Launcher" -framework AppKit -mmacosx-version-min=10.9
|
||||
else
|
||||
modify_plist "{MINIMUM_SYSTEM_VERSION}" "10.14" "${TEMPLATE_DIR}/Contents/Info.plist"
|
||||
clang -m64 launcher.m -o "${TEMPLATE_DIR}/Contents/MacOS/Launcher" -framework AppKit -mmacosx-version-min=10.14
|
||||
fi
|
||||
echo "APPL????" > "${TEMPLATE_DIR}/Contents/PkgInfo"
|
||||
cp Info.plist.in "${TEMPLATE_DIR}/Contents/Info.plist"
|
||||
modify_plist "{DEV_VERSION}" "${TAG}" "${TEMPLATE_DIR}/Contents/Info.plist"
|
||||
modify_plist "{FAQ_URL}" "http://wiki.openra.net/FAQ" "${TEMPLATE_DIR}/Contents/Info.plist"
|
||||
modify_plist "{MINIMUM_SYSTEM_VERSION}" "10.11" "${TEMPLATE_DIR}/Contents/Info.plist"
|
||||
|
||||
build_app "${PLATFORM}" "${TEMPLATE_DIR}" "${BUILTDIR}/OpenRA - Red Alert.app" "ra" "Red Alert" "699222659766026240"
|
||||
build_app "${PLATFORM}" "${TEMPLATE_DIR}" "${BUILTDIR}/OpenRA - Tiberian Dawn.app" "cnc" "Tiberian Dawn" "699223250181292033"
|
||||
build_app "${PLATFORM}" "${TEMPLATE_DIR}" "${BUILTDIR}/OpenRA - Dune 2000.app" "d2k" "Dune 2000" "712711732770111550"
|
||||
# Compile universal (x86_64 + arm64) Launcher and arch-specific apphosts
|
||||
clang apphost.c -o "${TEMPLATE_DIR}/Contents/MacOS/apphost-x86_64" -framework AppKit -target x86_64-apple-macos10.15
|
||||
clang apphost.c -o "${TEMPLATE_DIR}/Contents/MacOS/apphost-arm64" -framework AppKit -target arm64-apple-macos10.15
|
||||
clang apphost-mono.c -o "${TEMPLATE_DIR}/Contents/MacOS/apphost-mono" -framework AppKit -target x86_64-apple-macos10.11
|
||||
clang checkmono.c -o "${TEMPLATE_DIR}/Contents/MacOS/checkmono" -framework AppKit -target x86_64-apple-macos10.11
|
||||
clang launcher.m -o "${TEMPLATE_DIR}/Contents/MacOS/Launcher-x86_64" -framework AppKit -target x86_64-apple-macos10.11
|
||||
clang launcher.m -o "${TEMPLATE_DIR}/Contents/MacOS/Launcher-arm64" -framework AppKit -target arm64-apple-macos10.15
|
||||
lipo -create -output "${TEMPLATE_DIR}/Contents/MacOS/Launcher" "${TEMPLATE_DIR}/Contents/MacOS/Launcher-x86_64" "${TEMPLATE_DIR}/Contents/MacOS/Launcher-arm64"
|
||||
rm "${TEMPLATE_DIR}/Contents/MacOS/Launcher-x86_64" "${TEMPLATE_DIR}/Contents/MacOS/Launcher-arm64"
|
||||
|
||||
rm -rf "${TEMPLATE_DIR}"
|
||||
build_app "${TEMPLATE_DIR}" "${BUILTDIR}/OpenRA - Red Alert.app" "ra" "Red Alert" "699222659766026240"
|
||||
build_app "${TEMPLATE_DIR}" "${BUILTDIR}/OpenRA - Tiberian Dawn.app" "cnc" "Tiberian Dawn" "699223250181292033"
|
||||
build_app "${TEMPLATE_DIR}" "${BUILTDIR}/OpenRA - Dune 2000.app" "d2k" "Dune 2000" "712711732770111550"
|
||||
|
||||
echo "Packaging disk image"
|
||||
hdiutil create "${DMG_PATH}" -format UDRW -volname "OpenRA" -fs HFS+ -srcfolder build
|
||||
DMG_DEVICE=$(hdiutil attach -readwrite -noverify -noautoopen "${DMG_PATH}" | egrep '^/dev/' | sed 1q | awk '{print $1}')
|
||||
sleep 2
|
||||
rm -rf "${TEMPLATE_DIR}"
|
||||
|
||||
# Background image is created from source svg in artsrc repository
|
||||
mkdir "/Volumes/OpenRA/.background/"
|
||||
tiffutil -cathidpicheck "${ARTWORK_DIR}/macos-background.png" "${ARTWORK_DIR}/macos-background-2x.png" -out "/Volumes/OpenRA/.background/background.tiff"
|
||||
echo "Packaging disk image"
|
||||
hdiutil create "build.dmg" -format UDRW -volname "OpenRA" -fs HFS+ -srcfolder build
|
||||
DMG_DEVICE=$(hdiutil attach -readwrite -noverify -noautoopen "build.dmg" | egrep '^/dev/' | sed 1q | awk '{print $1}')
|
||||
sleep 2
|
||||
|
||||
cp "${BUILTDIR}/OpenRA - Red Alert.app/Contents/Resources/ra.icns" "/Volumes/OpenRA/.VolumeIcon.icns"
|
||||
# Background image is created from source svg in artsrc repository
|
||||
mkdir "/Volumes/OpenRA/.background/"
|
||||
tiffutil -cathidpicheck "${ARTWORK_DIR}/macos-background.png" "${ARTWORK_DIR}/macos-background-2x.png" -out "/Volumes/OpenRA/.background/background.tiff"
|
||||
|
||||
echo '
|
||||
tell application "Finder"
|
||||
tell disk "'OpenRA'"
|
||||
open
|
||||
set current view of container window to icon view
|
||||
set toolbar visible of container window to false
|
||||
set statusbar visible of container window to false
|
||||
set the bounds of container window to {400, 100, 1040, 580}
|
||||
set theViewOptions to the icon view options of container window
|
||||
set arrangement of theViewOptions to not arranged
|
||||
set icon size of theViewOptions to 72
|
||||
set background picture of theViewOptions to file ".background:background.tiff"
|
||||
make new alias file at container window to POSIX file "/Applications" with properties {name:"Applications"}
|
||||
set position of item "'OpenRA - Tiberian Dawn.app'" of container window to {160, 106}
|
||||
set position of item "'OpenRA - Red Alert.app'" of container window to {320, 106}
|
||||
set position of item "'OpenRA - Dune 2000.app'" of container window to {480, 106}
|
||||
set position of item "Applications" of container window to {320, 298}
|
||||
set position of item ".background" of container window to {160, 298}
|
||||
set position of item ".fseventsd" of container window to {160, 298}
|
||||
set position of item ".VolumeIcon.icns" of container window to {160, 298}
|
||||
update without registering applications
|
||||
delay 5
|
||||
close
|
||||
end tell
|
||||
end tell
|
||||
' | osascript
|
||||
cp "${BUILTDIR}/OpenRA - Red Alert.app/Contents/Resources/ra.icns" "/Volumes/OpenRA/.VolumeIcon.icns"
|
||||
|
||||
# HACK: Copy the volume icon again - something in the previous step seems to delete it...?
|
||||
cp "${BUILTDIR}/OpenRA - Red Alert.app/Contents/Resources/ra.icns" "/Volumes/OpenRA/.VolumeIcon.icns"
|
||||
SetFile -c icnC "/Volumes/OpenRA/.VolumeIcon.icns"
|
||||
SetFile -a C "/Volumes/OpenRA"
|
||||
echo '
|
||||
tell application "Finder"
|
||||
tell disk "'OpenRA'"
|
||||
open
|
||||
set current view of container window to icon view
|
||||
set toolbar visible of container window to false
|
||||
set statusbar visible of container window to false
|
||||
set the bounds of container window to {400, 100, 1040, 580}
|
||||
set theViewOptions to the icon view options of container window
|
||||
set arrangement of theViewOptions to not arranged
|
||||
set icon size of theViewOptions to 72
|
||||
set background picture of theViewOptions to file ".background:background.tiff"
|
||||
make new alias file at container window to POSIX file "/Applications" with properties {name:"Applications"}
|
||||
set position of item "'OpenRA - Tiberian Dawn.app'" of container window to {160, 106}
|
||||
set position of item "'OpenRA - Red Alert.app'" of container window to {320, 106}
|
||||
set position of item "'OpenRA - Dune 2000.app'" of container window to {480, 106}
|
||||
set position of item "Applications" of container window to {320, 298}
|
||||
set position of item ".background" of container window to {160, 298}
|
||||
set position of item ".fseventsd" of container window to {160, 298}
|
||||
set position of item ".VolumeIcon.icns" of container window to {160, 298}
|
||||
update without registering applications
|
||||
delay 5
|
||||
close
|
||||
end tell
|
||||
end tell
|
||||
' | osascript
|
||||
|
||||
# Replace duplicate .NET runtime files with hard links to improve compression
|
||||
if [ "${PLATFORM}" != "mono" ]; then
|
||||
for MOD in "Red Alert" "Tiberian Dawn"; do
|
||||
for f in "/Volumes/OpenRA/OpenRA - ${MOD}.app/Contents/MacOS"/*; do
|
||||
g="/Volumes/OpenRA/OpenRA - Dune 2000.app/Contents/MacOS/"$(basename "${f}")
|
||||
# HACK: Copy the volume icon again - something in the previous step seems to delete it...?
|
||||
cp "${BUILTDIR}/OpenRA - Red Alert.app/Contents/Resources/ra.icns" "/Volumes/OpenRA/.VolumeIcon.icns"
|
||||
SetFile -c icnC "/Volumes/OpenRA/.VolumeIcon.icns"
|
||||
SetFile -a C "/Volumes/OpenRA"
|
||||
|
||||
# Replace duplicate .NET runtime files with hard links to improve compression
|
||||
for MOD in "Red Alert" "Tiberian Dawn"; do
|
||||
for p in "x86_64" "arm64" "mono"; do
|
||||
for f in "/Volumes/OpenRA/OpenRA - ${MOD}.app/Contents/MacOS/${p}"/*; do
|
||||
g="/Volumes/OpenRA/OpenRA - Dune 2000.app/Contents/MacOS/${p}/"$(basename "${f}")
|
||||
hashf=$(shasum "${f}" | awk '{ print $1 }') || :
|
||||
hashg=$(shasum "${g}" | awk '{ print $1 }') || :
|
||||
if [ -n "${hashf}" ] && [ "${hashf}" = "${hashg}" ]; then
|
||||
echo "Deduplicating ${f}"
|
||||
rm "${f}"
|
||||
ln "${g}" "${f}"
|
||||
fi
|
||||
done
|
||||
done
|
||||
done
|
||||
|
||||
for MOD in "Red Alert" "Tiberian Dawn" "Dune 2000"; do
|
||||
for p in "arm64" "mono"; do
|
||||
for f in "/Volumes/OpenRA/OpenRA - ${MOD}.app/Contents/MacOS/x86_64"/*; do
|
||||
g="/Volumes/OpenRA/OpenRA - ${MOD}.app/Contents/MacOS/${p}/"$(basename "${f}")
|
||||
if [ -e "${g}" ]; then
|
||||
hashf=$(shasum "${f}" | awk '{ print $1 }') || :
|
||||
hashg=$(shasum "${g}" | awk '{ print $1 }') || :
|
||||
if [ -n "${hashf}" ] && [ "${hashf}" = "${hashg}" ]; then
|
||||
@@ -193,35 +211,37 @@ build_platform() {
|
||||
rm "${f}"
|
||||
ln "${g}" "${f}"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
chmod -Rf go-w /Volumes/OpenRA
|
||||
sync
|
||||
sync
|
||||
chmod -Rf go-w /Volumes/OpenRA
|
||||
sync
|
||||
sync
|
||||
|
||||
hdiutil detach "${DMG_DEVICE}"
|
||||
rm -rf "${BUILTDIR}"
|
||||
}
|
||||
hdiutil detach "${DMG_DEVICE}"
|
||||
rm -rf "${BUILTDIR}"
|
||||
|
||||
notarize_package() {
|
||||
DMG_PATH="${1}"
|
||||
NOTARIZE_DMG_PATH="${DMG_PATH%.*}"-notarization.dmg
|
||||
echo "Submitting ${DMG_PATH} for notarization"
|
||||
if [ -n "${MACOS_DEVELOPER_CERTIFICATE_BASE64}" ] && [ -n "${MACOS_DEVELOPER_CERTIFICATE_PASSWORD}" ] && [ -n "${MACOS_DEVELOPER_IDENTITY}" ]; then
|
||||
security delete-keychain build.keychain
|
||||
fi
|
||||
|
||||
if [ -n "${MACOS_DEVELOPER_USERNAME}" ] && [ -n "${MACOS_DEVELOPER_PASSWORD}" ] && [ -n "${MACOS_DEVELOPER_IDENTITY}" ]; then
|
||||
echo "Submitting build for notarization"
|
||||
|
||||
# Reset xcode search path to fix xcrun not finding altool
|
||||
sudo xcode-select -r
|
||||
|
||||
# Create a temporary read-only dmg for submission (notarization service rejects read/write images)
|
||||
hdiutil convert "${DMG_PATH}" -format ULFO -ov -o "${NOTARIZE_DMG_PATH}"
|
||||
hdiutil convert "build.dmg" -format ULFO -ov -o "build-notarization.dmg"
|
||||
|
||||
xcrun notarytool submit "${NOTARIZE_DMG_PATH}" --wait --apple-id "${MACOS_DEVELOPER_USERNAME}" --password "${MACOS_DEVELOPER_PASSWORD}" --team-id "${MACOS_DEVELOPER_IDENTITY}"
|
||||
xcrun notarytool submit "build-notarization.dmg" --wait --apple-id "${MACOS_DEVELOPER_USERNAME}" --password "${MACOS_DEVELOPER_PASSWORD}" --team-id "${MACOS_DEVELOPER_IDENTITY}"
|
||||
|
||||
rm "${NOTARIZE_DMG_PATH}"
|
||||
rm "build-notarization.dmg"
|
||||
|
||||
echo "${DMG_PATH}: Stapling tickets"
|
||||
DMG_DEVICE=$(hdiutil attach -readwrite -noverify -noautoopen "${DMG_PATH}" | egrep '^/dev/' | sed 1q | awk '{print $1}')
|
||||
echo "Stapling tickets"
|
||||
DMG_DEVICE=$(hdiutil attach -readwrite -noverify -noautoopen "build.dmg" | egrep '^/dev/' | sed 1q | awk '{print $1}')
|
||||
sleep 2
|
||||
|
||||
xcrun stapler staple "/Volumes/OpenRA/OpenRA - Red Alert.app"
|
||||
@@ -232,35 +252,7 @@ notarize_package() {
|
||||
sync
|
||||
|
||||
hdiutil detach "${DMG_DEVICE}"
|
||||
break
|
||||
}
|
||||
|
||||
finalize_package() {
|
||||
PLATFORM="${1}"
|
||||
INPUT_PATH="${2}"
|
||||
OUTPUT_PATH="${3}"
|
||||
|
||||
if [ "${PLATFORM}" = "mono" ]; then
|
||||
hdiutil convert "${INPUT_PATH}" -format UDZO -imagekey zlib-level=9 -ov -o "${OUTPUT_PATH}-mono.dmg"
|
||||
else
|
||||
# ULFO offers better compression and faster decompression speeds, but is only supported by 10.11+
|
||||
hdiutil convert "${INPUT_PATH}" -format ULFO -ov -o "${OUTPUT_PATH}.dmg"
|
||||
fi
|
||||
|
||||
rm "${INPUT_PATH}"
|
||||
}
|
||||
|
||||
PLATFORM="$3"
|
||||
DISK_IMAGE="$4"
|
||||
|
||||
build_platform "${PLATFORM}" "${DISK_IMAGE}"
|
||||
|
||||
if [ -n "${MACOS_DEVELOPER_CERTIFICATE_BASE64}" ] && [ -n "${MACOS_DEVELOPER_CERTIFICATE_PASSWORD}" ] && [ -n "${MACOS_DEVELOPER_IDENTITY}" ]; then
|
||||
security delete-keychain build.keychain
|
||||
fi
|
||||
|
||||
if [ -n "${MACOS_DEVELOPER_USERNAME}" ] && [ -n "${MACOS_DEVELOPER_PASSWORD}" ] && [ -n "${MACOS_DEVELOPER_IDENTITY}" ]; then
|
||||
notarize_package "${DISK_IMAGE}"
|
||||
fi
|
||||
|
||||
finalize_package "${PLATFORM}" "${DISK_IMAGE}" "${OUTPUTDIR}/OpenRA-${TAG}"
|
||||
hdiutil convert "build.dmg" -format ULFO -ov -o "${OUTPUTDIR}/OpenRA-${TAG}.dmg"
|
||||
rm "build.dmg"
|
||||
|
||||
73
packaging/macos/checkmono.c
Normal file
73
packaging/macos/checkmono.c
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
|
||||
//
|
||||
// NOTE: Mono.framework only ships intel dylibs, so cannot be loaded by the arm64 slice of the Launcher utility.
|
||||
// Splitting checkmono into its own intel-only utility allows it to be called through rosetta if the user
|
||||
// wants to force the game to run under mono-through-rosetta.
|
||||
//
|
||||
// Based on https://github.com/mono/monodevelop/blob/main/main/build/MacOSX/monostub.mm and https://github.com/mono/monodevelop/blob/main/main/build/MacOSX/monostub-utils.h
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#define SYSTEM_MONO_PATH "/Library/Frameworks/Mono.framework/Versions/Current/"
|
||||
#define SYSTEM_MONO_MIN_VERSION "6.4"
|
||||
|
||||
typedef char *(* mono_get_runtime_build_info)(void);
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
void *libmono = dlopen(SYSTEM_MONO_PATH "lib/libmonosgen-2.0.dylib", RTLD_LAZY);
|
||||
if (libmono == NULL)
|
||||
{
|
||||
fprintf (stderr, "Failed to load libmonosgen-2.0.dylib: %s\n", dlerror());
|
||||
return 1;
|
||||
}
|
||||
|
||||
mono_get_runtime_build_info _mono_get_runtime_build_info = (mono_get_runtime_build_info)dlsym(libmono, "mono_get_runtime_build_info");
|
||||
if (!_mono_get_runtime_build_info)
|
||||
{
|
||||
fprintf(stderr, "Could not load mono_get_runtime_build_info(): %s\n", dlerror());
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *version = _mono_get_runtime_build_info();
|
||||
char *req_end, *end;
|
||||
long req_val, val;
|
||||
char *req_version = SYSTEM_MONO_MIN_VERSION;
|
||||
|
||||
while (*req_version && *version)
|
||||
{
|
||||
req_val = strtol(req_version, &req_end, 10);
|
||||
if (req_version == req_end || (*req_end && *req_end != '.'))
|
||||
{
|
||||
fprintf(stderr, "Bad version requirement string '%s'\n", req_end);
|
||||
return 1;
|
||||
}
|
||||
|
||||
val = strtol(version, &end, 10);
|
||||
if (version == end || val < req_val)
|
||||
return 1;
|
||||
|
||||
if (val > req_val)
|
||||
return 0;
|
||||
|
||||
if (*req_end == '.' && *end != '.')
|
||||
return 1;
|
||||
|
||||
req_version = req_end;
|
||||
if (*req_version)
|
||||
req_version++;
|
||||
|
||||
version = end + 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,386 +0,0 @@
|
||||
/*
|
||||
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#define SYSTEM_MONO_PATH @"/Library/Frameworks/Mono.framework/Versions/Current/"
|
||||
#define SYSTEM_MONO_MIN_VERSION @"6.4"
|
||||
|
||||
typedef int (* mono_main)(int argc, char **argv);
|
||||
typedef void (* mono_free)(void *ptr);
|
||||
typedef char *(* mono_get_runtime_build_info)(void);
|
||||
|
||||
@interface OpenRALauncher : NSObject <NSApplicationDelegate>
|
||||
- (void)launchGameWithArgs: (NSArray *)gameArgs;
|
||||
@end
|
||||
|
||||
@implementation OpenRALauncher
|
||||
|
||||
BOOL launched = NO;
|
||||
NSTask *gameTask;
|
||||
|
||||
static int check_mono_version(const char *version, const char *req_version)
|
||||
{
|
||||
char *req_end, *end;
|
||||
long req_val, val;
|
||||
|
||||
while (*req_version)
|
||||
{
|
||||
req_val = strtol(req_version, &req_end, 10);
|
||||
if (req_version == req_end || (*req_end && *req_end != '.'))
|
||||
{
|
||||
fprintf(stderr, "Bad version requirement string '%s'\n", req_end);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
req_version = req_end;
|
||||
if (*req_version)
|
||||
req_version++;
|
||||
|
||||
val = strtol (version, &end, 10);
|
||||
if (version == end || val < req_val)
|
||||
return FALSE;
|
||||
|
||||
if (val > req_val)
|
||||
return TRUE;
|
||||
|
||||
if (*req_version == '.' && *end != '.')
|
||||
return FALSE;
|
||||
|
||||
version = end + 1;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
- (int)hasValidMono
|
||||
{
|
||||
void *libmono = dlopen([[SYSTEM_MONO_PATH stringByAppendingPathComponent: @"/lib/libmonosgen-2.0.dylib"] UTF8String], RTLD_LAZY);
|
||||
|
||||
if (libmono == NULL)
|
||||
{
|
||||
fprintf (stderr, "Failed to load libmonosgen-2.0.dylib: %s\n", dlerror());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
mono_get_runtime_build_info _mono_get_runtime_build_info = (mono_get_runtime_build_info)dlsym(libmono, "mono_get_runtime_build_info");
|
||||
if (!_mono_get_runtime_build_info)
|
||||
{
|
||||
fprintf(stderr, "Could not load mono_get_runtime_build_info(): %s\n", dlerror());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
char *mono_version = _mono_get_runtime_build_info();
|
||||
return check_mono_version(mono_version, [SYSTEM_MONO_MIN_VERSION UTF8String]);
|
||||
}
|
||||
|
||||
- (NSString *)modName
|
||||
{
|
||||
NSDictionary *plist = [[NSBundle mainBundle] infoDictionary];
|
||||
if (plist)
|
||||
{
|
||||
NSString *title = [plist objectForKey:@"CFBundleDisplayName"];
|
||||
if (title && [title length] > 0)
|
||||
return title;
|
||||
}
|
||||
|
||||
return @"OpenRA";
|
||||
}
|
||||
|
||||
- (void)exitWithMonoPrompt
|
||||
{
|
||||
[NSApp setActivationPolicy: NSApplicationActivationPolicyRegular];
|
||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||
|
||||
NSString *modName = [self modName];
|
||||
NSString *title = [NSString stringWithFormat: @"Cannot launch %@", modName];
|
||||
NSString *message = [NSString stringWithFormat: @"%@ requires Mono %@ or later. Please install Mono and try again.", modName, SYSTEM_MONO_MIN_VERSION];
|
||||
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:title];
|
||||
[alert setInformativeText:message];
|
||||
[alert addButtonWithTitle:@"Download Mono"];
|
||||
[alert addButtonWithTitle:@"Quit"];
|
||||
NSInteger answer = [alert runModal];
|
||||
[alert release];
|
||||
|
||||
if (answer == NSAlertFirstButtonReturn)
|
||||
[[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString:@"https://www.mono-project.com/download/"]];
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
- (void)exitWithCrashPrompt
|
||||
{
|
||||
[NSApp setActivationPolicy: NSApplicationActivationPolicyRegular];
|
||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||
|
||||
NSString *modName = [self modName];
|
||||
NSString *message = [NSString stringWithFormat: @"%@ has encountered a fatal error and must close.\nPlease refer to the crash logs and FAQ for more information.", modName];
|
||||
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:@"Fatal Error"];
|
||||
[alert setInformativeText:message];
|
||||
[alert addButtonWithTitle:@"View Logs"];
|
||||
[alert addButtonWithTitle:@"View FAQ"];
|
||||
[alert addButtonWithTitle:@"Quit"];
|
||||
|
||||
NSInteger answer = [alert runModal];
|
||||
[alert release];
|
||||
|
||||
if (answer == NSAlertFirstButtonReturn)
|
||||
{
|
||||
NSString *logDir = [@"~/Library/Application Support/OpenRA/Logs/" stringByExpandingTildeInPath];
|
||||
[[NSWorkspace sharedWorkspace] openFile: logDir withApplication:@"Finder"];
|
||||
}
|
||||
else if (answer == NSAlertSecondButtonReturn)
|
||||
{
|
||||
NSDictionary *plist = [[NSBundle mainBundle] infoDictionary];
|
||||
if (plist)
|
||||
{
|
||||
NSString *faqUrl = [plist objectForKey:@"FaqUrl"];
|
||||
if (faqUrl && [faqUrl length] > 0)
|
||||
[[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString:faqUrl]];
|
||||
}
|
||||
}
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Application was launched via a URL handler
|
||||
- (void)getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
|
||||
{
|
||||
NSMutableArray *gameArgs = [[[NSProcessInfo processInfo] arguments] mutableCopy];
|
||||
NSDictionary *plist = [[NSBundle mainBundle] infoDictionary];
|
||||
NSString *url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
|
||||
|
||||
if (plist)
|
||||
{
|
||||
NSString *joinServerUrl = [plist objectForKey:@"JoinServerUrlScheme"];
|
||||
if (joinServerUrl && [joinServerUrl length] > 0)
|
||||
{
|
||||
NSString *prefix = [joinServerUrl stringByAppendingString: @"://"];
|
||||
if ([url hasPrefix: prefix])
|
||||
{
|
||||
NSString *trimmed = [url substringFromIndex:[prefix length]];
|
||||
NSArray *parts = [trimmed componentsSeparatedByString:@":"];
|
||||
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
|
||||
|
||||
if ([parts count] == 2 && [formatter numberFromString: [parts objectAtIndex:1]] != nil)
|
||||
[gameArgs addObject: [NSString stringWithFormat: @"Launch.Connect=%@", trimmed]];
|
||||
|
||||
[formatter release];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[self launchGameWithArgs: gameArgs];
|
||||
[gameArgs release];
|
||||
}
|
||||
|
||||
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
|
||||
{
|
||||
// Register for url events
|
||||
NSDictionary *plist = [[NSBundle mainBundle] infoDictionary];
|
||||
if (plist)
|
||||
{
|
||||
NSString *joinServerUrl = [plist objectForKey:@"JoinServerUrlScheme"];
|
||||
NSString *bundleIdentifier = [plist objectForKey:@"CFBundleIdentifier"];
|
||||
if (joinServerUrl && [joinServerUrl length] > 0 && bundleIdentifier)
|
||||
{
|
||||
LSSetDefaultHandlerForURLScheme((CFStringRef)joinServerUrl, (CFStringRef)bundleIdentifier);
|
||||
[[NSAppleEventManager sharedAppleEventManager] setEventHandler:self andSelector:@selector(getUrl:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
||||
{
|
||||
[self launchGameWithArgs: [[NSProcessInfo processInfo] arguments]];
|
||||
}
|
||||
|
||||
- (BOOL)applicationShouldTerminateAfterLastWindowClosed: (NSApplication *)theApplication
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)launchGameWithArgs: (NSArray *)gameArgs
|
||||
{
|
||||
if (launched)
|
||||
{
|
||||
NSLog(@"launchgame is already running... ignoring request.");
|
||||
return;
|
||||
}
|
||||
|
||||
launched = YES;
|
||||
|
||||
if (![self hasValidMono])
|
||||
[self exitWithMonoPrompt];
|
||||
|
||||
// Default values - can be overriden by setting certain keys Info.plist
|
||||
NSString *gameName = @"OpenRA.dll";
|
||||
NSString *modId = nil;
|
||||
|
||||
NSDictionary *plist = [[NSBundle mainBundle] infoDictionary];
|
||||
if (plist)
|
||||
{
|
||||
NSString *exeValue = [plist objectForKey:@"MonoGameExe"];
|
||||
if (exeValue && [exeValue length] > 0)
|
||||
gameName = exeValue;
|
||||
|
||||
NSString *modIdValue = [plist objectForKey:@"ModId"];
|
||||
if (modIdValue && [modIdValue length] > 0)
|
||||
modId = modIdValue;
|
||||
}
|
||||
|
||||
NSString *exePath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent: @"Contents/MacOS/"];
|
||||
NSString *gamePath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent: @"Contents/Resources/"];
|
||||
NSString *appPath = [exePath stringByAppendingPathComponent: @"Launcher"];
|
||||
NSString *engineLaunchPath = [self resolveTranslocatedPath: appPath];
|
||||
|
||||
NSMutableArray *launchArgs = [NSMutableArray arrayWithCapacity: [gameArgs count] + 2];
|
||||
[launchArgs addObject: @"--debug"];
|
||||
[launchArgs addObject: [exePath stringByAppendingPathComponent: gameName]];
|
||||
[launchArgs addObject: [NSString stringWithFormat:@"Engine.LaunchPath=\"%@\"", engineLaunchPath]];
|
||||
[launchArgs addObject: [NSString stringWithFormat:@"Engine.EngineDir=../Resources"]];
|
||||
|
||||
if (modId)
|
||||
[launchArgs addObject: [NSString stringWithFormat:@"Game.Mod=%@", modId]];
|
||||
|
||||
[launchArgs addObjectsFromArray: gameArgs];
|
||||
|
||||
NSLog(@"Running launchgame with arguments:");
|
||||
for (size_t i = 0; i < [launchArgs count]; i++)
|
||||
NSLog(@"%@", [launchArgs objectAtIndex: i]);
|
||||
|
||||
gameTask = [[NSTask alloc] init];
|
||||
[gameTask setCurrentDirectoryPath: gamePath];
|
||||
[gameTask setLaunchPath: appPath];
|
||||
[gameTask setArguments: launchArgs];
|
||||
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver: self
|
||||
selector: @selector(taskExited:)
|
||||
name: NSTaskDidTerminateNotification
|
||||
object: gameTask
|
||||
];
|
||||
|
||||
[gameTask launch];
|
||||
}
|
||||
|
||||
- (NSString *)resolveTranslocatedPath: (NSString *)path
|
||||
{
|
||||
// macOS 10.12 introduced the "App Translocation" feature, which runs quarantined applications
|
||||
// from a transient read-only disk image. The read-only image isn't a problem, but the transient
|
||||
// path breaks the mod registration/switching feature.
|
||||
// This resolves the original path which can then be written into the mod metadata for future
|
||||
// launches (which will then be re-translocated)
|
||||
|
||||
// Running on macOS < 10.12
|
||||
if (floor(NSAppKitVersionNumber) <= 1404)
|
||||
return path;
|
||||
|
||||
void *handle = dlopen("/System/Library/Frameworks/Security.framework/Security", RTLD_LAZY);
|
||||
|
||||
// Failed to load security framework
|
||||
if (handle == NULL)
|
||||
return path;
|
||||
|
||||
Boolean (*mySecTranslocateIsTranslocatedURL)(CFURLRef path, bool *isTranslocated, CFErrorRef * __nullable error);
|
||||
mySecTranslocateIsTranslocatedURL = dlsym(handle, "SecTranslocateIsTranslocatedURL");
|
||||
|
||||
CFURLRef __nullable (*mySecTranslocateCreateOriginalPathForURL)(CFURLRef translocatedPath, CFErrorRef * __nullable error);
|
||||
mySecTranslocateCreateOriginalPathForURL = dlsym(handle, "SecTranslocateCreateOriginalPathForURL");
|
||||
|
||||
// Failed to resolve required functions
|
||||
if (mySecTranslocateIsTranslocatedURL == NULL || mySecTranslocateCreateOriginalPathForURL == NULL)
|
||||
return path;
|
||||
|
||||
bool isTranslocated = false;
|
||||
CFURLRef pathURLRef = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (__bridge CFStringRef)path, kCFURLPOSIXPathStyle, false);
|
||||
|
||||
if (mySecTranslocateIsTranslocatedURL(pathURLRef, &isTranslocated, NULL))
|
||||
{
|
||||
if (isTranslocated)
|
||||
{
|
||||
CFURLRef resolvedURL = mySecTranslocateCreateOriginalPathForURL(pathURLRef, NULL);
|
||||
path = [(NSURL *)(resolvedURL) path];
|
||||
}
|
||||
}
|
||||
|
||||
CFRelease(pathURLRef);
|
||||
return path;
|
||||
}
|
||||
|
||||
- (void)taskExited:(NSNotification *)note
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
removeObserver:self
|
||||
name:NSTaskDidTerminateNotification
|
||||
object:gameTask
|
||||
];
|
||||
|
||||
int ret = [gameTask terminationStatus];
|
||||
NSLog(@"launchgame exited with code %d", ret);
|
||||
[gameTask release];
|
||||
gameTask = nil;
|
||||
|
||||
// We're done here
|
||||
if (ret != 0)
|
||||
[self exitWithCrashPrompt];
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
struct rlimit limit;
|
||||
if (getrlimit (RLIMIT_NOFILE, &limit) == 0 && limit.rlim_cur < 1024)
|
||||
{
|
||||
limit.rlim_cur = MIN(limit.rlim_max, 1024);
|
||||
setrlimit(RLIMIT_NOFILE, &limit);
|
||||
}
|
||||
|
||||
void *libmono = dlopen([[SYSTEM_MONO_PATH stringByAppendingPathComponent: @"/lib/libmonosgen-2.0.dylib"] UTF8String], RTLD_LAZY);
|
||||
if (libmono == NULL)
|
||||
{
|
||||
fprintf (stderr, "Failed to load libmonosgen-2.0.dylib: %s\n", dlerror());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
mono_main _mono_main = (mono_main)dlsym(libmono, "mono_main");
|
||||
if (!_mono_main)
|
||||
{
|
||||
fprintf(stderr, "Could not load mono_main(): %s\n", dlerror());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
[pool drain];
|
||||
|
||||
return _mono_main(argc, argv);
|
||||
}
|
||||
|
||||
NSApplication *application = [NSApplication sharedApplication];
|
||||
OpenRALauncher *launcher = [[OpenRALauncher alloc] init];
|
||||
[NSApp setActivationPolicy: NSApplicationActivationPolicyProhibited];
|
||||
|
||||
[application setDelegate:launcher];
|
||||
[application run];
|
||||
|
||||
[launcher release];
|
||||
[pool drain];
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -8,6 +8,13 @@
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <dlfcn.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <mach/machine.h>
|
||||
|
||||
#define SYSTEM_MONO_PATH @"/Library/Frameworks/Mono.framework/Versions/Current/"
|
||||
#define SYSTEM_MONO_MIN_VERSION @"6.4"
|
||||
#define DOTNET_MIN_MACOS_VERSION 10.15
|
||||
|
||||
@interface OpenRALauncher : NSObject <NSApplicationDelegate>
|
||||
- (void)launchGameWithArgs: (NSArray *)gameArgs;
|
||||
@@ -31,8 +38,34 @@ NSTask *gameTask;
|
||||
return @"OpenRA";
|
||||
}
|
||||
|
||||
- (void)showCrashPrompt
|
||||
- (void)exitWithMonoPrompt
|
||||
{
|
||||
[NSApp setActivationPolicy: NSApplicationActivationPolicyRegular];
|
||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||
|
||||
NSString *modName = [self modName];
|
||||
NSString *title = [NSString stringWithFormat: @"Cannot launch %@", modName];
|
||||
NSString *message = [NSString stringWithFormat: @"%@ requires Mono %@ or later. Please install Mono and try again.", modName, SYSTEM_MONO_MIN_VERSION];
|
||||
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:title];
|
||||
[alert setInformativeText:message];
|
||||
[alert addButtonWithTitle:@"Download Mono"];
|
||||
[alert addButtonWithTitle:@"Quit"];
|
||||
NSInteger answer = [alert runModal];
|
||||
[alert release];
|
||||
|
||||
if (answer == NSAlertFirstButtonReturn)
|
||||
[[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString:@"https://www.mono-project.com/download/"]];
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
- (void)exitWithCrashPrompt
|
||||
{
|
||||
[NSApp setActivationPolicy: NSApplicationActivationPolicyRegular];
|
||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||
|
||||
NSString *modName = [self modName];
|
||||
NSString *message = [NSString stringWithFormat: @"%@ has encountered a fatal error and must close.\nPlease refer to the crash logs and FAQ for more information.", modName];
|
||||
|
||||
@@ -61,6 +94,8 @@ NSTask *gameTask;
|
||||
[[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString:faqUrl]];
|
||||
}
|
||||
}
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Application was launched via a URL handler
|
||||
@@ -120,6 +155,16 @@ NSTask *gameTask;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (int)hasValidMono
|
||||
{
|
||||
NSTask *task = [[NSTask alloc] init];
|
||||
[task setLaunchPath: [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent: @"Contents/MacOS/checkmono"]];
|
||||
[task launch];
|
||||
[task waitUntilExit];
|
||||
|
||||
return [task terminationStatus] == 0;
|
||||
}
|
||||
|
||||
- (void)launchGameWithArgs: (NSArray *)gameArgs
|
||||
{
|
||||
if (launched)
|
||||
@@ -130,6 +175,16 @@ NSTask *gameTask;
|
||||
|
||||
launched = YES;
|
||||
|
||||
BOOL useMono = NO;
|
||||
|
||||
if (@available(macOS 10.15, *))
|
||||
useMono = [[[NSProcessInfo processInfo] environment]objectForKey:@"OPENRA_PREFER_MONO"] != nil;
|
||||
else
|
||||
useMono = YES;
|
||||
|
||||
if (useMono && ![self hasValidMono])
|
||||
[self exitWithMonoPrompt];
|
||||
|
||||
// Default values - can be overriden by setting certain keys Info.plist
|
||||
NSString *modId = nil;
|
||||
|
||||
@@ -144,13 +199,44 @@ NSTask *gameTask;
|
||||
NSString *exePath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent: @"Contents/MacOS/"];
|
||||
NSString *gamePath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent: @"Contents/Resources/"];
|
||||
|
||||
NSString *launchPath = [exePath stringByAppendingPathComponent: @"OpenRA"];
|
||||
NSString *launchPath;
|
||||
NSString *dllPath;
|
||||
NSString *hostPath;
|
||||
|
||||
if (useMono)
|
||||
{
|
||||
launchPath = [exePath stringByAppendingPathComponent: @"apphost-mono"];
|
||||
hostPath = [SYSTEM_MONO_PATH stringByAppendingPathComponent: @"lib/libmonosgen-2.0.dylib"];;
|
||||
dllPath = [exePath stringByAppendingPathComponent: @"mono/OpenRA.dll"];
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t size;
|
||||
cpu_type_t type;
|
||||
size = sizeof(type);
|
||||
|
||||
if (sysctlbyname("hw.cputype", &type, &size, NULL, 0) == 0 && (type & 0xFF) == CPU_TYPE_ARM)
|
||||
{
|
||||
launchPath = [exePath stringByAppendingPathComponent: @"apphost-arm64"];
|
||||
hostPath = [exePath stringByAppendingPathComponent: @"arm64/libhostfxr.dylib"];;
|
||||
dllPath = [exePath stringByAppendingPathComponent: @"arm64/OpenRA.dll"];
|
||||
}
|
||||
else
|
||||
{
|
||||
launchPath = [exePath stringByAppendingPathComponent: @"apphost-x86_64"];
|
||||
hostPath = [exePath stringByAppendingPathComponent: @"x86_64/libhostfxr.dylib"];;
|
||||
dllPath = [exePath stringByAppendingPathComponent: @"x86_64/OpenRA.dll"];
|
||||
}
|
||||
}
|
||||
|
||||
NSString *appPath = [exePath stringByAppendingPathComponent: @"Launcher"];
|
||||
NSString *engineLaunchPath = [self resolveTranslocatedPath: appPath];
|
||||
|
||||
NSMutableArray *launchArgs = [NSMutableArray arrayWithCapacity: [gameArgs count] + 2];
|
||||
NSMutableArray *launchArgs = [NSMutableArray arrayWithCapacity: [gameArgs count] + 5];
|
||||
[launchArgs addObject: hostPath];
|
||||
[launchArgs addObject: dllPath];
|
||||
[launchArgs addObject: [NSString stringWithFormat:@"Engine.LaunchPath=\"%@\"", engineLaunchPath]];
|
||||
[launchArgs addObject: [NSString stringWithFormat:@"Engine.EngineDir=../Resources"]];
|
||||
[launchArgs addObject: [NSString stringWithFormat:@"Engine.EngineDir=../../Resources"]];
|
||||
|
||||
if (modId)
|
||||
[launchArgs addObject: [NSString stringWithFormat:@"Game.Mod=%@", modId]];
|
||||
@@ -229,21 +315,15 @@ NSTask *gameTask;
|
||||
];
|
||||
|
||||
int ret = [gameTask terminationStatus];
|
||||
|
||||
NSLog(@"launchgame exited with code %d", ret);
|
||||
[gameTask release];
|
||||
gameTask = nil;
|
||||
|
||||
// We're done here
|
||||
if (ret == 0)
|
||||
exit(0);
|
||||
if (ret != 0)
|
||||
[self exitWithCrashPrompt];
|
||||
|
||||
// Make the error dialog visible
|
||||
[NSApp setActivationPolicy: NSApplicationActivationPolicyRegular];
|
||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||
[self showCrashPrompt];
|
||||
|
||||
exit(1);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user