From 5d5dda70831acea203cb9bb53ad34eb26ab9881b Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Mon, 10 Aug 2026 23:31:04 -0400 Subject: [PATCH 1/6] feat(network): Add debug command-line automated match startup --- Core/GameEngine/CMakeLists.txt | 2 + .../Include/GameNetwork/LANAPICallbacks.h | 1 + .../Include/GameNetwork/NetworkAutoStart.h | 78 +++ Core/GameEngine/Source/Common/CommandLine.cpp | 110 ++++ .../Source/GameNetwork/LANAPICallbacks.cpp | 26 + .../Source/GameNetwork/NetworkAutoStart.cpp | 512 ++++++++++++++++++ .../GUICallbacks/Menus/LanGameOptionsMenu.cpp | 22 +- .../GUI/GUICallbacks/Menus/MainMenu.cpp | 10 + .../Menus/NetworkDirectConnect.cpp | 47 +- .../GUICallbacks/Menus/LanGameOptionsMenu.cpp | 22 +- .../GUI/GUICallbacks/Menus/MainMenu.cpp | 10 + .../Menus/NetworkDirectConnect.cpp | 47 +- 12 files changed, 865 insertions(+), 22 deletions(-) create mode 100644 Core/GameEngine/Include/GameNetwork/NetworkAutoStart.h create mode 100644 Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt index c462ee691f2..9d665ce8282 100644 --- a/Core/GameEngine/CMakeLists.txt +++ b/Core/GameEngine/CMakeLists.txt @@ -549,6 +549,7 @@ set(GAMEENGINE_SRC Include/GameNetwork/NetCommandWrapperList.h Include/GameNetwork/NetPacket.h Include/GameNetwork/NetPacketStructs.h + Include/GameNetwork/NetworkAutoStart.h Include/GameNetwork/NetworkDefs.h Include/GameNetwork/NetworkInterface.h Include/GameNetwork/networkutil.h @@ -1145,6 +1146,7 @@ set(GAMEENGINE_SRC Source/GameNetwork/NetPacket.cpp Source/GameNetwork/NetPacketStructs.cpp Source/GameNetwork/Network.cpp + Source/GameNetwork/NetworkAutoStart.cpp Source/GameNetwork/NetworkUtil.cpp Source/GameNetwork/Transport.cpp Source/GameNetwork/udp.cpp diff --git a/Core/GameEngine/Include/GameNetwork/LANAPICallbacks.h b/Core/GameEngine/Include/GameNetwork/LANAPICallbacks.h index fdc5212253a..88434cc22e1 100644 --- a/Core/GameEngine/Include/GameNetwork/LANAPICallbacks.h +++ b/Core/GameEngine/Include/GameNetwork/LANAPICallbacks.h @@ -73,6 +73,7 @@ extern const Color acceptFalseColor; void lanUpdateSlotList(); void updateGameOptions(); void setLANPlayerTooltip(LANPlayer* player); +void StartLANGame(); //Enum is used for the utility function so other windows do not need //to know about controls on LanGameOptions window. diff --git a/Core/GameEngine/Include/GameNetwork/NetworkAutoStart.h b/Core/GameEngine/Include/GameNetwork/NetworkAutoStart.h new file mode 100644 index 00000000000..28bfd9f433d --- /dev/null +++ b/Core/GameEngine/Include/GameNetwork/NetworkAutoStart.h @@ -0,0 +1,78 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2026 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +#if defined(RTS_DEBUG) + +#include "Common/AsciiString.h" +#include "Common/UnicodeString.h" +#include "GameNetwork/LANAPI.h" + +// TheSuperHackers @feature bobtista 10/08/2026 Automate network match startup +// for multi-instance testing. +class NetworkAutoStart +{ +public: + enum { MIN_EXPECTED_PLAYERS = 1 }; + + enum Mode + { + MODE_NONE, + MODE_DIRECT_CONNECT, + }; + + enum Role + { + ROLE_NONE, + ROLE_HOST, + ROLE_JOIN, + }; + + static Bool setMode(AsciiString mode); + static Bool setHost(Int expectedPlayers); + static Bool setJoin(AsciiString hostAddress); + static Bool setLocalAddress(AsciiString localAddress); + static Bool setPlayerName(AsciiString playerName); + static Bool setMapName(AsciiString mapName); + static Bool setTimeoutSeconds(Int seconds); + + static Bool hasArguments(); + static Bool isEnabled(); + static Bool shouldOpenDirectConnect(); + static void markDirectConnectOpened(); + + static AsciiString getMapName(); + static UnsignedInt getLocalAddress(); + static UnicodeString getPlayerName(); + + static void updateDirectConnect(); + static void updateGameOptions(); + static void onGameCreate(LANAPIInterface::ReturnType result); + static void onGameJoin(LANAPIInterface::ReturnType result); + static void onLocalAddressSet(Bool result); + static void onGameStartFailure(); + static void onGameStart(); + +private: + static Bool validateConfiguration(); + static Bool checkTimeout(); + static void fail(const char *message); +}; + +#endif diff --git a/Core/GameEngine/Source/Common/CommandLine.cpp b/Core/GameEngine/Source/Common/CommandLine.cpp index 0daeb20e9ff..ca8336cb51c 100644 --- a/Core/GameEngine/Source/Common/CommandLine.cpp +++ b/Core/GameEngine/Source/Common/CommandLine.cpp @@ -25,6 +25,8 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine +#include + #include "Common/ArchiveFileSystem.h" #include "Common/CommandLine.h" #include "Common/CRCDebug.h" @@ -36,6 +38,8 @@ #include "GameClient/TerrainVisual.h" // for TERRAIN_LOD_MIN definition #include "GameClient/GameText.h" #include "GameNetwork/NetworkDefs.h" +#include "GameNetwork/NetworkAutoStart.h" +#include "WWLib/trim.h" @@ -502,6 +506,101 @@ Int parseYRes(char *args[], int num) } #if defined(RTS_DEBUG) +static Bool parsePositiveInt(const char *text, Int &result) +{ + if (text == nullptr || *text < '0' || *text > '9') + return false; + + UnsignedInt value = 0; + do + { + const UnsignedInt digit = *text - '0'; + if (value > ((UnsignedInt)INT_MAX - digit) / 10u) + return false; + value = value * 10u + digit; + ++text; + } while (*text >= '0' && *text <= '9'); + + if (*text != '\0') + return false; + + result = (Int)value; + return true; +} + +Int parseAutoNetworkMode(char *args[], int num) +{ + if (num > 1 && NetworkAutoStart::setMode(args[1])) + return 2; + + printf("Invalid -autoNetworkMode. Supported value: direct\n"); + exit(1); + return 1; +} + +Int parseAutoNetworkHost(char *args[], int num) +{ + Int expectedPlayers = 0; + if (num > 1 && parsePositiveInt(args[1], expectedPlayers) && NetworkAutoStart::setHost(expectedPlayers)) + return 2; + + printf("Invalid -autoNetworkHost. Pass an expected player count from %d to %d and do not combine it with -autoNetworkJoin.\n", + NetworkAutoStart::MIN_EXPECTED_PLAYERS, MAX_SLOTS); + exit(1); + return 1; +} + +Int parseAutoNetworkJoin(char *args[], int num) +{ + if (num > 1 && NetworkAutoStart::setJoin(args[1])) + return 2; + + printf("Invalid -autoNetworkJoin. Pass a dotted IPv4 host address and do not combine it with -autoNetworkHost.\n"); + exit(1); + return 1; +} + +Int parseAutoNetworkLocalAddress(char *args[], int num) +{ + if (num > 1 && NetworkAutoStart::setLocalAddress(args[1])) + return 2; + + printf("Invalid -autoNetworkLocalAddress. Pass a dotted IPv4 local address.\n"); + exit(1); + return 1; +} + +Int parseAutoNetworkName(char *args[], int num) +{ + if (num > 1 && NetworkAutoStart::setPlayerName(args[1])) + return 2; + + printf("Invalid -autoNetworkName. Pass a non-empty player name.\n"); + exit(1); + return 1; +} + +Int parseAutoNetworkMap(char *args[], int num) +{ + if (num > 1 && NetworkAutoStart::setMapName(args[1])) + return 2; + + printf("Invalid -autoNetworkMap. Pass a non-empty map path.\n"); + exit(1); + return 1; +} + +Int parseAutoNetworkTimeout(char *args[], int num) +{ + Int timeoutSeconds = 0; + if (num > 1 && parsePositiveInt(args[1], timeoutSeconds) && NetworkAutoStart::setTimeoutSeconds(timeoutSeconds)) + return 2; + + printf("Invalid -autoNetworkTimeout. Pass a positive number of seconds.\n"); + exit(1); + return 1; +} + //============================================================================= //============================================================================= Int parseLatencyAverage(char *args[], int num) @@ -1179,11 +1278,22 @@ static CommandLineParam paramsForStartup[] = // The last successful selection wins; otherwise use the executable directory. { "-setCwd", parseSetCwd }, { "-useCwd", parseUseCwd }, +#if defined(RTS_DEBUG) + { "-autoNetworkMode", parseAutoNetworkMode }, +#endif }; // These Params are parsed during Engine Init before INI data is loaded static CommandLineParam paramsForEngineInit[] = { +#if defined(RTS_DEBUG) + { "-autoNetworkHost", parseAutoNetworkHost }, + { "-autoNetworkJoin", parseAutoNetworkJoin }, + { "-autoNetworkLocalAddress", parseAutoNetworkLocalAddress }, + { "-autoNetworkName", parseAutoNetworkName }, + { "-autoNetworkMap", parseAutoNetworkMap }, + { "-autoNetworkTimeout", parseAutoNetworkTimeout }, +#endif { "-nologo", parseNoLogo }, // TheSuperHackers @tweak Is now available in Release builds. { "-noshellmap", parseNoShellMap }, { "-noShellAnim", parseNoWindowAnimation }, // TheSuperHackers @tweak Is now available in Release builds. diff --git a/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp b/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp index a1ecfbb3238..6f889972434 100644 --- a/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp +++ b/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp @@ -45,6 +45,7 @@ #include "GameLogic/GameLogic.h" #include "GameNetwork/FileTransfer.h" #include "GameNetwork/LANAPICallbacks.h" +#include "GameNetwork/NetworkAutoStart.h" #include "GameNetwork/networkutil.h" LANAPI *TheLAN = nullptr; @@ -243,6 +244,9 @@ void LANAPI::OnGameStart() if (!filesOk || TheMapCache->findMap(m_currentGame->getMap()) == nullptr) { DEBUG_LOG(("After transfer, we didn't really have the map. Bailing...")); +#if defined(RTS_DEBUG) + NetworkAutoStart::onGameStartFailure(); +#endif OnPlayerLeave(m_name); removeGame(m_currentGame); m_currentGame = nullptr; @@ -271,6 +275,10 @@ void LANAPI::OnGameStart() // Set the seeds InitRandom( m_currentGame->getSeed() ); DEBUG_LOG(("InitRandom( %d )", m_currentGame->getSeed())); + +#if defined(RTS_DEBUG) + NetworkAutoStart::onGameStart(); +#endif } } @@ -515,6 +523,15 @@ void LANAPI::OnPlayerJoin( Int slot, UnicodeString playerName ) void LANAPI::OnGameJoin( ReturnType ret, LANGameInfo *theGame ) { +#if defined(RTS_DEBUG) + if (NetworkAutoStart::isEnabled()) + { + NetworkAutoStart::onGameJoin(ret); + if (ret != RET_OK) + return; + } +#endif + if (ret == RET_OK) { LANbuttonPushed = true; @@ -605,6 +622,15 @@ void LANAPI::OnGameList( LANGameInfo *gameList ) void LANAPI::OnGameCreate( ReturnType ret ) { +#if defined(RTS_DEBUG) + if (NetworkAutoStart::isEnabled()) + { + NetworkAutoStart::onGameCreate(ret); + if (ret != RET_OK) + return; + } +#endif + if (ret == RET_OK) { diff --git a/Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp b/Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp new file mode 100644 index 00000000000..36fd4610ecf --- /dev/null +++ b/Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp @@ -0,0 +1,512 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2026 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "PreRTS.h" + +#if defined(RTS_DEBUG) + +#include + +#include "GameClient/ClientInstance.h" +#include "GameClient/MapUtil.h" +#include "GameNetwork/LANAPICallbacks.h" +#include "GameNetwork/NetworkAutoStart.h" + +namespace +{ +enum { + DefaultStartupTimeoutMilliseconds = 30000, + ActionRetryMilliseconds = 1000, + MillisecondsPerSecond = 1000, + IPv4OctetCount = 4, + IPv4BitsPerOctet = 8, + MaxIPv4OctetValue = 255, +}; + +const UnsignedInt IPv4BroadcastAddress = UINT_MAX; + +NetworkAutoStart::Mode s_mode = NetworkAutoStart::MODE_NONE; +NetworkAutoStart::Role s_role = NetworkAutoStart::ROLE_NONE; +Int s_expectedPlayers = 0; +UnsignedInt s_hostAddress = 0; +UnsignedInt s_localAddress = 0; +AsciiString s_playerName; +AsciiString s_mapName; +UnsignedInt s_timeoutMilliseconds = DefaultStartupTimeoutMilliseconds; +UnsignedInt s_startTime = 0; +UnsignedInt s_lastActionTime = 0; +Bool s_hasArguments = false; +Bool s_directConnectOpened = false; +Bool s_actionPending = false; +Bool s_startRequested = false; +Bool s_gameStarted = false; +Bool s_failed = false; + +Bool ParseIPv4Address(AsciiString address, UnsignedInt &result) +{ + const char *cursor = address.str(); + result = 0; + for (Int octet = 0; octet < IPv4OctetCount; ++octet) + { + if (*cursor < '0' || *cursor > '9') + { + return false; + } + + UnsignedInt value = 0; + do + { + value = value * 10 + (*cursor - '0'); + if (value > MaxIPv4OctetValue) + { + return false; + } + ++cursor; + } while (*cursor >= '0' && *cursor <= '9'); + + result = (result << IPv4BitsPerOctet) | value; + if (octet + 1 < IPv4OctetCount) + { + if (*cursor != '.') + { + return false; + } + ++cursor; + } + else if (*cursor != '\0') + { + return false; + } + } + + return result != 0 && result != IPv4BroadcastAddress; +} + +Bool CanAcceptMap(LANGameInfo *game, LANGameSlot *slot) +{ + if (slot->hasMap()) + { + return true; + } + + const MapMetaData *mapData = TheMapCache->findMap(game->getMap()); + if (mapData != nullptr) + { + return !mapData->m_isOfficial; + } + + return WouldMapTransfer(game->getMap()); +} +} // namespace + +Bool NetworkAutoStart::setMode(AsciiString mode) +{ + s_hasArguments = true; + if (mode.compareNoCase("direct") == 0) + { + s_mode = MODE_DIRECT_CONNECT; + rts::ClientInstance::setMultiInstance(true); + rts::ClientInstance::skipPrimaryInstance(); + return true; + } + + return false; +} + +Bool NetworkAutoStart::setHost(Int expectedPlayers) +{ + s_hasArguments = true; + if (s_role == ROLE_JOIN || expectedPlayers < MIN_EXPECTED_PLAYERS || expectedPlayers > MAX_SLOTS) + { + return false; + } + + s_role = ROLE_HOST; + s_expectedPlayers = expectedPlayers; + return true; +} + +Bool NetworkAutoStart::setJoin(AsciiString hostAddress) +{ + s_hasArguments = true; + hostAddress.trim(); + if (s_role == ROLE_HOST || hostAddress.isEmpty()) + { + return false; + } + + UnsignedInt resolvedAddress = 0; + if (!ParseIPv4Address(hostAddress, resolvedAddress)) + { + return false; + } + + s_role = ROLE_JOIN; + s_hostAddress = resolvedAddress; + return true; +} + +Bool NetworkAutoStart::setLocalAddress(AsciiString localAddress) +{ + s_hasArguments = true; + localAddress.trim(); + if (localAddress.isEmpty()) + { + return false; + } + + return ParseIPv4Address(localAddress, s_localAddress); +} + +Bool NetworkAutoStart::setPlayerName(AsciiString playerName) +{ + s_hasArguments = true; + playerName.trim(); + if (playerName.isEmpty()) + { + return false; + } + + s_playerName = playerName; + return true; +} + +Bool NetworkAutoStart::setMapName(AsciiString mapName) +{ + s_hasArguments = true; + mapName.trim(); + if (mapName.isEmpty()) + { + return false; + } + + s_mapName = mapName; + return true; +} + +Bool NetworkAutoStart::setTimeoutSeconds(Int seconds) +{ + s_hasArguments = true; + if (seconds < 1 || (UnsignedInt)seconds > UINT_MAX / MillisecondsPerSecond) + { + return false; + } + + s_timeoutMilliseconds = (UnsignedInt)seconds * MillisecondsPerSecond; + return true; +} + +Bool NetworkAutoStart::hasArguments() +{ + return s_hasArguments; +} + +Bool NetworkAutoStart::isEnabled() +{ + return !s_failed && s_mode != MODE_NONE && s_role != ROLE_NONE; +} + +Bool NetworkAutoStart::validateConfiguration() +{ + if (s_failed) + { + return false; + } + + if (s_mode == MODE_NONE) + { + fail("-autoNetworkMode direct is required"); + return false; + } + + if (s_role == ROLE_NONE) + { + fail("either -autoNetworkHost or -autoNetworkJoin is required"); + return false; + } + + return true; +} + +Bool NetworkAutoStart::shouldOpenDirectConnect() +{ + if (!s_hasArguments || s_directConnectOpened || !validateConfiguration()) + { + return false; + } + + return s_mode == MODE_DIRECT_CONNECT; +} + +void NetworkAutoStart::markDirectConnectOpened() +{ + s_directConnectOpened = true; + if (s_startTime == 0) + { + s_startTime = timeGetTime(); + } +} + +AsciiString NetworkAutoStart::getMapName() +{ + return s_mapName; +} + +UnsignedInt NetworkAutoStart::getLocalAddress() +{ + return s_localAddress; +} + +UnicodeString NetworkAutoStart::getPlayerName() +{ + UnicodeString name; + if (s_playerName.isNotEmpty()) + { + name.translate(s_playerName); + } + else + { + name.format(L"AutoNet%02u", rts::ClientInstance::getInstanceId()); + } + name.truncateTo(g_lanPlayerNameLength); + return name; +} + +Bool NetworkAutoStart::checkTimeout() +{ + if (s_failed || s_gameStarted) + { + return true; + } + + const UnsignedInt now = timeGetTime(); + if (s_startTime != 0 && now - s_startTime >= s_timeoutMilliseconds) + { + fail("network match startup timed out"); + return true; + } + + return false; +} + +void NetworkAutoStart::fail(const char *message) +{ + if (s_failed) + { + return; + } + + s_failed = true; + s_actionPending = false; + DEBUG_LOG(("NetworkAutoStart failed: %s", message)); + printf("NetworkAutoStart failed: %s\n", message); +} + +void NetworkAutoStart::updateDirectConnect() +{ + if (!isEnabled() || s_mode != MODE_DIRECT_CONNECT || checkTimeout() || TheLAN == nullptr) + { + return; + } + + const UnsignedInt now = timeGetTime(); + if (s_actionPending || (s_lastActionTime != 0 && now - s_lastActionTime < ActionRetryMilliseconds)) + { + return; + } + + TheLAN->RequestSetName(getPlayerName()); + s_lastActionTime = now; + s_actionPending = true; + + if (s_role == ROLE_HOST) + { + DEBUG_LOG(("NetworkAutoStart creating Direct Connect game for %d players", s_expectedPlayers)); + TheLAN->RequestGameCreate(UnicodeString::TheEmptyString, true); + } + else + { + DEBUG_LOG(("NetworkAutoStart joining Direct Connect host 0x%08X", s_hostAddress)); + TheLAN->RequestGameJoinDirectConnect(s_hostAddress); + } +} + +void NetworkAutoStart::updateGameOptions() +{ + if (!isEnabled() || checkTimeout() || TheLAN == nullptr || TheLAN->GetMyGame() == nullptr) + { + return; + } + + LANGameInfo *game = TheLAN->GetMyGame(); + if (s_role == ROLE_JOIN) + { + const Int localSlot = game->getLocalSlotNum(); + if (localSlot < 0) + { + return; + } + + LANGameSlot *slot = game->getLANSlot(localSlot); + const UnsignedInt now = timeGetTime(); + if (slot != nullptr && !slot->isAccepted() && + (s_lastActionTime == 0 || now - s_lastActionTime >= ActionRetryMilliseconds)) + { + TheLAN->RequestHasMap(); + if (!CanAcceptMap(game, slot)) + { + fail("required map is unavailable and cannot be transferred"); + return; + } + + TheLAN->RequestAccept(); + s_lastActionTime = now; + } + return; + } + + if (s_startRequested) + { + return; + } + + const MapMetaData *mapData = TheMapCache->findMap(game->getMap()); + if (mapData == nullptr) + { + fail("selected map was not found"); + return; + } + if (mapData->m_numPlayers < s_expectedPlayers) + { + fail("selected map has fewer slots than -autoNetworkHost requires"); + return; + } + + Int humanPlayers = 0; + for (Int i = 0; i < MAX_SLOTS; ++i) + { + LANGameSlot *slot = game->getLANSlot(i); + if (slot != nullptr && slot->isHuman()) + { + ++humanPlayers; + } + } + + if (humanPlayers > s_expectedPlayers) + { + fail("more players joined than -autoNetworkHost expects"); + return; + } + if (humanPlayers != s_expectedPlayers) + { + return; + } + + LANGameSlot *hostSlot = game->getLANSlot(0); + if (hostSlot == nullptr) + { + fail("host slot is unavailable"); + return; + } + hostSlot->setAccept(); + for (Int i = 0; i < MAX_SLOTS; ++i) + { + LANGameSlot *slot = game->getLANSlot(i); + if (slot != nullptr && slot->isHuman() && !slot->isAccepted()) + { + return; + } + } + + const UnsignedInt now = timeGetTime(); + if (s_lastActionTime == 0 || now - s_lastActionTime >= ActionRetryMilliseconds) + { + DEBUG_LOG(("NetworkAutoStart starting Direct Connect game with %d players", humanPlayers)); + s_lastActionTime = now; + s_startRequested = true; + StartLANGame(); + } +} + +void NetworkAutoStart::onGameCreate(LANAPIInterface::ReturnType result) +{ + if (!isEnabled()) + { + return; + } + + if (result == LANAPIInterface::RET_OK) + { + return; + } + + s_actionPending = false; + fail("could not create Direct Connect game"); +} + +void NetworkAutoStart::onGameJoin(LANAPIInterface::ReturnType result) +{ + if (!isEnabled()) + { + return; + } + + if (result == LANAPIInterface::RET_OK) + { + return; + } + + s_actionPending = false; + if (result == LANAPIInterface::RET_TIMEOUT || result == LANAPIInterface::RET_GAME_GONE) + { + DEBUG_LOG(("NetworkAutoStart will retry Direct Connect join after result %d", result)); + return; + } + + fail("Direct Connect join was rejected"); +} + +void NetworkAutoStart::onLocalAddressSet(Bool result) +{ + if (isEnabled() && !result) + { + fail("could not bind the Direct Connect local address"); + } +} + +void NetworkAutoStart::onGameStartFailure() +{ + if (isEnabled()) + { + fail("required map could not be transferred"); + } +} + +void NetworkAutoStart::onGameStart() +{ + if (!isEnabled()) + { + return; + } + + s_gameStarted = true; + DEBUG_LOG(("NetworkAutoStart entered the network game")); + printf("NetworkAutoStart entered the network game\n"); +} + +#endif diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp index 0520332611d..ca821037df5 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp @@ -53,6 +53,7 @@ #include "GameNetwork/LANAPI.h" #include "GameNetwork/IPEnumeration.h" #include "GameNetwork/LANAPICallbacks.h" +#include "GameNetwork/NetworkAutoStart.h" #include "Common/MultiplayerSettings.h" #include "GameClient/GameText.h" #include "GameNetwork/GUIUtil.h" @@ -211,7 +212,7 @@ static void playerTooltip(GameWindow *window, setLANPlayerTooltip(player); } -void StartPressed() +void StartLANGame() { LANGameInfo *myGame = TheLAN->GetMyGame(); @@ -780,8 +781,13 @@ void LanGameOptionsMenuInit( WindowLayout *layout, void *userData ) slot->setColor( pref.getPreferredColor() ); slot->setPlayerTemplate( pref.getPreferredFaction() ); slot->setNATBehavior(FirewallHelperClass::FIREWALL_TYPE_SIMPLE); - game->setMap( pref.getPreferredMap() ); - AsciiString lowerMap = pref.getPreferredMap(); + AsciiString mapName = pref.getPreferredMap(); +#if defined(RTS_DEBUG) + if (NetworkAutoStart::isEnabled() && NetworkAutoStart::getMapName().isNotEmpty()) + mapName = NetworkAutoStart::getMapName(); +#endif + game->setMap(mapName); + AsciiString lowerMap = mapName; lowerMap.toLower(); std::map::iterator it = TheMapCache->find(lowerMap); if (it != TheMapCache->end()) @@ -975,6 +981,14 @@ void LanGameOptionsMenuShutdown( WindowLayout *layout, void *userData ) //------------------------------------------------------------------------------------------------- void LanGameOptionsMenuUpdate( WindowLayout * layout, void *userData) { +#if defined(RTS_DEBUG) + if (NetworkAutoStart::isEnabled() && TheLAN != nullptr) + { + TheLAN->update(); + NetworkAutoStart::updateGameOptions(); + } +#endif + if(LANisShuttingDown && TheShell->isAnimFinished() && TheTransitionHandler->isFinished()) shutdownComplete(layout); //TheLAN->update(); // this is handled in the lobby @@ -1160,7 +1174,7 @@ WindowMsgHandledType LanGameOptionsMenuSystem( GameWindow *window, UnsignedInt m { if (TheLAN->AmIHost()) { - StartPressed(); + StartLANGame(); //TheLAN->RequestGameStart(); } else diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp index 0097330bb16..fd6e2d30480 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp @@ -69,6 +69,7 @@ #include "GameNetwork/DownloadManager.h" #include "GameNetwork/GameSpy/MainMenuUtils.h" +#include "GameNetwork/NetworkAutoStart.h" #include "GameClient/InGameUI.h" @@ -748,6 +749,15 @@ void ResolutionDialogUpdate() void DownloadMenuUpdate( WindowLayout *layout, void *userData ); void MainMenuUpdate( WindowLayout *layout, void *userData ) { +#if defined(RTS_DEBUG) + if (NetworkAutoStart::shouldOpenDirectConnect()) + { + NetworkAutoStart::markDirectConnectOpened(); + TheShell->push("Menus/NetworkDirectConnect.wnd"); + return; + } +#endif + if( TheGameLogic->isInGame() && !TheGameLogic->isInShellGame() ) { return; diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp index 1412088c582..400cfcfc91d 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp @@ -49,6 +49,7 @@ #include "GameNetwork/IPEnumeration.h" #include "GameNetwork/LANAPI.h" #include "GameNetwork/LANAPICallbacks.h" +#include "GameNetwork/NetworkAutoStart.h" // window ids ------------------------------------------------------------------------------ @@ -252,12 +253,25 @@ void NetworkDirectConnectInit( WindowLayout *layout, void *userData ) LANbuttonPushed = false; LANisShuttingDown = false; - if (TheLAN == nullptr) + Bool automatedStartup = FALSE; + UnsignedInt autoLocalIP = 0; +#if defined(RTS_DEBUG) + if (NetworkAutoStart::isEnabled()) { - TheLAN = NEW LANAPI(); - TheLAN->init(); + automatedStartup = TRUE; + autoLocalIP = NetworkAutoStart::getLocalAddress(); + } +#endif + + if (!automatedStartup) + { + if (TheLAN == nullptr) + { + TheLAN = NEW LANAPI(); + TheLAN->init(); + } + TheLAN->reset(); } - TheLAN->reset(); buttonPushed = false; isShuttingDown = false; @@ -305,6 +319,8 @@ void NetworkDirectConnectInit( WindowLayout *layout, void *userData ) OptionPreferences prefs; UnsignedInt IP = prefs.getOnlineIPAddress(); + if (autoLocalIP != 0) + IP = autoLocalIP; IPEnumeration IPs; @@ -317,7 +333,7 @@ void NetworkDirectConnectInit( WindowLayout *layout, void *userData ) /// @todo: display error and exit lan lobby if no IPs are found } - Bool foundIP = FALSE; + Bool foundIP = autoLocalIP != 0; EnumeratedIP *tempIP = IPlist; while ((tempIP != nullptr) && (foundIP == FALSE)) { if (IP == tempIP->getIP()) { @@ -333,8 +349,17 @@ void NetworkDirectConnectInit( WindowLayout *layout, void *userData ) // IP = IPlist->getIP(); // } - TheLAN->init(); - TheLAN->SetLocalIP(IP); + if (automatedStartup) + { +#if defined(RTS_DEBUG) + NetworkAutoStart::onLocalAddressSet(TheLAN->SetLocalIP(IP)); +#endif + } + else + { + TheLAN->init(); + TheLAN->SetLocalIP(IP); + } } UnsignedInt ip = TheLAN->GetLocalIP(); @@ -393,6 +418,14 @@ void NetworkDirectConnectShutdown( WindowLayout *layout, void *userData ) //------------------------------------------------------------------------------------------------- void NetworkDirectConnectUpdate( WindowLayout * layout, void *userData) { +#if defined(RTS_DEBUG) + if (NetworkAutoStart::isEnabled() && TheLAN != nullptr) + { + TheLAN->update(); + NetworkAutoStart::updateDirectConnect(); + } +#endif + // We'll only be successful if we've requested to if(isShuttingDown && TheShell->isAnimFinished() && TheTransitionHandler->isFinished()) shutdownComplete(layout); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp index 609a0f772cd..f6d43ea874a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp @@ -56,6 +56,7 @@ #include "GameNetwork/LANAPI.h" #include "GameNetwork/IPEnumeration.h" #include "GameNetwork/LANAPICallbacks.h" +#include "GameNetwork/NetworkAutoStart.h" #include "Common/MultiplayerSettings.h" #include "GameClient/GameText.h" #include "GameNetwork/GUIUtil.h" @@ -218,7 +219,7 @@ static void playerTooltip(GameWindow *window, setLANPlayerTooltip(player); } -void StartPressed() +void StartLANGame() { LANGameInfo *myGame = TheLAN->GetMyGame(); @@ -856,10 +857,15 @@ void LanGameOptionsMenuInit( WindowLayout *layout, void *userData ) slot->setColor( pref.getPreferredColor() ); slot->setPlayerTemplate( pref.getPreferredFaction() ); slot->setNATBehavior(FirewallHelperClass::FIREWALL_TYPE_SIMPLE); - game->setMap( pref.getPreferredMap() ); + AsciiString mapName = pref.getPreferredMap(); +#if defined(RTS_DEBUG) + if (NetworkAutoStart::isEnabled() && NetworkAutoStart::getMapName().isNotEmpty()) + mapName = NetworkAutoStart::getMapName(); +#endif + game->setMap(mapName); game->setStartingCash( pref.getStartingCash() ); game->setSuperweaponRestriction( pref.getSuperweaponRestricted() ? 1 : 0 ); - AsciiString lowerMap = pref.getPreferredMap(); + AsciiString lowerMap = mapName; lowerMap.toLower(); std::map::iterator it = TheMapCache->find(lowerMap); if (it != TheMapCache->end()) @@ -1070,6 +1076,14 @@ void LanGameOptionsMenuShutdown( WindowLayout *layout, void *userData ) //------------------------------------------------------------------------------------------------- void LanGameOptionsMenuUpdate( WindowLayout * layout, void *userData) { +#if defined(RTS_DEBUG) + if (NetworkAutoStart::isEnabled() && TheLAN != nullptr) + { + TheLAN->update(); + NetworkAutoStart::updateGameOptions(); + } +#endif + if(LANisShuttingDown && TheShell->isAnimFinished() && TheTransitionHandler->isFinished()) shutdownComplete(layout); //TheLAN->update(); // this is handled in the lobby @@ -1263,7 +1277,7 @@ WindowMsgHandledType LanGameOptionsMenuSystem( GameWindow *window, UnsignedInt m { if (TheLAN->AmIHost()) { - StartPressed(); + StartLANGame(); //TheLAN->RequestGameStart(); } else diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp index f51953e454f..1b63af43e9d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp @@ -72,6 +72,7 @@ #include "GameNetwork/DownloadManager.h" #include "GameNetwork/GameSpy/MainMenuUtils.h" +#include "GameNetwork/NetworkAutoStart.h" #include "GameClient/InGameUI.h" @@ -785,6 +786,15 @@ void ResolutionDialogUpdate() void DownloadMenuUpdate( WindowLayout *layout, void *userData ); void MainMenuUpdate( WindowLayout *layout, void *userData ) { +#if defined(RTS_DEBUG) + if (NetworkAutoStart::shouldOpenDirectConnect()) + { + NetworkAutoStart::markDirectConnectOpened(); + TheShell->push("Menus/NetworkDirectConnect.wnd"); + return; + } +#endif + if( TheGameLogic->isInGame() && !TheGameLogic->isInShellGame() ) { return; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp index 7c9c462f9b9..9afa817e837 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp @@ -49,6 +49,7 @@ #include "GameNetwork/IPEnumeration.h" #include "GameNetwork/LANAPI.h" #include "GameNetwork/LANAPICallbacks.h" +#include "GameNetwork/NetworkAutoStart.h" // window ids ------------------------------------------------------------------------------ @@ -252,12 +253,25 @@ void NetworkDirectConnectInit( WindowLayout *layout, void *userData ) LANbuttonPushed = false; LANisShuttingDown = false; - if (TheLAN == nullptr) + Bool automatedStartup = FALSE; + UnsignedInt autoLocalIP = 0; +#if defined(RTS_DEBUG) + if (NetworkAutoStart::isEnabled()) { - TheLAN = NEW LANAPI(); - TheLAN->init(); + automatedStartup = TRUE; + autoLocalIP = NetworkAutoStart::getLocalAddress(); + } +#endif + + if (!automatedStartup) + { + if (TheLAN == nullptr) + { + TheLAN = NEW LANAPI(); + TheLAN->init(); + } + TheLAN->reset(); } - TheLAN->reset(); buttonPushed = false; isShuttingDown = false; @@ -305,6 +319,8 @@ void NetworkDirectConnectInit( WindowLayout *layout, void *userData ) OptionPreferences prefs; UnsignedInt IP = prefs.getOnlineIPAddress(); + if (autoLocalIP != 0) + IP = autoLocalIP; IPEnumeration IPs; @@ -317,7 +333,7 @@ void NetworkDirectConnectInit( WindowLayout *layout, void *userData ) /// @todo: display error and exit lan lobby if no IPs are found } - Bool foundIP = FALSE; + Bool foundIP = autoLocalIP != 0; EnumeratedIP *tempIP = IPlist; while ((tempIP != nullptr) && (foundIP == FALSE)) { if (IP == tempIP->getIP()) { @@ -333,8 +349,17 @@ void NetworkDirectConnectInit( WindowLayout *layout, void *userData ) // IP = IPlist->getIP(); // } - TheLAN->init(); - TheLAN->SetLocalIP(IP); + if (automatedStartup) + { +#if defined(RTS_DEBUG) + NetworkAutoStart::onLocalAddressSet(TheLAN->SetLocalIP(IP)); +#endif + } + else + { + TheLAN->init(); + TheLAN->SetLocalIP(IP); + } } UnsignedInt ip = TheLAN->GetLocalIP(); @@ -393,6 +418,14 @@ void NetworkDirectConnectShutdown( WindowLayout *layout, void *userData ) //------------------------------------------------------------------------------------------------- void NetworkDirectConnectUpdate( WindowLayout * layout, void *userData) { +#if defined(RTS_DEBUG) + if (NetworkAutoStart::isEnabled() && TheLAN != nullptr) + { + TheLAN->update(); + NetworkAutoStart::updateDirectConnect(); + } +#endif + // We'll only be successful if we've requested to if(isShuttingDown && TheShell->isAnimFinished() && TheTransitionHandler->isFinished()) shutdownComplete(layout); From 56ed502a14bc777074271eec730b2a65aaa68ac9 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 11 Aug 2026 13:53:40 -0400 Subject: [PATCH 2/6] fix(network): Use distinct loop counters for VC6 --- Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp b/Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp index 36fd4610ecf..638fca6293a 100644 --- a/Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp @@ -398,9 +398,9 @@ void NetworkAutoStart::updateGameOptions() } Int humanPlayers = 0; - for (Int i = 0; i < MAX_SLOTS; ++i) + for (Int humanIndex = 0; humanIndex < MAX_SLOTS; ++humanIndex) { - LANGameSlot *slot = game->getLANSlot(i); + LANGameSlot *slot = game->getLANSlot(humanIndex); if (slot != nullptr && slot->isHuman()) { ++humanPlayers; @@ -424,9 +424,9 @@ void NetworkAutoStart::updateGameOptions() return; } hostSlot->setAccept(); - for (Int i = 0; i < MAX_SLOTS; ++i) + for (Int acceptedIndex = 0; acceptedIndex < MAX_SLOTS; ++acceptedIndex) { - LANGameSlot *slot = game->getLANSlot(i); + LANGameSlot *slot = game->getLANSlot(acceptedIndex); if (slot != nullptr && slot->isHuman() && !slot->isAccepted()) { return; From 47596b0790ec78b21315bd4c4dfe8edceea06fed Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Mon, 14 Sep 2026 17:06:16 -0500 Subject: [PATCH 3/6] fix(network): Report automated match startup rejection --- .../Include/GameNetwork/LANAPICallbacks.h | 2 +- .../Include/GameNetwork/NetworkAutoStart.h | 6 ++--- Core/GameEngine/Source/Common/CommandLine.cpp | 2 +- .../Source/GameNetwork/NetworkAutoStart.cpp | 22 +++++++++---------- .../GUICallbacks/Menus/LanGameOptionsMenu.cpp | 14 +++++++----- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Core/GameEngine/Include/GameNetwork/LANAPICallbacks.h b/Core/GameEngine/Include/GameNetwork/LANAPICallbacks.h index 88434cc22e1..9f4ca2b6ee2 100644 --- a/Core/GameEngine/Include/GameNetwork/LANAPICallbacks.h +++ b/Core/GameEngine/Include/GameNetwork/LANAPICallbacks.h @@ -73,7 +73,7 @@ extern const Color acceptFalseColor; void lanUpdateSlotList(); void updateGameOptions(); void setLANPlayerTooltip(LANPlayer* player); -void StartLANGame(); +Bool StartLANGame(); //Enum is used for the utility function so other windows do not need //to know about controls on LanGameOptions window. diff --git a/Core/GameEngine/Include/GameNetwork/NetworkAutoStart.h b/Core/GameEngine/Include/GameNetwork/NetworkAutoStart.h index 28bfd9f433d..27243a8ce4f 100644 --- a/Core/GameEngine/Include/GameNetwork/NetworkAutoStart.h +++ b/Core/GameEngine/Include/GameNetwork/NetworkAutoStart.h @@ -24,8 +24,7 @@ #include "Common/UnicodeString.h" #include "GameNetwork/LANAPI.h" -// TheSuperHackers @feature bobtista 10/08/2026 Automate network match startup -// for multi-instance testing. +// Automate network match startup for multi-instance testing. class NetworkAutoStart { public: @@ -44,7 +43,7 @@ class NetworkAutoStart ROLE_JOIN, }; - static Bool setMode(AsciiString mode); + static Bool setMode(const AsciiString &mode); static Bool setHost(Int expectedPlayers); static Bool setJoin(AsciiString hostAddress); static Bool setLocalAddress(AsciiString localAddress); @@ -52,7 +51,6 @@ class NetworkAutoStart static Bool setMapName(AsciiString mapName); static Bool setTimeoutSeconds(Int seconds); - static Bool hasArguments(); static Bool isEnabled(); static Bool shouldOpenDirectConnect(); static void markDirectConnectOpened(); diff --git a/Core/GameEngine/Source/Common/CommandLine.cpp b/Core/GameEngine/Source/Common/CommandLine.cpp index ca8336cb51c..c7a36fa0b28 100644 --- a/Core/GameEngine/Source/Common/CommandLine.cpp +++ b/Core/GameEngine/Source/Common/CommandLine.cpp @@ -39,7 +39,6 @@ #include "GameClient/GameText.h" #include "GameNetwork/NetworkDefs.h" #include "GameNetwork/NetworkAutoStart.h" -#include "WWLib/trim.h" @@ -1279,6 +1278,7 @@ static CommandLineParam paramsForStartup[] = { "-setCwd", parseSetCwd }, { "-useCwd", parseUseCwd }, #if defined(RTS_DEBUG) + // TheSuperHackers @feature bobtista 10/08/2026 Automate network match startup for multi-instance testing. { "-autoNetworkMode", parseAutoNetworkMode }, #endif }; diff --git a/Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp b/Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp index 638fca6293a..e914fee12c7 100644 --- a/Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp @@ -57,7 +57,7 @@ Bool s_startRequested = false; Bool s_gameStarted = false; Bool s_failed = false; -Bool ParseIPv4Address(AsciiString address, UnsignedInt &result) +Bool ParseIPv4Address(const AsciiString &address, UnsignedInt &result) { const char *cursor = address.str(); result = 0; @@ -114,7 +114,7 @@ Bool CanAcceptMap(LANGameInfo *game, LANGameSlot *slot) } } // namespace -Bool NetworkAutoStart::setMode(AsciiString mode) +Bool NetworkAutoStart::setMode(const AsciiString &mode) { s_hasArguments = true; if (mode.compareNoCase("direct") == 0) @@ -211,11 +211,6 @@ Bool NetworkAutoStart::setTimeoutSeconds(Int seconds) return true; } -Bool NetworkAutoStart::hasArguments() -{ - return s_hasArguments; -} - Bool NetworkAutoStart::isEnabled() { return !s_failed && s_mode != MODE_NONE && s_role != ROLE_NONE; @@ -315,6 +310,7 @@ void NetworkAutoStart::fail(const char *message) s_actionPending = false; DEBUG_LOG(("NetworkAutoStart failed: %s", message)); printf("NetworkAutoStart failed: %s\n", message); + fflush(stdout); } void NetworkAutoStart::updateDirectConnect() @@ -438,8 +434,11 @@ void NetworkAutoStart::updateGameOptions() { DEBUG_LOG(("NetworkAutoStart starting Direct Connect game with %d players", humanPlayers)); s_lastActionTime = now; - s_startRequested = true; - StartLANGame(); + s_startRequested = StartLANGame(); + if (!s_startRequested) + { + fail("LAN start validation rejected the match; see LAN system messages"); + } } } @@ -505,8 +504,9 @@ void NetworkAutoStart::onGameStart() } s_gameStarted = true; - DEBUG_LOG(("NetworkAutoStart entered the network game")); - printf("NetworkAutoStart entered the network game\n"); + DEBUG_LOG(("NetworkAutoStart requested network game startup")); + printf("NetworkAutoStart requested network game startup\n"); + fflush(stdout); } #endif diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp index f6d43ea874a..ca8e2fc9b9d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp @@ -219,7 +219,7 @@ static void playerTooltip(GameWindow *window, setLANPlayerTooltip(player); } -void StartLANGame() +Bool StartLANGame() { LANGameInfo *myGame = TheLAN->GetMyGame(); @@ -228,7 +228,7 @@ void StartLANGame() Int playerCount = 0; if (!myGame) { - return; + return false; } myGame->getLANSlot(0)->setAccept(); // cause we are, of course! @@ -257,7 +257,7 @@ void StartLANGame() text.format(TheGameText->fetch("LAN:TooManyPlayers"), (md)?md->m_numPlayers:0); TheLAN->OnChat(L"SYSTEM", TheLAN->GetLocalIP(), text, LANAPI::LANCHAT_SYSTEM); } - return; + return false; } // Check for observer + AI players @@ -268,7 +268,7 @@ void StartLANGame() UnicodeString text = TheGameText->fetch("GUI:NeedHumanPlayers"); TheLAN->OnChat(L"SYSTEM", TheLAN->GetLocalIP(), text, LANAPI::LANCHAT_SYSTEM); } - return; + return false; } // Check for too few players @@ -280,7 +280,7 @@ void StartLANGame() text.format(TheGameText->fetch("LAN:NeedMorePlayers"),numUsers); TheLAN->OnChat(L"SYSTEM", TheLAN->GetLocalIP(), text, LANAPI::LANCHAT_SYSTEM); } - return; + return false; } // Check for too few teams @@ -309,7 +309,7 @@ void StartLANGame() text.format(TheGameText->fetch("LAN:NeedMoreTeams")); TheLAN->OnChat(L"SYSTEM", TheLAN->GetLocalIP(), text, LANAPI::LANCHAT_SYSTEM); } - return; + return false; } if (numRandom + teams.size() < 2) @@ -371,6 +371,7 @@ void StartLANGame() else TheLAN->RequestGameStart(); LANEnableStartButton(false); + return true; } else { @@ -382,6 +383,7 @@ void StartLANGame() } } + return false; } void LANEnableStartButton(Bool enabled) From 2b2fdfafe5ed5e2e93e1174a30e0cf9670255522 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Mon, 14 Sep 2026 17:06:16 -0500 Subject: [PATCH 4/6] fix(network): Match Generals LAN startup result handling --- .../GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp index ca821037df5..89b39721973 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp @@ -212,7 +212,7 @@ static void playerTooltip(GameWindow *window, setLANPlayerTooltip(player); } -void StartLANGame() +Bool StartLANGame() { LANGameInfo *myGame = TheLAN->GetMyGame(); @@ -221,7 +221,7 @@ void StartLANGame() Int playerCount = 0; if (!myGame) { - return; + return false; } myGame->getLANSlot(0)->setAccept(); // cause we are, of course! @@ -250,7 +250,7 @@ void StartLANGame() text.format(TheGameText->fetch("LAN:TooManyPlayers"), (md)?md->m_numPlayers:0); TheLAN->OnChat(L"SYSTEM", TheLAN->GetLocalIP(), text, LANAPI::LANCHAT_SYSTEM); } - return; + return false; } // Check for observer + AI players @@ -261,7 +261,7 @@ void StartLANGame() UnicodeString text = TheGameText->fetch("GUI:NeedHumanPlayers"); TheLAN->OnChat(L"SYSTEM", TheLAN->GetLocalIP(), text, LANAPI::LANCHAT_SYSTEM); } - return; + return false; } // Check for too few players @@ -273,7 +273,7 @@ void StartLANGame() text.format(TheGameText->fetch("LAN:NeedMorePlayers"),numUsers); TheLAN->OnChat(L"SYSTEM", TheLAN->GetLocalIP(), text, LANAPI::LANCHAT_SYSTEM); } - return; + return false; } // Check for too few teams @@ -302,7 +302,7 @@ void StartLANGame() text.format(TheGameText->fetch("LAN:NeedMoreTeams")); TheLAN->OnChat(L"SYSTEM", TheLAN->GetLocalIP(), text, LANAPI::LANCHAT_SYSTEM); } - return; + return false; } if (numRandom + teams.size() < 2) @@ -364,6 +364,7 @@ void StartLANGame() else TheLAN->RequestGameStart(); LANEnableStartButton(false); + return true; } else { @@ -375,6 +376,7 @@ void StartLANGame() } } + return false; } void LANEnableStartButton(Bool enabled) From 69f398e6958e66bee105025405d7e2ab0700e8cc Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 15 Sep 2026 12:28:17 -0400 Subject: [PATCH 5/6] fix(network): Drive automated startup through the LAN lobby --- Core/GameEngine/Include/GameClient/MapUtil.h | 1 + Core/GameEngine/Include/GameNetwork/LANAPI.h | 1 + .../Include/GameNetwork/NetworkAutoStart.h | 3 + .../Include/GameNetwork/networkutil.h | 2 + Core/GameEngine/Source/Common/CommandLine.cpp | 26 +++++- Core/GameEngine/Source/GameClient/MapUtil.cpp | 10 +++ Core/GameEngine/Source/GameNetwork/LANAPI.cpp | 15 ++-- .../Source/GameNetwork/LANAPICallbacks.cpp | 5 +- .../Source/GameNetwork/NetworkAutoStart.cpp | 82 +++++------------ .../Source/GameNetwork/NetworkUtil.cpp | 40 +++++++++ .../GameEngine/Source/Common/GameMain.cpp | 15 ++++ .../GUICallbacks/Menus/LanGameOptionsMenu.cpp | 7 +- .../GUI/GUICallbacks/Menus/LanLobbyMenu.cpp | 43 ++++++++- .../GUI/GUICallbacks/Menus/MainMenu.cpp | 6 +- .../Menus/NetworkDirectConnect.cpp | 87 ++++++++----------- 15 files changed, 210 insertions(+), 133 deletions(-) diff --git a/Core/GameEngine/Include/GameClient/MapUtil.h b/Core/GameEngine/Include/GameClient/MapUtil.h index 7cb5c71b9db..fbf34148894 100644 --- a/Core/GameEngine/Include/GameClient/MapUtil.h +++ b/Core/GameEngine/Include/GameClient/MapUtil.h @@ -148,3 +148,4 @@ Bool parseMapPreviewChunk(DataChunkInput &file, DataChunkInfo *info, void *userD void findDrawPositions( Int startX, Int startY, Int width, Int height, Region3D extent, ICoord2D *ul, ICoord2D *lr ); Bool WouldMapTransfer( const AsciiString& mapName ); +Bool CanTransferMap(const AsciiString &mapName); diff --git a/Core/GameEngine/Include/GameNetwork/LANAPI.h b/Core/GameEngine/Include/GameNetwork/LANAPI.h index df22c116d9f..9371fd5c4d1 100644 --- a/Core/GameEngine/Include/GameNetwork/LANAPI.h +++ b/Core/GameEngine/Include/GameNetwork/LANAPI.h @@ -283,6 +283,7 @@ class LANAPI : public LANAPIInterface virtual ~LANAPI() override; virtual void init() override; ///< Initialize or re-initialize the instance + Bool init(UnsignedInt localIP); virtual void reset() override; ///< reset the logic system virtual void update() override; ///< update the world diff --git a/Core/GameEngine/Include/GameNetwork/NetworkAutoStart.h b/Core/GameEngine/Include/GameNetwork/NetworkAutoStart.h index 27243a8ce4f..472623d5b18 100644 --- a/Core/GameEngine/Include/GameNetwork/NetworkAutoStart.h +++ b/Core/GameEngine/Include/GameNetwork/NetworkAutoStart.h @@ -52,6 +52,9 @@ class NetworkAutoStart static Bool setTimeoutSeconds(Int seconds); static Bool isEnabled(); + static Bool hasFailed(); + static Bool shouldOpenLobby(); + static void markLobbyOpened(); static Bool shouldOpenDirectConnect(); static void markDirectConnectOpened(); diff --git a/Core/GameEngine/Include/GameNetwork/networkutil.h b/Core/GameEngine/Include/GameNetwork/networkutil.h index dc9455aca55..f516ad5839c 100644 --- a/Core/GameEngine/Include/GameNetwork/networkutil.h +++ b/Core/GameEngine/Include/GameNetwork/networkutil.h @@ -29,6 +29,8 @@ UnsignedInt AssembleIp(UnsignedByte a, UnsignedByte b, UnsignedByte c, UnsignedByte d); UnsignedInt ResolveIP(AsciiString host); +// Parse four decimal IPv4 octets, excluding unspecified and broadcast addresses. +Bool ParseIPv4Address(const AsciiString &address, UnsignedInt &result); UnsignedShort GenerateNextCommandID(); Bool DoesCommandRequireACommandID(NetCommandType type); Bool CommandRequiresAck(const NetCommandMsg *msg); diff --git a/Core/GameEngine/Source/Common/CommandLine.cpp b/Core/GameEngine/Source/Common/CommandLine.cpp index c7a36fa0b28..64a420c006e 100644 --- a/Core/GameEngine/Source/Common/CommandLine.cpp +++ b/Core/GameEngine/Source/Common/CommandLine.cpp @@ -505,23 +505,29 @@ Int parseYRes(char *args[], int num) } #if defined(RTS_DEBUG) -static Bool parsePositiveInt(const char *text, Int &result) +static Bool parseNonNegativeInt(const char *text, Int &result) { if (text == nullptr || *text < '0' || *text > '9') + { return false; + } UnsignedInt value = 0; do { const UnsignedInt digit = *text - '0'; if (value > ((UnsignedInt)INT_MAX - digit) / 10u) + { return false; + } value = value * 10u + digit; ++text; } while (*text >= '0' && *text <= '9'); if (*text != '\0') + { return false; + } result = (Int)value; return true; @@ -530,7 +536,9 @@ static Bool parsePositiveInt(const char *text, Int &result) Int parseAutoNetworkMode(char *args[], int num) { if (num > 1 && NetworkAutoStart::setMode(args[1])) + { return 2; + } printf("Invalid -autoNetworkMode. Supported value: direct\n"); exit(1); @@ -540,8 +548,10 @@ Int parseAutoNetworkMode(char *args[], int num) Int parseAutoNetworkHost(char *args[], int num) { Int expectedPlayers = 0; - if (num > 1 && parsePositiveInt(args[1], expectedPlayers) && NetworkAutoStart::setHost(expectedPlayers)) + if (num > 1 && parseNonNegativeInt(args[1], expectedPlayers) && NetworkAutoStart::setHost(expectedPlayers)) + { return 2; + } printf("Invalid -autoNetworkHost. Pass an expected player count from %d to %d and do not combine it with -autoNetworkJoin.\n", NetworkAutoStart::MIN_EXPECTED_PLAYERS, MAX_SLOTS); @@ -552,7 +562,9 @@ Int parseAutoNetworkHost(char *args[], int num) Int parseAutoNetworkJoin(char *args[], int num) { if (num > 1 && NetworkAutoStart::setJoin(args[1])) + { return 2; + } printf("Invalid -autoNetworkJoin. Pass a dotted IPv4 host address and do not combine it with -autoNetworkHost.\n"); exit(1); @@ -562,7 +574,9 @@ Int parseAutoNetworkJoin(char *args[], int num) Int parseAutoNetworkLocalAddress(char *args[], int num) { if (num > 1 && NetworkAutoStart::setLocalAddress(args[1])) + { return 2; + } printf("Invalid -autoNetworkLocalAddress. Pass a dotted IPv4 local address.\n"); exit(1); @@ -572,7 +586,9 @@ Int parseAutoNetworkLocalAddress(char *args[], int num) Int parseAutoNetworkName(char *args[], int num) { if (num > 1 && NetworkAutoStart::setPlayerName(args[1])) + { return 2; + } printf("Invalid -autoNetworkName. Pass a non-empty player name.\n"); exit(1); @@ -582,7 +598,9 @@ Int parseAutoNetworkName(char *args[], int num) Int parseAutoNetworkMap(char *args[], int num) { if (num > 1 && NetworkAutoStart::setMapName(args[1])) + { return 2; + } printf("Invalid -autoNetworkMap. Pass a non-empty map path.\n"); exit(1); @@ -592,8 +610,10 @@ Int parseAutoNetworkMap(char *args[], int num) Int parseAutoNetworkTimeout(char *args[], int num) { Int timeoutSeconds = 0; - if (num > 1 && parsePositiveInt(args[1], timeoutSeconds) && NetworkAutoStart::setTimeoutSeconds(timeoutSeconds)) + if (num > 1 && parseNonNegativeInt(args[1], timeoutSeconds) && NetworkAutoStart::setTimeoutSeconds(timeoutSeconds)) + { return 2; + } printf("Invalid -autoNetworkTimeout. Pass a positive number of seconds.\n"); exit(1); diff --git a/Core/GameEngine/Source/GameClient/MapUtil.cpp b/Core/GameEngine/Source/GameClient/MapUtil.cpp index 1806c50741a..a1e1cfbd937 100644 --- a/Core/GameEngine/Source/GameClient/MapUtil.cpp +++ b/Core/GameEngine/Source/GameClient/MapUtil.cpp @@ -721,6 +721,16 @@ Bool WouldMapTransfer( const AsciiString& mapName ) return mapName.startsWithNoCase(TheMapCache->getUserMapDir()); } +Bool CanTransferMap(const AsciiString &mapName) +{ + const MapMetaData *mapData = TheMapCache->findMap(mapName); + if (mapData != nullptr) + { + return !mapData->m_isOfficial; + } + return WouldMapTransfer(mapName); +} + //------------------------------------------------------------------------------------------------- typedef std::set/**/> MapNameList; typedef std::map MapDisplayToFileNameList; diff --git a/Core/GameEngine/Source/GameNetwork/LANAPI.cpp b/Core/GameEngine/Source/GameNetwork/LANAPI.cpp index 8cbfbdea6c5..90dfc48e7cd 100644 --- a/Core/GameEngine/Source/GameNetwork/LANAPI.cpp +++ b/Core/GameEngine/Source/GameNetwork/LANAPI.cpp @@ -96,12 +96,15 @@ LANAPI::~LANAPI() } void LANAPI::init() +{ + init(m_localIP); +} + +Bool LANAPI::init(UnsignedInt localIP) { m_gameStartTime = 0; m_gameStartSeconds = 0; - m_transport->reset(); - m_transport->init(m_localIP, lobbyPort); - m_transport->allowBroadcasts(true); + const Bool bound = SetLocalIP(localIP); m_pendingAction = ACT_NONE; m_expiration = 0; @@ -135,6 +138,7 @@ void LANAPI::init() m_hostName = "unknown"; } #endif + return bound; } void LANAPI::reset() @@ -760,17 +764,14 @@ void LANAPI::RequestHasMap() UnicodeString text; UnicodeString mapDisplayName; const MapMetaData *mapData = TheMapCache->findMap( m_currentGame->getMap() ); - Bool willTransfer = TRUE; + Bool willTransfer = CanTransferMap(m_currentGame->getMap()); if (mapData) { mapDisplayName.format(L"%ls", mapData->m_displayName.str()); - if (mapData->m_isOfficial) - willTransfer = FALSE; } else { mapDisplayName.format(L"%hs", TheGameState->getMapLeafName(m_currentGame->getMap()).str()); - willTransfer = WouldMapTransfer(m_currentGame->getMap()); } if (willTransfer) text.format(TheGameText->fetch("GUI:LocalPlayerNoMapWillTransfer"), mapDisplayName.str()); diff --git a/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp b/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp index 6f889972434..c44ec3784b4 100644 --- a/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp +++ b/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp @@ -159,17 +159,14 @@ void LANAPI::OnHasMap( UnsignedInt playerIP, Bool status ) { UnicodeString mapDisplayName; const MapMetaData *mapData = TheMapCache->findMap( m_currentGame->getMap() ); - Bool willTransfer = TRUE; + Bool willTransfer = CanTransferMap(m_currentGame->getMap()); if (mapData) { mapDisplayName.format(L"%ls", mapData->m_displayName.str()); - if (mapData->m_isOfficial) - willTransfer = FALSE; } else { mapDisplayName.format(L"%hs", m_currentGame->getMap().str()); - willTransfer = WouldMapTransfer(m_currentGame->getMap()); } if (!status) { diff --git a/Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp b/Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp index e914fee12c7..3abffe8ca9b 100644 --- a/Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetworkAutoStart.cpp @@ -22,10 +22,12 @@ #include +#include "Common/GameEngine.h" #include "GameClient/ClientInstance.h" #include "GameClient/MapUtil.h" #include "GameNetwork/LANAPICallbacks.h" #include "GameNetwork/NetworkAutoStart.h" +#include "GameNetwork/networkutil.h" namespace { @@ -33,13 +35,8 @@ enum { DefaultStartupTimeoutMilliseconds = 30000, ActionRetryMilliseconds = 1000, MillisecondsPerSecond = 1000, - IPv4OctetCount = 4, - IPv4BitsPerOctet = 8, - MaxIPv4OctetValue = 255, }; -const UnsignedInt IPv4BroadcastAddress = UINT_MAX; - NetworkAutoStart::Mode s_mode = NetworkAutoStart::MODE_NONE; NetworkAutoStart::Role s_role = NetworkAutoStart::ROLE_NONE; Int s_expectedPlayers = 0; @@ -51,67 +48,13 @@ UnsignedInt s_timeoutMilliseconds = DefaultStartupTimeoutMilliseconds; UnsignedInt s_startTime = 0; UnsignedInt s_lastActionTime = 0; Bool s_hasArguments = false; +Bool s_lobbyOpened = false; Bool s_directConnectOpened = false; Bool s_actionPending = false; Bool s_startRequested = false; Bool s_gameStarted = false; Bool s_failed = false; -Bool ParseIPv4Address(const AsciiString &address, UnsignedInt &result) -{ - const char *cursor = address.str(); - result = 0; - for (Int octet = 0; octet < IPv4OctetCount; ++octet) - { - if (*cursor < '0' || *cursor > '9') - { - return false; - } - - UnsignedInt value = 0; - do - { - value = value * 10 + (*cursor - '0'); - if (value > MaxIPv4OctetValue) - { - return false; - } - ++cursor; - } while (*cursor >= '0' && *cursor <= '9'); - - result = (result << IPv4BitsPerOctet) | value; - if (octet + 1 < IPv4OctetCount) - { - if (*cursor != '.') - { - return false; - } - ++cursor; - } - else if (*cursor != '\0') - { - return false; - } - } - - return result != 0 && result != IPv4BroadcastAddress; -} - -Bool CanAcceptMap(LANGameInfo *game, LANGameSlot *slot) -{ - if (slot->hasMap()) - { - return true; - } - - const MapMetaData *mapData = TheMapCache->findMap(game->getMap()); - if (mapData != nullptr) - { - return !mapData->m_isOfficial; - } - - return WouldMapTransfer(game->getMap()); -} } // namespace Bool NetworkAutoStart::setMode(const AsciiString &mode) @@ -238,6 +181,22 @@ Bool NetworkAutoStart::validateConfiguration() return true; } +Bool NetworkAutoStart::hasFailed() +{ + return s_failed; +} + +Bool NetworkAutoStart::shouldOpenLobby() +{ + return s_hasArguments && !s_lobbyOpened && validateConfiguration(); +} + +void NetworkAutoStart::markLobbyOpened() +{ + s_lobbyOpened = true; + s_startTime = timeGetTime(); +} + Bool NetworkAutoStart::shouldOpenDirectConnect() { if (!s_hasArguments || s_directConnectOpened || !validateConfiguration()) @@ -311,6 +270,7 @@ void NetworkAutoStart::fail(const char *message) DEBUG_LOG(("NetworkAutoStart failed: %s", message)); printf("NetworkAutoStart failed: %s\n", message); fflush(stdout); + TheGameEngine->setQuitting(true); } void NetworkAutoStart::updateDirectConnect() @@ -364,7 +324,7 @@ void NetworkAutoStart::updateGameOptions() (s_lastActionTime == 0 || now - s_lastActionTime >= ActionRetryMilliseconds)) { TheLAN->RequestHasMap(); - if (!CanAcceptMap(game, slot)) + if (!slot->hasMap() && !CanTransferMap(game->getMap())) { fail("required map is unavailable and cannot be transferred"); return; diff --git a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp index b709e670d7e..86aeb85c7c7 100644 --- a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp @@ -67,6 +67,46 @@ void dumpBufferToLog(const void *vBuf, Int len, const char *fname, Int line) #endif // DEBUG_LOGGING +Bool ParseIPv4Address(const AsciiString &address, UnsignedInt &result) +{ + const char *cursor = address.str(); + result = 0; + for (Int octet = 0; octet < 4; ++octet) + { + if (*cursor < '0' || *cursor > '9') + { + return false; + } + + UnsignedInt value = 0; + do + { + value = value * 10 + (*cursor - '0'); + if (value > 255) + { + return false; + } + ++cursor; + } while (*cursor >= '0' && *cursor <= '9'); + + result = (result << 8) | value; + if (octet + 1 < 4) + { + if (*cursor != '.') + { + return false; + } + ++cursor; + } + else if (*cursor != '\0') + { + return false; + } + } + + return result != 0 && result != INADDR_BROADCAST; +} + /** * ResolveIP turns a string ("games2.westwood.com", or "192.168.0.1") into * a 32-bit unsigned integer. diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameMain.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameMain.cpp index ed94ec7bf54..aab72f0d297 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameMain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameMain.cpp @@ -31,6 +31,8 @@ #include "Common/FramePacer.h" #include "Common/GameEngine.h" #include "Common/ReplaySimulation.h" +#include "GameNetwork/NetworkAutoStart.h" +#include "GameNetwork/LANAPICallbacks.h" /** @@ -55,6 +57,19 @@ Int GameMain() TheGameEngine->execute(); } +#if defined(RTS_DEBUG) + if (NetworkAutoStart::hasFailed()) + { + if (TheLAN != nullptr && TheLAN->GetMyGame() != nullptr) + { + TheLAN->RequestGameLeave(); + } + delete TheLAN; + TheLAN = nullptr; + exitcode = 1; + } +#endif + // since execute() returned, we are exiting the game delete TheFramePacer; TheFramePacer = nullptr; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp index ca8e2fc9b9d..b33d9f844aa 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanGameOptionsMenu.cpp @@ -322,16 +322,14 @@ Bool StartLANGame() // see if everyone's accepted and count the number of players in the game UnicodeString mapDisplayName; const MapMetaData *mapData = TheMapCache->findMap( myGame->getMap() ); - Bool willTransfer = TRUE; + Bool willTransfer = CanTransferMap(myGame->getMap()); if (mapData) { mapDisplayName.format(L"%ls", mapData->m_displayName.str()); - willTransfer = !mapData->m_isOfficial; } else { mapDisplayName.format(L"%hs", myGame->getMap().str()); - willTransfer = WouldMapTransfer(myGame->getMap()); } for( i = 0; i < MAX_SLOTS; i++ ) { @@ -862,7 +860,9 @@ void LanGameOptionsMenuInit( WindowLayout *layout, void *userData ) AsciiString mapName = pref.getPreferredMap(); #if defined(RTS_DEBUG) if (NetworkAutoStart::isEnabled() && NetworkAutoStart::getMapName().isNotEmpty()) + { mapName = NetworkAutoStart::getMapName(); + } #endif game->setMap(mapName); game->setStartingCash( pref.getStartingCash() ); @@ -1081,7 +1081,6 @@ void LanGameOptionsMenuUpdate( WindowLayout * layout, void *userData) #if defined(RTS_DEBUG) if (NetworkAutoStart::isEnabled() && TheLAN != nullptr) { - TheLAN->update(); NetworkAutoStart::updateGameOptions(); } #endif diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanLobbyMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanLobbyMenu.cpp index 143fb6b9852..aa04a2f97e6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanLobbyMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/LanLobbyMenu.cpp @@ -60,6 +60,7 @@ #include "GameLogic/GameLogic.h" #include "GameNetwork/IPEnumeration.h" #include "GameNetwork/LANAPICallbacks.h" +#include "GameNetwork/NetworkAutoStart.h" #include "GameNetwork/LANGameInfo.h" Bool LANisShuttingDown = false; @@ -417,6 +418,12 @@ void LanLobbyMenuInit( WindowLayout *layout, void *userData ) // Choose an IP address, then initialize the LAN singleton UnsignedInt IP = TheGlobalData->m_defaultIP; +#if defined(RTS_DEBUG) + if (NetworkAutoStart::isEnabled() && NetworkAutoStart::getLocalAddress() != 0) + { + IP = NetworkAutoStart::getLocalAddress(); + } +#endif IPEnumeration IPs; const WideChar* IPSource; if (!IP) @@ -428,6 +435,13 @@ void LanLobbyMenuInit( WindowLayout *layout, void *userData ) IPlist = IPlist->getNext(); } */ +#if defined(RTS_DEBUG) + if (!IPlist && NetworkAutoStart::isEnabled()) + { + NetworkAutoStart::onLocalAddressSet(false); + return; + } +#endif DEBUG_ASSERTCRASH(IPlist, ("No IP addresses found!")); if (!IPlist) { @@ -448,8 +462,14 @@ void LanLobbyMenuInit( WindowLayout *layout, void *userData ) #endif // TheLAN->init() sets us to be in a LAN menu screen automatically. - TheLAN->init(); - if (TheLAN->SetLocalIP(IP) == FALSE) { + if (TheLAN->init(IP) == FALSE) { +#if defined(RTS_DEBUG) + if (NetworkAutoStart::isEnabled()) + { + NetworkAutoStart::onLocalAddressSet(false); + return; + } +#endif LANSocketErrorDetected = TRUE; } @@ -613,6 +633,25 @@ void LanLobbyMenuUpdate( WindowLayout * layout, void *userData) if (TheShell->isAnimFinished() && !LANbuttonPushed && TheLAN) TheLAN->update(); +#if defined(RTS_DEBUG) + if (NetworkAutoStart::hasFailed()) + { + return; + } + if (NetworkAutoStart::isEnabled() && LANSocketErrorDetected) + { + NetworkAutoStart::onLocalAddressSet(false); + return; + } + if (TheShell->isAnimFinished() && !LANbuttonPushed && NetworkAutoStart::shouldOpenDirectConnect()) + { + NetworkAutoStart::markDirectConnectOpened(); + TheWindowManager->winSendSystemMsg(buttonDirectConnect->winGetParent(), GBM_SELECTED, + (WindowMsgData)buttonDirectConnect, buttonDirectConnectID); + return; + } +#endif + if (LANSocketErrorDetected == TRUE) { LANSocketErrorDetected = FALSE; DEBUG_LOG(("SOCKET ERROR! BAILING!")); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp index 1b63af43e9d..5904d964cc0 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp @@ -787,10 +787,10 @@ void DownloadMenuUpdate( WindowLayout *layout, void *userData ); void MainMenuUpdate( WindowLayout *layout, void *userData ) { #if defined(RTS_DEBUG) - if (NetworkAutoStart::shouldOpenDirectConnect()) + if (NetworkAutoStart::shouldOpenLobby()) { - NetworkAutoStart::markDirectConnectOpened(); - TheShell->push("Menus/NetworkDirectConnect.wnd"); + NetworkAutoStart::markLobbyOpened(); + TheShell->push("Menus/LanLobbyMenu.wnd"); return; } #endif diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp index 9afa817e837..7c3d46342cf 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp @@ -253,25 +253,6 @@ void NetworkDirectConnectInit( WindowLayout *layout, void *userData ) LANbuttonPushed = false; LANisShuttingDown = false; - Bool automatedStartup = FALSE; - UnsignedInt autoLocalIP = 0; -#if defined(RTS_DEBUG) - if (NetworkAutoStart::isEnabled()) - { - automatedStartup = TRUE; - autoLocalIP = NetworkAutoStart::getLocalAddress(); - } -#endif - - if (!automatedStartup) - { - if (TheLAN == nullptr) - { - TheLAN = NEW LANAPI(); - TheLAN->init(); - } - TheLAN->reset(); - } buttonPushed = false; isShuttingDown = false; @@ -310,6 +291,14 @@ void NetworkDirectConnectInit( WindowLayout *layout, void *userData ) UnicodeString ipstr; + UnsignedInt autoLocalIP = 0; +#if defined(RTS_DEBUG) + if (NetworkAutoStart::isEnabled() && TheLAN != nullptr) + { + autoLocalIP = TheLAN->GetLocalIP(); + } +#endif + delete TheLAN; TheLAN = nullptr; @@ -320,45 +309,46 @@ void NetworkDirectConnectInit( WindowLayout *layout, void *userData ) OptionPreferences prefs; UnsignedInt IP = prefs.getOnlineIPAddress(); if (autoLocalIP != 0) + { IP = autoLocalIP; + } - IPEnumeration IPs; + if (autoLocalIP == 0) + { + IPEnumeration IPs; -// if (!IP) -// { - EnumeratedIP *IPlist = IPs.getAddresses(); - DEBUG_ASSERTCRASH(IPlist, ("No IP addresses found!")); - if (!IPlist) - { - /// @todo: display error and exit lan lobby if no IPs are found - } + // if (!IP) + // { + EnumeratedIP *IPlist = IPs.getAddresses(); + DEBUG_ASSERTCRASH(IPlist, ("No IP addresses found!")); + if (!IPlist) + { + /// @todo: display error and exit lan lobby if no IPs are found + } - Bool foundIP = autoLocalIP != 0; - EnumeratedIP *tempIP = IPlist; - while ((tempIP != nullptr) && (foundIP == FALSE)) { - if (IP == tempIP->getIP()) { - foundIP = TRUE; + Bool foundIP = FALSE; + EnumeratedIP *tempIP = IPlist; + while ((tempIP != nullptr) && (foundIP == FALSE)) { + if (IP == tempIP->getIP()) { + foundIP = TRUE; + } + tempIP = tempIP->getNext(); } - tempIP = tempIP->getNext(); - } - if (foundIP == FALSE) { - // The IP that we had no longer exists, we need to pick a new one. - IP = IPlist->getIP(); - } + if (foundIP == FALSE) { + // The IP that we had no longer exists, we need to pick a new one. + IP = IPlist->getIP(); + } -// IP = IPlist->getIP(); -// } - if (automatedStartup) + // IP = IPlist->getIP(); + // } + } + if (!TheLAN->init(IP)) { #if defined(RTS_DEBUG) - NetworkAutoStart::onLocalAddressSet(TheLAN->SetLocalIP(IP)); + NetworkAutoStart::onLocalAddressSet(false); #endif - } - else - { - TheLAN->init(); - TheLAN->SetLocalIP(IP); + LANSocketErrorDetected = TRUE; } } @@ -421,7 +411,6 @@ void NetworkDirectConnectUpdate( WindowLayout * layout, void *userData) #if defined(RTS_DEBUG) if (NetworkAutoStart::isEnabled() && TheLAN != nullptr) { - TheLAN->update(); NetworkAutoStart::updateDirectConnect(); } #endif From dfb1b502441de5bdd031068279fa855ada93f7cb Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 15 Sep 2026 12:35:06 -0400 Subject: [PATCH 6/6] fix(network): Declare the LAN socket error flag --- .../GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp index 7c3d46342cf..e81c01fe008 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp @@ -60,6 +60,7 @@ extern Bool LANbuttonPushed; extern Bool LANisShuttingDown; +extern Bool LANSocketErrorDetected; static Bool isShuttingDown = false; static Bool buttonPushed = false;