diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 5c31fce..57cca5f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -371,9 +371,124 @@ jobs:
name: PollyMC-Continued-Linux-AppImage
path: PollyMC-Continued-Linux-x86_64.AppImage
+ build-macos:
+ name: macOS (Universal)
+ runs-on: macos-26
+ permissions:
+ contents: read
+ env:
+ ARTIFACT_NAME: macOS-universal
+ ARTIFACT_ARCH: universal
+ BUILD_PLATFORM: macOS-universal
+ BUILD_TYPE: Release
+ EXPECTED_ARCHS: arm64 x86_64
+ EXPECTED_MIN_MACOS: "12.0"
+ MACOSX_DEPLOYMENT_TARGET: "12.0"
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+
+ - name: Install build tools
+ run: |
+ brew update
+ brew install ninja extra-cmake-modules temurin@17 mono
+ echo "JAVA_HOME=$(/usr/libexec/java_home -v 17)" >> "$GITHUB_ENV"
+ echo "VCPKG_ROOT=$VCPKG_INSTALLATION_ROOT" >> "$GITHUB_ENV"
+
+ - name: Install Qt
+ uses: jurplel/install-qt-action@v4
+ with:
+ aqtversion: "==3.1.*"
+ version: 6.9.3
+ modules: qtimageformats qtnetworkauth
+ cache: true
+
+ - name: Configure
+ run: |
+ cmake --preset macos_universal \
+ -DCMAKE_OSX_DEPLOYMENT_TARGET="$MACOSX_DEPLOYMENT_TARGET" \
+ -DLauncher_BUILD_RELEASE=ON
+
+ - name: Read and validate version
+ id: version
+ run: |
+ VERSION=$(cmake -N -LA build | sed -n 's/^Launcher_VERSION:STRING=//p')
+ if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+ echo "Invalid Launcher_VERSION: $VERSION" >&2
+ exit 1
+ fi
+ if [[ "$GITHUB_REF" == refs/tags/v* && "v$VERSION" != "$GITHUB_REF_NAME" ]]; then
+ echo "Tag $GITHUB_REF_NAME does not match launcher version $VERSION" >&2
+ exit 1
+ fi
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
+
+ - name: Build
+ run: cmake --build --preset macos_universal --config "$BUILD_TYPE"
+
+ - name: Test
+ run: ctest --test-dir build --build-config "$BUILD_TYPE" --output-on-failure
+
+ - name: Import signing certificate
+ env:
+ APPLE_CODESIGN_CERT: ${{ secrets.APPLE_CODESIGN_CERT }}
+ APPLE_CODESIGN_ID: ${{ secrets.APPLE_CODESIGN_ID }}
+ APPLE_CODESIGN_PASSWORD: ${{ secrets.APPLE_CODESIGN_PASSWORD }}
+ run: |
+ if [[ -z "$APPLE_CODESIGN_CERT" && -z "$APPLE_CODESIGN_ID" ]]; then
+ echo "No Apple signing certificate configured; the artifact will be ad-hoc signed."
+ exit 0
+ fi
+ if [[ -z "$APPLE_CODESIGN_CERT" || -z "$APPLE_CODESIGN_ID" || -z "$APPLE_CODESIGN_PASSWORD" ]]; then
+ echo "Apple signing secrets are only partially configured." >&2
+ exit 1
+ fi
+ KEYCHAIN_PATH="$RUNNER_TEMP/pollymc-build.keychain-db"
+ CERTIFICATE_PATH="$RUNNER_TEMP/pollymc-codesign.p12"
+ printf '%s' "$APPLE_CODESIGN_CERT" | base64 --decode > "$CERTIFICATE_PATH"
+ security create-keychain -p "$APPLE_CODESIGN_PASSWORD" "$KEYCHAIN_PATH"
+ security default-keychain -s "$KEYCHAIN_PATH"
+ security unlock-keychain -p "$APPLE_CODESIGN_PASSWORD" "$KEYCHAIN_PATH"
+ security import "$CERTIFICATE_PATH" -k "$KEYCHAIN_PATH" -P "$APPLE_CODESIGN_PASSWORD" -T /usr/bin/codesign
+ security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$APPLE_CODESIGN_PASSWORD" "$KEYCHAIN_PATH"
+ echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV"
+
+ - name: Package app, ZIP, and DMG
+ env:
+ APPLE_CODESIGN_ID: ${{ secrets.APPLE_CODESIGN_ID }}
+ APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
+ APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
+ APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
+ run: |
+ packaging/macos/package.sh \
+ build \
+ install-macos \
+ dist-macos \
+ "${{ steps.version.outputs.version }}" \
+ "$BUILD_TYPE"
+
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v7
+ with:
+ name: PollyMC-Continued-macOS-${{ steps.version.outputs.version }}
+ path: |
+ dist-macos/PollyMC-Continued-${{ steps.version.outputs.version }}-macOS-universal.zip
+ dist-macos/PollyMC-Continued-${{ steps.version.outputs.version }}-macOS-universal.dmg
+ dist-macos/PollyMC-Continued-${{ steps.version.outputs.version }}-macOS-universal.sha256
+ if-no-files-found: error
+
+ - name: Remove temporary signing keychain
+ if: ${{ always() }}
+ run: |
+ if [[ -n "${KEYCHAIN_PATH:-}" && -f "$KEYCHAIN_PATH" ]]; then
+ security delete-keychain "$KEYCHAIN_PATH"
+ fi
+
release:
name: Publish Release
- needs: [build-windows, build-linux]
+ needs: [build-windows, build-linux, build-macos]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
permissions:
@@ -425,3 +540,6 @@ jobs:
PollyMC-Continued-*-Setup.exe
PollyMC-Continued-*-Windows-portable.zip
PollyMC-Continued-Linux-x86_64.AppImage
+ PollyMC-Continued-*-macOS-universal.zip
+ PollyMC-Continued-*-macOS-universal.dmg
+ PollyMC-Continued-*-macOS-universal.sha256
diff --git a/.gitignore b/.gitignore
index 037dd3f..3efaed8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,6 +31,7 @@ build
# Install dirs
install
/install-*
+/dist-*
# Ctags File
tags
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ddd691d..91f1345 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,14 @@
cmake_minimum_required(VERSION 3.25) # Required for features like `CMAKE_MSVC_DEBUG_INFORMATION_FORMAT`
-project(Launcher LANGUAGES C CXX)
+set(Launcher_VERSION "9.0.8" CACHE STRING "Launcher version in MAJOR.MINOR.PATCH format")
+if(Launcher_VERSION STREQUAL "")
+ set(Launcher_VERSION "9.0.8" CACHE STRING "Launcher version in MAJOR.MINOR.PATCH format" FORCE)
+endif()
+if(NOT Launcher_VERSION MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$")
+ message(FATAL_ERROR "Launcher_VERSION must use MAJOR.MINOR.PATCH format (got '${Launcher_VERSION}')")
+endif()
+
+project(Launcher VERSION "${Launcher_VERSION}" LANGUAGES C CXX)
if(APPLE)
enable_language(OBJC OBJCXX)
endif()
@@ -157,6 +165,7 @@ if(ENABLE_LTO)
endif()
option(BUILD_TESTING "Build the testing tree." ON)
+option(Launcher_BUILD_RELEASE "Hide Git branch metadata from public release artifacts." OFF)
find_package(ECM NO_MODULE REQUIRED)
set(CMAKE_MODULE_PATH "${ECM_MODULE_PATH};${CMAKE_MODULE_PATH}")
@@ -178,10 +187,6 @@ set(Launcher_LOGIN_CALLBACK_URL "https://login.live.com/oauth20_desktop.srf" CAC
set(Launcher_LEGACY_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE STRING "URL for legacy (<=1.5.2) FML Libraries.")
######## Set version numbers ########
-set(Launcher_VERSION_MAJOR 9)
-set(Launcher_VERSION_MINOR 0)
-set(Launcher_VERSION_PATCH 0)
-
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}")
set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}.0")
set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},${Launcher_VERSION_PATCH},0")
@@ -275,6 +280,12 @@ include(GetGitRevisionDescription)
git_get_exact_tag(Launcher_GIT_TAG)
get_git_head_revision(Launcher_GIT_REFSPEC Launcher_GIT_COMMIT)
+if(Launcher_BUILD_RELEASE)
+ # Use stable metadata in packaged builds.
+ set(Launcher_GIT_REFSPEC "refs/heads/stable")
+ set(Launcher_GIT_TAG "${Launcher_VERSION_NAME}")
+endif()
+
message(STATUS "Git commit: ${Launcher_GIT_COMMIT}")
message(STATUS "Git tag: ${Launcher_GIT_TAG}")
message(STATUS "Git refspec: ${Launcher_GIT_REFSPEC}")
@@ -287,7 +298,7 @@ set(Launcher_BUILD_TIMESTAMP "${TODAY}")
# Find the required Qt parts
if(Launcher_QT_VERSION_MAJOR EQUAL 6)
set(QT_VERSION_MAJOR 6)
- find_package(Qt6 6.4 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml NetworkAuth OpenGL WebSockets)
+ find_package(Qt6 6.4 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml NetworkAuth OpenGL Svg WebSockets)
find_package(Qt6 COMPONENTS DBus)
list(APPEND Launcher_QT_DBUS Qt6::DBus)
else()
@@ -360,11 +371,11 @@ endif()
if(UNIX AND APPLE)
set(BINARY_DEST_DIR "${Launcher_Name}.app/Contents/MacOS")
- set(LIBRARY_DEST_DIR "${Launcher_Name}.app/Contents/MacOS")
- set(PLUGIN_DEST_DIR "${Launcher_Name}.app/Contents/MacOS")
+ set(LIBRARY_DEST_DIR "${Launcher_Name}.app/Contents/Frameworks")
+ set(PLUGIN_DEST_DIR "${Launcher_Name}.app/Contents/PlugIns")
set(FRAMEWORK_DEST_DIR "${Launcher_Name}.app/Contents/Frameworks")
set(RESOURCES_DEST_DIR "${Launcher_Name}.app/Contents/Resources")
- set(JARS_DEST_DIR "${Launcher_Name}.app/Contents/MacOS/jars")
+ set(JARS_DEST_DIR "${Launcher_Name}.app/Contents/Resources/jars")
# Mac bundle settings
set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_DisplayName}")
@@ -390,7 +401,8 @@ if(UNIX AND APPLE)
install(FILES ${Launcher_Branding_ICNS} DESTINATION ${RESOURCES_DEST_DIR} RENAME ${Launcher_Name}.icns)
find_program(ACTOOL_EXE actool DOC "Path to the apple asset catalog compiler")
- if(ACTOOL_EXE)
+ set(ICON_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_Branding_MAC_ICON}")
+ if(ACTOOL_EXE AND EXISTS "${ICON_SOURCE}")
execute_process(
COMMAND xcodebuild -version
OUTPUT_VARIABLE XCODE_VERSION_OUTPUT
@@ -407,7 +419,6 @@ if(UNIX AND APPLE)
if(XCODE_VERSION VERSION_GREATER_EQUAL 26.0)
set(ASSETS_OUT_DIR "${CMAKE_BINARY_DIR}/program_info")
set(GENERATED_ASSETS_CAR "${ASSETS_OUT_DIR}/Assets.car")
- set(ICON_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_Branding_MAC_ICON}")
add_custom_command(
OUTPUT "${GENERATED_ASSETS_CAR}"
@@ -428,7 +439,8 @@ if(UNIX AND APPLE)
else()
message(WARNING "Xcode ${XCODE_VERSION} is too old. Minimum required version is 26.0. Not compiling liquid glass icons.")
endif()
-
+ elseif(NOT EXISTS "${ICON_SOURCE}")
+ message(STATUS "Modern macOS icon source '${ICON_SOURCE}' is not present; using the bundled ICNS icon")
else()
message(WARNING "actool not found. Cannot compile macOS app icons.\n"
"Install Xcode command line tools: 'xcode-select --install'")
diff --git a/README.md b/README.md
index 18fac98..190f524 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ PollyMC-Continued lets you play Minecraft **without needing a Microsoft account*
## Download
-Grab the latest release from the [Releases](https://github.com/SharathGames1/PollyMC/releases) page.
+Grab the latest release from the [Releases](https://github.com/corecommit/PollyMC-Continued/releases) page.
## Building
@@ -49,6 +49,31 @@ cmake --install . --prefix C:/pollymc_build
"/c/Program Files (x86)/NSIS/makensis.exe" pollymc_installer.nsi
```
+### macOS
+
+Install the dependencies:
+
+```bash
+brew install cmake ninja qt extra-cmake-modules cmark qrencode libarchive tomlplusplus
+brew install --cask temurin@17
+```
+
+Build, test and package the native version for the current Mac:
+
+```bash
+bash packaging/macos/build-local.sh
+```
+
+The script uses the current macOS major version as the default deployment target, avoiding invalid or incompatible local bundles built against Homebrew libraries. Override it when needed:
+
+```bash
+MACOSX_DEPLOYMENT_TARGET=15.0 bash packaging/macos/build-local.sh
+```
+
+The resulting `dist-macos` directory contains a ready-to-run `PollyMC.app`, ZIP, DMG and SHA-256 checksums. The locally generated DMG file receives the same Finder icon as the application. The DMG also contains `PollyMC.app` and an `Applications` shortcut for normal drag-and-drop installation.
+
+The custom icon attached to the local DMG file is stored in a macOS resource fork. Release hosting services may discard that metadata, while the icon of the mounted DMG volume remains embedded in the disk image.
+
## Credits
- [Prism Launcher](https://github.com/PrismLauncher/PrismLauncher) — the base launcher
diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in
index eb40bac..9b1d6a4 100644
--- a/cmake/MacOSXBundleInfo.plist.in
+++ b/cmake/MacOSXBundleInfo.plist.in
@@ -13,11 +13,13 @@
NSPrincipalClass
NSApplication
NSHighResolutionCapable
- True
+
CFBundleDevelopmentRegion
English
CFBundleExecutable
${MACOSX_BUNDLE_EXECUTABLE_NAME}
+ CFBundleDisplayName
+ ${MACOSX_BUNDLE_BUNDLE_NAME}
CFBundleGetInfoString
${MACOSX_BUNDLE_INFO_STRING}
CFBundleIconFile
@@ -42,8 +44,8 @@
${MACOSX_BUNDLE_BUNDLE_VERSION}
CSResourcesFileMapped
- LSRequiresCarbon
-
+ LSMinimumSystemVersion
+ ${CMAKE_OSX_DEPLOYMENT_TARGET}
LSApplicationCategoryType
public.app-category.games
NSHumanReadableCopyright
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index 54cad5c..4e0cc97 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -1890,6 +1890,9 @@ QString Application::getJarPath(QString jarFile)
QStringList potentialPaths = {
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
FS::PathCombine(m_rootPath, "share", BuildConfig.LAUNCHER_NAME),
+#endif
+#if defined(Q_OS_MACOS)
+ FS::PathCombine(applicationDirPath(), "..", "Resources", "jars"),
#endif
FS::PathCombine(m_rootPath, "jars"), FS::PathCombine(applicationDirPath(), "jars"),
FS::PathCombine(applicationDirPath(), "..", "jars") // from inside build dir, for debuging
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 6e68b7c..7a963e1 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -1374,6 +1374,7 @@ target_link_libraries(Launcher_logic
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::NetworkAuth
Qt${QT_VERSION_MAJOR}::OpenGL
+ Qt${QT_VERSION_MAJOR}::Svg
Qt${QT_VERSION_MAJOR}::WebSockets
${Launcher_QT_DBUS}
${Launcher_QT_LIBS}
@@ -1418,7 +1419,10 @@ endif()
target_link_libraries(${Launcher_Name} Launcher_logic)
-if(DEFINED Launcher_APP_BINARY_NAME)
+if(APPLE)
+ # Match the app bundle name.
+ set_target_properties(${Launcher_Name} PROPERTIES OUTPUT_NAME "${Launcher_Name}")
+elseif(DEFINED Launcher_APP_BINARY_NAME)
set_target_properties(${Launcher_Name} PROPERTIES OUTPUT_NAME "${Launcher_APP_BINARY_NAME}")
endif()
if(DEFINED Launcher_BINARY_RPATH)
@@ -1583,6 +1587,24 @@ endif()
if(WIN32 OR (UNIX AND APPLE))
if(WIN32)
set(QT_DEPLOY_TOOL_OPTIONS "--no-opengl-sw --no-quick-import --no-system-d3d-compiler --no-system-dxc-compiler --skip-plugin-types generic,networkinformation")
+ elseif(APPLE)
+ # Homebrew may install optional Qt modules in separate prefixes.
+ set(QT_DEPLOY_TOOL_OPTIONS "-no-codesign")
+ set(_qt_optional_components Svg Pdf Qml Quick VirtualKeyboard)
+ set(_qt_deploy_lib_dirs)
+ foreach(_qt_component IN LISTS _qt_optional_components)
+ find_package(Qt6 QUIET COMPONENTS ${_qt_component})
+ set(_qt_component_dir_var "Qt6${_qt_component}_DIR")
+ if(DEFINED ${_qt_component_dir_var})
+ get_filename_component(_qt_component_prefix "${${_qt_component_dir_var}}/../../.." ABSOLUTE)
+ set(_qt_component_lib_dir "${_qt_component_prefix}/lib")
+ list(APPEND _qt_deploy_lib_dirs "${_qt_component_lib_dir}")
+ endif()
+ endforeach()
+ list(REMOVE_DUPLICATES _qt_deploy_lib_dirs)
+ foreach(_qt_deploy_lib_dir IN LISTS _qt_deploy_lib_dirs)
+ string(APPEND QT_DEPLOY_TOOL_OPTIONS " -libpath=${_qt_deploy_lib_dir}")
+ endforeach()
endif()
qt_generate_deploy_script(
@@ -1597,29 +1619,32 @@ if(WIN32 OR (UNIX AND APPLE))
PLUGINS_DIR ${PLUGIN_DEST_DIR}
NO_OVERWRITE
NO_TRANSLATIONS
+ NO_COMPILER_RUNTIME
DEPLOY_TOOL_OPTIONS ${QT_DEPLOY_TOOL_OPTIONS}
)"
)
- # Bundle our linked dependencies
- install(
- RUNTIME_DEPENDENCY_SET LAUNCHER_DEPENDENCY_SET
- COMPONENT bundle
- DIRECTORIES
- ${CMAKE_SYSTEM_LIBRARY_PATH}
- ${QT_LIBS_DIR}
- ${QT_LIBEXECS_DIR}
- PRE_EXCLUDE_REGEXES
- "^(api-ms-win|ext-ms)-.*\\.dll$"
- # FIXME: Why aren't these caught by the below regex???
- "^azure.*\\.dll$"
- "^vcruntime.*\\.dll$"
- POST_EXCLUDE_REGEXES
- "system32"
- LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
- RUNTIME DESTINATION ${BINARY_DEST_DIR}
- FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR}
- )
+ if(WIN32)
+ # macdeployqt handles runtime dependencies on macOS.
+ install(
+ RUNTIME_DEPENDENCY_SET LAUNCHER_DEPENDENCY_SET
+ COMPONENT bundle
+ DIRECTORIES
+ ${CMAKE_SYSTEM_LIBRARY_PATH}
+ ${QT_LIBS_DIR}
+ ${QT_LIBEXECS_DIR}
+ PRE_EXCLUDE_REGEXES
+ "^(api-ms-win|ext-ms)-.*\\.dll$"
+ # FIXME: Why aren't these caught by the below regex???
+ "^azure.*\\.dll$"
+ "^vcruntime.*\\.dll$"
+ POST_EXCLUDE_REGEXES
+ "system32"
+ LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
+ RUNTIME DESTINATION ${BINARY_DEST_DIR}
+ FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR}
+ )
+ endif()
# Deploy Qt plugins
install(
SCRIPT ${QT_DEPLOY_SCRIPT}
@@ -1694,7 +1719,7 @@ if(WIN32 OR (UNIX AND APPLE))
# Add qtlogging.ini as a config file
install(
FILES "qtlogging.ini"
- DESTINATION ${CMAKE_INSTALL_PREFIX}/${RESOURCES_DEST_DIR}
+ DESTINATION ${RESOURCES_DEST_DIR}
COMPONENT bundle
)
endif()
diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui
index 82d95a6..82cd53d 100644
--- a/launcher/ui/MainWindow.ui
+++ b/launcher/ui/MainWindow.ui
@@ -569,7 +569,7 @@
Close the current window
- QAction::QuitRole
+ QAction::MenuRole::NoRole
diff --git a/packaging/macos/build-local.sh b/packaging/macos/build-local.sh
new file mode 100644
index 0000000..0711998
--- /dev/null
+++ b/packaging/macos/build-local.sh
@@ -0,0 +1,107 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
+source_root=$(cd "$script_dir/../.." && pwd)
+cd "$source_root"
+
+fail() {
+ echo "Local macOS build failed: $*" >&2
+ exit 1
+}
+
+command -v git >/dev/null 2>&1 || fail "Git is required"
+command -v brew >/dev/null 2>&1 || fail "Homebrew is required"
+command -v cmake >/dev/null 2>&1 || fail "CMake is required (brew install cmake)"
+command -v ninja >/dev/null 2>&1 || fail "Ninja is required (brew install ninja)"
+xcode-select -p >/dev/null 2>&1 || fail "Xcode Command Line Tools are required (xcode-select --install)"
+
+java_home=$(/usr/libexec/java_home -v 17 2>/dev/null) || fail "JDK 17 is required (brew install --cask temurin@17)"
+export JAVA_HOME=$java_home
+
+formulae=(qt libarchive tomlplusplus cmark qrencode extra-cmake-modules)
+prefixes=()
+for formula in "${formulae[@]}"; do
+ prefix=$(brew --prefix "$formula" 2>/dev/null) || fail "Missing Homebrew dependency: $formula"
+ prefixes+=("$prefix")
+done
+if [[ -n ${CMAKE_PREFIX_PATH:-} ]]; then
+ prefixes+=("$CMAKE_PREFIX_PATH")
+fi
+export CMAKE_PREFIX_PATH=$(IFS=';'; echo "${prefixes[*]}")
+
+deployment_target=${MACOSX_DEPLOYMENT_TARGET:-$(sw_vers -productVersion | awk -F. '{print $1 ".0"}')}
+architecture=$(uname -m)
+build_dir=${BUILD_DIR:-build-macos}
+install_dir=${INSTALL_DIR:-install-macos}
+output_dir=${OUTPUT_DIR:-dist-macos}
+
+for directory in "$build_dir" "$install_dir" "$output_dir"; do
+ case "$directory" in
+ ''|/|.|..|/*|../*|*/../*|*/..) fail "Working directories must be relative paths inside the repository: $directory" ;;
+ esac
+done
+
+git submodule update --init --recursive
+cmake -E rm -rf "$build_dir" "$install_dir" "$output_dir"
+
+cmake -S . -B "$build_dir" -G Ninja \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH" \
+ -DCMAKE_OSX_DEPLOYMENT_TARGET="$deployment_target" \
+ -DLauncher_BUILD_RELEASE=ON \
+ -DLauncher_BUILD_PLATFORM="macOS-$architecture"
+
+cmake --build "$build_dir" --parallel
+ctest --test-dir "$build_dir" --output-on-failure
+
+version=$(cmake -N -LA "$build_dir" | sed -n 's/^Launcher_VERSION:STRING=//p')
+[[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || fail "Could not read Launcher_VERSION"
+
+ARTIFACT_ARCH=$architecture \
+EXPECTED_ARCHS=$architecture \
+EXPECTED_MIN_MACOS=$deployment_target \
+ "$script_dir/package.sh" "$build_dir" "$install_dir" "$output_dir" "$version"
+
+standalone_app="$output_dir/PollyMC.app"
+cmake -E rm -rf "$standalone_app"
+ditto --rsrc --extattr --noqtn --noacl "$install_dir/PollyMC.app" "$standalone_app"
+
+source_branch=$(git symbolic-ref --short -q HEAD 2>/dev/null || true)
+EXPECTED_VERSION=$version \
+EXPECTED_ARCHS=$architecture \
+EXPECTED_MIN_MACOS=$deployment_target \
+SOURCE_ROOT=$source_root \
+SOURCE_BRANCH=$source_branch \
+ "$script_dir/verify_bundle.sh" "$standalone_app"
+
+dmg_path="$output_dir/PollyMC-Continued-${version}-macOS-${architecture}.dmg"
+app_icon="$standalone_app/Contents/Resources/PollyMC.icns"
+[[ -f $dmg_path ]] || fail "DMG was not created: $dmg_path"
+[[ -s $app_icon ]] || fail "Application icon is missing: $app_icon"
+
+rez=$(xcrun --find Rez) || fail "Rez is required from Xcode Command Line Tools"
+setfile=$(xcrun --find SetFile) || fail "SetFile is required from Xcode Command Line Tools"
+getfileinfo=$(xcrun --find GetFileInfo) || fail "GetFileInfo is required from Xcode Command Line Tools"
+icon_resource=$(mktemp "${TMPDIR:-/tmp}/pollymc-dmg-icon.XXXXXX")
+trap 'cmake -E rm -f "$icon_resource"' EXIT
+escaped_icon=${app_icon//\\/\\\\}
+escaped_icon=${escaped_icon//\"/\\\"}
+printf "read 'icns' (-16455) \"%s\";\n" "$escaped_icon" > "$icon_resource"
+"$rez" -append "$icon_resource" -o "$dmg_path"
+"$setfile" -a C "$dmg_path"
+touch "$dmg_path"
+
+attributes=$("$getfileinfo" -a "$dmg_path")
+[[ $attributes == *C* ]] || fail "The DMG file custom icon flag was not set"
+[[ -s "$dmg_path/..namedfork/rsrc" ]] || fail "The DMG file icon resource is missing"
+
+echo
+echo "Created local macOS artifacts in $source_root/$output_dir:"
+echo " PollyMC.app"
+echo " $(basename "$dmg_path")"
+echo " PollyMC-Continued-${version}-macOS-${architecture}.zip"
+echo " PollyMC-Continued-${version}-macOS-${architecture}.sha256"
+echo
+echo "The DMG file icon is a macOS resource fork and is intended for local builds."
diff --git a/packaging/macos/package.sh b/packaging/macos/package.sh
new file mode 100755
index 0000000..2b659d9
--- /dev/null
+++ b/packaging/macos/package.sh
@@ -0,0 +1,226 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+if [[ $# -lt 4 || $# -gt 5 ]]; then
+ echo "Usage: $0 BUILD_DIR INSTALL_DIR OUTPUT_DIR VERSION [BUILD_CONFIG]" >&2
+ exit 2
+fi
+
+build_dir=$1
+install_dir=$2
+output_dir=$3
+version=$4
+build_config=${5:-Release}
+
+script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
+source_root=$(cd "$script_dir/../.." && pwd)
+
+case "$version" in
+ ''|*[!0-9.]*) echo "VERSION must use MAJOR.MINOR.PATCH format" >&2; exit 2 ;;
+esac
+[[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "VERSION must use MAJOR.MINOR.PATCH format" >&2; exit 2; }
+
+[[ $(uname -s) == Darwin ]] || { echo "macOS packaging must run on macOS" >&2; exit 1; }
+[[ -d $build_dir ]] || { echo "Build directory not found: $build_dir" >&2; exit 1; }
+
+for directory in "$install_dir" "$output_dir"; do
+ [[ -n $directory && $directory != / ]] || { echo "Refusing unsafe output directory: $directory" >&2; exit 1; }
+done
+
+cmake -E rm -rf "$install_dir"
+cmake -E make_directory "$install_dir" "$output_dir"
+cmake --install "$build_dir" --config "$build_config" --prefix "$install_dir"
+
+app="$install_dir/PollyMC.app"
+[[ -d $app ]] || { echo "Installed application not found: $app" >&2; exit 1; }
+app_executable="$app/Contents/MacOS/PollyMC"
+
+# Remove plugins for unused Qt modules.
+cmake -E rm -f \
+ "$app/Contents/PlugIns/imageformats/libqpdf.dylib" \
+ "$app/Contents/PlugIns/platforminputcontexts/libqtvirtualkeyboardplugin.dylib"
+
+find "$app" \( -name '.DS_Store' -o -name '._*' \) -delete
+xattr -cr "$app"
+chmod +x "$app_executable"
+
+# Remove build-machine search paths.
+while IFS= read -r -d '' candidate; do
+ file "$candidate" | grep -q 'Mach-O' || continue
+ while IFS= read -r rpath; do
+ case "$rpath" in
+ @*) ;;
+ *) install_name_tool -delete_rpath "$rpath" "$candidate" ;;
+ esac
+ done < <(otool -l "$candidate" | awk '/cmd LC_RPATH/ { getline; getline; print $2 }')
+done < <(find "$app/Contents" -type f -print0)
+
+if [[ -n ${APPLE_CODESIGN_ID:-} ]]; then
+ signing_identity=$APPLE_CODESIGN_ID
+ entitlements="$source_root/program_info/App.entitlements"
+ timestamp_args=(--timestamp)
+else
+ signing_identity=-
+ entitlements="$source_root/program_info/AdhocSignedApp.entitlements"
+ timestamp_args=(--timestamp=none)
+fi
+
+source_branch=$(git -C "$source_root" symbolic-ref --short -q HEAD 2>/dev/null || true)
+
+sign_code() {
+ codesign \
+ --force \
+ --sign "$signing_identity" \
+ --options runtime \
+ "${timestamp_args[@]}" \
+ "$1"
+}
+
+# Sign nested code before the app bundle.
+while IFS= read -r -d '' candidate; do
+ file "$candidate" | grep -q 'Mach-O' || continue
+ [[ $candidate == "$app_executable" ]] && continue
+ [[ $candidate == *.framework/* ]] && continue
+ sign_code "$candidate"
+done < <(find "$app/Contents" -type f -print0)
+
+while IFS= read -r -d '' framework; do
+ sign_code "$framework"
+done < <(find "$app/Contents/Frameworks" -type d -name '*.framework' -prune -print0)
+
+codesign \
+ --force \
+ --sign "$signing_identity" \
+ --entitlements "$entitlements" \
+ --options runtime \
+ "${timestamp_args[@]}" \
+ "$app"
+
+EXPECTED_VERSION=$version \
+EXPECTED_ARCHS="${EXPECTED_ARCHS:-}" \
+EXPECTED_MIN_MACOS="${EXPECTED_MIN_MACOS:-}" \
+SOURCE_ROOT=$source_root \
+SOURCE_BRANCH=$source_branch \
+ "$script_dir/verify_bundle.sh" "$app"
+
+artifact_arch=${ARTIFACT_ARCH:-$(uname -m)}
+artifact_base="PollyMC-Continued-${version}-macOS-${artifact_arch}"
+zip_path="$output_dir/$artifact_base.zip"
+dmg_path="$output_dir/$artifact_base.dmg"
+
+notarize_values=(
+ "${APPLE_NOTARIZE_APPLE_ID:-}"
+ "${APPLE_NOTARIZE_TEAM_ID:-}"
+ "${APPLE_NOTARIZE_PASSWORD:-}"
+)
+notarize_count=0
+for value in "${notarize_values[@]}"; do
+ [[ -z $value ]] || notarize_count=$((notarize_count + 1))
+done
+
+if [[ $notarize_count -ne 0 && $notarize_count -ne 3 ]]; then
+ echo "All notarization credentials must be provided together" >&2
+ exit 1
+fi
+
+if [[ $notarize_count -eq 3 ]]; then
+ notarize_zip="$output_dir/.notarize.zip"
+ ditto -c -k --sequesterRsrc --keepParent "$app" "$notarize_zip"
+ xcrun notarytool submit "$notarize_zip" \
+ --wait \
+ --apple-id "$APPLE_NOTARIZE_APPLE_ID" \
+ --team-id "$APPLE_NOTARIZE_TEAM_ID" \
+ --password "$APPLE_NOTARIZE_PASSWORD"
+ xcrun stapler staple "$app"
+ xcrun stapler validate "$app"
+ cmake -E rm -f "$notarize_zip"
+fi
+
+cmake -E rm -f "$zip_path" "$dmg_path"
+ditto -c -k --norsrc --noextattr --noqtn --noacl --keepParent "$app" "$zip_path"
+
+if unzip -Z1 "$zip_path" | grep -Eq '(^|/)__MACOSX/|(^|/)\._'; then
+ echo "ZIP contains Finder or AppleDouble metadata" >&2
+ exit 1
+fi
+
+zip_check_dir="$install_dir/zip-check"
+cmake -E rm -rf "$zip_check_dir"
+cmake -E make_directory "$zip_check_dir"
+ditto -x -k "$zip_path" "$zip_check_dir"
+EXPECTED_VERSION=$version \
+EXPECTED_ARCHS="${EXPECTED_ARCHS:-}" \
+EXPECTED_MIN_MACOS="${EXPECTED_MIN_MACOS:-}" \
+SOURCE_ROOT=$source_root \
+SOURCE_BRANCH=$source_branch \
+ "$script_dir/verify_bundle.sh" "$zip_check_dir/PollyMC.app"
+cmake -E rm -rf "$zip_check_dir"
+
+dmg_root="$install_dir/dmg-root"
+dmg_rw="$output_dir/$artifact_base-rw.dmg"
+dmg_mount="$install_dir/dmg-mount"
+dmg_check="$install_dir/dmg-check"
+cmake -E rm -rf "$dmg_root"
+cmake -E make_directory "$dmg_root"
+ditto --norsrc --noextattr --noqtn --noacl "$app" "$dmg_root/PollyMC.app"
+ln -s /Applications "$dmg_root/Applications"
+cp "$app/Contents/Resources/PollyMC.icns" "$dmg_root/.VolumeIcon.icns"
+
+setfile=$(xcrun --find SetFile)
+getfileinfo=$(xcrun --find GetFileInfo)
+
+hdiutil create \
+ -volname "PollyMC-Continued $version" \
+ -srcfolder "$dmg_root" \
+ -format UDRW \
+ -fs APFS \
+ -ov \
+ "$dmg_rw"
+
+cmake -E rm -rf "$dmg_mount"
+cmake -E make_directory "$dmg_mount"
+hdiutil attach -nobrowse -readwrite -mountpoint "$dmg_mount" "$dmg_rw" >/dev/null
+if ! "$setfile" -a C "$dmg_mount"; then
+ hdiutil detach "$dmg_mount" >/dev/null || true
+ exit 1
+fi
+sync
+hdiutil detach "$dmg_mount" >/dev/null
+
+hdiutil convert \
+ "$dmg_rw" \
+ -format UDZO \
+ -imagekey zlib-level=9 \
+ -o "$dmg_path"
+cmake -E rm -f "$dmg_rw"
+hdiutil verify "$dmg_path"
+
+cmake -E rm -rf "$dmg_check"
+cmake -E make_directory "$dmg_check"
+hdiutil attach -nobrowse -readonly -mountpoint "$dmg_check" "$dmg_path" >/dev/null
+dmg_attributes=$("$getfileinfo" -a "$dmg_check")
+if [[ $dmg_attributes != *C* || ! -s "$dmg_check/.VolumeIcon.icns" || ! -L "$dmg_check/Applications" ]]; then
+ hdiutil detach "$dmg_check" >/dev/null || true
+ echo "DMG layout or volume icon is invalid" >&2
+ exit 1
+fi
+if ! EXPECTED_VERSION=$version \
+ EXPECTED_ARCHS="${EXPECTED_ARCHS:-}" \
+ EXPECTED_MIN_MACOS="${EXPECTED_MIN_MACOS:-}" \
+ SOURCE_ROOT=$source_root \
+ SOURCE_BRANCH=$source_branch \
+ "$script_dir/verify_bundle.sh" "$dmg_check/PollyMC.app"; then
+ hdiutil detach "$dmg_check" >/dev/null || true
+ exit 1
+fi
+hdiutil detach "$dmg_check" >/dev/null
+cmake -E rm -rf "$dmg_check"
+
+(
+ cd "$output_dir"
+ shasum -a 256 "$(basename "$zip_path")" "$(basename "$dmg_path")" > "$artifact_base.sha256"
+)
+
+echo "Created $zip_path"
+echo "Created $dmg_path"
diff --git a/packaging/macos/verify_bundle.sh b/packaging/macos/verify_bundle.sh
new file mode 100755
index 0000000..08022c8
--- /dev/null
+++ b/packaging/macos/verify_bundle.sh
@@ -0,0 +1,212 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+if [[ $# -ne 1 ]]; then
+ echo "Usage: $0 /path/to/PollyMC.app" >&2
+ exit 2
+fi
+
+app=$1
+contents="$app/Contents"
+plist="$contents/Info.plist"
+
+fail() {
+ echo "macOS bundle verification failed: $*" >&2
+ exit 1
+}
+
+[[ $(uname -s) == Darwin ]] || fail "this check must run on macOS"
+[[ -d $app ]] || fail "application bundle not found: $app"
+[[ -f $plist ]] || fail "Info.plist is missing"
+plutil -lint "$plist" >/dev/null || fail "Info.plist is invalid"
+
+executable_name=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$plist")
+executable="$contents/MacOS/$executable_name"
+[[ -x $executable ]] || fail "bundle executable is missing or not executable"
+
+bundle_name=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleName' "$plist")
+display_name=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleDisplayName' "$plist")
+package_type=$(/usr/libexec/PlistBuddy -c 'Print :CFBundlePackageType' "$plist")
+minimum_system_version=$(/usr/libexec/PlistBuddy -c 'Print :LSMinimumSystemVersion' "$plist")
+[[ -n $bundle_name && $display_name == "$bundle_name" ]] || fail "bundle display name is missing or inconsistent"
+[[ $package_type == APPL ]] || fail "bundle package type is not APPL"
+[[ $minimum_system_version =~ ^[0-9]+(\.[0-9]+){1,2}$ ]] || fail "minimum macOS version is missing or invalid"
+
+bundle_version=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$plist")
+if [[ -n ${EXPECTED_VERSION:-} && $bundle_version != "$EXPECTED_VERSION" ]]; then
+ fail "bundle version is $bundle_version, expected $EXPECTED_VERSION"
+fi
+
+icon_name=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIconFile' "$plist")
+icon="$contents/Resources/$icon_name"
+[[ -f $icon ]] || icon="$icon.icns"
+[[ -s $icon ]] || fail "application icon is missing: $icon_name"
+
+icon_check_dir=$(mktemp -d)
+trap 'rm -rf "$icon_check_dir"' EXIT
+iconutil -c iconset "$icon" -o "$icon_check_dir/AppIcon.iconset"
+[[ -s "$icon_check_dir/AppIcon.iconset/icon_512x512@2x.png" ]] || fail "application icon has no 1024px representation"
+
+[[ -f "$contents/PlugIns/platforms/libqcocoa.dylib" ]] || fail "Qt Cocoa platform plugin is missing"
+[[ -f "$contents/PlugIns/iconengines/libqsvgicon.dylib" ]] || fail "Qt SVG icon engine is missing"
+[[ -f "$contents/Frameworks/QtSvg.framework/Versions/A/QtSvg" ]] || fail "Qt SVG framework is missing"
+[[ -f "$contents/Resources/qt.conf" ]] || fail "qt.conf is missing"
+
+for jar in JavaCheck.jar NewLaunch.jar NewLaunchLegacy.jar SkinAgent.jar; do
+ [[ -s "$contents/Resources/jars/$jar" ]] || fail "runtime component is missing: $jar"
+done
+
+if [[ -n ${EXPECTED_ARCHS:-} ]]; then
+ actual_archs=" $(lipo -archs "$executable") "
+ for expected_arch in $EXPECTED_ARCHS; do
+ [[ $actual_archs == *" $expected_arch "* ]] || fail "executable does not contain $expected_arch (has:${actual_archs})"
+ done
+fi
+
+executable_dir=$(dirname "$executable")
+executable_rpaths=()
+while IFS= read -r rpath; do
+ [[ -z $rpath ]] || executable_rpaths+=("$rpath")
+done < <(otool -l "$executable" | awk '/cmd LC_RPATH/ { getline; getline; print $2 }')
+
+expand_runtime_path() {
+ local runtime_path=$1
+ local loader_dir=$2
+ runtime_path=${runtime_path//@executable_path/$executable_dir}
+ runtime_path=${runtime_path//@loader_path/$loader_dir}
+ printf '%s\n' "$runtime_path"
+}
+
+version_is_at_most() {
+ awk -v actual="$1" -v maximum="$2" 'BEGIN {
+ actual_count = split(actual, actual_parts, ".")
+ maximum_count = split(maximum, maximum_parts, ".")
+ count = actual_count > maximum_count ? actual_count : maximum_count
+ for (i = 1; i <= count; i++) {
+ a = actual_parts[i] + 0
+ b = maximum_parts[i] + 0
+ if (a < b) exit 0
+ if (a > b) exit 1
+ }
+ exit 0
+ }'
+}
+
+if [[ -n ${EXPECTED_MIN_MACOS:-} && $minimum_system_version != "$EXPECTED_MIN_MACOS" ]]; then
+ fail "Info.plist requires macOS $minimum_system_version, expected $EXPECTED_MIN_MACOS"
+fi
+
+dependency_errors=0
+while IFS= read -r -d '' candidate; do
+ file "$candidate" | grep -q 'Mach-O' || continue
+ install_name=$(otool -D "$candidate" 2>/dev/null | awk 'NR == 2 { print $1 }')
+ loader_dir=$(dirname "$candidate")
+ candidate_rpaths=()
+
+ if [[ -n ${EXPECTED_ARCHS:-} ]]; then
+ candidate_archs=" $(lipo -archs "$candidate") "
+ for expected_arch in $EXPECTED_ARCHS; do
+ if [[ $candidate_archs != *" $expected_arch "* ]]; then
+ echo "Missing architecture $expected_arch in $candidate (has:${candidate_archs})" >&2
+ dependency_errors=1
+ fi
+ done
+ fi
+
+ if [[ -n ${EXPECTED_MIN_MACOS:-} ]]; then
+ while IFS= read -r minimum_macos; do
+ if ! version_is_at_most "$minimum_macos" "$EXPECTED_MIN_MACOS"; then
+ echo "$candidate requires macOS $minimum_macos (maximum allowed is $EXPECTED_MIN_MACOS)" >&2
+ dependency_errors=1
+ fi
+ done < <(vtool -show-build "$candidate" 2>/dev/null | awk '$1 == "minos" { print $2 }')
+ fi
+
+ while IFS= read -r rpath; do
+ [[ -z $rpath ]] && continue
+ candidate_rpaths+=("$rpath")
+ case "$rpath" in
+ @*) ;;
+ *)
+ echo "External rpath in $candidate: $rpath" >&2
+ dependency_errors=1
+ ;;
+ esac
+ done < <(otool -l "$candidate" | awk '/cmd LC_RPATH/ { getline; getline; print $2 }')
+
+ while IFS= read -r dependency; do
+ [[ -n $install_name && $dependency == "$install_name" ]] && continue
+ case "$dependency" in
+ @executable_path/*|@loader_path/*)
+ resolved_dependency=$(expand_runtime_path "$dependency" "$loader_dir")
+ if [[ ! -e $resolved_dependency ]]; then
+ echo "Unresolved dependency in $candidate: $dependency" >&2
+ dependency_errors=1
+ fi
+ ;;
+ @rpath/*)
+ dependency_suffix=${dependency#@rpath/}
+ dependency_found=0
+ for rpath in "${candidate_rpaths[@]}" "${executable_rpaths[@]}"; do
+ [[ -z $rpath ]] && continue
+ expanded_rpath=$(expand_runtime_path "$rpath" "$loader_dir")
+ if [[ -e "$expanded_rpath/$dependency_suffix" ]]; then
+ dependency_found=1
+ break
+ fi
+ done
+ if [[ $dependency_found -eq 0 ]]; then
+ echo "Unresolved dependency in $candidate: $dependency" >&2
+ dependency_errors=1
+ fi
+ ;;
+ /System/Library/*|/usr/lib/*) ;;
+ @*)
+ echo "Unsupported loader token in $candidate: $dependency" >&2
+ dependency_errors=1
+ ;;
+ *)
+ echo "External dependency in $candidate: $dependency" >&2
+ dependency_errors=1
+ ;;
+ esac
+ done < <(otool -L "$candidate" | awk 'NR > 1 { print $1 }')
+done < <(find "$contents" -type f -print0)
+[[ $dependency_errors -eq 0 ]] || fail "bundle contains dependencies outside the application or macOS"
+
+while IFS= read -r -d '' symlink; do
+ [[ -e $symlink ]] || fail "bundle contains a broken symlink: $symlink"
+done < <(find "$app" -type l -print0)
+
+for private_path in "${SOURCE_ROOT:-}" "${HOME:-}"; do
+ [[ -n $private_path && $private_path != / ]] || continue
+ if LC_ALL=C grep -R -a -F -q -- "$private_path" "$app"; then
+ fail "bundle contains a private build path"
+ fi
+done
+
+if [[ -n ${SOURCE_BRANCH:-} ]] && LC_ALL=C grep -R -a -F -q -- "$SOURCE_BRANCH" "$app"; then
+ fail "bundle contains the local source branch name"
+fi
+
+if find "$app" \( -name '.DS_Store' -o -name '._*' \) -print -quit | grep -q .; then
+ fail "bundle contains Finder or AppleDouble metadata"
+fi
+
+if find "$app" \
+ \( -iname '*.log' \
+ -o -iname 'accounts.json' \
+ -o -iname 'launcher_accounts.json' \
+ -o -iname 'pollymc.cfg' \
+ -o -iname 'prismlauncher.cfg' \
+ -o -iname 'instance.cfg' \
+ -o -type d -iname 'logs' \
+ -o -type d -iname 'instances' \
+ -o -type d -iname 'crash-reports' \) \
+ -print -quit | grep -q .; then
+ fail "bundle contains local launcher data or logs"
+fi
+
+codesign --verify --deep --strict --verbose=2 "$app"
+echo "Verified portable macOS bundle: $app ($bundle_version)"
diff --git a/program_info/AdhocSignedApp.entitlements b/program_info/AdhocSignedApp.entitlements
new file mode 100644
index 0000000..032308a
--- /dev/null
+++ b/program_info/AdhocSignedApp.entitlements
@@ -0,0 +1,12 @@
+
+
+
+
+ com.apple.security.cs.disable-library-validation
+
+ com.apple.security.device.audio-input
+
+ com.apple.security.device.camera
+
+
+
diff --git a/program_info/App.entitlements b/program_info/App.entitlements
new file mode 100644
index 0000000..73bf832
--- /dev/null
+++ b/program_info/App.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.device.audio-input
+
+ com.apple.security.device.camera
+
+
+
diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json
new file mode 100644
index 0000000..2081163
--- /dev/null
+++ b/vcpkg-configuration.json
@@ -0,0 +1,20 @@
+{
+ "default-registry": {
+ "kind": "git",
+ "baseline": "2d6a6cf3ac9a7cc93942c3d289a2f9c661a6f4a7",
+ "repository": "https://github.com/microsoft/vcpkg"
+ },
+ "registries": [
+ {
+ "kind": "artifact",
+ "location": "https://github.com/microsoft/vcpkg-ce-catalog/archive/refs/heads/main.zip",
+ "name": "microsoft"
+ }
+ ],
+ "overlay-ports": [
+ "./cmake/vcpkg-ports"
+ ],
+ "overlay-triplets": [
+ "./cmake/vcpkg-triplets"
+ ]
+}
diff --git a/vcpkg.json b/vcpkg.json
new file mode 100644
index 0000000..aad72d6
--- /dev/null
+++ b/vcpkg.json
@@ -0,0 +1,30 @@
+{
+ "dependencies": [
+ {
+ "name": "ecm",
+ "host": true
+ },
+ {
+ "name": "libqrencode",
+ "default-features": false
+ },
+ {
+ "name": "pkgconf",
+ "host": true
+ },
+ "cmark",
+ {
+ "name": "libarchive",
+ "default-features": false,
+ "features": [
+ "bzip2",
+ "lz4",
+ "lzma",
+ "lzo",
+ "zstd"
+ ]
+ },
+ "tomlplusplus",
+ "zlib"
+ ]
+}