Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Core/GameEngine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,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
Expand Down Expand Up @@ -1141,6 +1142,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
Expand Down
1 change: 1 addition & 0 deletions Core/GameEngine/Include/GameNetwork/LANAPICallbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
78 changes: 78 additions & 0 deletions Core/GameEngine/Include/GameNetwork/NetworkAutoStart.h
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

#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
110 changes: 110 additions & 0 deletions Core/GameEngine/Source/Common/CommandLine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine

#include <limits.h>

#include "Common/ArchiveFileSystem.h"
#include "Common/CommandLine.h"
#include "Common/CRCDebug.h"
Expand All @@ -35,6 +37,7 @@
#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"


Expand Down Expand Up @@ -484,6 +487,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)
Expand Down Expand Up @@ -1141,11 +1239,23 @@ static CommandLineParam paramsForStartup[] =
// (If you have 4 cores, call it with -jobs 4)
// If you do not call this, all replays will be simulated in sequence in the same process.
{ "-jobs", parseJobs },

#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.
Expand Down
26 changes: 26 additions & 0 deletions Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{

Expand Down
Loading
Loading