From cd6a5a6c0f3034869b369f07a373c84d75f03f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Wed, 23 Sep 2026 07:55:03 +0200 Subject: [PATCH 01/20] Add CI to build macOS (universal arm64+x86_64), Linux, and Windows clients macOS support for x86_64-only apps is going away, so CI now produces a single universal .app covering both Apple Silicon and Intel Macs (Qt's official macOS package has shipped universal since Qt 6.2). Linux and Windows client builds are added alongside it so all existing targets get automated builds too, uploaded as workflow artifacts on every push/PR and attached to GitHub Releases on version tags. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/build.yml | 152 ++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..dfe482f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,152 @@ +name: Build clients + +on: + push: + branches: [main] + tags: ["v*"] + pull_request: + workflow_dispatch: + +env: + QT_VERSION: "6.11.1" + +jobs: + macos: + name: macOS (universal x86_64 + arm64) + runs-on: macos-14 + steps: + - uses: actions/checkout@v4 + + - name: Install Qt + uses: jurplel/install-qt-action@v4 + with: + version: ${{ env.QT_VERSION }} + target: desktop + cache: true + + - name: Build + run: | + qmake -recursive QMAKE_APPLE_DEVICE_ARCHS="x86_64 arm64" MACOSX_DEPLOYMENT_TARGET="12.7.6" + make -j"$(sysctl -n hw.ncpu)" + + - name: Bundle app icon + run: | + mkdir -p ApolloExplorerPC/ApolloExplorer.app/Contents/Resources/ + cp ApolloExplorerPC/icons/ApolloExplorer.icns ApolloExplorerPC/ApolloExplorer.app/Contents/Resources/ + + - name: Deploy Qt frameworks + run: macdeployqt ApolloExplorerPC/ApolloExplorer.app -verbose=1 + + - name: Ad-hoc code sign + # Unsigned/unnotarized: no Apple Developer ID is available in CI. Ad-hoc + # signing (identity "-") is required regardless, since arm64 refuses to + # run any unsigned Mach-O binary at all. Users must still right-click -> + # Open (or `xattr -cr`) the first time because of the quarantine flag. + run: | + codesign --force --deep --sign - ApolloExplorerPC/ApolloExplorer.app + codesign --force --sign - acp/acp + codesign --force --sign - ash/ash + + - name: Package + run: | + mkdir -p ApolloExplorer-macOS + mv ApolloExplorerPC/ApolloExplorer.app ApolloExplorer-macOS/ + mv acp/acp ApolloExplorer-macOS/ + mv ash/ash ApolloExplorer-macOS/ + ditto -c -k --sequesterRsrc --keepParent ApolloExplorer-macOS ApolloExplorer-macOS-universal.zip + + - uses: actions/upload-artifact@v4 + with: + name: ApolloExplorer-macOS-universal + path: ApolloExplorer-macOS-universal.zip + + linux: + name: Linux (x86_64) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Qt + uses: jurplel/install-qt-action@v4 + with: + version: ${{ env.QT_VERSION }} + target: desktop + install-deps: true + cache: true + + - name: Install build tools + run: sudo apt-get update && sudo apt-get install -y build-essential + + - name: Build + run: | + qmake -recursive + make -j"$(nproc)" + + - name: Package + run: | + mkdir -p ApolloExplorer-Linux + mv ApolloExplorerPC/ApolloExplorer ApolloExplorer-Linux/ApolloExplorer + cp ApolloExplorerPC/ApolloExplorer.desktop ApolloExplorer-Linux/ApolloExplorer.desktop + mv acp/acp ApolloExplorer-Linux/acp + mv ash/ash ApolloExplorer-Linux/ash + tar -czf ApolloExplorer-Linux-x86_64.tar.gz ApolloExplorer-Linux + + - uses: actions/upload-artifact@v4 + with: + name: ApolloExplorer-Linux-x86_64 + path: ApolloExplorer-Linux-x86_64.tar.gz + + windows: + name: Windows (x86_64) + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Qt + uses: jurplel/install-qt-action@v4 + with: + version: ${{ env.QT_VERSION }} + target: desktop + arch: win64_msvc2022_64 + cache: true + + - name: Set up MSVC environment + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: amd64 + + - name: Build + shell: cmd + run: | + qmake -recursive -config release + nmake + + - name: Package + shell: pwsh + run: | + New-Item -ItemType Directory -Force -Path ApolloExplorer-Windows + Move-Item ApolloExplorerPC\release\ApolloExplorer.exe ApolloExplorer-Windows\ApolloExplorer.exe + Move-Item acp\release\acp.exe ApolloExplorer-Windows\acp.exe + Move-Item ash\release\ash.exe ApolloExplorer-Windows\ash.exe + Compress-Archive -Path ApolloExplorer-Windows\* -DestinationPath ApolloExplorer-Windows-x86_64.zip + + - uses: actions/upload-artifact@v4 + with: + name: ApolloExplorer-Windows-x86_64 + path: ApolloExplorer-Windows-x86_64.zip + + release: + name: Attach artifacts to release + if: startsWith(github.ref, 'refs/tags/v') + needs: [macos, linux, windows] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/download-artifact@v4 + with: + path: artifacts + + - uses: softprops/action-gh-release@v2 + with: + files: artifacts/**/* From 22ea6cac09820811b6765a7021865d10cea02e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Wed, 23 Sep 2026 07:56:25 +0200 Subject: [PATCH 02/20] Document real signing/notarization as a future TODO Keeps ad-hoc signing for now; leaves a placeholder noting the Apple Developer ID cert + notarytool steps needed to sign for real later. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/build.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dfe482f..58c7cc3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,6 +42,14 @@ jobs: # signing (identity "-") is required regardless, since arm64 refuses to # run any unsigned Mach-O binary at all. Users must still right-click -> # Open (or `xattr -cr`) the first time because of the quarantine flag. + # + # TODO: real signing/notarization, deferred for now. build_macos_client.sh + # does this locally with an Apple Developer Team ID (${APPLETEAMID}) via + # `codesign --sign ${APPLETEAMID}` + `macdeployqt -sign-for-notarization=` + # + `xcrun notarytool`. To wire this into CI: add the Developer ID cert + # (base64 .p12) and APPLETEAMID as repo secrets, import the cert into a + # temporary keychain, sign with it instead of "-", then notarize + staple + # before packaging. run: | codesign --force --deep --sign - ApolloExplorerPC/ApolloExplorer.app codesign --force --sign - acp/acp From 26329829eec97dfbea4948bdb247e353e3e5fb03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Wed, 23 Sep 2026 08:09:56 +0200 Subject: [PATCH 03/20] Pin Windows CI build to Qt 6.10.1 to work around aqtinstall bug aqtinstall cannot resolve Updates.xml for any Qt 6.11.x version on Windows targets, because Qt restructured its Windows repo layout starting at 6.11.0 (added an arm64 cross-compile variant folder). macOS and Linux repos were not restructured, so they stay on 6.11.1. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/build.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 58c7cc3..fd35a0c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -107,6 +107,13 @@ jobs: windows: name: Windows (x86_64) runs-on: windows-latest + env: + # aqtinstall cannot resolve any 6.11.x version for Windows targets: Qt + # restructured its Windows repo layout starting at 6.11.0 (added an + # arm64 cross-compile variant folder), which breaks Updates.xml + # resolution upstream in aqtinstall. Pin to the last known-good release + # here instead of env.QT_VERSION until aqtinstall ships a fix. + QT_VERSION: "6.10.1" steps: - uses: actions/checkout@v4 From 7cac921cde7ad909f55ef97d0fa8055fc8b6cef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Wed, 23 Sep 2026 08:14:48 +0200 Subject: [PATCH 04/20] Replace VLA with QByteArray in AmigaInfoFile icon decoding for MSVC portability char indexedImageData[width * height] is a GCC/Clang extension (VLA), not standard C++, and cl.exe rejects it outright. The resulting parse error cascaded into bogus "undeclared identifier" errors for the rest of the translation unit, breaking the Windows CI build. GCC/Clang silently accepted the VLA, so this never surfaced on macOS/Linux. Co-Authored-By: Claude Sonnet 5 --- AmigaIconReader/AmigaInfoFile.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/AmigaIconReader/AmigaInfoFile.cpp b/AmigaIconReader/AmigaInfoFile.cpp index a0e48a0..b6f4ff1 100644 --- a/AmigaIconReader/AmigaInfoFile.cpp +++ b/AmigaIconReader/AmigaInfoFile.cpp @@ -106,8 +106,7 @@ static inline void putByteArrayInBuffer( QByteArray &someBuffer, quint64 &someOf static inline QByteArray BitPlanarToChunky( QByteArray bitPlanarPixels, quint32 width, quint32 height, quint8 bitsPerPixel ) { //Set aside some memory for our chunky icon - char indexedImageData[ width * height ]; - memset( indexedImageData, 0, width * height ); + QByteArray indexedImageData( static_cast( width * height ), 0 ); //Load in the bitplanar bitmap quint64 pitplaneRowByteLength = ((width+15)/16)*2; @@ -123,15 +122,15 @@ static inline QByteArray BitPlanarToChunky( QByteArray bitPlanarPixels, quint32 quint8 bit = 7-x%8; quint8 srcPixelByte = bitPlanarPixels.constData()[ (bitPlane * bitplaneSizeInBytes ) + ( y*pitplaneRowByteLength ) + ( x/8 ) ]; srcPixelByte = qFromBigEndian( srcPixelByte ); - quint8 dstPixelByte = indexedImageData[ x+(y*width) ]; + quint8 dstPixelByte = static_cast( indexedImageData[ x+(y*width) ] ); quint8 bitComparator = (1<( resultingPixelByte ); } } } - return QByteArray( indexedImageData, width * height ); + return indexedImageData; } @@ -560,8 +559,7 @@ qint16 AmigaInfoFile::getPriority() QByteArray AmigaInfoFile::decodeOS2Icon(QByteArray data, quint64 &offset, quint8 depth, quint32 width, quint32 height ) { //Set aside some memory for our chunky icon - char indexedImageData[ width * height ]; - memset( indexedImageData, 0, width * height ); + QByteArray indexedImageData( static_cast( width * height ), 0 ); //Load in the bitplanar bitmap quint64 pitplaneRowByteLength = ((width+15)/16)*2; @@ -578,10 +576,10 @@ QByteArray AmigaInfoFile::decodeOS2Icon(QByteArray data, quint64 &offset, quint8 quint8 bit = 7-x%8; quint8 srcPixelByte = bitPlanes.constData()[ (bitPlane * bitplaneSizeInBytes ) + ( y*pitplaneRowByteLength ) + ( x/8 ) ]; srcPixelByte = qFromBigEndian( srcPixelByte ); - quint8 dstPixelByte = indexedImageData[ x+(y*width) ]; + quint8 dstPixelByte = static_cast( indexedImageData[ x+(y*width) ] ); quint8 bitComparator = (1<( resultingPixelByte ); } } } @@ -589,7 +587,7 @@ QByteArray AmigaInfoFile::decodeOS2Icon(QByteArray data, quint64 &offset, quint8 //Adjust the offset offset += bitplaneSizeInBytes * depth; - return QByteArray( indexedImageData, width * height ); + return indexedImageData; } From 00d1b1c577c9ea8efa81f32cf345c5e32ef1795b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Wed, 23 Sep 2026 08:19:01 +0200 Subject: [PATCH 05/20] Make AmigaInfoFile struct packing portable to MSVC __attribute__((packed)) is a GCC/Clang-only extension. cl.exe doesn't recognize it and fails to parse it as an attribute at all, producing "'packed': undeclared identifier" and cascading syntax errors through the rest of the header (and everywhere it's included), which is what broke the Windows CI build. Introduce AMIGA_PACKED / AMIGA_PACKED_STRUCT_BEGIN/END macros that expand to __attribute__((packed)) on GCC/Clang and to #pragma pack(push, 1)/pack(pop) on MSVC, preserving the same tightly-packed wire-format layout needed to parse Amiga .info files on all three compilers. IconType_t's packed attribute is dropped outright since it's never used as a struct field (only as a value type in getType()/setType()), so it has no effect on the parsed layout. Co-Authored-By: Claude Sonnet 5 --- AmigaIconReader/AmigaInfoFile.h | 38 +++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/AmigaIconReader/AmigaInfoFile.h b/AmigaIconReader/AmigaInfoFile.h index aba27f9..938efa4 100644 --- a/AmigaIconReader/AmigaInfoFile.h +++ b/AmigaIconReader/AmigaInfoFile.h @@ -20,7 +20,20 @@ typedef quint16 UWORD; typedef qint8 BYTE; #endif +// __attribute__((packed)) is a GCC/Clang extension; MSVC's cl.exe doesn't +// understand it and fails to parse the struct at all. Use #pragma pack on +// MSVC instead to get the same tightly-packed, wire-format-compatible layout. +#if defined(_MSC_VER) +#define AMIGA_PACKED_STRUCT_BEGIN __pragma(pack(push, 1)) +#define AMIGA_PACKED_STRUCT_END __pragma(pack(pop)) +#define AMIGA_PACKED +#else +#define AMIGA_PACKED_STRUCT_BEGIN +#define AMIGA_PACKED_STRUCT_END +#define AMIGA_PACKED __attribute__((packed)) +#endif +AMIGA_PACKED_STRUCT_BEGIN typedef struct { qint16 LeftEdge; /* starting offset relative to some origin */ @@ -32,8 +45,10 @@ typedef struct quint8 PlanePick; quint8 PlaneOnOff; quint32 NextImage; -} __attribute__((packed)) Image_t; +} AMIGA_PACKED Image_t; +AMIGA_PACKED_STRUCT_END +AMIGA_PACKED_STRUCT_BEGIN typedef struct { quint32 ga_Next; //always 0 qint16 ga_LeftEdge; @@ -59,8 +74,10 @@ typedef struct { quint32 ga_SpecialInfo; // quint16 ga_GadgetID; // quint32 ga_UserData; -} __attribute__((packed)) Gadget_t; +} AMIGA_PACKED Gadget_t; +AMIGA_PACKED_STRUCT_END +AMIGA_PACKED_STRUCT_BEGIN struct NewWindow { #if 0 @@ -81,21 +98,26 @@ struct NewWindow char padding[48]; #endif -} __attribute__((packed)); +} AMIGA_PACKED; +AMIGA_PACKED_STRUCT_END +AMIGA_PACKED_STRUCT_BEGIN typedef struct { quint32 dd_Flags; /* flags for drawer */ quint16 dd_ViewModes; /* view mode for drawer */ -} __attribute__((packed)) DrawerData2_t; +} AMIGA_PACKED DrawerData2_t; +AMIGA_PACKED_STRUCT_END +AMIGA_PACKED_STRUCT_BEGIN typedef struct { struct NewWindow dd_NewWindow; /* args to open window */ qint32 dd_CurrentX; /* current x coordinate of origin */ qint32 dd_CurrentY; /* current y coordinate of origin */ //DrawerData2_t dd_Extended; -} __attribute__((packed)) DrawerData_t; +} AMIGA_PACKED DrawerData_t; +AMIGA_PACKED_STRUCT_END @@ -109,8 +131,9 @@ typedef enum DEVICE = 6, KICK = 7, APPICON = 8 -} __attribute__((packed)) IconType_t; +} IconType_t; +AMIGA_PACKED_STRUCT_BEGIN typedef struct { quint16 do_Magic; //magic number at start of file. Always 0xE310 quint16 do_Version; //Always 1 @@ -133,7 +156,8 @@ typedef struct { //ToolWindow text if ic_ToolWindow not zero (format see below) // this is an extension, which was never implemented //struct DrawerData2 -} __attribute__((packed)) OS2Icon_t; +} AMIGA_PACKED OS2Icon_t; +AMIGA_PACKED_STRUCT_END class AmigaInfoFile : public QObject { From e4bac745c26156e25ad63e6cb33d99543e198a91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Wed, 23 Sep 2026 08:22:13 +0200 Subject: [PATCH 06/20] Use MSVC-portable __FUNCSIG__ instead of __PRETTY_FUNCTION__ in AEUtils logging macros __PRETTY_FUNCTION__ is a GCC/Clang extension and doesn't exist under MSVC, breaking the Windows CI build. __FUNCSIG__ is the MSVC equivalent. Co-Authored-By: Claude Sonnet 5 --- ApolloExplorerPC/AEUtils.h | 10 ++++++++-- acp/AEUtils.h | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/ApolloExplorerPC/AEUtils.h b/ApolloExplorerPC/AEUtils.h index b816a68..8eefb01 100755 --- a/ApolloExplorerPC/AEUtils.h +++ b/ApolloExplorerPC/AEUtils.h @@ -5,12 +5,18 @@ #include #include +#if defined(_MSC_VER) +#define AE_PRETTY_FUNCTION __FUNCSIG__ +#else +#define AE_PRETTY_FUNCTION __PRETTY_FUNCTION__ +#endif + #if DEBUG > 0 -#define DBGLOG qDebug().noquote() << __PRETTY_FUNCTION__ +#define DBGLOG qDebug().noquote() << AE_PRETTY_FUNCTION #else #define DBGLOG QNoDebug() #endif -#define WARNLOG qWarning().noquote() << __PRETTY_FUNCTION__ +#define WARNLOG qWarning().noquote() << AE_PRETTY_FUNCTION QString convertFromAmigaTextEncoding( char *text ); void convertFromUTF8ToAmigaTextEncoding( QString utf8Text, char *encodedText, quint64 encodedTextLength ); diff --git a/acp/AEUtils.h b/acp/AEUtils.h index fac255a..cf1cf3c 100755 --- a/acp/AEUtils.h +++ b/acp/AEUtils.h @@ -5,12 +5,18 @@ #include #include +#if defined(_MSC_VER) +#define AE_PRETTY_FUNCTION __FUNCSIG__ +#else +#define AE_PRETTY_FUNCTION __PRETTY_FUNCTION__ +#endif + #if DEBUG > 0 -#define DBGLOG qDebug().noquote() << __PRETTY_FUNCTION__ +#define DBGLOG qDebug().noquote() << AE_PRETTY_FUNCTION #else #define DBGLOG QNoDebug() #endif -#define WARNLOG qWarning().noquote() << __PRETTY_FUNCTION__ +#define WARNLOG qWarning().noquote() << AE_PRETTY_FUNCTION QString convertFromAmigaTextEncoding( char *text ); void convertFromUTF8ToAmigaTextEncoding( QString utf8Text, char *encodedText, quint64 encodedTextLength ); From f1dff8a657e1439f9fdd4c5e95c9f4f7d4c1e4fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Wed, 23 Sep 2026 08:29:25 +0200 Subject: [PATCH 07/20] Exclude ash from Windows builds: it needs POSIX termios ash is an interactive terminal client built on tcsetattr, STDIN_FILENO, and raw non-blocking read() from termios.h/unistd.h, none of which exist on Windows. It was never built by build_windows_client.ps1 either, so this doesn't remove Windows support that used to work. Porting its raw terminal handling to the Windows Console API would be a separate, larger effort. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/build.yml | 3 ++- ApolloExplorer.pro | 11 +++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd35a0c..f8118c8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -139,10 +139,11 @@ jobs: - name: Package shell: pwsh run: | + # ash is excluded on win32 in ApolloExplorer.pro (see comment there): + # it needs POSIX termios, which Windows doesn't have. New-Item -ItemType Directory -Force -Path ApolloExplorer-Windows Move-Item ApolloExplorerPC\release\ApolloExplorer.exe ApolloExplorer-Windows\ApolloExplorer.exe Move-Item acp\release\acp.exe ApolloExplorer-Windows\acp.exe - Move-Item ash\release\ash.exe ApolloExplorer-Windows\ash.exe Compress-Archive -Path ApolloExplorer-Windows\* -DestinationPath ApolloExplorer-Windows-x86_64.zip - uses: actions/upload-artifact@v4 diff --git a/ApolloExplorer.pro b/ApolloExplorer.pro index 6ee0c03..cb60729 100755 --- a/ApolloExplorer.pro +++ b/ApolloExplorer.pro @@ -1,4 +1,11 @@ TEMPLATE=subdirs SUBDIRS=ApolloExplorerPC AmigaIconReader \ - acp \ - ash + acp + +# ash is an interactive terminal client built on POSIX termios/tcsetattr and +# raw STDIN_FILENO reads, none of which exist on Windows. It was never built +# by build_windows_client.ps1 either; porting it to the Windows Console API +# is a separate, larger effort. +!win32 { + SUBDIRS += ash +} From 05acb0e6d081eaeb8cf4f9f4d36b6b7444502b2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Wed, 23 Sep 2026 08:30:58 +0200 Subject: [PATCH 08/20] Port ash's interactive raw-mode terminal handling to Windows ash/main.cpp used POSIX-only termios raw mode, isatty, and fcntl-based non-blocking reads to drive its interactive shell, none of which exist on MSVC. Add a Windows path using conio.h's _getch() for unbuffered, unechoed keystroke reads (which needs no raw-mode toggling), _isatty for the TTY check, and _write for the SIGINT handler. Windows arrow/ function keys arrive as a 0/0xE0-prefixed two-byte sequence instead of an ANSI escape sequence, so they're swallowed in readInteractiveByte() rather than via the POSIX ESC-sequence discard path. Also guard the acp file transfer progress bars' unistd.h include (needed only for the already-guarded ioctl/TIOCGWINSZ terminal-width lookup) so it doesn't pull in a header MSVC doesn't have. Co-Authored-By: Claude Sonnet 5 --- acp/filedownloader.cpp | 2 +- acp/fileuploader.cpp | 2 +- ash/main.cpp | 66 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/acp/filedownloader.cpp b/acp/filedownloader.cpp index 6a61a6b..772fc35 100644 --- a/acp/filedownloader.cpp +++ b/acp/filedownloader.cpp @@ -10,8 +10,8 @@ #include #if __linux__ || __APPLE__ #include -#endif #include +#endif FileDownloader::FileDownloader( QString remoteSources, QString localDestination, QString remoteHost, QObject *parent ) : QObject{parent}, diff --git a/acp/fileuploader.cpp b/acp/fileuploader.cpp index 7cfd3d4..a4de5e9 100644 --- a/acp/fileuploader.cpp +++ b/acp/fileuploader.cpp @@ -6,9 +6,9 @@ #include #if __linux || __APPLE__ #include +#include #endif #include -#include #define DEBUG 0 #include "AEUtils.h" diff --git a/ash/main.cpp b/ash/main.cpp index 6bc91c5..da0c4ad 100644 --- a/ash/main.cpp +++ b/ash/main.cpp @@ -5,10 +5,16 @@ #include #include #include +#include + +#ifdef _WIN32 +#include +#include +#else #include #include -#include #include +#endif #define DEBUG 0 #include "AEUtils.h" @@ -19,20 +25,30 @@ // ---------- terminal raw-mode helpers ---------- +#ifndef _WIN32 static struct termios g_SavedTermios; +#endif static bool g_RawModeActive = false; static void disableRawMode() { +#ifdef _WIN32 + g_RawModeActive = false; +#else if( g_RawModeActive ) { tcsetattr( STDIN_FILENO, TCSAFLUSH, &g_SavedTermios ); g_RawModeActive = false; } +#endif } static void enableRawMode() { +#ifdef _WIN32 + // _getch() (used for interactive input below) reads unbuffered, + // unechoed keystrokes directly, so there is no console mode to flip. +#else tcgetattr( STDIN_FILENO, &g_SavedTermios ); atexit( disableRawMode ); @@ -44,17 +60,48 @@ static void enableRawMode() raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; tcsetattr( STDIN_FILENO, TCSAFLUSH, &raw ); +#endif g_RawModeActive = true; } static void sigintHandler( int sig ) { disableRawMode(); +#ifdef _WIN32 + _write( _fileno( stdout ), "\r\n", 2 ); +#else write( STDOUT_FILENO, "\r\n", 2 ); +#endif signal( sig, SIG_DFL ); raise( sig ); } +// Reads one interactive input unit from the console. +// >= 0 : a regular byte to process +// -1 : end of input (EOF) +// -2 : a special key already consumed (Windows arrow/function keys +// arrive as a two-byte sequence; the caller should ignore these) +static int readInteractiveByte() +{ +#ifdef _WIN32 + int ch = _getch(); + if( ch == EOF ) + return -1; + if( ch == 0 || ch == 0xE0 ) + { + // Discard the scan-code byte that follows a function/arrow key. + _getch(); + return -2; + } + return ch; +#else + char c; + if( read( STDIN_FILENO, &c, 1 ) != 1 ) + return -1; + return (unsigned char)c; +#endif +} + // ---------- host resolution helpers ---------- static QStringList getHostList( QSharedPointer settings, const QStringList &positionalArguments ) @@ -199,7 +246,11 @@ int main( int argc, char *argv[] ) QString command = positional.join( " " ); exitCode = session.execute( command ); } +#ifdef _WIN32 + else if( !_isatty( _fileno( stdin ) ) ) +#else else if( !isatty( STDIN_FILENO ) ) +#endif { // Non-TTY (pipe/script): read lines without raw mode or tab completion char lineBuf[4096]; @@ -231,9 +282,13 @@ int main( int argc, char *argv[] ) printPrompt(); - char c; - while( read( STDIN_FILENO, &c, 1 ) == 1 ) + int ic; + while( ( ic = readInteractiveByte() ) != -1 ) { + if( ic == -2 ) + continue; + char c = (char)ic; + if( c == '\t' ) { bool isDoubleTab = lastWasTab; @@ -317,12 +372,17 @@ int main( int argc, char *argv[] ) } else if( c == 27 ) { +#ifndef _WIN32 // ESC: swallow the rest of the escape sequence (arrow keys etc.) int flags = fcntl( STDIN_FILENO, F_GETFL, 0 ); fcntl( STDIN_FILENO, F_SETFL, flags | O_NONBLOCK ); char discard[8]; while( read( STDIN_FILENO, discard, sizeof(discard) ) > 0 ) {} fcntl( STDIN_FILENO, F_SETFL, flags ); +#endif + // On Windows, arrow/function keys are already swallowed + // by readInteractiveByte(), so a lone ESC here is just + // the Escape key itself; nothing more to discard. } else if( (unsigned char)c >= 32 && (unsigned char)c < 127 ) { From 26768e4fe45dd7af2de10ee919c6cb5b353de32b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Wed, 23 Sep 2026 08:31:35 +0200 Subject: [PATCH 09/20] Revert "Port ash's interactive raw-mode terminal handling to Windows" This reverts commit 05acb0e6d081eaeb8cf4f9f4d36b6b7444502b2b. --- acp/filedownloader.cpp | 2 +- acp/fileuploader.cpp | 2 +- ash/main.cpp | 66 ++---------------------------------------- 3 files changed, 5 insertions(+), 65 deletions(-) diff --git a/acp/filedownloader.cpp b/acp/filedownloader.cpp index 772fc35..6a61a6b 100644 --- a/acp/filedownloader.cpp +++ b/acp/filedownloader.cpp @@ -10,8 +10,8 @@ #include #if __linux__ || __APPLE__ #include -#include #endif +#include FileDownloader::FileDownloader( QString remoteSources, QString localDestination, QString remoteHost, QObject *parent ) : QObject{parent}, diff --git a/acp/fileuploader.cpp b/acp/fileuploader.cpp index a4de5e9..7cfd3d4 100644 --- a/acp/fileuploader.cpp +++ b/acp/fileuploader.cpp @@ -6,9 +6,9 @@ #include #if __linux || __APPLE__ #include -#include #endif #include +#include #define DEBUG 0 #include "AEUtils.h" diff --git a/ash/main.cpp b/ash/main.cpp index da0c4ad..6bc91c5 100644 --- a/ash/main.cpp +++ b/ash/main.cpp @@ -5,16 +5,10 @@ #include #include #include -#include - -#ifdef _WIN32 -#include -#include -#else #include #include +#include #include -#endif #define DEBUG 0 #include "AEUtils.h" @@ -25,30 +19,20 @@ // ---------- terminal raw-mode helpers ---------- -#ifndef _WIN32 static struct termios g_SavedTermios; -#endif static bool g_RawModeActive = false; static void disableRawMode() { -#ifdef _WIN32 - g_RawModeActive = false; -#else if( g_RawModeActive ) { tcsetattr( STDIN_FILENO, TCSAFLUSH, &g_SavedTermios ); g_RawModeActive = false; } -#endif } static void enableRawMode() { -#ifdef _WIN32 - // _getch() (used for interactive input below) reads unbuffered, - // unechoed keystrokes directly, so there is no console mode to flip. -#else tcgetattr( STDIN_FILENO, &g_SavedTermios ); atexit( disableRawMode ); @@ -60,48 +44,17 @@ static void enableRawMode() raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; tcsetattr( STDIN_FILENO, TCSAFLUSH, &raw ); -#endif g_RawModeActive = true; } static void sigintHandler( int sig ) { disableRawMode(); -#ifdef _WIN32 - _write( _fileno( stdout ), "\r\n", 2 ); -#else write( STDOUT_FILENO, "\r\n", 2 ); -#endif signal( sig, SIG_DFL ); raise( sig ); } -// Reads one interactive input unit from the console. -// >= 0 : a regular byte to process -// -1 : end of input (EOF) -// -2 : a special key already consumed (Windows arrow/function keys -// arrive as a two-byte sequence; the caller should ignore these) -static int readInteractiveByte() -{ -#ifdef _WIN32 - int ch = _getch(); - if( ch == EOF ) - return -1; - if( ch == 0 || ch == 0xE0 ) - { - // Discard the scan-code byte that follows a function/arrow key. - _getch(); - return -2; - } - return ch; -#else - char c; - if( read( STDIN_FILENO, &c, 1 ) != 1 ) - return -1; - return (unsigned char)c; -#endif -} - // ---------- host resolution helpers ---------- static QStringList getHostList( QSharedPointer settings, const QStringList &positionalArguments ) @@ -246,11 +199,7 @@ int main( int argc, char *argv[] ) QString command = positional.join( " " ); exitCode = session.execute( command ); } -#ifdef _WIN32 - else if( !_isatty( _fileno( stdin ) ) ) -#else else if( !isatty( STDIN_FILENO ) ) -#endif { // Non-TTY (pipe/script): read lines without raw mode or tab completion char lineBuf[4096]; @@ -282,13 +231,9 @@ int main( int argc, char *argv[] ) printPrompt(); - int ic; - while( ( ic = readInteractiveByte() ) != -1 ) + char c; + while( read( STDIN_FILENO, &c, 1 ) == 1 ) { - if( ic == -2 ) - continue; - char c = (char)ic; - if( c == '\t' ) { bool isDoubleTab = lastWasTab; @@ -372,17 +317,12 @@ int main( int argc, char *argv[] ) } else if( c == 27 ) { -#ifndef _WIN32 // ESC: swallow the rest of the escape sequence (arrow keys etc.) int flags = fcntl( STDIN_FILENO, F_GETFL, 0 ); fcntl( STDIN_FILENO, F_SETFL, flags | O_NONBLOCK ); char discard[8]; while( read( STDIN_FILENO, discard, sizeof(discard) ) > 0 ) {} fcntl( STDIN_FILENO, F_SETFL, flags ); -#endif - // On Windows, arrow/function keys are already swallowed - // by readInteractiveByte(), so a lone ESC here is just - // the Escape key itself; nothing more to discard. } else if( (unsigned char)c >= 32 && (unsigned char)c < 127 ) { From c3b0f90777c055ed9b540e780afb75d35064de70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Wed, 23 Sep 2026 08:37:23 +0200 Subject: [PATCH 10/20] Guard unistd.h include in filedownloader.cpp for MSVC Only used for the ioctl/TIOCGWINSZ terminal-width lookup, which is already gated behind #if __linux__ || __APPLE__ a few lines below, but the #include itself was left unconditional, so cl.exe still tried (and failed) to find unistd.h. fileuploader.cpp already guards this correctly; bring filedownloader.cpp in line with it. Co-Authored-By: Claude Sonnet 5 --- acp/filedownloader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acp/filedownloader.cpp b/acp/filedownloader.cpp index 6a61a6b..772fc35 100644 --- a/acp/filedownloader.cpp +++ b/acp/filedownloader.cpp @@ -10,8 +10,8 @@ #include #if __linux__ || __APPLE__ #include -#endif #include +#endif FileDownloader::FileDownloader( QString remoteSources, QString localDestination, QString remoteHost, QObject *parent ) : QObject{parent}, From f0d9b136626663c846ad1bd587324f8ed81a1e73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Wed, 23 Sep 2026 08:40:14 +0200 Subject: [PATCH 11/20] Guard unistd.h include in fileuploader.cpp for MSVC Same issue as filedownloader.cpp: unistd.h is POSIX-only and must not be included when building with MSVC. Co-Authored-By: Claude Sonnet 5 --- acp/fileuploader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acp/fileuploader.cpp b/acp/fileuploader.cpp index 7cfd3d4..a4de5e9 100644 --- a/acp/fileuploader.cpp +++ b/acp/fileuploader.cpp @@ -6,9 +6,9 @@ #include #if __linux || __APPLE__ #include +#include #endif #include -#include #define DEBUG 0 #include "AEUtils.h" From 695711d86fbb6a9f4d2e9c12f624871de0e1b7d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Wed, 23 Sep 2026 11:11:02 +0200 Subject: [PATCH 12/20] Add experimental Amiga cross-build job using bebbo/amiga-gcc Bridges the project's non-standard ndk32-include/Include_H path expected by Amiga/Makefile onto the standard ndk-include layout produced by `make ndk`. Marked continue-on-error since the resulting binary hasn't been verified on real hardware/FS-UAE yet. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/build.yml | 64 ++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f8118c8..af6b625 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -151,10 +151,72 @@ jobs: name: ApolloExplorer-Windows-x86_64 path: ApolloExplorer-Windows-x86_64.zip + amiga: + name: Amiga server (m68k, experimental) + runs-on: ubuntu-latest + # Best-effort: this cross-toolchain isn't officially supported by upstream + # and hasn't been verified against real hardware/emulator yet. A failure + # here must not block the desktop-client release. + continue-on-error: true + steps: + - uses: actions/checkout@v4 + + - name: Install toolchain build dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential lhasa gettext texinfo flex bison \ + libgmp-dev libmpfr-dev libmpc-dev rsync + + - name: Cache amiga-gcc toolchain + id: cache-toolchain + uses: actions/cache@v4 + with: + path: /opt/amiga + key: amiga-gcc-${{ runner.os }}-amiga6-ndk3.2-libamiga-v1 + + - name: Build m68k-amigaos-gcc toolchain (bebbo/amiga-gcc) + if: steps.cache-toolchain.outputs.cache-hit != 'true' + run: | + git clone --depth 1 https://github.com/bebbo/amiga-gcc /tmp/amiga-gcc + cd /tmp/amiga-gcc + make update + # min: m68k-amigaos-gcc + binutils + libnix runtime + sys-include + # ndk: generates NDK3.2 proto/clib/inline/lvo headers into ndk-include/ + # netinclude: merges (Roadshow/AmiTCP) into ndk-include/ + # libamiga: installs the prebuilt libamiga.a needed by -lamiga + make min ndk netinclude libamiga NDK=3.2 PREFIX=/opt/amiga -j"$(nproc)" + + - name: Bridge NDK includes to the ApolloCrossDev layout + # Amiga/Makefile (the WDrijver/ApolloCrossDev toolchain convention) expects + # headers at ndk32-include/Include_H — the raw Aminet NDK3.2 layout — while + # bebbo/amiga-gcc installs its generated, netinclude-merged headers at + # ndk-include/. Symlink the two so both conventions resolve to the same, + # complete header set without duplicating or hand-maintaining headers. + run: | + mkdir -p /opt/amiga/m68k-amigaos/ndk32-include + ln -sfn /opt/amiga/m68k-amigaos/ndk-include /opt/amiga/m68k-amigaos/ndk32-include/Include_H + + - name: Build + run: | + export PATH="/opt/amiga/bin:$PATH" + make -C Amiga PREFIX=/opt/amiga + + - name: Package + run: | + mkdir -p ApolloExplorer-Amiga + mv Amiga/ApolloExplorerSrv ApolloExplorer-Amiga/ + mv Amiga/ApolloExplorerTool ApolloExplorer-Amiga/ + tar -czf ApolloExplorer-Amiga-m68k.tar.gz ApolloExplorer-Amiga + + - uses: actions/upload-artifact@v4 + with: + name: ApolloExplorer-Amiga-m68k + path: ApolloExplorer-Amiga-m68k.tar.gz + release: name: Attach artifacts to release if: startsWith(github.ref, 'refs/tags/v') - needs: [macos, linux, windows] + needs: [macos, linux, windows, amiga] runs-on: ubuntu-latest permissions: contents: write From 4ca807d5f1afdc97398ce52c9598971967d99754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Wed, 23 Sep 2026 11:13:15 +0200 Subject: [PATCH 13/20] Fix bebbo/amiga-gcc clone URL: repo moved from GitHub to Codeberg The CI run confirmed github.com/bebbo/amiga-gcc now 404s; the project's canonical home is codeberg.org/bebbo/amiga-gcc. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index af6b625..396cfdb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -177,7 +177,8 @@ jobs: - name: Build m68k-amigaos-gcc toolchain (bebbo/amiga-gcc) if: steps.cache-toolchain.outputs.cache-hit != 'true' run: | - git clone --depth 1 https://github.com/bebbo/amiga-gcc /tmp/amiga-gcc + # bebbo/amiga-gcc migrated off GitHub; Codeberg is its current home. + git clone --depth 1 https://codeberg.org/bebbo/amiga-gcc /tmp/amiga-gcc cd /tmp/amiga-gcc make update # min: m68k-amigaos-gcc + binutils + libnix runtime + sys-include From 821152f262ecbc64ee8efea55609bcdf94836be3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Wed, 23 Sep 2026 11:23:51 +0200 Subject: [PATCH 14/20] Build amiga-gcc toolchain serially to avoid a libnix4 make -j race CI hit a classic parallel-make race: concurrent recursive make jobs generating libnix's export-libnix4.c wrote duplicate stub entries, causing "redefinition of ..." compile errors. Dropping -j makes the one-time toolchain build slower but reliable; actions/cache means it only runs on a cache miss. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 396cfdb..5021209 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -185,7 +185,11 @@ jobs: # ndk: generates NDK3.2 proto/clib/inline/lvo headers into ndk-include/ # netinclude: merges (Roadshow/AmiTCP) into ndk-include/ # libamiga: installs the prebuilt libamiga.a needed by -lamiga - make min ndk netinclude libamiga NDK=3.2 PREFIX=/opt/amiga -j"$(nproc)" + # + # No -j here: parallel jobs race on generating libnix's + # export-libnix4.c (duplicate stub entries -> redefinition errors). + # Serial is slower but this only runs once thanks to actions/cache. + make min ndk netinclude libamiga NDK=3.2 PREFIX=/opt/amiga - name: Bridge NDK includes to the ApolloCrossDev layout # Amiga/Makefile (the WDrijver/ApolloCrossDev toolchain convention) expects From 516f91247195ede5e4f2d0c96b56529f43af6fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Wed, 23 Sep 2026 11:49:52 +0200 Subject: [PATCH 15/20] Skip amiga-gcc's broken, unused libnix4.library target make min (binutils gcc gprof libnix libgcc libnix4.library) fails deterministically, not from -j: upstream's libnix4-support generator emits a duplicate __vfwprintf_total_size stub, breaking export-libnix4.c regardless of parallelism. libnix4.library builds a shared-library variant of libnix that Amiga/Makefile never links against (it uses -noixemul, plain static libnix), so build min's other pieces directly instead of the aggregate target, restoring -j now that the failure is confirmed unrelated to parallel make. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/build.yml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5021209..75ee95d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -181,15 +181,16 @@ jobs: git clone --depth 1 https://codeberg.org/bebbo/amiga-gcc /tmp/amiga-gcc cd /tmp/amiga-gcc make update - # min: m68k-amigaos-gcc + binutils + libnix runtime + sys-include - # ndk: generates NDK3.2 proto/clib/inline/lvo headers into ndk-include/ - # netinclude: merges (Roadshow/AmiTCP) into ndk-include/ - # libamiga: installs the prebuilt libamiga.a needed by -lamiga - # - # No -j here: parallel jobs race on generating libnix's - # export-libnix4.c (duplicate stub entries -> redefinition errors). - # Serial is slower but this only runs once thanks to actions/cache. - make min ndk netinclude libamiga NDK=3.2 PREFIX=/opt/amiga + # `min` (binutils gcc gprof libnix libgcc libnix4.library) fails + # deterministically (not a -j race - reproduces serially too): + # upstream's libnix4-support generator emits a duplicate + # __vfwprintf_total_size stub, breaking export-libnix4.c. That + # target builds libnix4.library, a shared-library variant of + # libnix that Amiga/Makefile never uses (it links -noixemul, + # i.e. plain static libnix) - so build min's other pieces + # directly and skip the broken, unneeded target. + # ndk/netinclude/libamiga: NDK3.2 headers + -lamiga, as before. + make binutils gcc gprof libnix libgcc ndk netinclude libamiga NDK=3.2 PREFIX=/opt/amiga -j"$(nproc)" - name: Bridge NDK includes to the ApolloCrossDev layout # Amiga/Makefile (the WDrijver/ApolloCrossDev toolchain convention) expects From 006ea0990128414178c7b49c4b56de7682e46f58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Wed, 23 Sep 2026 12:03:56 +0200 Subject: [PATCH 16/20] Build the 'all' Makefile target so ApolloExplorerTool actually builds Amiga/Makefile's first (default) target is just the server binary (ApolloExplorerSrv); Willem's own workflow always runs `make all` to also get ApolloExplorerTool. Toolchain now builds cleanly end-to-end (binutils/gcc/libnix/ndk/netinclude/libamiga all succeeded last run), so this was the last blocker before the whole job - and its toolchain cache - can go green. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 75ee95d..584730f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -205,7 +205,9 @@ jobs: - name: Build run: | export PATH="/opt/amiga/bin:$PATH" - make -C Amiga PREFIX=/opt/amiga + # Amiga/Makefile's first (default) target is just the server binary; + # `all` is required to also build ApolloExplorerTool. + make -C Amiga PREFIX=/opt/amiga all - name: Package run: | From 0d87b6050874a5bc2279da12284a23a9c21f7066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Sat, 26 Sep 2026 07:31:07 +0200 Subject: [PATCH 17/20] ci(amiga): ship ApolloExplorerSrv.info, AEKill and AEKill.info in the Amiga package Same files as the upstream 1.1.3 release 'Amiga Server' folder. --- .github/workflows/build.yml | 3 +++ Amiga/dist/AEKill | 1 + Amiga/dist/AEKill.info | Bin 0 -> 898 bytes Amiga/dist/ApolloExplorerSrv.info | Bin 0 -> 6852 bytes 4 files changed, 4 insertions(+) create mode 100644 Amiga/dist/AEKill create mode 100644 Amiga/dist/AEKill.info create mode 100644 Amiga/dist/ApolloExplorerSrv.info diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 584730f..f74a71e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -214,6 +214,9 @@ jobs: mkdir -p ApolloExplorer-Amiga mv Amiga/ApolloExplorerSrv ApolloExplorer-Amiga/ mv Amiga/ApolloExplorerTool ApolloExplorer-Amiga/ + # Workbench icons + the AEKill IconX script (runs `ApolloExplorerTool kill`), + # same set as the upstream release's "Amiga Server" folder. + cp Amiga/dist/ApolloExplorerSrv.info Amiga/dist/AEKill Amiga/dist/AEKill.info ApolloExplorer-Amiga/ tar -czf ApolloExplorer-Amiga-m68k.tar.gz ApolloExplorer-Amiga - uses: actions/upload-artifact@v4 diff --git a/Amiga/dist/AEKill b/Amiga/dist/AEKill new file mode 100644 index 0000000..4c4f437 --- /dev/null +++ b/Amiga/dist/AEKill @@ -0,0 +1 @@ +ApolloExplorerTool kill diff --git a/Amiga/dist/AEKill.info b/Amiga/dist/AEKill.info new file mode 100644 index 0000000000000000000000000000000000000000..673a657e1c71c6e2492f7b1a378087b73efcec2f GIT binary patch literal 898 zcmbtSJxc>Y5S=B_Y>^^Jid1@qNV+>gK?35%#3F~}LX0Gxg`MC>VPT=So1j0yRDw-P z8?j2A`w8hR4Ok25Od+nbJ1en}G}&YB&3nVWW#8U=1{mn&Ko*wqSwzlQ`q9DKGLyeA zmc;Q|kDc5-Nz`>smqV>~NwE9fhtzaL9ZyYwW6M+V72)WYTa=N}6IN$xWH?haXWznb zP-DU`qvu$Bok!mm)?`PiF>MDRus>32c=JLSsW-zTt49MnS06?fg1G1l;-{8B+j)OL zI{A&n;8UW1jQem0AfxwH=q>S#J%H6t4U!t$;8?WWC+xtI(WN9V14+DVdEL$fpLFue zN}pe9UkPvv0O=K_CWv5e5VJ4yf3lC~%9vxubQdl1N#YrC$J|03&CGtzjE9&pWp3{b zv(|hh)a$J5owg6q18^{7gP1X4APKvc*X=y;Nv~Pm=fv@l+6O<^n30gbuv|+z1HE{u uP&qz7)4!n6^36)M95%O0VYT47o1VMjdG0z^@vsrPrTy?=&utbf4fp}M;u`G$ literal 0 HcmV?d00001 diff --git a/Amiga/dist/ApolloExplorerSrv.info b/Amiga/dist/ApolloExplorerSrv.info new file mode 100644 index 0000000000000000000000000000000000000000..82870ff71483fc106413305422d3869e06712bd4 GIT binary patch literal 6852 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 literal 0 HcmV?d00001 From c7ce943bcd861e00ff0f62746e0d75766cea7b5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Sat, 26 Sep 2026 07:31:07 +0200 Subject: [PATCH 18/20] fix(scanner): don't show a bogus INVALID host on macOS QSettings fallbacks made childGroups() of the static-hosts settings include the global preferences domain (com.apple.* keys -> group 'com'), which was turned into a host named INVALID with no address. Disable fallbacks and skip any group that is not an IP address. --- ApolloExplorerPC/scanningwindow.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ApolloExplorerPC/scanningwindow.cpp b/ApolloExplorerPC/scanningwindow.cpp index 5f1b209..b9cbe8a 100755 --- a/ApolloExplorerPC/scanningwindow.cpp +++ b/ApolloExplorerPC/scanningwindow.cpp @@ -91,7 +91,10 @@ ScanningWindow::ScanningWindow(QWidget *parent) : } m_Settings->endGroup(); - //Load the static hosts + //Load the static hosts. + //Fallbacks must be off: on macOS they pull in the global preferences domain, + //whose "com.apple.*" keys show up as bogus groups (e.g. "com") => an "INVALID" host. + m_StaticHosts->setFallbacksEnabled( false ); populateStaticHosts(); } @@ -139,6 +142,11 @@ void ScanningWindow::populateStaticHosts() for( auto iter = staticHostList.begin(); iter != staticHostList.end(); iter++ ) { //Extract from the hosts file QString hostIP = (*iter); + QHostAddress ipAddress; + if( !ipAddress.setAddress( hostIP ) ) { + DBGLOG << "Ignoring static host entry that is not an IP address: " << hostIP; + continue; + } m_StaticHosts->beginGroup( hostIP ); QString hostname = m_StaticHosts->value( STATIC_HOSTS_NAME, "INVALID" ).toString(); QString osname = m_StaticHosts->value( STATIC_HOSTS_OS_NAME, "INVALID" ).toString(); @@ -147,8 +155,6 @@ void ScanningWindow::populateStaticHosts() //Form a host object AmigaHost::HardwareType hardwareType = AmigaHost::hardwareTypeFromString( hardwareName ); - QHostAddress ipAddress; - ipAddress.setAddress( hostIP ); AmigaHost *host = new AmigaHost(99999,hostname,osname,osversion,hardwareType, ipAddress, true, this ); onNewDeviceDiscoveredSlot( QSharedPointer( host ) ); m_StaticHosts->endGroup(); From 1c289c9852424bb9ac000032f230bd23390de9f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Sat, 26 Sep 2026 07:49:56 +0200 Subject: [PATCH 19/20] ci(amiga): upload the package folder directly so the artifact zip unpacks to ApolloExplorer-Amiga/ --- .github/workflows/build.yml | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f74a71e..9aeda57 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -211,18 +211,19 @@ jobs: - name: Package run: | - mkdir -p ApolloExplorer-Amiga - mv Amiga/ApolloExplorerSrv ApolloExplorer-Amiga/ - mv Amiga/ApolloExplorerTool ApolloExplorer-Amiga/ + # Staged under package/ so the artifact zip unpacks straight to an + # ApolloExplorer-Amiga/ folder (no nested archive). + mkdir -p package/ApolloExplorer-Amiga + mv Amiga/ApolloExplorerSrv package/ApolloExplorer-Amiga/ + mv Amiga/ApolloExplorerTool package/ApolloExplorer-Amiga/ # Workbench icons + the AEKill IconX script (runs `ApolloExplorerTool kill`), # same set as the upstream release's "Amiga Server" folder. - cp Amiga/dist/ApolloExplorerSrv.info Amiga/dist/AEKill Amiga/dist/AEKill.info ApolloExplorer-Amiga/ - tar -czf ApolloExplorer-Amiga-m68k.tar.gz ApolloExplorer-Amiga + cp Amiga/dist/ApolloExplorerSrv.info Amiga/dist/AEKill Amiga/dist/AEKill.info package/ApolloExplorer-Amiga/ - uses: actions/upload-artifact@v4 with: name: ApolloExplorer-Amiga-m68k - path: ApolloExplorer-Amiga-m68k.tar.gz + path: package/ release: name: Attach artifacts to release @@ -236,6 +237,15 @@ jobs: with: path: artifacts + - name: Zip the Amiga folder for the release + # The Amiga artifact is a plain folder (not an archive); bundle it as a + # single zip so the release doesn't get five loose files. + run: | + cd artifacts/ApolloExplorer-Amiga-m68k + zip -r ../ApolloExplorer-Amiga-m68k.zip ApolloExplorer-Amiga + cd .. + rm -rf ApolloExplorer-Amiga-m68k + - uses: softprops/action-gh-release@v2 with: files: artifacts/**/* From 1c457496189a3df655104b967312e1e1eb9eeb5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youen=20Ch=C3=A9n=C3=A9?= Date: Sat, 26 Sep 2026 08:06:15 +0200 Subject: [PATCH 20/20] ci: upload each package as a single archive (no zip-in-zip) and bundle Qt DLLs in the Windows package upload-artifact@v7 archive:false makes the download the package itself. The Windows zip only had the two .exe files: windeployqt now deploys the Qt DLLs, plugins and MSVC runtime into the packaged folder. --- .github/workflows/build.yml | 56 ++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9aeda57..5f8aeb3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,10 +63,11 @@ jobs: mv ash/ash ApolloExplorer-macOS/ ditto -c -k --sequesterRsrc --keepParent ApolloExplorer-macOS ApolloExplorer-macOS-universal.zip - - uses: actions/upload-artifact@v4 + # archive: false => the download is this zip itself, not a zip wrapping it. + - uses: actions/upload-artifact@v7 with: - name: ApolloExplorer-macOS-universal path: ApolloExplorer-macOS-universal.zip + archive: false linux: name: Linux (x86_64) @@ -99,10 +100,11 @@ jobs: mv ash/ash ApolloExplorer-Linux/ash tar -czf ApolloExplorer-Linux-x86_64.tar.gz ApolloExplorer-Linux - - uses: actions/upload-artifact@v4 + # archive: false => the download is the tar.gz itself (keeps exec bits). + - uses: actions/upload-artifact@v7 with: - name: ApolloExplorer-Linux-x86_64 path: ApolloExplorer-Linux-x86_64.tar.gz + archive: false windows: name: Windows (x86_64) @@ -144,12 +146,18 @@ jobs: New-Item -ItemType Directory -Force -Path ApolloExplorer-Windows Move-Item ApolloExplorerPC\release\ApolloExplorer.exe ApolloExplorer-Windows\ApolloExplorer.exe Move-Item acp\release\acp.exe ApolloExplorer-Windows\acp.exe - Compress-Archive -Path ApolloExplorer-Windows\* -DestinationPath ApolloExplorer-Windows-x86_64.zip - - - uses: actions/upload-artifact@v4 + # Bundle the Qt DLLs, plugins and MSVC runtime next to the exes; the + # .pro files' post-link windeployqt only fills release\, not this folder. + windeployqt --release --compiler-runtime ApolloExplorer-Windows\ApolloExplorer.exe + windeployqt --release --compiler-runtime ApolloExplorer-Windows\acp.exe + # Zip the folder itself so it unzips to ApolloExplorer-Windows\. + Compress-Archive -Path ApolloExplorer-Windows -DestinationPath ApolloExplorer-Windows-x86_64.zip + + # archive: false => the download is this zip itself, not a zip wrapping it. + - uses: actions/upload-artifact@v7 with: - name: ApolloExplorer-Windows-x86_64 path: ApolloExplorer-Windows-x86_64.zip + archive: false amiga: name: Amiga server (m68k, experimental) @@ -211,19 +219,20 @@ jobs: - name: Package run: | - # Staged under package/ so the artifact zip unpacks straight to an - # ApolloExplorer-Amiga/ folder (no nested archive). - mkdir -p package/ApolloExplorer-Amiga - mv Amiga/ApolloExplorerSrv package/ApolloExplorer-Amiga/ - mv Amiga/ApolloExplorerTool package/ApolloExplorer-Amiga/ + mkdir -p ApolloExplorer-Amiga + mv Amiga/ApolloExplorerSrv ApolloExplorer-Amiga/ + mv Amiga/ApolloExplorerTool ApolloExplorer-Amiga/ # Workbench icons + the AEKill IconX script (runs `ApolloExplorerTool kill`), # same set as the upstream release's "Amiga Server" folder. - cp Amiga/dist/ApolloExplorerSrv.info Amiga/dist/AEKill Amiga/dist/AEKill.info package/ApolloExplorer-Amiga/ + cp Amiga/dist/ApolloExplorerSrv.info Amiga/dist/AEKill Amiga/dist/AEKill.info ApolloExplorer-Amiga/ + # Unzips to an ApolloExplorer-Amiga/ folder. + zip -r ApolloExplorer-Amiga-m68k.zip ApolloExplorer-Amiga - - uses: actions/upload-artifact@v4 + # archive: false => the download is this zip itself, not a zip wrapping it. + - uses: actions/upload-artifact@v7 with: - name: ApolloExplorer-Amiga-m68k - path: package/ + path: ApolloExplorer-Amiga-m68k.zip + archive: false release: name: Attach artifacts to release @@ -233,18 +242,13 @@ jobs: permissions: contents: write steps: - - uses: actions/download-artifact@v4 + # v8 is required to download artifacts uploaded with `archive: false`. + - uses: actions/download-artifact@v8 with: path: artifacts + merge-multiple: true - - name: Zip the Amiga folder for the release - # The Amiga artifact is a plain folder (not an archive); bundle it as a - # single zip so the release doesn't get five loose files. - run: | - cd artifacts/ApolloExplorer-Amiga-m68k - zip -r ../ApolloExplorer-Amiga-m68k.zip ApolloExplorer-Amiga - cd .. - rm -rf ApolloExplorer-Amiga-m68k + - run: ls -laR artifacts - uses: softprops/action-gh-release@v2 with: