From 67dc3a129a94e4192cdd52019027e0c0d98c0c42 Mon Sep 17 00:00:00 2001 From: Dmytro Shvaika Date: Tue, 7 Jul 2026 18:27:45 +0300 Subject: [PATCH 1/3] bugfix(network): Skip point-to-point and link-local interfaces when enumerating LAN IP addresses Cellular (pdp_ip*) and VPN tunnel (utun*) interfaces sorted below the Wi-Fi address and were picked as the default LAN IP on iOS and macOS, so LAN games bound to an interface no LAN peer can reach. Co-Authored-By: Claude Fable 5 --- .../Source/GameNetwork/IPEnumeration.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Core/GameEngine/Source/GameNetwork/IPEnumeration.cpp b/Core/GameEngine/Source/GameNetwork/IPEnumeration.cpp index a7a4844be..ad3e2a1dc 100644 --- a/Core/GameEngine/Source/GameNetwork/IPEnumeration.cpp +++ b/Core/GameEngine/Source/GameNetwork/IPEnumeration.cpp @@ -112,10 +112,25 @@ EnumeratedIP * IPEnumeration::getAddresses() continue; } + // GeneralsX @bugfix 07/07/2026 Skip point-to-point interfaces: cellular (pdp_ip* on iOS) and + // VPN tunnels (utun*) are not reachable by LAN peers, and their addresses sort numerically + // below the Wi-Fi/Ethernet address, so the LAN lobby would pick them as the default IP. + if ((ifa->ifa_flags & IFF_POINTOPOINT) != 0) + { + continue; + } + const sockaddr_in *addr = reinterpret_cast(ifa->ifa_addr); // GeneralsX @bugfix BenderAI 31/03/2026 Use ntohl to convert from network byte order before extracting octets; // reading s_addr byte-by-byte on little-endian platforms reverses the IPv4 octets. const UnsignedInt hostAddr = ntohl(addr->sin_addr.s_addr); + + // GeneralsX @bugfix 07/07/2026 Skip 169.254.0.0/16 link-local addresses (awdl0/llw0 on Apple + // platforms, or any interface that failed DHCP); they cannot host a LAN game. + if ((hostAddr & 0xFFFF0000u) == 0xA9FE0000u) + { + continue; + } addNewIP( (UnsignedByte)((hostAddr >> 24) & 0xFF), (UnsignedByte)((hostAddr >> 16) & 0xFF), From 91a601eda04ea4f0127316c0579485feb730b7c1 Mon Sep 17 00:00:00 2001 From: Dmytro Shvaika Date: Tue, 7 Jul 2026 18:28:04 +0300 Subject: [PATCH 2/3] bugfix(system): Use monotonic clock for timeGetTime on POSIX platforms gettimeofday follows wall time, which steps backward on NTP/carrier time sync (frequent on iOS). Every elapsed-time gate in the engine (shell updates, animations, network resend timers) then stalls until the clock catches back up, freezing UI response for the size of the step while rendering and input events keep flowing. Co-Authored-By: Claude Fable 5 --- Core/Libraries/Source/WWVegas/WWLib/systimer.h | 11 +++++++---- Generals/Code/CompatLib/Include/windows_compat.h | 9 ++++++--- GeneralsMD/Code/CompatLib/Include/windows_compat.h | 9 ++++++--- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/Core/Libraries/Source/WWVegas/WWLib/systimer.h b/Core/Libraries/Source/WWVegas/WWLib/systimer.h index 8313aa49d..624b9a39d 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/systimer.h +++ b/Core/Libraries/Source/WWVegas/WWLib/systimer.h @@ -46,13 +46,16 @@ #define TIMEGETTIME SystemTime.Get #define MS_TIMER_SECOND 1000 #else -#include +#include +// GeneralsX @bugfix 07/07/2026 Use the monotonic clock, not gettimeofday: wall time steps +// backward on NTP/carrier sync (frequent on iOS), and every elapsed-time gate in the engine +// (shell updates, animations, network resends) then stalls until the clock catches back up. inline unsigned long systimerGetMS() { - struct timeval tv; - gettimeofday(&tv, nullptr); - return (tv.tv_sec * 1000) + (tv.tv_usec / 1000); + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (unsigned long)((unsigned long long)ts.tv_sec * 1000ull + (unsigned long long)ts.tv_nsec / 1000000ull); } #define TIMEGETTIME systimerGetMS diff --git a/Generals/Code/CompatLib/Include/windows_compat.h b/Generals/Code/CompatLib/Include/windows_compat.h index 899529924..e39d34084 100644 --- a/Generals/Code/CompatLib/Include/windows_compat.h +++ b/Generals/Code/CompatLib/Include/windows_compat.h @@ -183,11 +183,14 @@ inline BOOL GetVersionEx(OSVERSIONINFO* lpVersionInfo) { // TheSuperHackers @build 10/02/2026 Bender // Timing functions: timeBeginPeriod(), timeEndPeriod() for Linux +// GeneralsX @bugfix 07/07/2026 Use the monotonic clock, not gettimeofday: wall time steps +// backward on NTP/carrier sync (frequent on iOS), and every elapsed-time gate in the engine +// (shell updates, animations, network resends) then stalls until the clock catches back up. static inline DWORD timeGetTime(void) { - struct timeval tv; - gettimeofday(&tv, nullptr); - return (DWORD)((tv.tv_sec * 1000) + (tv.tv_usec / 1000)); + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (DWORD)((unsigned long long)ts.tv_sec * 1000ull + (unsigned long long)ts.tv_nsec / 1000000ull); } static inline DWORD timeBeginPeriod(DWORD period) diff --git a/GeneralsMD/Code/CompatLib/Include/windows_compat.h b/GeneralsMD/Code/CompatLib/Include/windows_compat.h index 899529924..e39d34084 100644 --- a/GeneralsMD/Code/CompatLib/Include/windows_compat.h +++ b/GeneralsMD/Code/CompatLib/Include/windows_compat.h @@ -183,11 +183,14 @@ inline BOOL GetVersionEx(OSVERSIONINFO* lpVersionInfo) { // TheSuperHackers @build 10/02/2026 Bender // Timing functions: timeBeginPeriod(), timeEndPeriod() for Linux +// GeneralsX @bugfix 07/07/2026 Use the monotonic clock, not gettimeofday: wall time steps +// backward on NTP/carrier sync (frequent on iOS), and every elapsed-time gate in the engine +// (shell updates, animations, network resends) then stalls until the clock catches back up. static inline DWORD timeGetTime(void) { - struct timeval tv; - gettimeofday(&tv, nullptr); - return (DWORD)((tv.tv_sec * 1000) + (tv.tv_usec / 1000)); + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (DWORD)((unsigned long long)ts.tv_sec * 1000ull + (unsigned long long)ts.tv_nsec / 1000000ull); } static inline DWORD timeBeginPeriod(DWORD period) From 918edd71e6356d5c7bb7e39ef07abb1eb13d9d0c Mon Sep 17 00:00:00 2001 From: Dmytro Shvaika Date: Tue, 7 Jul 2026 18:28:04 +0300 Subject: [PATCH 3/3] fix(ios): Extract install device identifier by UUID shape in packaging script The devicectl column position shifts with multi-word device names (e.g. "iPhone 15 Pro Max"), so parse the identifier by its UUID shape instead of by column index. Co-Authored-By: Claude Fable 5 --- scripts/build/ios/package-ios-zh.sh | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/scripts/build/ios/package-ios-zh.sh b/scripts/build/ios/package-ios-zh.sh index 93d75dee0..871c70e2b 100755 --- a/scripts/build/ios/package-ios-zh.sh +++ b/scripts/build/ios/package-ios-zh.sh @@ -190,11 +190,9 @@ echo "==> App ready: ${APP}" if [[ "${DO_INSTALL}" == "1" ]]; then echo "==> Installing to connected device" - DEVICE_ID=$(xcrun devicectl list devices 2>/dev/null | awk '/connected/{print $(NF-2); exit}') - if [[ -z "${DEVICE_ID}" ]]; then - # fall back: parse the identifier column (3rd-from-last varies with model names) - DEVICE_ID=$(xcrun devicectl list devices 2>/dev/null | grep -i connected | grep -oE '[0-9A-F-]{36}' | head -1) - fi + # Extract the identifier by shape (UUID), not by column position — the column + # index shifts with multi-word device/model names (e.g. "iPhone 15 Pro Max"). + DEVICE_ID=$(xcrun devicectl list devices 2>/dev/null | grep -i connected | grep -oE '[0-9A-F]{8}(-[0-9A-F]{4}){3}-[0-9A-F]{12}' | head -1) if [[ -z "${DEVICE_ID}" ]]; then echo "ERROR: no connected device found (xcrun devicectl list devices)" exit 1