From 20d5465eb0e3a30cb55156ab6b81bbed61bbf3ca Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 14:24:06 +0200 Subject: [PATCH 01/33] migrate from other PR --- .github/workflows/build.yml | 166 ++++++------------------ CMakeLists.txt | 1 + cmake/FindWrapOpenGL.cmake | 43 +++++++ configure.sh | 247 ++++++++++++++++++++---------------- test/CMakeLists.txt | 2 - 5 files changed, 218 insertions(+), 241 deletions(-) create mode 100644 cmake/FindWrapOpenGL.cmake diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e77c7004..71f62a221 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,6 @@ -# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. -# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml +# Build matrix: each platform job installs Qt, runs ./configure.sh (which +# handles deps, qtapng, themes, cmake+ninja, and windeployqt on Windows), +# then deploys + uploads. name: CI Build on: @@ -28,84 +29,39 @@ jobs: - uses: actions/checkout@master with: submodules: recursive - + - name: Install Qt uses: jurplel/install-qt-action@v4 with: version: 6.5.3 target: desktop arch: win64_mingw + tools: 'tools_mingw1310' cache: true cache-key-prefix: install-qt-action modules: 'qtimageformats qtwebsockets' - - name: Install Windows Discord RPC - shell: bash - run: | - curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-win.zip -o discord_rpc.zip - unzip discord_rpc.zip - cp ./discord-rpc/win64-dynamic/lib/discord-rpc.lib ./lib/ - cp ./discord-rpc/win64-dynamic/bin/discord-rpc.dll ./bin/ - cp ./discord-rpc/win64-dynamic/include/discord*.h ./lib/ - - - name: Install Windows BASS + - name: Configure and build shell: bash - run: | - curl http://www.un4seen.com/files/bass24.zip -o bass.zip - unzip -d bass -o bass.zip - cp ./bass/c/bass.h ./lib - cp ./bass/c/x64/bass.lib ./lib/ - cp ./bass/x64/bass.dll ./bin/ - - curl http://www.un4seen.com/files/bassopus24.zip -o bassopus.zip - unzip -d bass -o bassopus.zip - cp ./bass/c/bassopus.h ./lib - cp ./bass/c/x64/bassopus.lib ./lib/ - cp ./bass/x64/bassopus.dll ./bin/ - - - name: Clone Apng plugin - uses: actions/checkout@master - with: - repository: jurplel/QtApng - path: ./qtapng - - - name: Build Apng plugin - run: | - cd ./qtapng - cmake . -G "MinGW Makefiles" - cmake --build . --config Release - mkdir -p ${{ github.workspace }}/bin/imageformats/ - cp plugins/imageformats/qapng.dll ${{ github.workspace }}/bin/imageformats/qapng.dll + run: ./configure.sh QT_PATH="$QT_ROOT_DIR" BUILD_TYPE=Release - - name: Build - run: | - cmake . -G "MinGW Makefiles" -D CMAKE_BUILD_TYPE=Release - cmake --build . --config Release - - - name: Deploy Windows - working-directory: ${{github.workspace}}/bin/ + - name: Run tests shell: bash - run: | - windeployqt --no-quick-import --no-translations --no-compiler-runtime --no-opengl-sw ./Attorney_Online.exe + run: ctest --output-on-failure - - name: Clone Themes - uses: actions/checkout@master - with: - repository: AttorneyOnline/AO2-Themes - path: "bin/base/themes" - - - name: Cleanup Themes Checkout + - name: Stage MinGW runtime DLLs + shell: pwsh run: | - rm ./bin/base/themes/.gitignore - rm ./bin/base/themes/.gitattributes - Remove-Item -Path "./bin/base/themes/.git" -Recurse -Force + Copy-Item "$Env:IQTA_TOOLS\mingw1310_64\bin\libgcc_s_seh-1.dll" bin\ + Copy-Item "$Env:IQTA_TOOLS\mingw1310_64\bin\libstdc++-6.dll" bin\ + Copy-Item "$Env:IQTA_TOOLS\mingw1310_64\bin\libwinpthread-1.dll" bin\ - name: Upload Artifact uses: actions/upload-artifact@master with: name: Attorney_Online-Windows - path: ${{github.workspace}}/bin - + path: ${{ github.workspace }}/bin + build-linux: needs: formatting-check runs-on: ubuntu-22.04 @@ -126,101 +82,53 @@ jobs: cache-key-prefix: install-qt-action modules: 'qtimageformats qtwebsockets' - - name: Install Linux Discord RPC - run: | - curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip -o discord_rpc.zip - unzip discord_rpc.zip - cp ./discord-rpc/linux-dynamic/lib/libdiscord-rpc.so ./lib/ - cp ./discord-rpc/linux-dynamic/lib/libdiscord-rpc.so ./bin/ - cp ./discord-rpc/linux-dynamic/include/discord*.h ./src/ + - name: Install system build tools + run: sudo apt-get update && sudo apt-get install -y ninja-build patchelf libxcb-cursor0 - - name: Install Linux BASS - run: | - curl http://www.un4seen.com/files/bass24-linux.zip -o bass.zip - unzip -d bass -o bass.zip - cp ./bass/c/bass.h ./lib - cp ./bass/libs/x86_64/libbass.so ./lib/ - cp ./bass/libs/x86_64/libbass.so ./bin/ - - curl http://www.un4seen.com/files/bassopus24-linux.zip -o bassopus.zip - unzip -d bass -o bassopus.zip - cp ./bass/c/bassopus.h ./lib - cp ./bass/libs/x86_64/libbassopus.so ./lib/ - cp ./bass/libs/x86_64/libbassopus.so ./bin/ - - - name: Clone Apng plugin - uses: actions/checkout@master - with: - repository: jurplel/QtApng - path: ./qtapng + - name: Configure and build + run: ./configure.sh QT_PATH="$QT_ROOT_DIR" BUILD_TYPE=Release - - name: Build Apng plugin - run: | - cd ./qtapng - cmake . -D CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE="${{ github.workspace }}/bin/imageformats/" - cmake --build . --config Release + - name: Run tests + env: + QT_QPA_PLATFORM: offscreen + run: ctest --output-on-failure - # install plugin - cp plugins/imageformats/libqapng.so ${QT_ROOT_DIR}/plugins/imageformats - - - name: Build + - name: Stage APNG plugin for AppImage run: | - cmake . - cmake --build . --config Release - - - name: Clone Themes - uses: actions/checkout@master - with: - repository: AttorneyOnline/AO2-Themes - path: "bin/base/themes" + mkdir -p "$QT_ROOT_DIR/plugins/imageformats" + cp qtapng/plugins/imageformats/libqapng.so "$QT_ROOT_DIR/plugins/imageformats/" - - name: Cleanup Themes Checkout - run: | - rm ./bin/base/themes/.gitignore - rm ./bin/base/themes/.gitattributes - rm -r ./bin/base/themes/.git - - - name: Deploy Linux + - name: Package dynamic tarball shell: bash run: | - cd ${{ github.workspace }}/bin - mkdir ./imageformats - cp ../qtapng/plugins/imageformats/libqapng.so ./imageformats + cd "${{ github.workspace }}/bin" cp ../data/logo-client.png ./icon.png cp ../README_LINUX.md . cp ../scripts/DYNAMIC_INSTALL.sh ./INSTALL.sh - chmod +x INSTALL.sh - chmod +x Attorney_Online - + chmod +x INSTALL.sh Attorney_Online patchelf --add-rpath . Attorney_Online - cd .. tar --transform='flags=r;s|bin|Attorney Online|' -cvf Attorney_Online-Dynamic.tar bin - - name: Create AppImage + - name: Build AppImage shell: bash run: | - # necessary, apparently - sudo apt install libxcb-cursor0 # from https://github.com/probonopd/go-appimage/blob/master/src/appimagetool/README.md wget -c https://github.com/$(wget -q https://github.com/probonopd/go-appimage/releases/expanded_assets/continuous -O - | grep "appimagetool-.*-x86_64.AppImage" | head -n 1 | cut -d '"' -f 2) mv appimagetool-*-x86_64.AppImage appimagetool chmod +x appimagetool - mkdir -p AppDir/usr/bin - mkdir -p AppDir/usr/lib/plugins/imageformats - mkdir -p AppDir/usr/share/applications - + mkdir -p AppDir/usr/bin AppDir/usr/lib/plugins/imageformats AppDir/usr/share/applications cp bin/Attorney_Online AppDir/usr/bin - cp bin/lib*.so AppDir/usr/lib + cp bin/lib*.so AppDir/usr/lib 2>/dev/null || true cp scripts/Attorney_Online.desktop AppDir/usr/share/applications cp data/logo-client.png AppDir/Attorney_Online.png GIT_SHORT_SHA="${GITHUB_SHA::8}" - QTDIR=${QT_ROOT_DIR} ./appimagetool deploy AppDir/usr/share/applications/Attorney_Online.desktop + QTDIR="$QT_ROOT_DIR" ./appimagetool deploy AppDir/usr/share/applications/Attorney_Online.desktop ARCH=x86_64 VERSION=${GIT_SHORT_SHA} ./appimagetool AppDir - - name: Deploy AppImage + - name: Package AppImage tarball shell: bash run: | mkdir bin-appimage @@ -229,9 +137,7 @@ jobs: cp README_LINUX.md bin-appimage cp scripts/APPIMAGE_INSTALL.sh bin-appimage/INSTALL.sh cp Attorney_Online-*-x86_64.AppImage bin-appimage - chmod +x bin-appimage/INSTALL.sh - chmod +x bin-appimage/Attorney_Online-*-x86_64.AppImage - + chmod +x bin-appimage/INSTALL.sh bin-appimage/Attorney_Online-*-x86_64.AppImage tar --transform='flags=r;s|bin-appimage|Attorney Online|' -cvf Attorney_Online-AppImage.tar bin-appimage - name: Upload Dynamic Artifact diff --git a/CMakeLists.txt b/CMakeLists.txt index ebaaae253..07f659df9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,6 +135,7 @@ if(AO_ENABLE_DISCORD_RPC) endif() if(AO_BUILD_TESTS) + enable_testing() add_subdirectory(test) endif() diff --git a/cmake/FindWrapOpenGL.cmake b/cmake/FindWrapOpenGL.cmake new file mode 100644 index 000000000..737b0b2ac --- /dev/null +++ b/cmake/FindWrapOpenGL.cmake @@ -0,0 +1,43 @@ +# Override for Qt's FindWrapOpenGL.cmake on macOS. +# +# Qt 6.5–6.8's bundled FindWrapOpenGL.cmake unconditionally adds +# `-framework AGL` to QtGui's public link interface. AGL was removed from +# the macOS SDK around Xcode 16 / macOS 15, so the link fails with +# "framework 'AGL' not found". Fixed upstream in Qt 6.9. +# +# This file is a copy of Qt's module with the single AGL target_link_libraries +# line removed. It is picked up via CMAKE_MODULE_PATH before Qt's copy. +# +# Original: Copyright (C) 2022 The Qt Company Ltd. SPDX BSD-3-Clause. + +if(TARGET WrapOpenGL::WrapOpenGL) + set(WrapOpenGL_FOUND ON) + return() +endif() + +set(WrapOpenGL_FOUND OFF) + +find_package(OpenGL ${WrapOpenGL_FIND_VERSION}) + +if (OpenGL_FOUND) + set(WrapOpenGL_FOUND ON) + + add_library(WrapOpenGL::WrapOpenGL INTERFACE IMPORTED) + if(APPLE) + get_target_property(__opengl_fw_lib_path OpenGL::GL IMPORTED_LOCATION) + if(__opengl_fw_lib_path AND NOT __opengl_fw_lib_path MATCHES "/([^/]+)\\.framework$") + get_filename_component(__opengl_fw_path "${__opengl_fw_lib_path}" DIRECTORY) + endif() + + if(NOT __opengl_fw_path) + set(__opengl_fw_path "-framework OpenGL") + endif() + + target_link_libraries(WrapOpenGL::WrapOpenGL INTERFACE ${__opengl_fw_path}) + else() + target_link_libraries(WrapOpenGL::WrapOpenGL INTERFACE OpenGL::GL) + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(WrapOpenGL DEFAULT_MSG WrapOpenGL_FOUND) diff --git a/configure.sh b/configure.sh index ebb8b16e4..a3c3b3521 100755 --- a/configure.sh +++ b/configure.sh @@ -16,17 +16,27 @@ detect_platform() { echo "${platform}" } +detect_arch() { + case "$(uname -m)" in + x86_64|amd64) echo "x86_64";; + arm64|aarch64) echo "arm64";; + *) echo "unknown";; + esac +} + # Basic data such as platform can be global PLATFORM=$(detect_platform) +ARCH=$(detect_arch) BUILD_CONFIG="Debug" -QT_VERSION="6.5.3" +QT_MIN_VERSION="6.5.0" print_help() { echo "Usage: $0 [options]" echo "Options:" echo " -h, --help: Print this help message" echo " clean: Remove all files from lib, bin and tmp" - echo " QT_ROOT=path: Specify the root path to where Qt is installed (eg. /c/Qt/)" + echo " QT_PATH=path: Use this Qt toolchain directly, skipping auto-detection (eg. /c/Qt/6.5.3/mingw_64)" + echo " BUILD_TYPE=Debug|Release: CMake build type (default: Debug)" } # Check if a given command returns a non-zero exit code @@ -42,30 +52,16 @@ check_command() { } find_qt() { + # Auto-detect the Qt root by checking common install locations. + # Emits the path on stdout, or empty string if nothing was found. local qt_root="" - - # Function to check if a dir exists - check_path() { - if [[ -d "$1" ]]; then - qt_root="$1" - return 0 - else - return 1 - fi - } - - # Check common Qt installation paths on different OSes if [[ "$PLATFORM" == "windows" ]]; then - # Windows paths, maybe check for more in the future - check_path "/c/Qt" - elif [[ "$PLATFORM" == "linux" ]]; then - check_path "$HOME/Qt" - elif [[ "$PLATFORM" == "macos" ]]; then - check_path "$HOME/Qt" + qt_root="/c/Qt" + else + qt_root="$HOME/Qt" fi - # If qt-cmake is found, print the path - if [[ -n "$qt_root" ]]; then + if [[ -d "$qt_root" ]]; then echo "$qt_root" else echo "" @@ -73,91 +69,96 @@ find_qt() { } find_qtpath() { - local qt_path="" - - check_path() { - if [[ -d "$1" ]]; then - qt_path="$1" - return 0 - else - return 1 - fi - } - + # Pick the newest installed Qt under $QT_ROOT whose version is at least + # QT_MIN_VERSION and which has the toolchain subdir for this platform. + local toolchain="" if [[ "$PLATFORM" == "windows" ]]; then - check_path "${QT_ROOT}/${QT_VERSION}/mingw_64" + toolchain="mingw_64" elif [[ "$PLATFORM" == "linux" ]]; then - check_path "${QT_ROOT}/${QT_VERSION}/gcc_64" + toolchain="gcc_64" elif [[ "$PLATFORM" == "macos" ]]; then - check_path "${QT_ROOT}/${QT_VERSION}/macos" + toolchain="macos" fi - echo "$qt_path" + local best_ver="" + local best_path="" + + shopt -s nullglob + local dir ver + for dir in "$QT_ROOT"/*/ ; do + ver=$(basename "$dir") + [[ "$ver" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || continue + [[ -d "${dir}${toolchain}" ]] || continue + # Skip versions below the floor + if [[ "$(printf '%s\n%s\n' "$QT_MIN_VERSION" "$ver" | sort -V | head -n 1)" != "$QT_MIN_VERSION" ]]; then + continue + fi + if [[ -z "$best_ver" || "$(printf '%s\n%s\n' "$best_ver" "$ver" | sort -V | tail -n 1)" == "$ver" ]]; then + best_ver="$ver" + best_path="${dir}${toolchain}" + fi + done + shopt -u nullglob + + echo "$best_path" } find_cmake() { + # Prefer the cmake bundled with Qt; emits the path on stdout, or empty + # string if none is bundled (the caller falls back to cmake on PATH). local cmake_path="" - - # Function to check if a file exists - check_path() { - if [[ -f "$1" ]]; then - cmake_path="$1" - return 0 - else - return 1 - fi - } - - # See if we can find the cmake bundled with Qt if [[ "$PLATFORM" == "windows" ]]; then - check_path "${QT_ROOT}/Tools/CMake_64/bin/cmake.exe" + cmake_path="${QT_ROOT}/Tools/CMake_64/bin/cmake.exe" elif [[ "$PLATFORM" == "linux" ]]; then - check_path "${QT_ROOT}/Tools/CMake/bin/cmake" + cmake_path="${QT_ROOT}/Tools/CMake/bin/cmake" elif [[ "$PLATFORM" == "macos" ]]; then - check_path "${QT_ROOT}/Tools/CMake/CMake.app/Contents/bin/cmake" - else - echo "Unsupported platform: ${PLATFORM}" - return 1 + cmake_path="${QT_ROOT}/Tools/CMake/CMake.app/Contents/bin/cmake" fi - # If cmake is found, print the path - if [[ -n "$cmake_path" ]]; then + if [[ -f "$cmake_path" ]]; then echo "$cmake_path" - return 0 else echo "" - return 1 fi } find_mingw() { - # Find a mingw installation bundled with Qt - - QT_TOOLS_PATH="${QT_ROOT}/Tools" - - mingw_dir=$(find "${QT_TOOLS_PATH}" -maxdepth 1 -type d -name "mingw*" -print0 | xargs -0 ls -td | head -n 1) - - # Find returns . if the directory is not found - if [[ "$mingw_dir" == "." ]]; then - mingw_dir="" + # Find the newest MinGW installation bundled under ${QT_ROOT}/Tools/. + # Emits the path on stdout, or empty if the Tools dir or mingw dir is absent. + local tools_path="${QT_ROOT}/Tools" + if [[ ! -d "$tools_path" ]]; then + echo "" + return 0 fi + local mingw_dir="" + mingw_dir=$(find "$tools_path" -maxdepth 1 -type d -name "mingw*" -print0 \ + | xargs -0 -r ls -td 2>/dev/null \ + | head -n 1) + echo "$mingw_dir" } find_ninja() { - # Find a ninja installation bundled with Qt - QT_TOOLS_PATH="${QT_ROOT}/Tools" - - local ninja_path="" - + # Prefer the ninja bundled with Qt, fall back to ninja on PATH. + local bundled="" if [[ "$PLATFORM" == "windows" ]]; then - ninja_path="${QT_TOOLS_PATH}/Ninja/ninja.exe" + bundled="${QT_ROOT}/Tools/Ninja/ninja.exe" else - ninja_path="${QT_TOOLS_PATH}/Ninja/ninja" + bundled="${QT_ROOT}/Tools/Ninja/ninja" + fi + + if [[ -f "$bundled" ]]; then + echo "$bundled" + return 0 + fi + + if command -v ninja >/dev/null 2>&1; then + echo "ninja" + return 0 fi - echo "$ninja_path" + echo "" } get_zip() { @@ -192,10 +193,13 @@ get_zip() { return 1 fi - # First, check that all the specified files exist in the zip archive + # First, check that all the specified files exist in the zip archive. + # Snapshot the listing into a variable — piping to `grep -q` under + # `set -o pipefail` can trip SIGPIPE on `unzip` and spuriously fail. + zip_listing=$(unzip -l "$tmp_zip") for arg in "$@" ; do src_file="${arg%%:*}" - if ! unzip -l "$tmp_zip" | grep -q "$src_file"; then + if ! grep -q "$src_file" <<< "$zip_listing"; then echo "Error: The file '$src_file' does not exist in the zip archive $tmp_zip." return 1 fi @@ -210,7 +214,7 @@ get_zip() { # Create the destination directory if it doesn't exist mkdir -p "$dst_dir" - unzip -j "$tmp_zip" "$src_file" -d "$dst_dir" + unzip -o -j "$tmp_zip" "$src_file" -d "$dst_dir" shift done @@ -293,10 +297,18 @@ get_discordrpc() { discord-rpc/linux-dynamic/include/discord_rpc.h:./lib \ discord-rpc/linux-dynamic/include/discord_register.h:./lib elif [[ "$PLATFORM" == "macos" ]]; then - get_zip https://github.com/discord/discord-rpc/releases/download/v3.4.0/discord-rpc-osx.zip \ - discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib:./lib \ - discord-rpc/osx-dynamic/include/discord_rpc.h:./lib \ - discord-rpc/osx-dynamic/include/discord_rpc.h:./lib + if [[ "$ARCH" == "x86_64" ]]; then + get_zip https://github.com/discord/discord-rpc/releases/download/v3.4.0/discord-rpc-osx.zip \ + discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib:./lib \ + discord-rpc/osx-dynamic/include/discord_rpc.h:./lib \ + discord-rpc/osx-dynamic/include/discord_register.h:./lib + else + # The official discord-rpc v3.4.0 release only ships an x86_64 + # dylib and the repo was archived in 2018, so there is no native + # arm64 build. Skip the download — Discord RPC is disabled at + # build time on arm64 macOS via -DAO_ENABLE_DISCORD_RPC=OFF below. + echo "Skipping Discord RPC on macOS ${ARCH} (no native binary available)." + fi fi } @@ -333,6 +345,7 @@ get_qtapng() { -G Ninja \ -DCMAKE_MAKE_PROGRAM="$NINJA" \ -DCMAKE_PREFIX_PATH="$QT_PATH" \ + -DCMAKE_MODULE_PATH="${SCRIPT_DIR}/cmake" \ -DCMAKE_C_COMPILER="$CC" \ -DCMAKE_CXX_COMPILER="$CXX" @@ -387,31 +400,38 @@ configure() { exit 1 fi - # Now we look for qt - QT_ROOT="" - - # If QT_ROOT=path is passed, use that - if [ "$#" -gt 0 ] && [ "${1%%=*}" = "QT_ROOT" ]; then - QT_ROOT="${1#*=}" + # Parse KEY=VALUE overrides + QT_PATH="" + while [ "$#" -gt 0 ]; do + case "$1" in + QT_PATH=*) QT_PATH="${1#*=}" ;; + BUILD_TYPE=*) BUILD_CONFIG="${1#*=}" ;; + *) echo "Unknown argument: $1"; print_help; exit 1 ;; + esac shift - # Try to find it otherwise + done + + # Resolve QT_PATH: explicit override wins, otherwise auto-detect under $HOME/Qt. + # QT_ROOT is the parent of the version dir, derived from QT_PATH. Tools/ lives + # under it (find_cmake / find_mingw / find_ninja look there). + if [ -n "$QT_PATH" ]; then + if [ ! -d "$QT_PATH" ]; then + echo "$QT_PATH is not a directory. Aborting." + exit 1 + fi + QT_ROOT="$(cd "$QT_PATH/../.." && pwd)" else QT_ROOT=$(find_qt) if [ -z "$QT_ROOT" ]; then echo "Qt not found. Aborting."; exit 1; fi - fi - if [ ! -d "$QT_ROOT" ]; then - echo "$QT_ROOT is not a directory. Aborting." - exit 1 + QT_PATH=$(find_qtpath) + if [ -z "$QT_PATH" ] || [ ! -d "$QT_PATH" ]; then + echo "No Qt >= ${QT_MIN_VERSION} found under ${QT_ROOT}. Aborting." + exit 1 + fi fi echo "Using Qt root: $QT_ROOT" - - QT_PATH=$(find_qtpath) - if [ ! -d "$QT_PATH" ]; then - echo "$QT_PATH is not a directory. Aborting." - exit 1 - fi echo "Using Qt installation: $QT_PATH" # Check for cmake, and prefer the one bundled with Qt @@ -428,20 +448,21 @@ configure() { check_command "$CMAKE" --version || { echo "cmake not working. Aborting."; exit 1; } echo "Using cmake: $CMAKE" - # Find the compiler bundled in Qt + # Prefer the MinGW bundled with Qt on Windows; fall back to gcc/g++ on PATH. + # On non-Windows platforms the system compiler is usually safe. CC="" CXX="" - # If we're on Windows, find mingw if [[ "$PLATFORM" == "windows" ]]; then MINGW_PATH=$(find_mingw) - if [ -z "$MINGW_PATH" ]; then - echo "MinGW not found. Aborting." - exit 1 + if [ -n "$MINGW_PATH" ]; then + CC="${MINGW_PATH}/bin/gcc.exe" + CXX="${MINGW_PATH}/bin/g++.exe" + else + echo "No MinGW bundled with Qt found. Trying PATH..." + CC="gcc" + CXX="g++" fi - CC="${MINGW_PATH}/bin/gcc.exe" - CXX="${MINGW_PATH}/bin/g++.exe" else - # On non-Windows platforms, use the system compiler, as it's usually safe CC="gcc" CXX="g++" fi @@ -472,15 +493,23 @@ configure() { get_qtapng get_themes + # Discord RPC has no native arm64 macOS binary, so turn it off there. + EXTRA_CMAKE_FLAGS="" + if [[ "$PLATFORM" == "macos" && "$ARCH" != "x86_64" ]]; then + EXTRA_CMAKE_FLAGS="-DAO_ENABLE_DISCORD_RPC=OFF" + fi + # Typically, IDEs like running cmake themselves, but we need the binary to fix dependencies correctly FULL_CMAKE_CMD="\ $CMAKE . \ -G Ninja \ -DCMAKE_MAKE_PROGRAM=${NINJA} \ -DCMAKE_PREFIX_PATH=${QT_PATH} \ +-DCMAKE_MODULE_PATH=${SCRIPT_DIR}/cmake \ -DCMAKE_BUILD_TYPE=${BUILD_CONFIG} \ -DCMAKE_C_COMPILER=${CC} \ --DCMAKE_CXX_COMPILER=${CXX}" +-DCMAKE_CXX_COMPILER=${CXX} \ +${EXTRA_CMAKE_FLAGS}" $FULL_CMAKE_CMD $NINJA diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c78740c1c..c9aecd395 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,8 +4,6 @@ find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Test REQUIRED) set(CMAKE_INCLUDE_CURRENT_DIR ON) -enable_testing(true) - set(SKIP_AUTOMOC ON) function(ao_declare_test test_id) From 4da388b90f1b3ba991c1eb7763ba320d5668c6f0 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 15:42:49 +0200 Subject: [PATCH 02/33] Add mac build scripts --- .github/workflows/build.yml | 44 +++++++++++++++++++++++++++++++++++++ CMakeLists.txt | 15 +++++++++++++ configure.sh | 18 +++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 71f62a221..762acce42 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -151,3 +151,47 @@ jobs: with: name: Attorney_Online-Linux-AppImage path: Attorney_Online-AppImage.tar + + build-macos: + # Note that we just do an ARM build here (no Intel release) + needs: formatting-check + runs-on: macos-14 + steps: + - uses: actions/checkout@master + with: + submodules: recursive + + - name: Install Qt + uses: jurplel/install-qt-action@v4 + with: + aqtversion: '==3.1.*' + version: '6.5.3' + host: 'mac' + target: 'desktop' + arch: 'clang_64' + cache: true + cache-key-prefix: install-qt-action + modules: 'qtimageformats qtwebsockets' + + - name: Install system build tools + run: brew install ninja + + - name: Configure and build + run: ./configure.sh QT_PATH="$QT_ROOT_DIR" BUILD_TYPE=Release + + - name: Run tests + env: + QT_QPA_PLATFORM: offscreen + run: ctest --output-on-failure + + - name: Package macOS zip + shell: bash + run: | + cp data/logo-client.png bin/icon.png + cd bin && zip -r -y ../Attorney_Online-macOS.zip Attorney_Online.app base icon.png + + - name: Upload Artifact + uses: actions/upload-artifact@master + with: + name: Attorney_Online-macOS + path: Attorney_Online-macOS.zip diff --git a/CMakeLists.txt b/CMakeLists.txt index 07f659df9..6236fa38a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -142,3 +142,18 @@ endif() set_target_properties(Attorney_Online PROPERTIES LIBRARY_OUTPUT_DIRECTORY $<1:${CMAKE_CURRENT_LIST_DIR}/bin> RUNTIME_OUTPUT_DIRECTORY $<1:${CMAKE_CURRENT_LIST_DIR}/bin>) + +if(APPLE) + set(MACOSX_BUNDLE_ICON "${CMAKE_CURRENT_LIST_DIR}/data/logo-client.icns") + target_sources(Attorney_Online PRIVATE ${MACOSX_BUNDLE_ICON}) + set_source_files_properties(${MACOSX_BUNDLE_ICON} PROPERTIES + MACOSX_PACKAGE_LOCATION Resources) + + set_target_properties(Attorney_Online PROPERTIES + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_BUNDLE_NAME "Attorney Online" + MACOSX_BUNDLE_GUI_IDENTIFIER "org.attorneyonline.Attorney_Online" + MACOSX_BUNDLE_BUNDLE_VERSION "2" + MACOSX_BUNDLE_SHORT_VERSION_STRING "2.0" + MACOSX_BUNDLE_ICON_FILE "logo-client.icns") +endif() diff --git a/configure.sh b/configure.sh index a3c3b3521..bb48bc0bd 100755 --- a/configure.sh +++ b/configure.sh @@ -518,6 +518,24 @@ ${EXTRA_CMAKE_FLAGS}" echo "Fixing dependencies..." windeployqt="${QT_PATH}/bin/windeployqt.exe" "$windeployqt" --no-quick-import --no-translations --no-compiler-runtime --no-opengl-sw ./bin/Attorney_Online.exe + elif [[ "$PLATFORM" == "macos" ]]; then + echo "Bundling dependencies into the .app..." + APP="./bin/Attorney_Online.app" + + # BASS (and BASSOPUS/Discord if present) are loaded via @loader_path, + # i.e. relative to the executable, so they go beside it in Contents/MacOS. + cp ./lib/libbass.dylib ./lib/libbassopus.dylib "${APP}/Contents/MacOS/" + if [ -f ./lib/libdiscord-rpc.dylib ]; then + cp ./lib/libdiscord-rpc.dylib "${APP}/Contents/MacOS/" + fi + + # The APNG image plugin must live under the bundle's PlugIns dir before + # macdeployqt runs, so its Qt references get rewritten along with the rest. + mkdir -p "${APP}/Contents/PlugIns/imageformats" + cp ./bin/imageformats/libqapng.dylib "${APP}/Contents/PlugIns/imageformats/" + + macdeployqt="${QT_PATH}/bin/macdeployqt" + "$macdeployqt" "$APP" -verbose=1 fi echo "Configuration and build complete." From 8d5d83d568426dbc60a268401f876fbab71f5513 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 16:29:16 +0200 Subject: [PATCH 03/33] Fix build process with build scripts --- .github/workflows/build.yml | 100 ++++++------------ .gitignore | 2 + configure.sh | 197 ++++++++++++++++++------------------ scripts/package-linux.sh | 74 ++++++++++++++ scripts/package-macos.sh | 48 +++++++++ scripts/package-windows.sh | 41 ++++++++ 6 files changed, 295 insertions(+), 167 deletions(-) create mode 100755 scripts/package-linux.sh create mode 100755 scripts/package-macos.sh create mode 100755 scripts/package-windows.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 762acce42..1d2a3b7a5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,20 +41,25 @@ jobs: cache-key-prefix: install-qt-action modules: 'qtimageformats qtwebsockets' - - name: Configure and build + - name: Configure shell: bash - run: ./configure.sh QT_PATH="$QT_ROOT_DIR" BUILD_TYPE=Release + env: + BUILD_CONFIG: Release + run: ./configure.sh + + - name: Build + shell: bash + run: | + source build.env + "$NINJA" - name: Run tests shell: bash run: ctest --output-on-failure - - name: Stage MinGW runtime DLLs - shell: pwsh - run: | - Copy-Item "$Env:IQTA_TOOLS\mingw1310_64\bin\libgcc_s_seh-1.dll" bin\ - Copy-Item "$Env:IQTA_TOOLS\mingw1310_64\bin\libstdc++-6.dll" bin\ - Copy-Item "$Env:IQTA_TOOLS\mingw1310_64\bin\libwinpthread-1.dll" bin\ + - name: Package + shell: bash + run: ./scripts/package-windows.sh - name: Upload Artifact uses: actions/upload-artifact@master @@ -82,63 +87,23 @@ jobs: cache-key-prefix: install-qt-action modules: 'qtimageformats qtwebsockets' - - name: Install system build tools - run: sudo apt-get update && sudo apt-get install -y ninja-build patchelf libxcb-cursor0 + - name: Configure + env: + BUILD_CONFIG: Release + run: ./configure.sh - - name: Configure and build - run: ./configure.sh QT_PATH="$QT_ROOT_DIR" BUILD_TYPE=Release + - name: Build + run: | + source build.env + "$NINJA" - name: Run tests env: QT_QPA_PLATFORM: offscreen run: ctest --output-on-failure - - name: Stage APNG plugin for AppImage - run: | - mkdir -p "$QT_ROOT_DIR/plugins/imageformats" - cp qtapng/plugins/imageformats/libqapng.so "$QT_ROOT_DIR/plugins/imageformats/" - - - name: Package dynamic tarball - shell: bash - run: | - cd "${{ github.workspace }}/bin" - cp ../data/logo-client.png ./icon.png - cp ../README_LINUX.md . - cp ../scripts/DYNAMIC_INSTALL.sh ./INSTALL.sh - chmod +x INSTALL.sh Attorney_Online - patchelf --add-rpath . Attorney_Online - cd .. - tar --transform='flags=r;s|bin|Attorney Online|' -cvf Attorney_Online-Dynamic.tar bin - - - name: Build AppImage - shell: bash - run: | - # from https://github.com/probonopd/go-appimage/blob/master/src/appimagetool/README.md - wget -c https://github.com/$(wget -q https://github.com/probonopd/go-appimage/releases/expanded_assets/continuous -O - | grep "appimagetool-.*-x86_64.AppImage" | head -n 1 | cut -d '"' -f 2) - mv appimagetool-*-x86_64.AppImage appimagetool - chmod +x appimagetool - - mkdir -p AppDir/usr/bin AppDir/usr/lib/plugins/imageformats AppDir/usr/share/applications - cp bin/Attorney_Online AppDir/usr/bin - cp bin/lib*.so AppDir/usr/lib 2>/dev/null || true - cp scripts/Attorney_Online.desktop AppDir/usr/share/applications - cp data/logo-client.png AppDir/Attorney_Online.png - - GIT_SHORT_SHA="${GITHUB_SHA::8}" - QTDIR="$QT_ROOT_DIR" ./appimagetool deploy AppDir/usr/share/applications/Attorney_Online.desktop - ARCH=x86_64 VERSION=${GIT_SHORT_SHA} ./appimagetool AppDir - - - name: Package AppImage tarball - shell: bash - run: | - mkdir bin-appimage - cp -r bin/base bin-appimage - cp data/logo-client.png bin-appimage/icon.png - cp README_LINUX.md bin-appimage - cp scripts/APPIMAGE_INSTALL.sh bin-appimage/INSTALL.sh - cp Attorney_Online-*-x86_64.AppImage bin-appimage - chmod +x bin-appimage/INSTALL.sh bin-appimage/Attorney_Online-*-x86_64.AppImage - tar --transform='flags=r;s|bin-appimage|Attorney Online|' -cvf Attorney_Online-AppImage.tar bin-appimage + - name: Package dynamic tarball and AppImage + run: ./scripts/package-linux.sh - name: Upload Dynamic Artifact uses: actions/upload-artifact@master @@ -173,22 +138,23 @@ jobs: cache-key-prefix: install-qt-action modules: 'qtimageformats qtwebsockets' - - name: Install system build tools - run: brew install ninja + - name: Configure + env: + BUILD_CONFIG: Release + run: ./configure.sh - - name: Configure and build - run: ./configure.sh QT_PATH="$QT_ROOT_DIR" BUILD_TYPE=Release + - name: Build + run: | + source build.env + "$NINJA" - name: Run tests env: QT_QPA_PLATFORM: offscreen run: ctest --output-on-failure - - name: Package macOS zip - shell: bash - run: | - cp data/logo-client.png bin/icon.png - cd bin && zip -r -y ../Attorney_Online-macOS.zip Attorney_Online.app base icon.png + - name: Package app + run: ./scripts/package-macos.sh - name: Upload Artifact uses: actions/upload-artifact@master diff --git a/.gitignore b/.gitignore index acb71559c..3d8461dcb 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,7 @@ CMakeCache.txt CMakeFiles/ Testing/ build.ninja +CTestTestfile.cmake cmake_install.cmake test/CMakeFiles/ test/CTestTestfile.cmake @@ -64,3 +65,4 @@ test/cmake_install.cmake test/test_aopacket_autogen/ test/test_aopacket cmake_cmd.txt +build.env diff --git a/configure.sh b/configure.sh index bb48bc0bd..dced15e32 100755 --- a/configure.sh +++ b/configure.sh @@ -24,24 +24,26 @@ detect_arch() { esac } -# Basic data such as platform can be global PLATFORM=$(detect_platform) ARCH=$(detect_arch) -BUILD_CONFIG="Debug" QT_MIN_VERSION="6.5.0" +# BUILD_CONFIG (Debug|Release, default Debug) and QT_ROOT_DIR (the Qt toolchain +# dir; empty auto-detects under ~/Qt) are read from the environment. +BUILD_CONFIG="${BUILD_CONFIG:-Debug}" +QT_ROOT_DIR="${QT_ROOT_DIR:-}" + print_help() { - echo "Usage: $0 [options]" - echo "Options:" + echo "Usage: [BUILD_CONFIG=Debug|Release] $0 [command]" + echo "Commands:" echo " -h, --help: Print this help message" - echo " clean: Remove all files from lib, bin and tmp" - echo " QT_PATH=path: Use this Qt toolchain directly, skipping auto-detection (eg. /c/Qt/6.5.3/mingw_64)" - echo " BUILD_TYPE=Debug|Release: CMake build type (default: Debug)" + echo " clean: Remove everything configure.sh writes (deps, build files, build.env, cmake_cmd.txt)" + echo "Environment variables:" + echo " BUILD_CONFIG=Debug|Release: CMake build type (default: Debug)" } -# Check if a given command returns a non-zero exit code check_command() { - # Hack to not make the whole script exit.. + # set +e around the probe so a failure returns non-zero instead of exiting. set +e if ! "$@" &> /dev/null; then set -e @@ -52,8 +54,7 @@ check_command() { } find_qt() { - # Auto-detect the Qt root by checking common install locations. - # Emits the path on stdout, or empty string if nothing was found. + # Emit the Qt root if a common install location exists, else empty string. local qt_root="" if [[ "$PLATFORM" == "windows" ]]; then qt_root="/c/Qt" @@ -69,8 +70,8 @@ find_qt() { } find_qtpath() { - # Pick the newest installed Qt under $QT_ROOT whose version is at least - # QT_MIN_VERSION and which has the toolchain subdir for this platform. + # Emit the newest Qt >= QT_MIN_VERSION under $QT_ROOT that has this + # platform's toolchain subdir, else empty string. local toolchain="" if [[ "$PLATFORM" == "windows" ]]; then toolchain="mingw_64" @@ -89,7 +90,6 @@ find_qtpath() { ver=$(basename "$dir") [[ "$ver" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || continue [[ -d "${dir}${toolchain}" ]] || continue - # Skip versions below the floor if [[ "$(printf '%s\n%s\n' "$QT_MIN_VERSION" "$ver" | sort -V | head -n 1)" != "$QT_MIN_VERSION" ]]; then continue fi @@ -104,8 +104,7 @@ find_qtpath() { } find_cmake() { - # Prefer the cmake bundled with Qt; emits the path on stdout, or empty - # string if none is bundled (the caller falls back to cmake on PATH). + # Emit the cmake bundled with Qt, else empty (caller falls back to PATH). local cmake_path="" if [[ "$PLATFORM" == "windows" ]]; then cmake_path="${QT_ROOT}/Tools/CMake_64/bin/cmake.exe" @@ -123,8 +122,7 @@ find_cmake() { } find_mingw() { - # Find the newest MinGW installation bundled under ${QT_ROOT}/Tools/. - # Emits the path on stdout, or empty if the Tools dir or mingw dir is absent. + # Emit the newest MinGW bundled under ${QT_ROOT}/Tools/, else empty. local tools_path="${QT_ROOT}/Tools" if [[ ! -d "$tools_path" ]]; then echo "" @@ -162,7 +160,6 @@ find_ninja() { } get_zip() { - # Check if at least two arguments are provided if [ "$#" -lt 2 ]; then echo "Usage: get_zip [ ...]" return 1 @@ -170,16 +167,12 @@ get_zip() { mkdir -p ./tmp - # URL of the zip file url="$1" shift zip_filename=$(basename "$url") - - # Temporary file to store the downloaded zip tmp_zip=./tmp/"$zip_filename" - # Download the zip file curl -L "$url" -o "$tmp_zip" if [ $? -ne 0 ]; then echo "Failed to download the zip file from $url" @@ -187,15 +180,13 @@ get_zip() { return 1 fi - # Sanity check: zip file is there if [ ! -f "$tmp_zip" ]; then echo "Error: The zip file '$tmp_zip' does not exist." return 1 fi - # First, check that all the specified files exist in the zip archive. - # Snapshot the listing into a variable — piping to `grep -q` under - # `set -o pipefail` can trip SIGPIPE on `unzip` and spuriously fail. + # Snapshot the listing first — piping unzip to `grep -q` under pipefail can + # trip SIGPIPE on unzip and spuriously fail. zip_listing=$(unzip -l "$tmp_zip") for arg in "$@" ; do src_file="${arg%%:*}" @@ -205,13 +196,11 @@ get_zip() { fi done - # Extract the specified files to their destinations while [ "$#" -gt 0 ]; do src_dst="$1" src_file="${src_dst%%:*}" dst_dir="${src_dst##*:}" - # Create the destination directory if it doesn't exist mkdir -p "$dst_dir" unzip -o -j "$tmp_zip" "$src_file" -d "$dst_dir" @@ -219,13 +208,11 @@ get_zip() { shift done - # Clean up the temporary zip file rm -rf "$tmp_zip" } get_bass() { echo "Checking for BASS..." - # If lib/bass.h exists, assume that BASS is already present if [ -f "./lib/bass.h" ]; then echo "BASS is installed." return 0 @@ -251,7 +238,6 @@ get_bass() { get_bassopus() { echo "Checking for BASSOPUS..." - # If lib/bassopus.h exists, assume that BASSOPUS is already present if [ -f "./lib/bassopus.h" ]; then echo "BASSOPUS is installed." return 0 @@ -277,7 +263,6 @@ get_bassopus() { get_discordrpc() { echo "Checking for Discord RPC..." - # If lib/discord_rpc.h exists, assume that Discord RPC is already present if [ -f "./lib/discord_rpc.h" ]; then echo "Discord RPC is installed." return 0 @@ -303,10 +288,9 @@ get_discordrpc() { discord-rpc/osx-dynamic/include/discord_rpc.h:./lib \ discord-rpc/osx-dynamic/include/discord_register.h:./lib else - # The official discord-rpc v3.4.0 release only ships an x86_64 - # dylib and the repo was archived in 2018, so there is no native - # arm64 build. Skip the download — Discord RPC is disabled at - # build time on arm64 macOS via -DAO_ENABLE_DISCORD_RPC=OFF below. + # discord-rpc v3.4.0 only ships an x86_64 dylib and the repo was + # archived in 2018, so there is no arm64 build — Discord RPC is + # disabled at build time on arm64 macOS (-DAO_ENABLE_DISCORD_RPC=OFF). echo "Skipping Discord RPC on macOS ${ARCH} (no native binary available)." fi fi @@ -344,7 +328,7 @@ get_qtapng() { $CMAKE . \ -G Ninja \ -DCMAKE_MAKE_PROGRAM="$NINJA" \ - -DCMAKE_PREFIX_PATH="$QT_PATH" \ + -DCMAKE_PREFIX_PATH="$QT_ROOT_DIR" \ -DCMAKE_MODULE_PATH="${SCRIPT_DIR}/cmake" \ -DCMAKE_C_COMPILER="$CC" \ -DCMAKE_CXX_COMPILER="$CXX" @@ -375,66 +359,91 @@ get_themes() { rm -rf ./bin/base/themes/.git } +install_build_tools() { + # Install the non-Qt build tools if missing. The presence check keeps local + # re-runs from invoking sudo/brew; fresh CI runners install here. + if [[ "$PLATFORM" == "linux" ]]; then + if ! command -v ninja >/dev/null 2>&1 || ! command -v patchelf >/dev/null 2>&1; then + echo "Installing system build tools (ninja, patchelf, libxcb-cursor0)..." + sudo apt-get update + sudo apt-get install -y ninja-build patchelf libxcb-cursor0 + fi + elif [[ "$PLATFORM" == "macos" ]]; then + if ! command -v ninja >/dev/null 2>&1; then + echo "Installing system build tools (ninja)..." + brew install ninja + fi + fi +} + +clean() { + echo "Cleaning up all files written by configure.sh..." + + # Downloaded dependencies, cloned qtapng, and build/runtime output. + rm -rf ./lib/* ./bin/* ./tmp/* ./qtapng/ + + # Files configure.sh writes directly. + rm -f ./build.env ./cmake_cmd.txt + + # In-source CMake/Ninja build artifacts. + rm -rf ./.cmake/ ./.qt/ ./CMakeFiles/ ./Attorney_Online_autogen/ ./Testing/ + rm -f ./CMakeCache.txt ./cmake_install.cmake ./CTestTestfile.cmake ./build.ninja ./.ninja_deps ./.ninja_log + rm -rf ./test/CMakeFiles/ ./test/test_aopacket_autogen/ + rm -f ./test/CTestTestfile.cmake ./test/cmake_install.cmake ./test/test_aopacket +} + configure() { - # If -h is passed, print help if [ "$#" -gt 0 ] && { [ "$1" = "-h" ] || [ "$1" = "--help" ]; }; then print_help exit 0 fi - # If clean is passed, remove all files from lib, bin and tmp - if [ "$#" -gt 0 ] && { [ "$1" = "clean" ]; }; then - echo "Cleaning up... removing lib, bin and tmp" - rm -rf ./lib/* - rm -rf ./bin/* - rm -rf ./tmp/* - rm -rf ./qtapng/ + if [ "$#" -gt 0 ] && [ "$1" = "clean" ]; then + clean exit 0 fi echo "Platform: ${PLATFORM}" - # If platform is unknown, terminate if [ "$PLATFORM" == "unknown" ]; then echo "Unknown platform. Aborting." exit 1 fi - # Parse KEY=VALUE overrides - QT_PATH="" - while [ "$#" -gt 0 ]; do - case "$1" in - QT_PATH=*) QT_PATH="${1#*=}" ;; - BUILD_TYPE=*) BUILD_CONFIG="${1#*=}" ;; - *) echo "Unknown argument: $1"; print_help; exit 1 ;; - esac - shift - done + # Only 'clean' and '-h' are arguments (handled above); configuration comes + # from the environment. + if [ "$#" -gt 0 ]; then + echo "Unknown argument: $1" + echo "(Configuration such as BUILD_CONFIG is read from the environment, not arguments.)" + print_help + exit 1 + fi + + install_build_tools - # Resolve QT_PATH: explicit override wins, otherwise auto-detect under $HOME/Qt. - # QT_ROOT is the parent of the version dir, derived from QT_PATH. Tools/ lives - # under it (find_cmake / find_mingw / find_ninja look there). - if [ -n "$QT_PATH" ]; then - if [ ! -d "$QT_PATH" ]; then - echo "$QT_PATH is not a directory. Aborting." + # Resolve the Qt toolchain dir (env value wins, else auto-detect under ~/Qt). + # QT_ROOT is its grandparent — where Tools/ lives, so find_cmake / find_mingw + # / find_ninja look there. + if [ -n "$QT_ROOT_DIR" ]; then + if [ ! -d "$QT_ROOT_DIR" ]; then + echo "$QT_ROOT_DIR is not a directory. Aborting." exit 1 fi - QT_ROOT="$(cd "$QT_PATH/../.." && pwd)" + QT_ROOT="$(cd "$QT_ROOT_DIR/../.." && pwd)" else QT_ROOT=$(find_qt) if [ -z "$QT_ROOT" ]; then echo "Qt not found. Aborting."; exit 1; fi - QT_PATH=$(find_qtpath) - if [ -z "$QT_PATH" ] || [ ! -d "$QT_PATH" ]; then + QT_ROOT_DIR=$(find_qtpath) + if [ -z "$QT_ROOT_DIR" ] || [ ! -d "$QT_ROOT_DIR" ]; then echo "No Qt >= ${QT_MIN_VERSION} found under ${QT_ROOT}. Aborting." exit 1 fi fi echo "Using Qt root: $QT_ROOT" - echo "Using Qt installation: $QT_PATH" + echo "Using Qt installation: $QT_ROOT_DIR" - # Check for cmake, and prefer the one bundled with Qt CMAKE=$(find_cmake) if [ -z "$CMAKE" ]; then echo "No cmake bundled with Qt found. Trying path..." @@ -449,7 +458,6 @@ configure() { echo "Using cmake: $CMAKE" # Prefer the MinGW bundled with Qt on Windows; fall back to gcc/g++ on PATH. - # On non-Windows platforms the system compiler is usually safe. CC="" CXX="" if [[ "$PLATFORM" == "windows" ]]; then @@ -477,16 +485,13 @@ configure() { check_command "$NINJA" --version || { echo "Ninja not working. Aborting"; exit 1; } echo "Using Ninja: $NINJA" - # Check basic dependencies check_command curl --help || { echo "Command curl not found. Aborting"; exit 1; } check_command unzip --help || { echo "Command unzip not found. Aborting"; exit 1; } - # Make sure key folders exist mkdir -p ./tmp/ mkdir -p ./lib/ mkdir -p ./bin/ - # Get the dependencies get_bass get_bassopus get_discordrpc @@ -499,12 +504,11 @@ configure() { EXTRA_CMAKE_FLAGS="-DAO_ENABLE_DISCORD_RPC=OFF" fi - # Typically, IDEs like running cmake themselves, but we need the binary to fix dependencies correctly FULL_CMAKE_CMD="\ $CMAKE . \ -G Ninja \ -DCMAKE_MAKE_PROGRAM=${NINJA} \ --DCMAKE_PREFIX_PATH=${QT_PATH} \ +-DCMAKE_PREFIX_PATH=${QT_ROOT_DIR} \ -DCMAKE_MODULE_PATH=${SCRIPT_DIR}/cmake \ -DCMAKE_BUILD_TYPE=${BUILD_CONFIG} \ -DCMAKE_C_COMPILER=${CC} \ @@ -512,35 +516,28 @@ $CMAKE . \ ${EXTRA_CMAKE_FLAGS}" $FULL_CMAKE_CMD - $NINJA - if [[ "$PLATFORM" == "windows" ]]; then - echo "Fixing dependencies..." - windeployqt="${QT_PATH}/bin/windeployqt.exe" - "$windeployqt" --no-quick-import --no-translations --no-compiler-runtime --no-opengl-sw ./bin/Attorney_Online.exe - elif [[ "$PLATFORM" == "macos" ]]; then - echo "Bundling dependencies into the .app..." - APP="./bin/Attorney_Online.app" - - # BASS (and BASSOPUS/Discord if present) are loaded via @loader_path, - # i.e. relative to the executable, so they go beside it in Contents/MacOS. - cp ./lib/libbass.dylib ./lib/libbassopus.dylib "${APP}/Contents/MacOS/" - if [ -f ./lib/libdiscord-rpc.dylib ]; then - cp ./lib/libdiscord-rpc.dylib "${APP}/Contents/MacOS/" - fi - - # The APNG image plugin must live under the bundle's PlugIns dir before - # macdeployqt runs, so its Qt references get rewritten along with the rest. - mkdir -p "${APP}/Contents/PlugIns/imageformats" - cp ./bin/imageformats/libqapng.dylib "${APP}/Contents/PlugIns/imageformats/" + # Record the resolved environment so the build step and packaging scripts + # can reuse it without re-running detection. + { + echo "PLATFORM=\"${PLATFORM}\"" + echo "ARCH=\"${ARCH}\"" + echo "QT_ROOT_DIR=\"${QT_ROOT_DIR}\"" + echo "NINJA=\"${NINJA}\"" + echo "CMAKE=\"${CMAKE}\"" + echo "BUILD_CONFIG=\"${BUILD_CONFIG}\"" + echo "MINGW_PATH=\"${MINGW_PATH:-}\"" + } > build.env - macdeployqt="${QT_PATH}/bin/macdeployqt" - "$macdeployqt" "$APP" -verbose=1 - fi - - echo "Configuration and build complete." - echo "Full cmake cmd: $FULL_CMAKE_CMD" echo "$FULL_CMAKE_CMD" > cmake_cmd.txt + + # configure.sh stops at a ready-to-build tree; building and packaging are + # separate steps. + echo + echo "Dependencies fetched and build files generated (build type: ${BUILD_CONFIG})." + echo "To compile, run:" + echo + echo " ${NINJA}" } configure "$@" diff --git a/scripts/package-linux.sh b/scripts/package-linux.sh new file mode 100755 index 000000000..af6c8f7ad --- /dev/null +++ b/scripts/package-linux.sh @@ -0,0 +1,74 @@ +#!/bin/bash + +# Packages the Linux build into two distributables at the repo root: +# - Attorney_Online-Dynamic.tar : dynamic build (expects system Qt at runtime) +# - Attorney_Online-AppImage.tar : self-contained AppImage +# +# Prerequisites: run ./configure.sh (fetches deps, generates build files, writes +# build.env) and then build (run the command configure.sh prints). + +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +ROOT_DIR=$(cd "${SCRIPT_DIR}/.." && pwd) +cd "${ROOT_DIR}" + +if [ -f ./build.env ]; then + # shellcheck disable=SC1091 + source ./build.env +fi +: "${QT_ROOT_DIR:?QT_ROOT_DIR not set, run ./configure.sh first (it writes build.env)}" + +if [ ! -f ./bin/Attorney_Online ]; then + echo "Error: ./bin/Attorney_Online not found. Build first with the command configure.sh printed." + exit 1 +fi + +# Stage the APNG plugin into the Qt install so appimagetool bundles it. +mkdir -p "${QT_ROOT_DIR}/plugins/imageformats" +cp ./qtapng/plugins/imageformats/libqapng.so "${QT_ROOT_DIR}/plugins/imageformats/" + +# --- Dynamic tarball (expects system Qt at runtime) --- +( + cd ./bin + cp ../data/logo-client.png ./icon.png + cp ../README_LINUX.md . + cp ../scripts/DYNAMIC_INSTALL.sh ./INSTALL.sh + chmod +x INSTALL.sh Attorney_Online + patchelf --add-rpath . Attorney_Online +) +tar --transform='flags=r;s|bin|Attorney Online|' -cvf Attorney_Online-Dynamic.tar bin + +# --- AppImage (self-contained) --- +# from https://github.com/probonopd/go-appimage/blob/master/src/appimagetool/README.md +wget -c "https://github.com/$(wget -q https://github.com/probonopd/go-appimage/releases/expanded_assets/continuous -O - | grep "appimagetool-.*-x86_64.AppImage" | head -n 1 | cut -d '"' -f 2)" +mv appimagetool-*-x86_64.AppImage appimagetool +chmod +x appimagetool + +mkdir -p AppDir/usr/bin AppDir/usr/lib/plugins/imageformats AppDir/usr/share/applications +cp bin/Attorney_Online AppDir/usr/bin +cp bin/lib*.so AppDir/usr/lib 2>/dev/null || true +cp scripts/Attorney_Online.desktop AppDir/usr/share/applications +cp data/logo-client.png AppDir/Attorney_Online.png + +# Prefer the CI-provided commit SHA; fall back to git (or "dev") for local runs. +GIT_SHORT_SHA="${GITHUB_SHA:-}" +GIT_SHORT_SHA="${GIT_SHORT_SHA:0:8}" +if [ -z "$GIT_SHORT_SHA" ]; then + GIT_SHORT_SHA="$(git rev-parse --short=8 HEAD 2>/dev/null || echo dev)" +fi +QTDIR="$QT_ROOT_DIR" ./appimagetool deploy AppDir/usr/share/applications/Attorney_Online.desktop +ARCH=x86_64 VERSION="${GIT_SHORT_SHA}" ./appimagetool AppDir + +# --- AppImage tarball --- +rm -rf bin-appimage +mkdir bin-appimage +cp -r bin/base bin-appimage +cp data/logo-client.png bin-appimage/icon.png +cp README_LINUX.md bin-appimage +cp scripts/APPIMAGE_INSTALL.sh bin-appimage/INSTALL.sh +cp Attorney_Online-*-x86_64.AppImage bin-appimage +chmod +x bin-appimage/INSTALL.sh bin-appimage/Attorney_Online-*-x86_64.AppImage +tar --transform='flags=r;s|bin-appimage|Attorney Online|' -cvf Attorney_Online-AppImage.tar bin-appimage + +echo "Wrote ${ROOT_DIR}/Attorney_Online-Dynamic.tar and ${ROOT_DIR}/Attorney_Online-AppImage.tar" diff --git a/scripts/package-macos.sh b/scripts/package-macos.sh new file mode 100755 index 000000000..cfb1a79be --- /dev/null +++ b/scripts/package-macos.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# Packages the macOS build into a self-contained, distributable .app + zip. +# +# Prerequisites: run ./configure.sh (fetches deps, generates build files, writes +# build.env) and then build (run the command configure.sh prints). This script +# assembles bin/Attorney_Online.app and zips it alongside the base/ assets. + +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +ROOT_DIR=$(cd "${SCRIPT_DIR}/.." && pwd) +cd "${ROOT_DIR}" + +if [ -f ./build.env ]; then + # shellcheck disable=SC1091 + source ./build.env +fi +: "${QT_ROOT_DIR:?QT_ROOT_DIR not set, run ./configure.sh first (it writes build.env)}" + +APP="./bin/Attorney_Online.app" +if [ ! -d "$APP" ]; then + echo "Error: ${APP} not found. Build first with the command configure.sh printed." + exit 1 +fi + +echo "Bundling dependencies into ${APP}..." + +# BASS (and BASSOPUS/Discord if present) are loaded via @loader_path, i.e. +# relative to the executable, so they go beside it in Contents/MacOS. +cp ./lib/libbass.dylib ./lib/libbassopus.dylib "${APP}/Contents/MacOS/" +if [ -f ./lib/libdiscord-rpc.dylib ]; then + cp ./lib/libdiscord-rpc.dylib "${APP}/Contents/MacOS/" +fi + +# The APNG image plugin must live under the bundle's PlugIns dir before +# macdeployqt runs, so its Qt references get rewritten along with the rest. +mkdir -p "${APP}/Contents/PlugIns/imageformats" +cp ./bin/imageformats/libqapng.dylib "${APP}/Contents/PlugIns/imageformats/" + +"${QT_ROOT_DIR}/bin/macdeployqt" "$APP" -verbose=1 + +# Ship the self-contained app plus the base/ assets, matching the layout of the +# Windows/Linux artifacts. -y preserves the framework symlinks macdeployqt makes. +cp ./data/logo-client.png ./bin/icon.png +( cd ./bin && zip -r -y ../Attorney_Online-macOS.zip Attorney_Online.app base icon.png ) + +echo "Wrote ${ROOT_DIR}/Attorney_Online-macOS.zip" diff --git a/scripts/package-windows.sh b/scripts/package-windows.sh new file mode 100755 index 000000000..7afc9b5aa --- /dev/null +++ b/scripts/package-windows.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# Gathers the Qt runtime beside the built Windows .exe via windeployqt, so bin/ +# is a self-contained, runnable folder. +# +# Prerequisites: run ./configure.sh (fetches deps, generates build files, writes +# build.env) and then build (run the command configure.sh prints). + +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +ROOT_DIR=$(cd "${SCRIPT_DIR}/.." && pwd) +cd "${ROOT_DIR}" + +if [ -f ./build.env ]; then + # shellcheck disable=SC1091 + source ./build.env +fi +: "${QT_ROOT_DIR:?QT_ROOT_DIR not set, run ./configure.sh first (it writes build.env)}" + +EXE="./bin/Attorney_Online.exe" +if [ ! -f "$EXE" ]; then + echo "Error: ${EXE} not found. Build first with the command configure.sh printed." + exit 1 +fi + +echo "Running windeployqt on ${EXE}..." +"${QT_ROOT_DIR}/bin/windeployqt.exe" \ + --no-quick-import --no-translations --no-compiler-runtime --no-opengl-sw "$EXE" + +# windeployqt runs with --no-compiler-runtime because its MinGW runtime +# deployment is unreliable in CI, so stage those DLLs ourselves from MINGW_PATH +# (resolved version-agnostically by configure.sh). Skip when it's unset, e.g. a +# local build where MinGW is already on PATH. +if [ -n "${MINGW_PATH:-}" ]; then + for dll in libgcc_s_seh-1.dll libstdc++-6.dll libwinpthread-1.dll; do + cp "${MINGW_PATH}/bin/${dll}" ./bin/ + done +fi + +echo "Windows runtime staged in ${ROOT_DIR}/bin" From 9cf558bd8cbd8da22e5ddf007d230b895b75d398 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 16:30:08 +0200 Subject: [PATCH 04/33] Cool spacing --- configure.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.sh b/configure.sh index dced15e32..7a54cd369 100755 --- a/configure.sh +++ b/configure.sh @@ -537,7 +537,7 @@ ${EXTRA_CMAKE_FLAGS}" echo "Dependencies fetched and build files generated (build type: ${BUILD_CONFIG})." echo "To compile, run:" echo - echo " ${NINJA}" + echo "${NINJA}" } configure "$@" From f7291a9ab7cb5d32f646732e9fe19b21bc73b026 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 16:36:14 +0200 Subject: [PATCH 05/33] fix apng --- CMakeLists.txt | 14 ++++++++++++++ configure.sh | 6 +++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6236fa38a..62900394b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -156,4 +156,18 @@ if(APPLE) MACOSX_BUNDLE_BUNDLE_VERSION "2" MACOSX_BUNDLE_SHORT_VERSION_STRING "2.0" MACOSX_BUNDLE_ICON_FILE "logo-client.icns") + + # configure.sh builds the APNG image plugin into bin/imageformats. Qt searches + # a bundle's Contents/PlugIns, not bin/, so copy it in at build time. Otherwise + # a plain build fails at launch with "Missing image formats: apng". + # (webp/gif come from the Qt install's own plugins.) + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/bin/imageformats/libqapng.dylib") + add_custom_command(TARGET Attorney_Online POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory + "$/Contents/PlugIns/imageformats" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${CMAKE_CURRENT_LIST_DIR}/bin/imageformats/libqapng.dylib" + "$/Contents/PlugIns/imageformats/" + VERBATIM) + endif() endif() diff --git a/configure.sh b/configure.sh index 7a54cd369..bc4cb5568 100755 --- a/configure.sh +++ b/configure.sh @@ -185,7 +185,7 @@ get_zip() { return 1 fi - # Snapshot the listing first — piping unzip to `grep -q` under pipefail can + # Snapshot the listing first; piping unzip to `grep -q` under pipefail can # trip SIGPIPE on unzip and spuriously fail. zip_listing=$(unzip -l "$tmp_zip") for arg in "$@" ; do @@ -289,7 +289,7 @@ get_discordrpc() { discord-rpc/osx-dynamic/include/discord_register.h:./lib else # discord-rpc v3.4.0 only ships an x86_64 dylib and the repo was - # archived in 2018, so there is no arm64 build — Discord RPC is + # archived in 2018, so there is no arm64 build, so Discord RPC is # disabled at build time on arm64 macOS (-DAO_ENABLE_DISCORD_RPC=OFF). echo "Skipping Discord RPC on macOS ${ARCH} (no native binary available)." fi @@ -422,7 +422,7 @@ configure() { install_build_tools # Resolve the Qt toolchain dir (env value wins, else auto-detect under ~/Qt). - # QT_ROOT is its grandparent — where Tools/ lives, so find_cmake / find_mingw + # QT_ROOT is its grandparent, where Tools/ lives, so find_cmake / find_mingw # / find_ninja look there. if [ -n "$QT_ROOT_DIR" ]; then if [ ! -d "$QT_ROOT_DIR" ]; then From 2549523d332113158d14d1ac2e6a610c1555f15b Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 17:04:23 +0200 Subject: [PATCH 06/33] Fix qt lookup on arm --- configure.sh | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/configure.sh b/configure.sh index bc4cb5568..7d144fde8 100755 --- a/configure.sh +++ b/configure.sh @@ -70,32 +70,53 @@ find_qt() { } find_qtpath() { - # Emit the newest Qt >= QT_MIN_VERSION under $QT_ROOT that has this - # platform's toolchain subdir, else empty string. - local toolchain="" + # Emit the newest Qt >= QT_MIN_VERSION under $QT_ROOT that has a usable + # desktop toolchain for this platform/arch, else empty string. + local -a candidates=() if [[ "$PLATFORM" == "windows" ]]; then - toolchain="mingw_64" - elif [[ "$PLATFORM" == "linux" ]]; then - toolchain="gcc_64" + candidates=(mingw_64) elif [[ "$PLATFORM" == "macos" ]]; then - toolchain="macos" + candidates=(macos) + elif [[ "$PLATFORM" == "linux" ]]; then + if [[ "$ARCH" == "arm64" ]]; then + candidates=(gcc_arm64 arm64) + else + candidates=(gcc_64) + fi fi local best_ver="" local best_path="" shopt -s nullglob - local dir ver + local dir ver tc sub path for dir in "$QT_ROOT"/*/ ; do ver=$(basename "$dir") [[ "$ver" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || continue - [[ -d "${dir}${toolchain}" ]] || continue + + # Prefer a known toolchain dir name; otherwise fall back to any subdir + # that provides the Qt6 CMake package (what we pass as CMAKE_PREFIX_PATH) + # and isn't a cross-compile target (android/wasm/ios). + path="" + for tc in "${candidates[@]}"; do + [[ -d "${dir}${tc}" ]] && { path="${dir}${tc}"; break; } + done + if [[ -z "$path" ]]; then + for sub in "${dir}"*/ ; do + case "$(basename "$sub")" in android*|wasm*|ios*) continue ;; esac + if [[ -d "${sub}lib/cmake/Qt6" ]]; then + path="${sub%/}"; break + fi + done + fi + [[ -n "$path" ]] || continue + if [[ "$(printf '%s\n%s\n' "$QT_MIN_VERSION" "$ver" | sort -V | head -n 1)" != "$QT_MIN_VERSION" ]]; then continue fi if [[ -z "$best_ver" || "$(printf '%s\n%s\n' "$best_ver" "$ver" | sort -V | tail -n 1)" == "$ver" ]]; then best_ver="$ver" - best_path="${dir}${toolchain}" + best_path="$path" fi done shopt -u nullglob From 699449ac9bb4c63750bf093f6a8a5c450b47a79a Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 17:08:05 +0200 Subject: [PATCH 07/33] system pkg on linux --- configure.sh | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/configure.sh b/configure.sh index 7d144fde8..3b2beb1b8 100755 --- a/configure.sh +++ b/configure.sh @@ -384,10 +384,18 @@ install_build_tools() { # Install the non-Qt build tools if missing. The presence check keeps local # re-runs from invoking sudo/brew; fresh CI runners install here. if [[ "$PLATFORM" == "linux" ]]; then - if ! command -v ninja >/dev/null 2>&1 || ! command -v patchelf >/dev/null 2>&1; then - echo "Installing system build tools (ninja, patchelf, libxcb-cursor0)..." + local -a pkgs=() + command -v ninja >/dev/null 2>&1 || pkgs+=(ninja-build) + command -v patchelf >/dev/null 2>&1 || pkgs+=(patchelf) + command -v cmake >/dev/null 2>&1 || pkgs+=(cmake) + command -v curl >/dev/null 2>&1 || pkgs+=(curl) + if [ ${#pkgs[@]} -gt 0 ]; then + # libxcb-cursor0 is a runtime lib with no command to probe; include it + # whenever we install so a fresh runner can launch Qt apps. + pkgs+=(libxcb-cursor0) + echo "Installing system packages: ${pkgs[*]}" sudo apt-get update - sudo apt-get install -y ninja-build patchelf libxcb-cursor0 + sudo apt-get install -y "${pkgs[@]}" fi elif [[ "$PLATFORM" == "macos" ]]; then if ! command -v ninja >/dev/null 2>&1; then From 76ad978dff640910c62e6935f76cc81cb14be63b Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 17:14:42 +0200 Subject: [PATCH 08/33] Thank you opengl very cool --- configure.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/configure.sh b/configure.sh index 3b2beb1b8..117d3e09c 100755 --- a/configure.sh +++ b/configure.sh @@ -389,10 +389,15 @@ install_build_tools() { command -v patchelf >/dev/null 2>&1 || pkgs+=(patchelf) command -v cmake >/dev/null 2>&1 || pkgs+=(cmake) command -v curl >/dev/null 2>&1 || pkgs+=(curl) + # Library packages have no command to probe, so check the dpkg database. + # OpenGL dev libs satisfy Qt6Gui's WrapOpenGL; libxcb-cursor0 lets Qt apps + # launch. mesa-common-dev/libglvnd-dev provide the GL headers and the + # libOpenGL/libGLX that CMake's find_package(OpenGL) looks for. + local libs + for libs in libxcb-cursor0 libgl1-mesa-dev libglvnd-dev mesa-common-dev; do + dpkg -s "$libs" >/dev/null 2>&1 || pkgs+=("$libs") + done if [ ${#pkgs[@]} -gt 0 ]; then - # libxcb-cursor0 is a runtime lib with no command to probe; include it - # whenever we install so a fresh runner can launch Qt apps. - pkgs+=(libxcb-cursor0) echo "Installing system packages: ${pkgs[*]}" sudo apt-get update sudo apt-get install -y "${pkgs[@]}" From f93f0bb97a759791e71aacdc8c10c7ee3c48ee9a Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 17:19:11 +0200 Subject: [PATCH 09/33] no hARM done --- configure.sh | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/configure.sh b/configure.sh index 117d3e09c..e8b7edbe6 100755 --- a/configure.sh +++ b/configure.sh @@ -246,10 +246,12 @@ get_bass() { c/x64/bass.lib:./lib \ x64/bass.dll:./bin elif [[ "$PLATFORM" == "linux" ]]; then + local arch_dir="x86_64" + [[ "$ARCH" == "arm64" ]] && arch_dir="aarch64" get_zip https://www.un4seen.com/files/bass24-linux.zip \ c/bass.h:./lib \ - libs/x86_64/libbass.so:./lib \ - libs/x86_64/libbass.so:./bin + "libs/${arch_dir}/libbass.so:./lib" \ + "libs/${arch_dir}/libbass.so:./bin" elif [[ "$PLATFORM" == "macos" ]]; then get_zip https://www.un4seen.com/files/bass24-osx.zip \ c/bass.h:./lib \ @@ -271,10 +273,12 @@ get_bassopus() { c/x64/bassopus.lib:./lib \ x64/bassopus.dll:./bin elif [[ "$PLATFORM" == "linux" ]]; then + local arch_dir="x86_64" + [[ "$ARCH" == "arm64" ]] && arch_dir="aarch64" get_zip https://www.un4seen.com/files/bassopus24-linux.zip \ c/bassopus.h:./lib \ - libs/x86_64/libbassopus.so:./lib \ - libs/x86_64/libbassopus.so:./bin + "libs/${arch_dir}/libbassopus.so:./lib" \ + "libs/${arch_dir}/libbassopus.so:./bin" elif [[ "$PLATFORM" == "macos" ]]; then get_zip https://www.un4seen.com/files/bassopus24-osx.zip \ c/bassopus.h:./lib \ @@ -297,11 +301,17 @@ get_discordrpc() { discord-rpc/win64-dynamic/include/discord_rpc.h:./lib \ discord-rpc/win64-dynamic/include/discord_register.h:./lib elif [[ "$PLATFORM" == "linux" ]]; then - get_zip https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip \ - discord-rpc/linux-dynamic/lib/libdiscord-rpc.so:./lib \ - discord-rpc/linux-dynamic/lib/libdiscord-rpc.so:./bin \ - discord-rpc/linux-dynamic/include/discord_rpc.h:./lib \ - discord-rpc/linux-dynamic/include/discord_register.h:./lib + if [[ "$ARCH" == "x86_64" ]]; then + get_zip https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip \ + discord-rpc/linux-dynamic/lib/libdiscord-rpc.so:./lib \ + discord-rpc/linux-dynamic/lib/libdiscord-rpc.so:./bin \ + discord-rpc/linux-dynamic/include/discord_rpc.h:./lib \ + discord-rpc/linux-dynamic/include/discord_register.h:./lib + else + # discord-rpc v3.4.0 only ships x86_64 binaries, so there is no arm64 + # build. Disabled at build time via -DAO_ENABLE_DISCORD_RPC=OFF below. + echo "Skipping Discord RPC on Linux ${ARCH} (no native binary available)." + fi elif [[ "$PLATFORM" == "macos" ]]; then if [[ "$ARCH" == "x86_64" ]]; then get_zip https://github.com/discord/discord-rpc/releases/download/v3.4.0/discord-rpc-osx.zip \ @@ -532,9 +542,9 @@ configure() { get_qtapng get_themes - # Discord RPC has no native arm64 macOS binary, so turn it off there. + # Discord RPC only ships x86_64 binaries, so turn it off on arm64. EXTRA_CMAKE_FLAGS="" - if [[ "$PLATFORM" == "macos" && "$ARCH" != "x86_64" ]]; then + if [[ "$ARCH" != "x86_64" ]]; then EXTRA_CMAKE_FLAGS="-DAO_ENABLE_DISCORD_RPC=OFF" fi From 8eb53abcfa02be3df9f39268de1dce7ebf2e872a Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 17:28:49 +0200 Subject: [PATCH 10/33] Real step names --- .github/workflows/build.yml | 58 ++++++++++++++++++++++++++++++++++++- scripts/package-linux.sh | 15 ++++++---- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1d2a3b7a5..41429f0be 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: jobs: formatting-check: - name: formatting-check + name: Check formatting runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -23,6 +23,7 @@ jobs: fallback-style: LLVM build-windows: + name: Windows x86_64 needs: formatting-check runs-on: windows-latest steps: @@ -68,6 +69,7 @@ jobs: path: ${{ github.workspace }}/bin build-linux: + name: Linux x86_64 needs: formatting-check runs-on: ubuntu-22.04 steps: @@ -117,7 +119,61 @@ jobs: name: Attorney_Online-Linux-AppImage path: Attorney_Online-AppImage.tar + build-linux-arm: + name: Linux arm64 + needs: formatting-check + # GitHub-hosted ARM64 Linux runner (free for public repos). + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@master + with: + submodules: recursive + + - name: Install Qt + uses: jurplel/install-qt-action@v4 + with: + aqtversion: '==3.1.*' + # Qt ships Linux ARM64 desktop binaries only from 6.7 onward. + version: '6.8.3' + host: 'linux_arm64' + target: 'desktop' + arch: 'linux_gcc_arm64' + cache: true + cache-key-prefix: install-qt-action + modules: 'qtimageformats qtwebsockets' + + - name: Configure + env: + BUILD_CONFIG: Release + run: ./configure.sh + + - name: Build + run: | + source build.env + "$NINJA" + + - name: Run tests + env: + QT_QPA_PLATFORM: offscreen + run: ctest --output-on-failure + + - name: Package dynamic tarball and AppImage + run: ./scripts/package-linux.sh + + - name: Upload Dynamic Artifact + uses: actions/upload-artifact@master + with: + name: Attorney_Online-Linux-arm64-Dynamic + path: Attorney_Online-Dynamic.tar + + - name: Upload AppImage Artifact + uses: actions/upload-artifact@master + with: + name: Attorney_Online-Linux-arm64-AppImage + path: Attorney_Online-AppImage.tar + build-macos: + name: macOS arm64 # Note that we just do an ARM build here (no Intel release) needs: formatting-check runs-on: macos-14 diff --git a/scripts/package-linux.sh b/scripts/package-linux.sh index af6c8f7ad..2b855ede3 100755 --- a/scripts/package-linux.sh +++ b/scripts/package-linux.sh @@ -40,9 +40,14 @@ cp ./qtapng/plugins/imageformats/libqapng.so "${QT_ROOT_DIR}/plugins/imageformat tar --transform='flags=r;s|bin|Attorney Online|' -cvf Attorney_Online-Dynamic.tar bin # --- AppImage (self-contained) --- +case "$(uname -m)" in + aarch64|arm64) APPIMAGE_ARCH=aarch64 ;; + *) APPIMAGE_ARCH=x86_64 ;; +esac + # from https://github.com/probonopd/go-appimage/blob/master/src/appimagetool/README.md -wget -c "https://github.com/$(wget -q https://github.com/probonopd/go-appimage/releases/expanded_assets/continuous -O - | grep "appimagetool-.*-x86_64.AppImage" | head -n 1 | cut -d '"' -f 2)" -mv appimagetool-*-x86_64.AppImage appimagetool +wget -c "https://github.com/$(wget -q https://github.com/probonopd/go-appimage/releases/expanded_assets/continuous -O - | grep "appimagetool-.*-${APPIMAGE_ARCH}.AppImage" | head -n 1 | cut -d '"' -f 2)" +mv appimagetool-*-"${APPIMAGE_ARCH}".AppImage appimagetool chmod +x appimagetool mkdir -p AppDir/usr/bin AppDir/usr/lib/plugins/imageformats AppDir/usr/share/applications @@ -58,7 +63,7 @@ if [ -z "$GIT_SHORT_SHA" ]; then GIT_SHORT_SHA="$(git rev-parse --short=8 HEAD 2>/dev/null || echo dev)" fi QTDIR="$QT_ROOT_DIR" ./appimagetool deploy AppDir/usr/share/applications/Attorney_Online.desktop -ARCH=x86_64 VERSION="${GIT_SHORT_SHA}" ./appimagetool AppDir +ARCH="${APPIMAGE_ARCH}" VERSION="${GIT_SHORT_SHA}" ./appimagetool AppDir # --- AppImage tarball --- rm -rf bin-appimage @@ -67,8 +72,8 @@ cp -r bin/base bin-appimage cp data/logo-client.png bin-appimage/icon.png cp README_LINUX.md bin-appimage cp scripts/APPIMAGE_INSTALL.sh bin-appimage/INSTALL.sh -cp Attorney_Online-*-x86_64.AppImage bin-appimage -chmod +x bin-appimage/INSTALL.sh bin-appimage/Attorney_Online-*-x86_64.AppImage +cp Attorney_Online-*-"${APPIMAGE_ARCH}".AppImage bin-appimage +chmod +x bin-appimage/INSTALL.sh bin-appimage/Attorney_Online-*-"${APPIMAGE_ARCH}".AppImage tar --transform='flags=r;s|bin-appimage|Attorney Online|' -cvf Attorney_Online-AppImage.tar bin-appimage echo "Wrote ${ROOT_DIR}/Attorney_Online-Dynamic.tar and ${ROOT_DIR}/Attorney_Online-AppImage.tar" From 5001549eaf94f60097beb9338f46a56dfdb3aafe Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 17:36:51 +0200 Subject: [PATCH 11/33] thank you apple very cool --- scripts/package-macos.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scripts/package-macos.sh b/scripts/package-macos.sh index cfb1a79be..848c814aa 100755 --- a/scripts/package-macos.sh +++ b/scripts/package-macos.sh @@ -40,6 +40,17 @@ cp ./bin/imageformats/libqapng.dylib "${APP}/Contents/PlugIns/imageformats/" "${QT_ROOT_DIR}/bin/macdeployqt" "$APP" -verbose=1 +# macdeployqt and the dylib/plugin copies above rewrite or insert Mach-O files, +# which invalidates their code signatures. Without a valid (even ad-hoc) +# signature, Gatekeeper reports the app as "damaged" on other Macs, so re-sign +# the bundle inside-out. This is NOT Developer ID signing or notarization: a +# downloaded copy is still quarantined, so users clear it with +# xattr -dr com.apple.quarantine Attorney_Online.app +# or right-click Open. +codesign --force --sign - "${APP}/Contents/MacOS/"*.dylib 2>/dev/null || true +codesign --force --sign - "${APP}/Contents/PlugIns/imageformats/"*.dylib 2>/dev/null || true +codesign --force --deep --sign - "$APP" + # Ship the self-contained app plus the base/ assets, matching the layout of the # Windows/Linux artifacts. -y preserves the framework symlinks macdeployqt makes. cp ./data/logo-client.png ./bin/icon.png From 2a5c6668e1d436b66cc9024fb86e39c9722a7567 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 17:44:03 +0200 Subject: [PATCH 12/33] de-yap --- .github/workflows/build.yml | 6 +----- configure.sh | 9 ++++----- scripts/package-macos.sh | 12 ++++-------- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 41429f0be..8e2e3155c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,4 @@ -# Build matrix: each platform job installs Qt, runs ./configure.sh (which -# handles deps, qtapng, themes, cmake+ninja, and windeployqt on Windows), -# then deploys + uploads. +# Per-platform matrix: install Qt, configure + build, test, package, upload. name: CI Build on: @@ -122,7 +120,6 @@ jobs: build-linux-arm: name: Linux arm64 needs: formatting-check - # GitHub-hosted ARM64 Linux runner (free for public repos). runs-on: ubuntu-24.04-arm steps: - uses: actions/checkout@master @@ -174,7 +171,6 @@ jobs: build-macos: name: macOS arm64 - # Note that we just do an ARM build here (no Intel release) needs: formatting-check runs-on: macos-14 steps: diff --git a/configure.sh b/configure.sh index e8b7edbe6..a4dfb06c4 100755 --- a/configure.sh +++ b/configure.sh @@ -34,12 +34,13 @@ BUILD_CONFIG="${BUILD_CONFIG:-Debug}" QT_ROOT_DIR="${QT_ROOT_DIR:-}" print_help() { - echo "Usage: [BUILD_CONFIG=Debug|Release] $0 [command]" + echo "Usage: [BUILD_CONFIG=Debug|Release] [QT_ROOT_DIR=path] $0 [command]" echo "Commands:" echo " -h, --help: Print this help message" echo " clean: Remove everything configure.sh writes (deps, build files, build.env, cmake_cmd.txt)" echo "Environment variables:" echo " BUILD_CONFIG=Debug|Release: CMake build type (default: Debug)" + echo " QT_ROOT_DIR=path: Qt toolchain dir to use directly (eg. ~/Qt/6.8.3/gcc_arm64); empty auto-detects under ~/Qt" } check_command() { @@ -399,10 +400,8 @@ install_build_tools() { command -v patchelf >/dev/null 2>&1 || pkgs+=(patchelf) command -v cmake >/dev/null 2>&1 || pkgs+=(cmake) command -v curl >/dev/null 2>&1 || pkgs+=(curl) - # Library packages have no command to probe, so check the dpkg database. - # OpenGL dev libs satisfy Qt6Gui's WrapOpenGL; libxcb-cursor0 lets Qt apps - # launch. mesa-common-dev/libglvnd-dev provide the GL headers and the - # libOpenGL/libGLX that CMake's find_package(OpenGL) looks for. + # Library packages have no command to probe, so check dpkg. The GL dev + # libs satisfy Qt6Gui's WrapOpenGL; libxcb-cursor0 lets Qt apps launch. local libs for libs in libxcb-cursor0 libgl1-mesa-dev libglvnd-dev mesa-common-dev; do dpkg -s "$libs" >/dev/null 2>&1 || pkgs+=("$libs") diff --git a/scripts/package-macos.sh b/scripts/package-macos.sh index 848c814aa..94fbba568 100755 --- a/scripts/package-macos.sh +++ b/scripts/package-macos.sh @@ -40,19 +40,15 @@ cp ./bin/imageformats/libqapng.dylib "${APP}/Contents/PlugIns/imageformats/" "${QT_ROOT_DIR}/bin/macdeployqt" "$APP" -verbose=1 -# macdeployqt and the dylib/plugin copies above rewrite or insert Mach-O files, -# which invalidates their code signatures. Without a valid (even ad-hoc) -# signature, Gatekeeper reports the app as "damaged" on other Macs, so re-sign -# the bundle inside-out. This is NOT Developer ID signing or notarization: a -# downloaded copy is still quarantined, so users clear it with +# macdeployqt and the copies above rewrite Mach-O files, invalidating their +# signatures; without a valid one Gatekeeper calls the app "damaged", so re-sign +# ad-hoc inside-out. Not notarized, so a downloaded copy is still quarantined: # xattr -dr com.apple.quarantine Attorney_Online.app -# or right-click Open. codesign --force --sign - "${APP}/Contents/MacOS/"*.dylib 2>/dev/null || true codesign --force --sign - "${APP}/Contents/PlugIns/imageformats/"*.dylib 2>/dev/null || true codesign --force --deep --sign - "$APP" -# Ship the self-contained app plus the base/ assets, matching the layout of the -# Windows/Linux artifacts. -y preserves the framework symlinks macdeployqt makes. +# Ship the app plus base/ assets. -y preserves macdeployqt's framework symlinks. cp ./data/logo-client.png ./bin/icon.png ( cd ./bin && zip -r -y ../Attorney_Online-macOS.zip Attorney_Online.app base icon.png ) From 59867362ce00870c2bf5b66404b8e4d04622c336 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 18:03:38 +0200 Subject: [PATCH 13/33] Just log base folder in About --- src/lobby.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lobby.cpp b/src/lobby.cpp index c019135d6..2074dd840 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -3,6 +3,7 @@ #include "aoapplication.h" #include "aoutils.h" #include "demoserver.h" +#include "file_functions.h" #include "gui_utils.h" #include "networkmanager.h" #include "widgets/direct_connect_dialog.h" @@ -305,11 +306,13 @@ void Lobby::on_about_clicked() "respective owners." "

Running on Qt version %2 with the BASS audio engine.
" "APNG plugin loaded: %3" - "

Built on %4") + "

Built on %4" + "

Base folder: %5") .arg(ao_app->get_version_string()) .arg(QLatin1String(QT_VERSION_STR)) .arg(hasApng ? tr("Yes") : tr("No")) - .arg(QLatin1String(__DATE__)); + .arg(QLatin1String(__DATE__)) + .arg(get_base_path()); QMessageBox::about(this, tr("About"), msg); } From 6c7ffd6aa844b71173c262e4526b95910f434078 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 18:31:01 +0200 Subject: [PATCH 14/33] fix appimage and don't double-compress --- .github/workflows/build.yml | 8 ++++---- scripts/APPIMAGE_INSTALL.sh | 6 +++--- scripts/package-linux.sh | 15 +++++++-------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8e2e3155c..2fb3bb2dd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -109,13 +109,13 @@ jobs: uses: actions/upload-artifact@master with: name: Attorney_Online-Linux-Dynamic - path: Attorney_Online-Dynamic.tar + path: bin - name: Upload AppImage Artifact uses: actions/upload-artifact@master with: name: Attorney_Online-Linux-AppImage - path: Attorney_Online-AppImage.tar + path: bin-appimage build-linux-arm: name: Linux arm64 @@ -161,13 +161,13 @@ jobs: uses: actions/upload-artifact@master with: name: Attorney_Online-Linux-arm64-Dynamic - path: Attorney_Online-Dynamic.tar + path: bin - name: Upload AppImage Artifact uses: actions/upload-artifact@master with: name: Attorney_Online-Linux-arm64-AppImage - path: Attorney_Online-AppImage.tar + path: bin-appimage build-macos: name: macOS arm64 diff --git a/scripts/APPIMAGE_INSTALL.sh b/scripts/APPIMAGE_INSTALL.sh index 8599cd5a5..fc7c01ffd 100644 --- a/scripts/APPIMAGE_INSTALL.sh +++ b/scripts/APPIMAGE_INSTALL.sh @@ -11,7 +11,7 @@ cd "${SCRIPT_DIR}" mkdir -p ~/.local/share/applications # desktop files don't like spaces in the Exec field, we have to replace them with "\s" -appimage="$(echo Attorney_Online-*-x86_64.AppImage)" +appimage="$(echo Attorney_Online-*.AppImage)" escaped_exec="$(echo "$(pwd)" | sed 's/ /\\s/g')"/"$appimage" desktop_file="\ @@ -26,7 +26,7 @@ Icon=$(pwd)/icon.png" echo "$desktop_file" > ~/.local/share/applications/'Attorney Online'.desktop #marking the program as executable -chmod +x Attorney_Online-*-x86_64.AppImage +chmod +x Attorney_Online-*.AppImage #running the executable -./Attorney_Online-*-x86_64.AppImage +./Attorney_Online-*.AppImage diff --git a/scripts/package-linux.sh b/scripts/package-linux.sh index 2b855ede3..af9e19872 100755 --- a/scripts/package-linux.sh +++ b/scripts/package-linux.sh @@ -1,8 +1,9 @@ #!/bin/bash -# Packages the Linux build into two distributables at the repo root: -# - Attorney_Online-Dynamic.tar : dynamic build (expects system Qt at runtime) -# - Attorney_Online-AppImage.tar : self-contained AppImage +# Stages the Linux build into two distributable folders at the repo root +# (CI zips them on upload, so no intermediate archive here): +# - bin/ : dynamic build (expects system Qt at runtime) +# - bin-appimage/ : self-contained AppImage plus base/ assets # # Prerequisites: run ./configure.sh (fetches deps, generates build files, writes # build.env) and then build (run the command configure.sh prints). @@ -28,7 +29,7 @@ fi mkdir -p "${QT_ROOT_DIR}/plugins/imageformats" cp ./qtapng/plugins/imageformats/libqapng.so "${QT_ROOT_DIR}/plugins/imageformats/" -# --- Dynamic tarball (expects system Qt at runtime) --- +# --- Dynamic build folder (expects system Qt at runtime) --- ( cd ./bin cp ../data/logo-client.png ./icon.png @@ -37,7 +38,6 @@ cp ./qtapng/plugins/imageformats/libqapng.so "${QT_ROOT_DIR}/plugins/imageformat chmod +x INSTALL.sh Attorney_Online patchelf --add-rpath . Attorney_Online ) -tar --transform='flags=r;s|bin|Attorney Online|' -cvf Attorney_Online-Dynamic.tar bin # --- AppImage (self-contained) --- case "$(uname -m)" in @@ -65,7 +65,7 @@ fi QTDIR="$QT_ROOT_DIR" ./appimagetool deploy AppDir/usr/share/applications/Attorney_Online.desktop ARCH="${APPIMAGE_ARCH}" VERSION="${GIT_SHORT_SHA}" ./appimagetool AppDir -# --- AppImage tarball --- +# --- AppImage bundle folder --- rm -rf bin-appimage mkdir bin-appimage cp -r bin/base bin-appimage @@ -74,6 +74,5 @@ cp README_LINUX.md bin-appimage cp scripts/APPIMAGE_INSTALL.sh bin-appimage/INSTALL.sh cp Attorney_Online-*-"${APPIMAGE_ARCH}".AppImage bin-appimage chmod +x bin-appimage/INSTALL.sh bin-appimage/Attorney_Online-*-"${APPIMAGE_ARCH}".AppImage -tar --transform='flags=r;s|bin-appimage|Attorney Online|' -cvf Attorney_Online-AppImage.tar bin-appimage -echo "Wrote ${ROOT_DIR}/Attorney_Online-Dynamic.tar and ${ROOT_DIR}/Attorney_Online-AppImage.tar" +echo "Staged ${ROOT_DIR}/bin (dynamic) and ${ROOT_DIR}/bin-appimage (AppImage)" From 72fd58bfd2bcfb0458451fda8fe34bfe093b0fb9 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 18:43:59 +0200 Subject: [PATCH 15/33] just wow --- scripts/package-linux.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/package-linux.sh b/scripts/package-linux.sh index af9e19872..3b6de7ad2 100755 --- a/scripts/package-linux.sh +++ b/scripts/package-linux.sh @@ -63,6 +63,12 @@ if [ -z "$GIT_SHORT_SHA" ]; then GIT_SHORT_SHA="$(git rev-parse --short=8 HEAD 2>/dev/null || echo dev)" fi QTDIR="$QT_ROOT_DIR" ./appimagetool deploy AppDir/usr/share/applications/Attorney_Online.desktop + +# go-appimage's deploy bundles the ELF interpreter without an executable bit, so +# AppRun's `exec ld-linux-*.so ...` fails at runtime with "Permission denied". +# Restore the bit before building the image. +find AppDir -type f -name 'ld-linux-*' -exec chmod +x {} + + ARCH="${APPIMAGE_ARCH}" VERSION="${GIT_SHORT_SHA}" ./appimagetool AppDir # --- AppImage bundle folder --- From 73fb53cf7c9e54f6abf0feb0d54a6cfeeb12fd72 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 19:11:32 +0200 Subject: [PATCH 16/33] thank you macos very cool --- CMakeLists.txt | 12 ++++++++++++ scripts/package-macos.sh | 11 ++++++----- src/file_functions.cpp | 31 +++++++++++++++++++++++++++++++ src/file_functions.h | 4 ++++ src/main.cpp | 5 +++++ 5 files changed, 58 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 62900394b..bbcd954df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -150,6 +150,7 @@ if(APPLE) MACOSX_PACKAGE_LOCATION Resources) set_target_properties(Attorney_Online PROPERTIES + OUTPUT_NAME "Attorney Online" MACOSX_BUNDLE TRUE MACOSX_BUNDLE_BUNDLE_NAME "Attorney Online" MACOSX_BUNDLE_GUI_IDENTIFIER "org.attorneyonline.Attorney_Online" @@ -170,4 +171,15 @@ if(APPLE) "$/Contents/PlugIns/imageformats/" VERBATIM) endif() + + # Bundle the default base (themes, fetched by configure.sh) as read-only + # defaults in Contents/Resources. On first run the app copies it to the + # writable base in Application Support (see seed_base_if_missing). + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/bin/base") + add_custom_command(TARGET Attorney_Online POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_LIST_DIR}/bin/base" + "$/Contents/Resources/base" + VERBATIM) + endif() endif() diff --git a/scripts/package-macos.sh b/scripts/package-macos.sh index 94fbba568..e8b0736c9 100755 --- a/scripts/package-macos.sh +++ b/scripts/package-macos.sh @@ -4,7 +4,7 @@ # # Prerequisites: run ./configure.sh (fetches deps, generates build files, writes # build.env) and then build (run the command configure.sh prints). This script -# assembles bin/Attorney_Online.app and zips it alongside the base/ assets. +# assembles bin/"Attorney Online.app" and zips it into Attorney_Online-macOS.zip. set -euo pipefail @@ -18,7 +18,7 @@ if [ -f ./build.env ]; then fi : "${QT_ROOT_DIR:?QT_ROOT_DIR not set, run ./configure.sh first (it writes build.env)}" -APP="./bin/Attorney_Online.app" +APP="./bin/Attorney Online.app" if [ ! -d "$APP" ]; then echo "Error: ${APP} not found. Build first with the command configure.sh printed." exit 1 @@ -43,13 +43,14 @@ cp ./bin/imageformats/libqapng.dylib "${APP}/Contents/PlugIns/imageformats/" # macdeployqt and the copies above rewrite Mach-O files, invalidating their # signatures; without a valid one Gatekeeper calls the app "damaged", so re-sign # ad-hoc inside-out. Not notarized, so a downloaded copy is still quarantined: -# xattr -dr com.apple.quarantine Attorney_Online.app +# xattr -dr com.apple.quarantine "Attorney Online.app" codesign --force --sign - "${APP}/Contents/MacOS/"*.dylib 2>/dev/null || true codesign --force --sign - "${APP}/Contents/PlugIns/imageformats/"*.dylib 2>/dev/null || true codesign --force --deep --sign - "$APP" -# Ship the app plus base/ assets. -y preserves macdeployqt's framework symlinks. +# Ship the self-contained app (base is bundled inside it, seeded to Application +# Support on first run). -y preserves macdeployqt's framework symlinks. cp ./data/logo-client.png ./bin/icon.png -( cd ./bin && zip -r -y ../Attorney_Online-macOS.zip Attorney_Online.app base icon.png ) +( cd ./bin && zip -r -y ../Attorney_Online-macOS.zip "Attorney Online.app" icon.png ) echo "Wrote ${ROOT_DIR}/Attorney_Online-macOS.zip" diff --git a/src/file_functions.cpp b/src/file_functions.cpp index cbdd640d1..94b194dc4 100644 --- a/src/file_functions.cpp +++ b/src/file_functions.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include bool file_exists(QString file_path) { @@ -77,5 +79,34 @@ QString get_app_path() QString get_base_path() { +#ifdef Q_OS_MAC + // The .app is relocatable and can run read-only under Gatekeeper App + // Translocation, so the writable base lives in Application Support. Defaults + // are seeded there from the bundle on first run (see seed_base_if_missing). + return QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)) + .absoluteFilePath("base") + "/"; +#else return QDir(get_app_path()).absoluteFilePath("base") + "/"; +#endif +} + +void seed_base_if_missing() +{ +#ifdef Q_OS_MAC + const QString user_base = get_base_path(); + if (dir_exists(user_base)) + { + return; + } + + // Read-only defaults shipped inside the bundle at Contents/Resources/base. + const QString bundled_base = QDir::cleanPath(QCoreApplication::applicationDirPath() + "/../Resources/base"); + if (!dir_exists(bundled_base)) + { + return; // e.g. a non-bundle run; nothing to seed + } + + // ditto creates the destination tree and preserves symlinks/permissions. + QProcess::execute("ditto", {bundled_base, user_base}); +#endif } diff --git a/src/file_functions.h b/src/file_functions.h index 252148fd0..617222518 100644 --- a/src/file_functions.h +++ b/src/file_functions.h @@ -8,3 +8,7 @@ bool exists(QString p_path); QString get_app_path(); QString get_base_path(); + +// macOS only: on first run, seed the writable base (in Application Support) from +// the read-only default base bundled in the .app. No-op on other platforms. +void seed_base_if_missing(); diff --git a/src/main.cpp b/src/main.cpp index 2743eeb24..91c6442f3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,11 @@ int main(int argc, char *argv[]) QApplication app(argc, argv); + // On macOS, seed the writable base in Application Support from the bundled + // defaults on first run (no-op on other platforms). Must run before any base + // access below. + seed_base_if_missing(); + #ifdef ANDROID if (QtAndroid::checkPermission("android.permission.READ_EXTERNAL_STORAGE") == QtAndroid::PermissionResult::Denied) { From df11913fb86e9e39f84057d42681710a70788fb7 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 19:16:26 +0200 Subject: [PATCH 17/33] formatting --- src/file_functions.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/file_functions.cpp b/src/file_functions.cpp index 94b194dc4..6199f34ed 100644 --- a/src/file_functions.cpp +++ b/src/file_functions.cpp @@ -83,8 +83,7 @@ QString get_base_path() // The .app is relocatable and can run read-only under Gatekeeper App // Translocation, so the writable base lives in Application Support. Defaults // are seeded there from the bundle on first run (see seed_base_if_missing). - return QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)) - .absoluteFilePath("base") + "/"; + return QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)).absoluteFilePath("base") + "/"; #else return QDir(get_app_path()).absoluteFilePath("base") + "/"; #endif From 619508888ddeb37bc81c92ebc28850a251c7b574 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 19:21:08 +0200 Subject: [PATCH 18/33] don't include icon.png in mac release --- scripts/package-macos.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/package-macos.sh b/scripts/package-macos.sh index e8b0736c9..fd96b1fbf 100755 --- a/scripts/package-macos.sh +++ b/scripts/package-macos.sh @@ -48,9 +48,9 @@ codesign --force --sign - "${APP}/Contents/MacOS/"*.dylib 2>/dev/null || true codesign --force --sign - "${APP}/Contents/PlugIns/imageformats/"*.dylib 2>/dev/null || true codesign --force --deep --sign - "$APP" -# Ship the self-contained app (base is bundled inside it, seeded to Application -# Support on first run). -y preserves macdeployqt's framework symlinks. -cp ./data/logo-client.png ./bin/icon.png -( cd ./bin && zip -r -y ../Attorney_Online-macOS.zip "Attorney Online.app" icon.png ) +# Ship just the self-contained app: base is bundled inside it (seeded to +# Application Support on first run) and the icon lives in Contents/Resources, so +# there's nothing to ship alongside. -y preserves macdeployqt's framework symlinks. +( cd ./bin && zip -r -y ../Attorney_Online-macOS.zip "Attorney Online.app" ) echo "Wrote ${ROOT_DIR}/Attorney_Online-macOS.zip" From 227b8656b04239b7afe4a7117967d5cfae889411 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 19:27:39 +0200 Subject: [PATCH 19/33] Unused macOS script --- scripts/macos_release.sh | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 scripts/macos_release.sh diff --git a/scripts/macos_release.sh b/scripts/macos_release.sh deleted file mode 100644 index 2ed736492..000000000 --- a/scripts/macos_release.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh - -# This script prepares the compiled bundle for shipping as a standalone release -# Assumes the Qt bin folder is in PATH -# Should be used on a "Release" build from QT creator -# Note that this DOES NOT add the base/ folder - -# Exit on errors and unset variables -set -eu - -ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/" - -cd ${ROOT_DIR} - -# This thing basically does all the work -/usr/local/opt/qt/bin/macdeployqt ../bin/Attorney_Online.app - -# Need to add the dependencies -cp ../lib/* ../bin/Attorney_Online.app/Contents/Frameworks - -# libbass has a funny path for some reason, just use rpath -install_name_tool -change @loader_path/libbass.dylib @rpath/libbass.dylib ../bin/Attorney_Online.app/Contents/MacOS/Attorney_Online -install_name_tool -change @loader_path/libbassopus.dylib @rpath/libbassopus.dylib ../bin/Attorney_Online.app/Contents/MacOS/Attorney_Online - -zip -r -9 ../bin/Attorney_Online_macOS.zip ../bin/ \ No newline at end of file From ba5e9da364170dc4743efa2390479ae5e7968122 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 19:28:50 +0200 Subject: [PATCH 20/33] Update README --- README.md | 57 +++++++++++++++++++++++++------------------------------ 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 92f91d790..3b1709357 100644 --- a/README.md +++ b/README.md @@ -10,30 +10,24 @@ ### Prerequisites -Unix-like systems will expect a C toolchain, installed using eg.: +A C/C++ toolchain: Ubuntu: `sudo apt install build-essential` macOS: `xcode-select --install` -You may also need openGL libraries to compile QApng, eg: +Qt **6.5 or newer**, installed via the [Qt online installer](https://doc.qt.io/qt-6/qt-online-installation.html). +Check off the following under "Additional Libraries": +- Qt Image Formats +- Qt WebSockets -Ubuntu: `sudo apt install libgl1-mesa-dev` -macOS: `brew install glfw glew` +On Windows, also check off a toolchain (MinGW), CMake, and Ninja under +"Developer and Designer tools". ### Setup -This program's main dependency is Qt and the currently recommended version for development is **6.5.3**. See [this link](https://doc.qt.io/qt-6/qt-online-installation.html) -on how to install Qt. You will need to check off the following under "Additional Libraries": -- Qt Image formats -- Qt WebSockets - -Under "Developer and Designer tools", you may also want to check off: -- CMake -- Ninja -- If you're on Windows, a toolchain (MinGW) - -Assuming all this is in place, you should be able to run `configure.sh` to generate the necessary build files. -This also compiles the program and shows a cmake command that can be used to recreate the build files. +Run `./configure.sh`. It detects Qt, installs any other build tools it needs, +fetches the remaining dependencies, and generates the build files, then prints +the command to compile the program. ### Content @@ -42,28 +36,29 @@ You can get it from https://ao-dl.b-cdn.net/vanilla_full_2024_8_2.zip This should be put in `./bin/base` -### Formatting - -All code should be formatted according to the `.clang-format` file. -This will be checked by CI and will fail if the code is not formatted correctly. +## Formatting -## Running Tests -Running tests requires Catch2 and cmake +All code must be formatted according to the `.clang-format` file. CI runs a +`clang-format` check (version 17) and fails the build if any file under `src/` +is not formatted correctly. Format your changes before pushing: ```sh -mkdir cbuild && cd cbuild -cmake .. -make test +clang-format -i src/ # format a specific file +git clang-format # format only the lines you changed +``` -# usage: run all tests -./test/test +## Running Tests -# usage: Optionally specify tests and success verbosity -./test/test [bass] --success -``` +Tests are written with [Qt Test](https://doc.qt.io/qt-6/qtest-overview.html) and +registered with CTest. After running `./configure.sh` and building, run them from +the repo root: -`[noci]` tag is used to disable a test on GitHub actions +```sh +ctest --output-on-failure +# or run a single test binary directly, e.g. +./test/test_aopacket +``` ## Credits From 0251a47665f12bb3c5b6cdf0ba94b45609b8d28f Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 19:51:09 +0200 Subject: [PATCH 21/33] Bit better logging --- configure.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/configure.sh b/configure.sh index a4dfb06c4..8a788ddd6 100755 --- a/configure.sh +++ b/configure.sh @@ -446,7 +446,8 @@ configure() { exit 0 fi - echo "Platform: ${PLATFORM}" + echo "Platform: ${PLATFORM} (${ARCH})" + echo "Build config: ${BUILD_CONFIG}" if [ "$PLATFORM" == "unknown" ]; then echo "Unknown platform. Aborting." From 6fe82c07d7c3cc41658a238b1c91fc21d19ac7f1 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 20:12:16 +0200 Subject: [PATCH 22/33] Make package scripts produce .zip files --- .github/workflows/build.yml | 36 +++++++++++++++++++++--------------- CMakeLists.txt | 1 + scripts/package-linux.sh | 9 ++++++++- scripts/package-macos.sh | 16 ++++++++++------ scripts/package-windows.sh | 13 +++++++++++-- 5 files changed, 51 insertions(+), 24 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2fb3bb2dd..d6521cf1d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,10 +61,11 @@ jobs: run: ./scripts/package-windows.sh - name: Upload Artifact - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v7 with: name: Attorney_Online-Windows - path: ${{ github.workspace }}/bin + path: dist/AttorneyOnline-windows-*.zip + archive: false build-linux: name: Linux x86_64 @@ -102,20 +103,22 @@ jobs: QT_QPA_PLATFORM: offscreen run: ctest --output-on-failure - - name: Package dynamic tarball and AppImage + - name: Package run: ./scripts/package-linux.sh - name: Upload Dynamic Artifact - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v7 with: name: Attorney_Online-Linux-Dynamic - path: bin + path: dist/AttorneyOnline-linux-*-dynamic.zip + archive: false - name: Upload AppImage Artifact - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v7 with: name: Attorney_Online-Linux-AppImage - path: bin-appimage + path: dist/AttorneyOnline-linux-*-appimage.zip + archive: false build-linux-arm: name: Linux arm64 @@ -154,20 +157,22 @@ jobs: QT_QPA_PLATFORM: offscreen run: ctest --output-on-failure - - name: Package dynamic tarball and AppImage + - name: Package run: ./scripts/package-linux.sh - name: Upload Dynamic Artifact - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v7 with: name: Attorney_Online-Linux-arm64-Dynamic - path: bin + path: dist/AttorneyOnline-linux-*-dynamic.zip + archive: false - name: Upload AppImage Artifact - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v7 with: name: Attorney_Online-Linux-arm64-AppImage - path: bin-appimage + path: dist/AttorneyOnline-linux-*-appimage.zip + archive: false build-macos: name: macOS arm64 @@ -205,11 +210,12 @@ jobs: QT_QPA_PLATFORM: offscreen run: ctest --output-on-failure - - name: Package app + - name: Package run: ./scripts/package-macos.sh - name: Upload Artifact - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v7 with: name: Attorney_Online-macOS - path: Attorney_Online-macOS.zip + path: dist/AttorneyOnline-macos-*.zip + archive: false diff --git a/CMakeLists.txt b/CMakeLists.txt index bbcd954df..a7a55943c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,6 +108,7 @@ qt_add_executable(Attorney_Online set_target_properties(Attorney_Online PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin") if(WIN32) + set_target_properties(Attorney_Online PROPERTIES OUTPUT_NAME "Attorney Online") if(CMAKE_BUILD_TYPE STREQUAL "Release") set_property(TARGET Attorney_Online PROPERTY WIN32_EXECUTABLE true) set(APP_ICON_RESOURCE_WINDOWS "${CMAKE_CURRENT_SOURCE_DIR}/data/logo-client.rc") diff --git a/scripts/package-linux.sh b/scripts/package-linux.sh index 3b6de7ad2..e1cefa7ab 100755 --- a/scripts/package-linux.sh +++ b/scripts/package-linux.sh @@ -81,4 +81,11 @@ cp scripts/APPIMAGE_INSTALL.sh bin-appimage/INSTALL.sh cp Attorney_Online-*-"${APPIMAGE_ARCH}".AppImage bin-appimage chmod +x bin-appimage/INSTALL.sh bin-appimage/Attorney_Online-*-"${APPIMAGE_ARCH}".AppImage -echo "Staged ${ROOT_DIR}/bin (dynamic) and ${ROOT_DIR}/bin-appimage (AppImage)" +# Package each folder into a single, checksummable zip named for +# platform/arch/commit. -y preserves the AppImage's executable bit and symlinks. +BASE="AttorneyOnline-${PLATFORM}-${ARCH}-${GIT_SHORT_SHA}" +mkdir -p dist +( cd bin && zip -r -y "../dist/${BASE}-dynamic.zip" . ) +( cd bin-appimage && zip -r -y "../dist/${BASE}-appimage.zip" . ) + +echo "Wrote ${ROOT_DIR}/dist/${BASE}-dynamic.zip and ${ROOT_DIR}/dist/${BASE}-appimage.zip" diff --git a/scripts/package-macos.sh b/scripts/package-macos.sh index fd96b1fbf..e7029cdf6 100755 --- a/scripts/package-macos.sh +++ b/scripts/package-macos.sh @@ -48,9 +48,13 @@ codesign --force --sign - "${APP}/Contents/MacOS/"*.dylib 2>/dev/null || true codesign --force --sign - "${APP}/Contents/PlugIns/imageformats/"*.dylib 2>/dev/null || true codesign --force --deep --sign - "$APP" -# Ship just the self-contained app: base is bundled inside it (seeded to -# Application Support on first run) and the icon lives in Contents/Resources, so -# there's nothing to ship alongside. -y preserves macdeployqt's framework symlinks. -( cd ./bin && zip -r -y ../Attorney_Online-macOS.zip "Attorney Online.app" ) - -echo "Wrote ${ROOT_DIR}/Attorney_Online-macOS.zip" +# Package the self-contained app into a single, checksummable zip named for +# platform/arch/commit (base is bundled inside the app, icon in Contents/Resources, +# so nothing ships alongside). -y preserves macdeployqt's framework symlinks. +sha="${GITHUB_SHA:-}"; sha="${sha:0:8}" +[ -z "$sha" ] && sha="$(git rev-parse --short=8 HEAD 2>/dev/null || echo dev)" +ZIP="AttorneyOnline-${PLATFORM}-${ARCH}-${sha}.zip" +mkdir -p dist +( cd ./bin && zip -r -y "../dist/${ZIP}" "Attorney Online.app" ) + +echo "Wrote ${ROOT_DIR}/dist/${ZIP}" diff --git a/scripts/package-windows.sh b/scripts/package-windows.sh index 7afc9b5aa..fdd05cfb8 100755 --- a/scripts/package-windows.sh +++ b/scripts/package-windows.sh @@ -18,7 +18,7 @@ if [ -f ./build.env ]; then fi : "${QT_ROOT_DIR:?QT_ROOT_DIR not set, run ./configure.sh first (it writes build.env)}" -EXE="./bin/Attorney_Online.exe" +EXE="./bin/Attorney Online.exe" if [ ! -f "$EXE" ]; then echo "Error: ${EXE} not found. Build first with the command configure.sh printed." exit 1 @@ -38,4 +38,13 @@ if [ -n "${MINGW_PATH:-}" ]; then done fi -echo "Windows runtime staged in ${ROOT_DIR}/bin" +# Package bin/ into a single, checksummable zip named for platform/arch/commit. +# Use pwsh so zip entries use forward slashes (Windows PowerShell 5.1 uses +# backslashes, which some tools mishandle); git-bash may not ship `zip`. +sha="${GITHUB_SHA:-}"; sha="${sha:0:8}" +[ -z "$sha" ] && sha="$(git rev-parse --short=8 HEAD 2>/dev/null || echo dev)" +ZIP="AttorneyOnline-${PLATFORM}-${ARCH}-${sha}.zip" +mkdir -p dist +pwsh -NoProfile -Command "Compress-Archive -Path 'bin/*' -DestinationPath 'dist/${ZIP}' -Force" + +echo "Wrote ${ROOT_DIR}/dist/${ZIP}" From 10ce5f59bad9ad1fa8598b627a4f4315fae19de2 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 20:15:06 +0200 Subject: [PATCH 23/33] Update filename --- scripts/wasabi_program.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/wasabi_program.sh b/scripts/wasabi_program.sh index 37feac6eb..3fed2cf4b 100755 --- a/scripts/wasabi_program.sh +++ b/scripts/wasabi_program.sh @@ -28,7 +28,7 @@ export ARCHIVE="Attorney_Online_${VERSION}_${ARTIFACT_SUFFIX}" ${S3_COPY} ${S3_MANIFESTS}/${MANIFEST} . node $(dirname $0)/update_manifest.js ${MANIFEST} ${VERSION} \ - -f ${ARCHIVE} -e Attorney_Online.exe + -f ${ARCHIVE} -e "Attorney Online.exe" ${S3_COPY} ${ARCHIVE} ${S3_ARCHIVES} ${S3_COPY} ${MANIFEST} ${S3_MANIFESTS} From 1aca4978319e34226f29475579e83e95c961ef07 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 20:30:58 +0200 Subject: [PATCH 24/33] don't put .gitignore in dist --- .github/workflows/build.yml | 8 ++++---- scripts/package-linux.sh | 9 +++++---- scripts/package-windows.sh | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d6521cf1d..e9c0320a0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -110,14 +110,14 @@ jobs: uses: actions/upload-artifact@v7 with: name: Attorney_Online-Linux-Dynamic - path: dist/AttorneyOnline-linux-*-dynamic.zip + path: dist/AttorneyOnline-linuxdynamic-*.zip archive: false - name: Upload AppImage Artifact uses: actions/upload-artifact@v7 with: name: Attorney_Online-Linux-AppImage - path: dist/AttorneyOnline-linux-*-appimage.zip + path: dist/AttorneyOnline-linuxappimage-*.zip archive: false build-linux-arm: @@ -164,14 +164,14 @@ jobs: uses: actions/upload-artifact@v7 with: name: Attorney_Online-Linux-arm64-Dynamic - path: dist/AttorneyOnline-linux-*-dynamic.zip + path: dist/AttorneyOnline-linuxdynamic-*.zip archive: false - name: Upload AppImage Artifact uses: actions/upload-artifact@v7 with: name: Attorney_Online-Linux-arm64-AppImage - path: dist/AttorneyOnline-linux-*-appimage.zip + path: dist/AttorneyOnline-linuxappimage-*.zip archive: false build-macos: diff --git a/scripts/package-linux.sh b/scripts/package-linux.sh index e1cefa7ab..6838a3e31 100755 --- a/scripts/package-linux.sh +++ b/scripts/package-linux.sh @@ -83,9 +83,10 @@ chmod +x bin-appimage/INSTALL.sh bin-appimage/Attorney_Online-*-"${APPIMAGE_ARCH # Package each folder into a single, checksummable zip named for # platform/arch/commit. -y preserves the AppImage's executable bit and symlinks. -BASE="AttorneyOnline-${PLATFORM}-${ARCH}-${GIT_SHORT_SHA}" +DYN_ZIP="AttorneyOnline-${PLATFORM}dynamic-${ARCH}-${GIT_SHORT_SHA}.zip" +APP_ZIP="AttorneyOnline-${PLATFORM}appimage-${ARCH}-${GIT_SHORT_SHA}.zip" mkdir -p dist -( cd bin && zip -r -y "../dist/${BASE}-dynamic.zip" . ) -( cd bin-appimage && zip -r -y "../dist/${BASE}-appimage.zip" . ) +( cd bin && zip -r -y "../dist/${DYN_ZIP}" . -x '.gitignore' ) +( cd bin-appimage && zip -r -y "../dist/${APP_ZIP}" . ) -echo "Wrote ${ROOT_DIR}/dist/${BASE}-dynamic.zip and ${ROOT_DIR}/dist/${BASE}-appimage.zip" +echo "Wrote ${ROOT_DIR}/dist/${DYN_ZIP} and ${ROOT_DIR}/dist/${APP_ZIP}" diff --git a/scripts/package-windows.sh b/scripts/package-windows.sh index fdd05cfb8..3ed1302e4 100755 --- a/scripts/package-windows.sh +++ b/scripts/package-windows.sh @@ -45,6 +45,6 @@ sha="${GITHUB_SHA:-}"; sha="${sha:0:8}" [ -z "$sha" ] && sha="$(git rev-parse --short=8 HEAD 2>/dev/null || echo dev)" ZIP="AttorneyOnline-${PLATFORM}-${ARCH}-${sha}.zip" mkdir -p dist -pwsh -NoProfile -Command "Compress-Archive -Path 'bin/*' -DestinationPath 'dist/${ZIP}' -Force" +pwsh -NoProfile -Command "Compress-Archive -Path (Get-ChildItem -Path bin -Exclude '.gitignore').FullName -DestinationPath 'dist/${ZIP}' -Force" echo "Wrote ${ROOT_DIR}/dist/${ZIP}" From a48cc003c0b6d4ddfe1f7da2c1dee1529ec1e0e3 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 20:50:45 +0200 Subject: [PATCH 25/33] Attorney_Online -> AttorneyOnline Use the CamelCase variant everywhere, except macos+win dist, where we add a space. The Linux .desktop file gets a space too --- .github/workflows/build.yml | 12 +++--- .gitignore | 4 +- CMakeLists.txt | 43 +++++++++++-------- README_LINUX.md | 28 ------------ configure.sh | 2 +- ..._Online.desktop => AttorneyOnline.desktop} | 4 +- ...PPIMAGE_INSTALL.sh => appimage_install.sh} | 8 ++-- ...{DYNAMIC_INSTALL.sh => dynamic_install.sh} | 6 +-- scripts/package-linux.sh | 28 ++++++------ scripts/package-macos.sh | 2 +- scripts/wasabi_program.sh | 2 +- 11 files changed, 58 insertions(+), 81 deletions(-) delete mode 100644 README_LINUX.md rename scripts/{Attorney_Online.desktop => AttorneyOnline.desktop} (71%) rename scripts/{APPIMAGE_INSTALL.sh => appimage_install.sh} (78%) rename scripts/{DYNAMIC_INSTALL.sh => dynamic_install.sh} (83%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e9c0320a0..35dd95f91 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,7 +63,7 @@ jobs: - name: Upload Artifact uses: actions/upload-artifact@v7 with: - name: Attorney_Online-Windows + name: AttorneyOnline-Windows path: dist/AttorneyOnline-windows-*.zip archive: false @@ -109,14 +109,14 @@ jobs: - name: Upload Dynamic Artifact uses: actions/upload-artifact@v7 with: - name: Attorney_Online-Linux-Dynamic + name: AttorneyOnline-Linux-Dynamic path: dist/AttorneyOnline-linuxdynamic-*.zip archive: false - name: Upload AppImage Artifact uses: actions/upload-artifact@v7 with: - name: Attorney_Online-Linux-AppImage + name: AttorneyOnline-Linux-AppImage path: dist/AttorneyOnline-linuxappimage-*.zip archive: false @@ -163,14 +163,14 @@ jobs: - name: Upload Dynamic Artifact uses: actions/upload-artifact@v7 with: - name: Attorney_Online-Linux-arm64-Dynamic + name: AttorneyOnline-Linux-arm64-Dynamic path: dist/AttorneyOnline-linuxdynamic-*.zip archive: false - name: Upload AppImage Artifact uses: actions/upload-artifact@v7 with: - name: Attorney_Online-Linux-arm64-AppImage + name: AttorneyOnline-Linux-arm64-AppImage path: dist/AttorneyOnline-linuxappimage-*.zip archive: false @@ -216,6 +216,6 @@ jobs: - name: Upload Artifact uses: actions/upload-artifact@v7 with: - name: Attorney_Online-macOS + name: AttorneyOnline-macOS path: dist/AttorneyOnline-macos-*.zip archive: false diff --git a/.gitignore b/.gitignore index 3d8461dcb..cc9ef9351 100644 --- a/.gitignore +++ b/.gitignore @@ -35,7 +35,7 @@ base/serverlist.txt Makefile* object_script* /android/gradle* -/Attorney_Online_*.rc +/AttorneyOnline_*.rc /attorney_online_*_plugin_import.cpp server/__pycache__ discord/ @@ -52,7 +52,7 @@ cmake-build-* .ninja_deps .ninja_log .qt/ -Attorney_Online_autogen/ +AttorneyOnline_autogen/ CMakeCache.txt CMakeFiles/ Testing/ diff --git a/CMakeLists.txt b/CMakeLists.txt index a7a55943c..d0843e10a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ option(AO_ENABLE_DISCORD_RPC "Enable Discord Rich Presence" ON) find_package(QT NAMES Qt6) find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets Concurrent WebSockets UiTools) -qt_add_executable(Attorney_Online +qt_add_executable(AttorneyOnline src/aoapplication.cpp src/aoapplication.h src/aoblipplayer.cpp @@ -105,20 +105,25 @@ qt_add_executable(Attorney_Online src/network/serverinfo.h src/network/serverinfo.cpp ) -set_target_properties(Attorney_Online PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin") +set_target_properties(AttorneyOnline PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin") if(WIN32) - set_target_properties(Attorney_Online PROPERTIES OUTPUT_NAME "Attorney Online") + set_target_properties(AttorneyOnline PROPERTIES OUTPUT_NAME "Attorney Online") if(CMAKE_BUILD_TYPE STREQUAL "Release") - set_property(TARGET Attorney_Online PROPERTY WIN32_EXECUTABLE true) + set_property(TARGET AttorneyOnline PROPERTY WIN32_EXECUTABLE true) set(APP_ICON_RESOURCE_WINDOWS "${CMAKE_CURRENT_SOURCE_DIR}/data/logo-client.rc") - target_sources(Attorney_Online PRIVATE ${APP_ICON_RESOURCE_WINDOWS}) + target_sources(AttorneyOnline PRIVATE ${APP_ICON_RESOURCE_WINDOWS}) endif() endif() -target_include_directories(Attorney_Online PRIVATE src lib) -target_link_directories(Attorney_Online PRIVATE lib) -target_link_libraries(Attorney_Online PRIVATE +# Linux avoids spaces in filenames; the executable ships as AttorneyOnline.bin. +if(UNIX AND NOT APPLE) + set_target_properties(AttorneyOnline PROPERTIES SUFFIX ".bin") +endif() + +target_include_directories(AttorneyOnline PRIVATE src lib) +target_link_directories(AttorneyOnline PRIVATE lib) +target_link_libraries(AttorneyOnline PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Network @@ -131,8 +136,8 @@ target_link_libraries(Attorney_Online PRIVATE ) if(AO_ENABLE_DISCORD_RPC) - target_compile_definitions(Attorney_Online PRIVATE AO_ENABLE_DISCORD_RPC) - target_link_libraries(Attorney_Online PRIVATE discord-rpc) + target_compile_definitions(AttorneyOnline PRIVATE AO_ENABLE_DISCORD_RPC) + target_link_libraries(AttorneyOnline PRIVATE discord-rpc) endif() if(AO_BUILD_TESTS) @@ -140,21 +145,21 @@ if(AO_BUILD_TESTS) add_subdirectory(test) endif() -set_target_properties(Attorney_Online PROPERTIES +set_target_properties(AttorneyOnline PROPERTIES LIBRARY_OUTPUT_DIRECTORY $<1:${CMAKE_CURRENT_LIST_DIR}/bin> RUNTIME_OUTPUT_DIRECTORY $<1:${CMAKE_CURRENT_LIST_DIR}/bin>) if(APPLE) set(MACOSX_BUNDLE_ICON "${CMAKE_CURRENT_LIST_DIR}/data/logo-client.icns") - target_sources(Attorney_Online PRIVATE ${MACOSX_BUNDLE_ICON}) + target_sources(AttorneyOnline PRIVATE ${MACOSX_BUNDLE_ICON}) set_source_files_properties(${MACOSX_BUNDLE_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) - set_target_properties(Attorney_Online PROPERTIES + set_target_properties(AttorneyOnline PROPERTIES OUTPUT_NAME "Attorney Online" MACOSX_BUNDLE TRUE MACOSX_BUNDLE_BUNDLE_NAME "Attorney Online" - MACOSX_BUNDLE_GUI_IDENTIFIER "org.attorneyonline.Attorney_Online" + MACOSX_BUNDLE_GUI_IDENTIFIER "org.attorneyonline.AttorneyOnline" MACOSX_BUNDLE_BUNDLE_VERSION "2" MACOSX_BUNDLE_SHORT_VERSION_STRING "2.0" MACOSX_BUNDLE_ICON_FILE "logo-client.icns") @@ -164,12 +169,12 @@ if(APPLE) # a plain build fails at launch with "Missing image formats: apng". # (webp/gif come from the Qt install's own plugins.) if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/bin/imageformats/libqapng.dylib") - add_custom_command(TARGET Attorney_Online POST_BUILD + add_custom_command(TARGET AttorneyOnline POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory - "$/Contents/PlugIns/imageformats" + "$/Contents/PlugIns/imageformats" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_LIST_DIR}/bin/imageformats/libqapng.dylib" - "$/Contents/PlugIns/imageformats/" + "$/Contents/PlugIns/imageformats/" VERBATIM) endif() @@ -177,10 +182,10 @@ if(APPLE) # defaults in Contents/Resources. On first run the app copies it to the # writable base in Application Support (see seed_base_if_missing). if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/bin/base") - add_custom_command(TARGET Attorney_Online POST_BUILD + add_custom_command(TARGET AttorneyOnline POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_LIST_DIR}/bin/base" - "$/Contents/Resources/base" + "$/Contents/Resources/base" VERBATIM) endif() endif() diff --git a/README_LINUX.md b/README_LINUX.md deleted file mode 100644 index 146799b3e..000000000 --- a/README_LINUX.md +++ /dev/null @@ -1,28 +0,0 @@ -## Running on Linux - -There are two download options for running on Linux: the **dynamically-linked** build and the **AppImage**. The dynamic build is lighter, but might only run on newer systems. The AppImage is a bit bigger, but should run seamlessly on most systems (anything newer than Ubuntu 22.04 LTS). - -Each version also accompanies an `INSTALL.sh` script that will create a desktop file for AO pointing to where the script was ran. This will enable you to run AO from an app launcher. Note that moving AO's folder will require running this script again. - -### AppImage - -If you downloaded the **AppImage** version, it should just be plug-and-play. If you run into errors or bugs, contact us in our [Discord server](https://discord.gg/wWvQ3pw) or open an [issue on GitHub](https://github.com/AttorneyOnline/AO2-Client/issues). - -### Dynamic - -If you downloaded the **dynamically-linked** version, use the `launch.sh` script to run AO. - -You may need to install some libraries in your system. These are the commands to run on a terminal for some distributions: - -* Arch Linux: -``` -$ sudo pacman -S qt6-base qt6-tools qt6-websockets qt6-imageformats -``` -* Fedora: -``` -$ sudo dnf install qt6-qtbase qt6-qttools qt6-qtwebsockets qt6-qtimageformats -``` -* Ubuntu 22.04 LTS: -``` -$ sudo apt-get install qt6base-dev libqt6uitools6 libqt6websockets6 qt6-image-formats-plugins -``` diff --git a/configure.sh b/configure.sh index 8a788ddd6..279c924da 100755 --- a/configure.sh +++ b/configure.sh @@ -429,7 +429,7 @@ clean() { rm -f ./build.env ./cmake_cmd.txt # In-source CMake/Ninja build artifacts. - rm -rf ./.cmake/ ./.qt/ ./CMakeFiles/ ./Attorney_Online_autogen/ ./Testing/ + rm -rf ./.cmake/ ./.qt/ ./CMakeFiles/ ./AttorneyOnline_autogen/ ./Testing/ rm -f ./CMakeCache.txt ./cmake_install.cmake ./CTestTestfile.cmake ./build.ninja ./.ninja_deps ./.ninja_log rm -rf ./test/CMakeFiles/ ./test/test_aopacket_autogen/ rm -f ./test/CTestTestfile.cmake ./test/cmake_install.cmake ./test/test_aopacket diff --git a/scripts/Attorney_Online.desktop b/scripts/AttorneyOnline.desktop similarity index 71% rename from scripts/Attorney_Online.desktop rename to scripts/AttorneyOnline.desktop index 16ed60029..6f6a8d076 100644 --- a/scripts/Attorney_Online.desktop +++ b/scripts/AttorneyOnline.desktop @@ -2,6 +2,6 @@ Type=Application Name=Attorney Online Comment=The courtroom drama simulator -Exec=Attorney_Online -Icon=Attorney_Online +Exec=AttorneyOnline.bin +Icon=AttorneyOnline Categories=Game; diff --git a/scripts/APPIMAGE_INSTALL.sh b/scripts/appimage_install.sh similarity index 78% rename from scripts/APPIMAGE_INSTALL.sh rename to scripts/appimage_install.sh index fc7c01ffd..bee50c011 100644 --- a/scripts/APPIMAGE_INSTALL.sh +++ b/scripts/appimage_install.sh @@ -11,8 +11,8 @@ cd "${SCRIPT_DIR}" mkdir -p ~/.local/share/applications # desktop files don't like spaces in the Exec field, we have to replace them with "\s" -appimage="$(echo Attorney_Online-*.AppImage)" -escaped_exec="$(echo "$(pwd)" | sed 's/ /\\s/g')"/"$appimage" +appimage="AttorneyOnline.AppImage" +escaped_exec="$(echo "$(pwd)/$appimage" | sed 's/ /\\s/g')" desktop_file="\ [Desktop Entry] @@ -26,7 +26,7 @@ Icon=$(pwd)/icon.png" echo "$desktop_file" > ~/.local/share/applications/'Attorney Online'.desktop #marking the program as executable -chmod +x Attorney_Online-*.AppImage +chmod +x "$appimage" #running the executable -./Attorney_Online-*.AppImage +./"$appimage" diff --git a/scripts/DYNAMIC_INSTALL.sh b/scripts/dynamic_install.sh similarity index 83% rename from scripts/DYNAMIC_INSTALL.sh rename to scripts/dynamic_install.sh index a9fbc2c40..499c18a7c 100644 --- a/scripts/DYNAMIC_INSTALL.sh +++ b/scripts/dynamic_install.sh @@ -11,7 +11,7 @@ cd "${SCRIPT_DIR}" mkdir -p ~/.local/share/applications # desktop files don't like spaces in the Exec field, we have to replace them with "\s" -escaped_exec="$(echo "$(pwd)" | sed 's/ /\\s/g')"/Attorney_Online +escaped_exec="$(echo "$(pwd)/AttorneyOnline.bin" | sed 's/ /\\s/g')" desktop_file="\ [Desktop Entry] @@ -25,5 +25,5 @@ Icon=$(pwd)/icon.png" echo "$desktop_file" > ~/.local/share/applications/'Attorney Online'.desktop #running the program -chmod +x Attorney_Online -./Attorney_Online +chmod +x AttorneyOnline.bin +./AttorneyOnline.bin diff --git a/scripts/package-linux.sh b/scripts/package-linux.sh index 6838a3e31..a51919414 100755 --- a/scripts/package-linux.sh +++ b/scripts/package-linux.sh @@ -20,8 +20,8 @@ if [ -f ./build.env ]; then fi : "${QT_ROOT_DIR:?QT_ROOT_DIR not set, run ./configure.sh first (it writes build.env)}" -if [ ! -f ./bin/Attorney_Online ]; then - echo "Error: ./bin/Attorney_Online not found. Build first with the command configure.sh printed." +if [ ! -f "./bin/AttorneyOnline.bin" ]; then + echo "Error: ./bin/AttorneyOnline.bin not found. Build first with the command configure.sh printed." exit 1 fi @@ -33,10 +33,10 @@ cp ./qtapng/plugins/imageformats/libqapng.so "${QT_ROOT_DIR}/plugins/imageformat ( cd ./bin cp ../data/logo-client.png ./icon.png - cp ../README_LINUX.md . - cp ../scripts/DYNAMIC_INSTALL.sh ./INSTALL.sh - chmod +x INSTALL.sh Attorney_Online - patchelf --add-rpath . Attorney_Online + cp ../scripts/DYNAMIC_README.md ./README.md + cp ../scripts/dynamic_install.sh ./install.sh + chmod +x install.sh AttorneyOnline.bin + patchelf --add-rpath . AttorneyOnline.bin ) # --- AppImage (self-contained) --- @@ -51,10 +51,10 @@ mv appimagetool-*-"${APPIMAGE_ARCH}".AppImage appimagetool chmod +x appimagetool mkdir -p AppDir/usr/bin AppDir/usr/lib/plugins/imageformats AppDir/usr/share/applications -cp bin/Attorney_Online AppDir/usr/bin +cp bin/AttorneyOnline.bin AppDir/usr/bin cp bin/lib*.so AppDir/usr/lib 2>/dev/null || true -cp scripts/Attorney_Online.desktop AppDir/usr/share/applications -cp data/logo-client.png AppDir/Attorney_Online.png +cp scripts/AttorneyOnline.desktop AppDir/usr/share/applications +cp data/logo-client.png AppDir/AttorneyOnline.png # Prefer the CI-provided commit SHA; fall back to git (or "dev") for local runs. GIT_SHORT_SHA="${GITHUB_SHA:-}" @@ -62,7 +62,7 @@ GIT_SHORT_SHA="${GIT_SHORT_SHA:0:8}" if [ -z "$GIT_SHORT_SHA" ]; then GIT_SHORT_SHA="$(git rev-parse --short=8 HEAD 2>/dev/null || echo dev)" fi -QTDIR="$QT_ROOT_DIR" ./appimagetool deploy AppDir/usr/share/applications/Attorney_Online.desktop +QTDIR="$QT_ROOT_DIR" ./appimagetool deploy AppDir/usr/share/applications/AttorneyOnline.desktop # go-appimage's deploy bundles the ELF interpreter without an executable bit, so # AppRun's `exec ld-linux-*.so ...` fails at runtime with "Permission denied". @@ -76,10 +76,10 @@ rm -rf bin-appimage mkdir bin-appimage cp -r bin/base bin-appimage cp data/logo-client.png bin-appimage/icon.png -cp README_LINUX.md bin-appimage -cp scripts/APPIMAGE_INSTALL.sh bin-appimage/INSTALL.sh -cp Attorney_Online-*-"${APPIMAGE_ARCH}".AppImage bin-appimage -chmod +x bin-appimage/INSTALL.sh bin-appimage/Attorney_Online-*-"${APPIMAGE_ARCH}".AppImage +cp scripts/APPIMAGE_README.md bin-appimage/README.md +cp scripts/appimage_install.sh bin-appimage/install.sh +cp ./*-"${APPIMAGE_ARCH}".AppImage "bin-appimage/AttorneyOnline.AppImage" +chmod +x "bin-appimage/install.sh" "bin-appimage/AttorneyOnline.AppImage" # Package each folder into a single, checksummable zip named for # platform/arch/commit. -y preserves the AppImage's executable bit and symlinks. diff --git a/scripts/package-macos.sh b/scripts/package-macos.sh index e7029cdf6..0d0905171 100755 --- a/scripts/package-macos.sh +++ b/scripts/package-macos.sh @@ -4,7 +4,7 @@ # # Prerequisites: run ./configure.sh (fetches deps, generates build files, writes # build.env) and then build (run the command configure.sh prints). This script -# assembles bin/"Attorney Online.app" and zips it into Attorney_Online-macOS.zip. +# assembles bin/"Attorney Online.app" and zips it into dist/. set -euo pipefail diff --git a/scripts/wasabi_program.sh b/scripts/wasabi_program.sh index 3fed2cf4b..60f5b4912 100755 --- a/scripts/wasabi_program.sh +++ b/scripts/wasabi_program.sh @@ -24,7 +24,7 @@ export S3_MANIFESTS="s3://ao-manifests" export S3_ARCHIVES="s3://ao-downloads" export VERSION=$(git describe --tags) -export ARCHIVE="Attorney_Online_${VERSION}_${ARTIFACT_SUFFIX}" +export ARCHIVE="AttorneyOnline_${VERSION}_${ARTIFACT_SUFFIX}" ${S3_COPY} ${S3_MANIFESTS}/${MANIFEST} . node $(dirname $0)/update_manifest.js ${MANIFEST} ${VERSION} \ From 1e55b47c7f27635cce5e81911a8e757d4e3b5458 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 20:53:12 +0200 Subject: [PATCH 26/33] Add linux readmes --- scripts/APPIMAGE_README.md | 15 +++++++++++++++ scripts/DYNAMIC_README.md | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 scripts/APPIMAGE_README.md create mode 100644 scripts/DYNAMIC_README.md diff --git a/scripts/APPIMAGE_README.md b/scripts/APPIMAGE_README.md new file mode 100644 index 000000000..78de57dc3 --- /dev/null +++ b/scripts/APPIMAGE_README.md @@ -0,0 +1,15 @@ +## Attorney Online for Linux (AppImage) + +This is the **AppImage** build. It bundles Qt and should run on most systems +(anything newer than Ubuntu 22.04 LTS) with no extra setup. + +### Running + +The AppImage is plug-and-play, just run the `.AppImage` file. Or run +`./install.sh` to add Attorney Online to your app launcher; it creates a desktop +entry pointing to this folder. Note that moving this folder requires running +`install.sh` again. + +If you run into errors or bugs, reach us in our +[Discord server](https://discord.gg/wWvQ3pw) or open an +[issue on GitHub](https://github.com/AttorneyOnline/AO2-Client/issues). diff --git a/scripts/DYNAMIC_README.md b/scripts/DYNAMIC_README.md new file mode 100644 index 000000000..faf141edc --- /dev/null +++ b/scripts/DYNAMIC_README.md @@ -0,0 +1,37 @@ +## Attorney Online for Linux (dynamic build) + +This is the **dynamically-linked** build. It's lighter than the AppImage but uses +your system's Qt, so it may only run on newer distributions. + +### Running + +Run the binary directly: + +``` +./AttorneyOnline.bin +``` + +Or run `./install.sh` to add Attorney Online to your app launcher — it creates a +desktop entry pointing to this folder. Note that moving this folder requires +running `install.sh` again. + +### Dependencies + +You may need to install Qt libraries first. Commands for some distributions: + +#### Arch Linux +``` +sudo pacman -S qt6-base qt6-tools qt6-websockets qt6-imageformats +``` +#### Fedora +``` +sudo dnf install qt6-qtbase qt6-qttools qt6-qtwebsockets qt6-qtimageformats +``` +#### Ubuntu 22.04 LTS +``` +sudo apt-get install qt6base-dev libqt6uitools6 libqt6websockets6 qt6-image-formats-plugins +``` + +If you run into errors or bugs, reach us in our +[Discord server](https://discord.gg/wWvQ3pw) or open an +[issue on GitHub](https://github.com/AttorneyOnline/AO2-Client/issues). From 12b2fe2a0ae6eea3008828d7dece7d0f409a02b5 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 22:10:46 +0200 Subject: [PATCH 27/33] Make it behave correctly on leeenox --- scripts/AttorneyOnline.desktop | 1 + scripts/appimage_install.sh | 5 +++-- scripts/dynamic_install.sh | 5 +++-- src/main.cpp | 5 +++++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/scripts/AttorneyOnline.desktop b/scripts/AttorneyOnline.desktop index 6f6a8d076..880168a4a 100644 --- a/scripts/AttorneyOnline.desktop +++ b/scripts/AttorneyOnline.desktop @@ -4,4 +4,5 @@ Name=Attorney Online Comment=The courtroom drama simulator Exec=AttorneyOnline.bin Icon=AttorneyOnline +StartupWMClass=AttorneyOnline Categories=Game; diff --git a/scripts/appimage_install.sh b/scripts/appimage_install.sh index bee50c011..a1692c2ef 100644 --- a/scripts/appimage_install.sh +++ b/scripts/appimage_install.sh @@ -21,9 +21,10 @@ Name=Attorney Online Comment=The courtroom drama simulator Path=$(pwd) Exec=\"$escaped_exec\" -Icon=$(pwd)/icon.png" +Icon=$(pwd)/icon.png +StartupWMClass=AttorneyOnline" -echo "$desktop_file" > ~/.local/share/applications/'Attorney Online'.desktop +echo "$desktop_file" > ~/.local/share/applications/AttorneyOnline.desktop #marking the program as executable chmod +x "$appimage" diff --git a/scripts/dynamic_install.sh b/scripts/dynamic_install.sh index 499c18a7c..428914cf0 100644 --- a/scripts/dynamic_install.sh +++ b/scripts/dynamic_install.sh @@ -20,9 +20,10 @@ Name=Attorney Online Comment=The courtroom drama simulator Path=$(pwd) Exec=\"$escaped_exec\" -Icon=$(pwd)/icon.png" +Icon=$(pwd)/icon.png +StartupWMClass=AttorneyOnline" -echo "$desktop_file" > ~/.local/share/applications/'Attorney Online'.desktop +echo "$desktop_file" > ~/.local/share/applications/AttorneyOnline.desktop #running the program chmod +x AttorneyOnline.bin diff --git a/src/main.cpp b/src/main.cpp index 91c6442f3..420433b63 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,11 @@ int main(int argc, char *argv[]) QApplication app(argc, argv); + // Associate the app's windows with the AttorneyOnline.desktop entry so the + // desktop environment shows the right name/icon for the running app (Wayland + // app_id / X11 WM_CLASS) instead of the raw executable name. + QApplication::setDesktopFileName("AttorneyOnline"); + // On macOS, seed the writable base in Application Support from the bundled // defaults on first run (no-op on other platforms). Must run before any base // access below. From f07a8600abedb51c618e142b80e86e49e43fe7ed Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 22:13:08 +0200 Subject: [PATCH 28/33] Don't launch with install script --- scripts/appimage_install.sh | 4 ++-- scripts/dynamic_install.sh | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/appimage_install.sh b/scripts/appimage_install.sh index a1692c2ef..1508a5100 100644 --- a/scripts/appimage_install.sh +++ b/scripts/appimage_install.sh @@ -29,5 +29,5 @@ echo "$desktop_file" > ~/.local/share/applications/AttorneyOnline.desktop #marking the program as executable chmod +x "$appimage" -#running the executable -./"$appimage" +echo "Attorney Online has been added to your app menu." +echo "You can also run it directly with: ./$appimage" diff --git a/scripts/dynamic_install.sh b/scripts/dynamic_install.sh index 428914cf0..fdc2da616 100644 --- a/scripts/dynamic_install.sh +++ b/scripts/dynamic_install.sh @@ -25,6 +25,8 @@ StartupWMClass=AttorneyOnline" echo "$desktop_file" > ~/.local/share/applications/AttorneyOnline.desktop -#running the program +#make the program executable chmod +x AttorneyOnline.bin -./AttorneyOnline.bin + +echo "Attorney Online has been added to your app menu." +echo "You can also run it directly with: ./AttorneyOnline.bin" From c3bc5e5d3b9efd2b87dcc4d6d292cf6629ab3216 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 22:23:24 +0200 Subject: [PATCH 29/33] prefer system tools --- configure.sh | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/configure.sh b/configure.sh index 279c924da..2b35e828b 100755 --- a/configure.sh +++ b/configure.sh @@ -126,7 +126,12 @@ find_qtpath() { } find_cmake() { - # Emit the cmake bundled with Qt, else empty (caller falls back to PATH). + # Prefer cmake on PATH, fall back to the cmake bundled with Qt; empty if neither. + if command -v cmake >/dev/null 2>&1; then + echo "cmake" + return 0 + fi + local cmake_path="" if [[ "$PLATFORM" == "windows" ]]; then cmake_path="${QT_ROOT}/Tools/CMake_64/bin/cmake.exe" @@ -160,7 +165,12 @@ find_mingw() { } find_ninja() { - # Prefer the ninja bundled with Qt, fall back to ninja on PATH. + # Prefer ninja on PATH, fall back to the ninja bundled with Qt; empty if neither. + if command -v ninja >/dev/null 2>&1; then + echo "ninja" + return 0 + fi + local bundled="" if [[ "$PLATFORM" == "windows" ]]; then bundled="${QT_ROOT}/Tools/Ninja/ninja.exe" @@ -173,11 +183,6 @@ find_ninja() { return 0 fi - if command -v ninja >/dev/null 2>&1; then - echo "ninja" - return 0 - fi - echo "" } @@ -490,12 +495,8 @@ configure() { CMAKE=$(find_cmake) if [ -z "$CMAKE" ]; then - echo "No cmake bundled with Qt found. Trying path..." - if ! check_command cmake ; then - echo "CMake not found. Aborting." - exit 1 - fi - CMAKE="cmake" + echo "CMake not found (checked PATH and Qt Tools). Aborting." + exit 1 fi check_command "$CMAKE" --version || { echo "cmake not working. Aborting."; exit 1; } From c669f4276bdbe9364c6efeeeaae75bd289bfa5c9 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 22:24:19 +0200 Subject: [PATCH 30/33] show the builds --- .github/workflows/build.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 35dd95f91..e62e1ebf4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -219,3 +219,20 @@ jobs: name: AttorneyOnline-macOS path: dist/AttorneyOnline-macos-*.zip archive: false + + summary: + name: Summary + needs: [build-windows, build-linux, build-linux-arm, build-macos] + if: always() + runs-on: ubuntu-latest + steps: + - name: Point to build artifacts + run: | + URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + echo "::notice title=Build artifacts::The packaged .zip artifacts are in the Artifacts section of the run summary: $URL" + { + echo "### 📦 Build artifacts" + echo "" + echo "The builds are finished. Download the packaged \`.zip\` files from the" + echo "**Artifacts** section at the bottom of the [run summary]($URL)." + } >> "$GITHUB_STEP_SUMMARY" From fe2e57f933a3e7b2e1b5fd43ef33f59951286644 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 22:30:10 +0200 Subject: [PATCH 31/33] or not --- .github/workflows/build.yml | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e62e1ebf4..35dd95f91 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -219,20 +219,3 @@ jobs: name: AttorneyOnline-macOS path: dist/AttorneyOnline-macos-*.zip archive: false - - summary: - name: Summary - needs: [build-windows, build-linux, build-linux-arm, build-macos] - if: always() - runs-on: ubuntu-latest - steps: - - name: Point to build artifacts - run: | - URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - echo "::notice title=Build artifacts::The packaged .zip artifacts are in the Artifacts section of the run summary: $URL" - { - echo "### 📦 Build artifacts" - echo "" - echo "The builds are finished. Download the packaged \`.zip\` files from the" - echo "**Artifacts** section at the bottom of the [run summary]($URL)." - } >> "$GITHUB_STEP_SUMMARY" From 552e688c1e43f28d8911eb8a52f483d3d62f4848 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 22:43:22 +0200 Subject: [PATCH 32/33] De-yap package windows --- scripts/package-windows.sh | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/scripts/package-windows.sh b/scripts/package-windows.sh index 3ed1302e4..8c3ba4031 100755 --- a/scripts/package-windows.sh +++ b/scripts/package-windows.sh @@ -1,11 +1,5 @@ #!/bin/bash -# Gathers the Qt runtime beside the built Windows .exe via windeployqt, so bin/ -# is a self-contained, runnable folder. -# -# Prerequisites: run ./configure.sh (fetches deps, generates build files, writes -# build.env) and then build (run the command configure.sh prints). - set -euo pipefail SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) @@ -28,19 +22,14 @@ echo "Running windeployqt on ${EXE}..." "${QT_ROOT_DIR}/bin/windeployqt.exe" \ --no-quick-import --no-translations --no-compiler-runtime --no-opengl-sw "$EXE" -# windeployqt runs with --no-compiler-runtime because its MinGW runtime -# deployment is unreliable in CI, so stage those DLLs ourselves from MINGW_PATH -# (resolved version-agnostically by configure.sh). Skip when it's unset, e.g. a -# local build where MinGW is already on PATH. +# needed becauce windeployqt is a bit unreliable with MINGW if [ -n "${MINGW_PATH:-}" ]; then for dll in libgcc_s_seh-1.dll libstdc++-6.dll libwinpthread-1.dll; do cp "${MINGW_PATH}/bin/${dll}" ./bin/ done fi -# Package bin/ into a single, checksummable zip named for platform/arch/commit. -# Use pwsh so zip entries use forward slashes (Windows PowerShell 5.1 uses -# backslashes, which some tools mishandle); git-bash may not ship `zip`. +# Package bin/ into one zip sha="${GITHUB_SHA:-}"; sha="${sha:0:8}" [ -z "$sha" ] && sha="$(git rev-parse --short=8 HEAD 2>/dev/null || echo dev)" ZIP="AttorneyOnline-${PLATFORM}-${ARCH}-${sha}.zip" From 39e1c9ef694fa68ba1a9875142755500afff4969 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Mon, 24 Aug 2026 23:06:17 +0200 Subject: [PATCH 33/33] Slim it down --- CMakeLists.txt | 7 ------- configure.sh | 34 +++++++--------------------------- scripts/appimage_install.sh | 6 +----- scripts/dynamic_install.sh | 5 +---- scripts/package-linux.sh | 22 +++------------------- scripts/package-macos.sh | 19 ++----------------- scripts/package-windows.sh | 1 - src/file_functions.cpp | 7 +------ src/main.cpp | 8 ++------ 9 files changed, 17 insertions(+), 92 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d0843e10a..3e39f8c43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,10 +164,6 @@ if(APPLE) MACOSX_BUNDLE_SHORT_VERSION_STRING "2.0" MACOSX_BUNDLE_ICON_FILE "logo-client.icns") - # configure.sh builds the APNG image plugin into bin/imageformats. Qt searches - # a bundle's Contents/PlugIns, not bin/, so copy it in at build time. Otherwise - # a plain build fails at launch with "Missing image formats: apng". - # (webp/gif come from the Qt install's own plugins.) if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/bin/imageformats/libqapng.dylib") add_custom_command(TARGET AttorneyOnline POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory @@ -178,9 +174,6 @@ if(APPLE) VERBATIM) endif() - # Bundle the default base (themes, fetched by configure.sh) as read-only - # defaults in Contents/Resources. On first run the app copies it to the - # writable base in Application Support (see seed_base_if_missing). if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/bin/base") add_custom_command(TARGET AttorneyOnline POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory diff --git a/configure.sh b/configure.sh index 2b35e828b..aab937f30 100755 --- a/configure.sh +++ b/configure.sh @@ -212,8 +212,7 @@ get_zip() { return 1 fi - # Snapshot the listing first; piping unzip to `grep -q` under pipefail can - # trip SIGPIPE on unzip and spuriously fail. + # First, check that all the specified files exist in the zip archive zip_listing=$(unzip -l "$tmp_zip") for arg in "$@" ; do src_file="${arg%%:*}" @@ -314,8 +313,7 @@ get_discordrpc() { discord-rpc/linux-dynamic/include/discord_rpc.h:./lib \ discord-rpc/linux-dynamic/include/discord_register.h:./lib else - # discord-rpc v3.4.0 only ships x86_64 binaries, so there is no arm64 - # build. Disabled at build time via -DAO_ENABLE_DISCORD_RPC=OFF below. + # discord-rpc v3.4.0 only ships x86_64 binaries, no arm64 echo "Skipping Discord RPC on Linux ${ARCH} (no native binary available)." fi elif [[ "$PLATFORM" == "macos" ]]; then @@ -325,9 +323,7 @@ get_discordrpc() { discord-rpc/osx-dynamic/include/discord_rpc.h:./lib \ discord-rpc/osx-dynamic/include/discord_register.h:./lib else - # discord-rpc v3.4.0 only ships an x86_64 dylib and the repo was - # archived in 2018, so there is no arm64 build, so Discord RPC is - # disabled at build time on arm64 macOS (-DAO_ENABLE_DISCORD_RPC=OFF). + # discord-rpc v3.4.0 only ships x86_64 binaries, no arm64 echo "Skipping Discord RPC on macOS ${ARCH} (no native binary available)." fi fi @@ -397,16 +393,13 @@ get_themes() { } install_build_tools() { - # Install the non-Qt build tools if missing. The presence check keeps local - # re-runs from invoking sudo/brew; fresh CI runners install here. if [[ "$PLATFORM" == "linux" ]]; then local -a pkgs=() command -v ninja >/dev/null 2>&1 || pkgs+=(ninja-build) command -v patchelf >/dev/null 2>&1 || pkgs+=(patchelf) command -v cmake >/dev/null 2>&1 || pkgs+=(cmake) command -v curl >/dev/null 2>&1 || pkgs+=(curl) - # Library packages have no command to probe, so check dpkg. The GL dev - # libs satisfy Qt6Gui's WrapOpenGL; libxcb-cursor0 lets Qt apps launch. + # GL devlibs satisfy Qt6Gui's WrapOpenGL, libxcb-cursor0 lets Qt apps launch local libs for libs in libxcb-cursor0 libgl1-mesa-dev libglvnd-dev mesa-common-dev; do dpkg -s "$libs" >/dev/null 2>&1 || pkgs+=("$libs") @@ -427,13 +420,8 @@ install_build_tools() { clean() { echo "Cleaning up all files written by configure.sh..." - # Downloaded dependencies, cloned qtapng, and build/runtime output. rm -rf ./lib/* ./bin/* ./tmp/* ./qtapng/ - - # Files configure.sh writes directly. rm -f ./build.env ./cmake_cmd.txt - - # In-source CMake/Ninja build artifacts. rm -rf ./.cmake/ ./.qt/ ./CMakeFiles/ ./AttorneyOnline_autogen/ ./Testing/ rm -f ./CMakeCache.txt ./cmake_install.cmake ./CTestTestfile.cmake ./build.ninja ./.ninja_deps ./.ninja_log rm -rf ./test/CMakeFiles/ ./test/test_aopacket_autogen/ @@ -459,20 +447,15 @@ configure() { exit 1 fi - # Only 'clean' and '-h' are arguments (handled above); configuration comes - # from the environment. if [ "$#" -gt 0 ]; then echo "Unknown argument: $1" - echo "(Configuration such as BUILD_CONFIG is read from the environment, not arguments.)" print_help exit 1 fi install_build_tools - # Resolve the Qt toolchain dir (env value wins, else auto-detect under ~/Qt). - # QT_ROOT is its grandparent, where Tools/ lives, so find_cmake / find_mingw - # / find_ninja look there. + # find Qt installation if not defined explicitly if [ -n "$QT_ROOT_DIR" ]; then if [ ! -d "$QT_ROOT_DIR" ]; then echo "$QT_ROOT_DIR is not a directory. Aborting." @@ -502,7 +485,7 @@ configure() { check_command "$CMAKE" --version || { echo "cmake not working. Aborting."; exit 1; } echo "Using cmake: $CMAKE" - # Prefer the MinGW bundled with Qt on Windows; fall back to gcc/g++ on PATH. + # Strongly prefer MinGW bundled with Qt on Windows CC="" CXX="" if [[ "$PLATFORM" == "windows" ]]; then @@ -562,8 +545,7 @@ ${EXTRA_CMAKE_FLAGS}" $FULL_CMAKE_CMD - # Record the resolved environment so the build step and packaging scripts - # can reuse it without re-running detection. + # Record the resolved environment so it can be reused later { echo "PLATFORM=\"${PLATFORM}\"" echo "ARCH=\"${ARCH}\"" @@ -576,8 +558,6 @@ ${EXTRA_CMAKE_FLAGS}" echo "$FULL_CMAKE_CMD" > cmake_cmd.txt - # configure.sh stops at a ready-to-build tree; building and packaging are - # separate steps. echo echo "Dependencies fetched and build files generated (build type: ${BUILD_CONFIG})." echo "To compile, run:" diff --git a/scripts/appimage_install.sh b/scripts/appimage_install.sh index 1508a5100..e80385e2d 100644 --- a/scripts/appimage_install.sh +++ b/scripts/appimage_install.sh @@ -1,16 +1,13 @@ #!/usr/bin/env sh -# exit on error set -e -# Move to script's directory SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" cd "${SCRIPT_DIR}" -#add .desktop file (which should allow most DE's easy access to the program mkdir -p ~/.local/share/applications -# desktop files don't like spaces in the Exec field, we have to replace them with "\s" +# desktop files don't like spaces in the Exec field, replace with "\s" appimage="AttorneyOnline.AppImage" escaped_exec="$(echo "$(pwd)/$appimage" | sed 's/ /\\s/g')" @@ -26,7 +23,6 @@ StartupWMClass=AttorneyOnline" echo "$desktop_file" > ~/.local/share/applications/AttorneyOnline.desktop -#marking the program as executable chmod +x "$appimage" echo "Attorney Online has been added to your app menu." diff --git a/scripts/dynamic_install.sh b/scripts/dynamic_install.sh index fdc2da616..b88294798 100644 --- a/scripts/dynamic_install.sh +++ b/scripts/dynamic_install.sh @@ -1,16 +1,13 @@ #!/usr/bin/env sh -# exit on error set -e -# Move to script's directory SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" cd "${SCRIPT_DIR}" -#add .desktop file (which should allow most DE's easy access to the program mkdir -p ~/.local/share/applications -# desktop files don't like spaces in the Exec field, we have to replace them with "\s" +# desktop files don't like spaces in the Exec field, replace with "\s" escaped_exec="$(echo "$(pwd)/AttorneyOnline.bin" | sed 's/ /\\s/g')" desktop_file="\ diff --git a/scripts/package-linux.sh b/scripts/package-linux.sh index a51919414..e75eacf05 100755 --- a/scripts/package-linux.sh +++ b/scripts/package-linux.sh @@ -1,13 +1,5 @@ #!/bin/bash -# Stages the Linux build into two distributable folders at the repo root -# (CI zips them on upload, so no intermediate archive here): -# - bin/ : dynamic build (expects system Qt at runtime) -# - bin-appimage/ : self-contained AppImage plus base/ assets -# -# Prerequisites: run ./configure.sh (fetches deps, generates build files, writes -# build.env) and then build (run the command configure.sh prints). - set -euo pipefail SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) @@ -25,11 +17,10 @@ if [ ! -f "./bin/AttorneyOnline.bin" ]; then exit 1 fi -# Stage the APNG plugin into the Qt install so appimagetool bundles it. mkdir -p "${QT_ROOT_DIR}/plugins/imageformats" cp ./qtapng/plugins/imageformats/libqapng.so "${QT_ROOT_DIR}/plugins/imageformats/" -# --- Dynamic build folder (expects system Qt at runtime) --- +### Dynamic build (expects system Qt at runtime) ( cd ./bin cp ../data/logo-client.png ./icon.png @@ -39,13 +30,12 @@ cp ./qtapng/plugins/imageformats/libqapng.so "${QT_ROOT_DIR}/plugins/imageformat patchelf --add-rpath . AttorneyOnline.bin ) -# --- AppImage (self-contained) --- +### AppImage (self-contained) case "$(uname -m)" in aarch64|arm64) APPIMAGE_ARCH=aarch64 ;; *) APPIMAGE_ARCH=x86_64 ;; esac -# from https://github.com/probonopd/go-appimage/blob/master/src/appimagetool/README.md wget -c "https://github.com/$(wget -q https://github.com/probonopd/go-appimage/releases/expanded_assets/continuous -O - | grep "appimagetool-.*-${APPIMAGE_ARCH}.AppImage" | head -n 1 | cut -d '"' -f 2)" mv appimagetool-*-"${APPIMAGE_ARCH}".AppImage appimagetool chmod +x appimagetool @@ -56,7 +46,6 @@ cp bin/lib*.so AppDir/usr/lib 2>/dev/null || true cp scripts/AttorneyOnline.desktop AppDir/usr/share/applications cp data/logo-client.png AppDir/AttorneyOnline.png -# Prefer the CI-provided commit SHA; fall back to git (or "dev") for local runs. GIT_SHORT_SHA="${GITHUB_SHA:-}" GIT_SHORT_SHA="${GIT_SHORT_SHA:0:8}" if [ -z "$GIT_SHORT_SHA" ]; then @@ -64,14 +53,11 @@ if [ -z "$GIT_SHORT_SHA" ]; then fi QTDIR="$QT_ROOT_DIR" ./appimagetool deploy AppDir/usr/share/applications/AttorneyOnline.desktop -# go-appimage's deploy bundles the ELF interpreter without an executable bit, so -# AppRun's `exec ld-linux-*.so ...` fails at runtime with "Permission denied". -# Restore the bit before building the image. +# go-appimage clobbers permissions, needs fixing find AppDir -type f -name 'ld-linux-*' -exec chmod +x {} + ARCH="${APPIMAGE_ARCH}" VERSION="${GIT_SHORT_SHA}" ./appimagetool AppDir -# --- AppImage bundle folder --- rm -rf bin-appimage mkdir bin-appimage cp -r bin/base bin-appimage @@ -81,8 +67,6 @@ cp scripts/appimage_install.sh bin-appimage/install.sh cp ./*-"${APPIMAGE_ARCH}".AppImage "bin-appimage/AttorneyOnline.AppImage" chmod +x "bin-appimage/install.sh" "bin-appimage/AttorneyOnline.AppImage" -# Package each folder into a single, checksummable zip named for -# platform/arch/commit. -y preserves the AppImage's executable bit and symlinks. DYN_ZIP="AttorneyOnline-${PLATFORM}dynamic-${ARCH}-${GIT_SHORT_SHA}.zip" APP_ZIP="AttorneyOnline-${PLATFORM}appimage-${ARCH}-${GIT_SHORT_SHA}.zip" mkdir -p dist diff --git a/scripts/package-macos.sh b/scripts/package-macos.sh index 0d0905171..600bb39b4 100755 --- a/scripts/package-macos.sh +++ b/scripts/package-macos.sh @@ -1,11 +1,5 @@ #!/bin/bash -# Packages the macOS build into a self-contained, distributable .app + zip. -# -# Prerequisites: run ./configure.sh (fetches deps, generates build files, writes -# build.env) and then build (run the command configure.sh prints). This script -# assembles bin/"Attorney Online.app" and zips it into dist/. - set -euo pipefail SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) @@ -26,31 +20,22 @@ fi echo "Bundling dependencies into ${APP}..." -# BASS (and BASSOPUS/Discord if present) are loaded via @loader_path, i.e. -# relative to the executable, so they go beside it in Contents/MacOS. cp ./lib/libbass.dylib ./lib/libbassopus.dylib "${APP}/Contents/MacOS/" if [ -f ./lib/libdiscord-rpc.dylib ]; then cp ./lib/libdiscord-rpc.dylib "${APP}/Contents/MacOS/" fi -# The APNG image plugin must live under the bundle's PlugIns dir before -# macdeployqt runs, so its Qt references get rewritten along with the rest. mkdir -p "${APP}/Contents/PlugIns/imageformats" cp ./bin/imageformats/libqapng.dylib "${APP}/Contents/PlugIns/imageformats/" "${QT_ROOT_DIR}/bin/macdeployqt" "$APP" -verbose=1 -# macdeployqt and the copies above rewrite Mach-O files, invalidating their -# signatures; without a valid one Gatekeeper calls the app "damaged", so re-sign -# ad-hoc inside-out. Not notarized, so a downloaded copy is still quarantined: -# xattr -dr com.apple.quarantine "Attorney Online.app" +# macdeployqt invalidates signatures, we need to fix that here or else macOS will call the app damaged and refuse to run +# note that it still needs to be manually approved in Privacy & Security but oh well codesign --force --sign - "${APP}/Contents/MacOS/"*.dylib 2>/dev/null || true codesign --force --sign - "${APP}/Contents/PlugIns/imageformats/"*.dylib 2>/dev/null || true codesign --force --deep --sign - "$APP" -# Package the self-contained app into a single, checksummable zip named for -# platform/arch/commit (base is bundled inside the app, icon in Contents/Resources, -# so nothing ships alongside). -y preserves macdeployqt's framework symlinks. sha="${GITHUB_SHA:-}"; sha="${sha:0:8}" [ -z "$sha" ] && sha="$(git rev-parse --short=8 HEAD 2>/dev/null || echo dev)" ZIP="AttorneyOnline-${PLATFORM}-${ARCH}-${sha}.zip" diff --git a/scripts/package-windows.sh b/scripts/package-windows.sh index 8c3ba4031..81af52220 100755 --- a/scripts/package-windows.sh +++ b/scripts/package-windows.sh @@ -29,7 +29,6 @@ if [ -n "${MINGW_PATH:-}" ]; then done fi -# Package bin/ into one zip sha="${GITHUB_SHA:-}"; sha="${sha:0:8}" [ -z "$sha" ] && sha="$(git rev-parse --short=8 HEAD 2>/dev/null || echo dev)" ZIP="AttorneyOnline-${PLATFORM}-${ARCH}-${sha}.zip" diff --git a/src/file_functions.cpp b/src/file_functions.cpp index 6199f34ed..74869ecd7 100644 --- a/src/file_functions.cpp +++ b/src/file_functions.cpp @@ -80,9 +80,6 @@ QString get_app_path() QString get_base_path() { #ifdef Q_OS_MAC - // The .app is relocatable and can run read-only under Gatekeeper App - // Translocation, so the writable base lives in Application Support. Defaults - // are seeded there from the bundle on first run (see seed_base_if_missing). return QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)).absoluteFilePath("base") + "/"; #else return QDir(get_app_path()).absoluteFilePath("base") + "/"; @@ -98,14 +95,12 @@ void seed_base_if_missing() return; } - // Read-only defaults shipped inside the bundle at Contents/Resources/base. const QString bundled_base = QDir::cleanPath(QCoreApplication::applicationDirPath() + "/../Resources/base"); if (!dir_exists(bundled_base)) { - return; // e.g. a non-bundle run; nothing to seed + return; } - // ditto creates the destination tree and preserves symlinks/permissions. QProcess::execute("ditto", {bundled_base, user_base}); #endif } diff --git a/src/main.cpp b/src/main.cpp index 420433b63..f54734206 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,14 +20,10 @@ int main(int argc, char *argv[]) QApplication app(argc, argv); - // Associate the app's windows with the AttorneyOnline.desktop entry so the - // desktop environment shows the right name/icon for the running app (Wayland - // app_id / X11 WM_CLASS) instead of the raw executable name. QApplication::setDesktopFileName("AttorneyOnline"); - // On macOS, seed the writable base in Application Support from the bundled - // defaults on first run (no-op on other platforms). Must run before any base - // access below. + // On macOS, we distribute with a built-in base in the .app but this is not writeable + // so we copy the base folder into the preferred base path (~/Library/Application Support/Attorney Online) seed_base_if_missing(); #ifdef ANDROID