422 lines
11 KiB
C++
422 lines
11 KiB
C++
/*
|
|
** Command & Conquer Generals Zero Hour(tm)
|
|
** Copyright 2025 Electronic Arts Inc.
|
|
**
|
|
** 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/>.
|
|
*/
|
|
|
|
#include "global.h"
|
|
#include "matcher.h"
|
|
#include "encrypt.h"
|
|
#include "timezone.h"
|
|
#include "debug.h"
|
|
|
|
#ifdef _WINDOWS
|
|
#define usleep(x) Sleep((x)/100000)
|
|
#endif
|
|
|
|
MatcherClass::MatcherClass(void)
|
|
{
|
|
m_lastRotation = 0;
|
|
m_baseNick = "matcher";
|
|
m_joinSuccess = false;
|
|
done = 0;
|
|
|
|
m_rotateLogs = false;
|
|
quiet = false;
|
|
}
|
|
|
|
Wstring MatcherClass::getString(const Wstring& key)
|
|
{
|
|
Wstring res;
|
|
Global.GetString(key, res);
|
|
return res;
|
|
}
|
|
|
|
void logIt(const char *Txt)
|
|
{
|
|
// intentionally crash if we can't open it
|
|
FILE *fp = fopen("logIt.txt", "w");
|
|
fputs(Txt, fp);
|
|
fclose(fp);
|
|
}
|
|
|
|
void MatcherClass::readLoop(void)
|
|
{
|
|
int delay = -1;
|
|
Global.config.getInt("ROTATEDELAY", delay);
|
|
DBGMSG("ROTATEDELAY: " << delay);
|
|
|
|
do
|
|
{
|
|
static time_t lastLogTime = 0;
|
|
time_t now = time(NULL);
|
|
if (now > lastLogTime + 300)
|
|
{
|
|
lastLogTime = now;
|
|
INFMSG("still here" << endl);
|
|
}
|
|
|
|
logIt("peerThink\n");
|
|
peerThink(m_peer);
|
|
|
|
logIt("peerIsConnected\n");
|
|
if (peerIsConnected(m_peer))
|
|
{
|
|
logIt("checkMatches()\n");
|
|
checkMatches();
|
|
logIt("checkMatches() done\n");
|
|
}
|
|
else
|
|
done = true;
|
|
|
|
msleep(1);
|
|
|
|
// rotate logs if it's time
|
|
if (delay != -1)
|
|
{
|
|
#ifdef _UNIX
|
|
Xtime xtime;
|
|
time_t curtime;
|
|
curtime = time(NULL);
|
|
// get the number of seconds that have passed since midnight
|
|
// of the current day.
|
|
curtime -= TimezoneOffset();
|
|
time_t timeofday = curtime % (delay);
|
|
if ((timeofday > 0) && (timeofday <= 300))
|
|
{
|
|
rotateOutput();
|
|
rotateParanoid();
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
while (!done);
|
|
DBGMSG("Bailing out of readLoop!" << endl);
|
|
INFMSG("Bailing out of readLoop!" << endl);
|
|
ERRMSG("Bailing out of readLoop!" << endl);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void DisconnectedCallback ( PEER peer, const char * reason, void * param)
|
|
{
|
|
DBGMSG("Disconnected: " << reason);
|
|
MatcherClass *matcher = (MatcherClass *)param;
|
|
if (matcher)
|
|
matcher->handleDisconnect( reason );
|
|
}
|
|
|
|
static void RoomMessageCallback ( PEER peer, RoomType roomType, const char * nick, const char * message, MessageType messageType, void * param)
|
|
{
|
|
DBGMSG("(PUBLIC) " << nick << ": " << message);
|
|
MatcherClass *matcher = (MatcherClass *)param;
|
|
if (matcher)
|
|
matcher->handleRoomMessage( nick, message, messageType );
|
|
}
|
|
|
|
static void PlayerMessageCallback ( PEER peer, const char * nick, const char * message, MessageType messageType, void * param)
|
|
{
|
|
DBGMSG("(PRIVATE) " << nick << ": " << message);
|
|
MatcherClass *matcher = (MatcherClass *)param;
|
|
if (matcher)
|
|
matcher->handlePlayerMessage( nick, message, messageType );
|
|
}
|
|
|
|
static void PlayerJoinedCallback ( PEER peer, RoomType roomType, const char * nick, void * param)
|
|
{
|
|
DBGMSG(nick << " joined the room");
|
|
MatcherClass *matcher = (MatcherClass *)param;
|
|
if (matcher)
|
|
matcher->handlePlayerJoined( nick );
|
|
}
|
|
|
|
static void PlayerLeftCallback ( PEER peer, RoomType roomType, const char * nick, const char * reason, void * param)
|
|
{
|
|
DBGMSG(nick << " left the room");
|
|
MatcherClass *matcher = (MatcherClass *)param;
|
|
if (matcher)
|
|
matcher->handlePlayerLeft( nick );
|
|
}
|
|
|
|
static void PlayerChangedNickCallback ( PEER peer, RoomType roomType, const char * oldNick, const char * newNick, void * param)
|
|
{
|
|
INFMSG(oldNick << " changed nicks to " << newNick);
|
|
MatcherClass *matcher = (MatcherClass *)param;
|
|
if (matcher)
|
|
matcher->handlePlayerChangedNick( oldNick, newNick );
|
|
}
|
|
|
|
static void EnumPlayersCallback ( PEER peer, PEERBool success, RoomType roomType, int index, const char * nick, int flags, void * param)
|
|
{
|
|
MatcherClass *matcher = (MatcherClass *)param;
|
|
if (matcher)
|
|
matcher->handlePlayerEnum( success == PEERTrue, index, nick, flags);
|
|
}
|
|
|
|
static int s_groupID = 0;
|
|
static void ListGroupRoomsCallback ( PEER peer, PEERBool success, int groupID, SBServer server, const char * name, int numWaiting, int maxWaiting, int numGames, int numPlaying, void * param)
|
|
{
|
|
if (success && name && !strcasecmp(name, "QuickMatch"))
|
|
{
|
|
s_groupID = groupID;
|
|
}
|
|
}
|
|
|
|
static void ConnectCallback ( PEER peer, PEERBool success, void * param)
|
|
{
|
|
MatcherClass *matcher = (MatcherClass *)param;
|
|
if (matcher)
|
|
matcher->handleConnect( success == PEERTrue );
|
|
}
|
|
|
|
static void JoinCallback ( PEER peer, PEERBool success, PEERJoinResult result, RoomType roomType, void * param)
|
|
{
|
|
MatcherClass *matcher = (MatcherClass *)param;
|
|
if (matcher)
|
|
matcher->handleJoin( success == PEERTrue );
|
|
}
|
|
|
|
static void NickErrorCallback ( PEER peer, int type, const char * badNick, void * param)
|
|
{
|
|
ERRMSG("Nick error with " << badNick);
|
|
|
|
if(type == PEER_IN_USE)
|
|
{
|
|
int len = strlen(badNick);
|
|
std::string nickStr = badNick;
|
|
int newVal = 0;
|
|
if (badNick[len-1] == '}' && badNick[len-3] == '{' && isdigit(badNick[len-2]))
|
|
{
|
|
newVal = badNick[len-2] - '0' + 1;
|
|
nickStr.erase(len-3, 3);
|
|
}
|
|
|
|
nickStr.append("{");
|
|
char tmp[2];
|
|
tmp[0] = '0'+newVal;
|
|
tmp[1] = '\0';
|
|
nickStr.append(tmp);
|
|
nickStr.append("}");
|
|
DBGMSG("Nickname taken: was "<<badNick<<", new val = "<<newVal<<", new nick = "<<nickStr.c_str());
|
|
|
|
if (newVal < 10)
|
|
{
|
|
// Retry the connect with a similar nick.
|
|
peerRetryWithNick(peer, nickStr.c_str());
|
|
}
|
|
else
|
|
{
|
|
// Cancel the connect.
|
|
peerRetryWithNick(peer, NULL);
|
|
MatcherClass *matcher = (MatcherClass *)param;
|
|
if (matcher)
|
|
matcher->handleNickError( badNick );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Cancel the connect.
|
|
peerRetryWithNick(peer, NULL);
|
|
MatcherClass *matcher = (MatcherClass *)param;
|
|
if (matcher)
|
|
matcher->handleNickError( badNick );
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
void callbackEach( CHAT chat, CHATBool success, int index, const char *channel,
|
|
const char *topic, int numUsers, void *param )
|
|
{
|
|
DEBUG_LOG(("Chat channel success: %d\n", success));
|
|
if (!success)
|
|
{
|
|
return;
|
|
}
|
|
DEBUG_LOG(("Channel[%d]: %s (%s), %d users\n",
|
|
index, channel, topic, numUsers));
|
|
}
|
|
|
|
void callbackAll( CHAT chat, CHATBool success, int numChannels, const char **channels,
|
|
const char **topics, int *numUsers, void *param )
|
|
{
|
|
DEBUG_LOG(("Chat channels success: %d\n", success));
|
|
if (!success)
|
|
{
|
|
return;
|
|
}
|
|
|
|
DEBUG_LOG(("%d channels found\n", numChannels));
|
|
|
|
for (int i=0; i<numChannels; ++i)
|
|
{
|
|
DEBUG_LOG(("Channel[%d]: %s (%s), %d users\n",
|
|
i, channels[i], topics[i], numUsers[i]));
|
|
}
|
|
}
|
|
|
|
void MatcherClass::handleConnect( bool success )
|
|
{
|
|
m_connectSuccess = success;
|
|
|
|
//DEBUG_LOG(("Enumerating chat channels\n"));
|
|
//chatEnumChannels( peerGetChat(m_peer), "", callbackEach, callbackAll, NULL, CHATTrue );
|
|
//DEBUG_LOG(("Done enumerating chat channels\n"));
|
|
}
|
|
|
|
void MatcherClass::handleGroupRoomList( bool success, int groupID, const char *name )
|
|
{
|
|
}
|
|
|
|
void MatcherClass::handleJoin( bool success )
|
|
{
|
|
m_joinSuccess = success;
|
|
if (m_joinSuccess)
|
|
{
|
|
DBGMSG("Joined room - listing players");
|
|
peerEnumPlayers(m_peer, GroupRoom, EnumPlayersCallback, this);
|
|
}
|
|
}
|
|
|
|
void MatcherClass::handleNickError( const char *badNick )
|
|
{
|
|
exit(1);
|
|
}
|
|
|
|
static void AuthenticateCDKeyCallback
|
|
(
|
|
PEER peer,
|
|
int result,
|
|
const char * message,
|
|
void * param
|
|
)
|
|
{
|
|
bool *val = (bool *)param;
|
|
if (val)
|
|
{
|
|
*val = (result >= 1);
|
|
}
|
|
}
|
|
|
|
void MatcherClass::connectAndLoop(void)
|
|
{
|
|
// Game-specific initializations, if neccessary
|
|
init();
|
|
|
|
// Check for possible quit from init()-based self-tests
|
|
if (done)
|
|
return ;
|
|
|
|
// Defaults.
|
|
////////////
|
|
Wstring title = "gmtest";
|
|
Wstring secretKey = "HA6zkS";
|
|
Wstring serialNo = "";
|
|
m_profileID = 0;
|
|
Global.config.getString("Nick", m_baseNick, "LOGIN");
|
|
DBGMSG("base nick is " << m_baseNick.get());
|
|
m_baseNick.toLower();
|
|
Global.config.getString("Title", title, "LOGIN");
|
|
Global.config.getString("SecretKey", secretKey, "LOGIN");
|
|
Global.config.getInt("ProfileID", m_profileID, "LOGIN");
|
|
Global.config.getString("CDKey", serialNo, "LOGIN");
|
|
PEERCallbacks callbacks;
|
|
PEERBool pingRooms[NumRooms];
|
|
PEERBool crossPingRooms[NumRooms];
|
|
|
|
// Setup the callbacks.
|
|
///////////////////////
|
|
memset(&callbacks, 0, sizeof(PEERCallbacks));
|
|
callbacks.disconnected = DisconnectedCallback;
|
|
callbacks.playerChangedNick = PlayerChangedNickCallback;
|
|
callbacks.playerJoined = PlayerJoinedCallback;
|
|
callbacks.playerLeft = PlayerLeftCallback;
|
|
callbacks.roomMessage = RoomMessageCallback;
|
|
callbacks.playerMessage = PlayerMessageCallback;
|
|
callbacks.param = this;
|
|
|
|
// Init.
|
|
////////
|
|
m_peer = peerInitialize(&callbacks);
|
|
if(!m_peer)
|
|
{
|
|
ERRMSG("Failed to init peer object" << endl);
|
|
return;
|
|
}
|
|
|
|
// Ping/cross-ping in no room.
|
|
/////////////////////////////////
|
|
pingRooms[TitleRoom] = PEERFalse;
|
|
pingRooms[GroupRoom] = PEERFalse;
|
|
pingRooms[StagingRoom] = PEERFalse;
|
|
crossPingRooms[TitleRoom] = PEERFalse;
|
|
crossPingRooms[GroupRoom] = PEERFalse;
|
|
crossPingRooms[StagingRoom] = PEERFalse;
|
|
|
|
// Set the title.
|
|
/////////////////
|
|
if(!peerSetTitle(m_peer, title.get(), secretKey.get(), title.get(), secretKey.get(), 0, 30, PEERTrue, pingRooms, crossPingRooms))
|
|
{
|
|
peerShutdown(m_peer);
|
|
ERRMSG("Failed to set the title" << endl);
|
|
return;
|
|
}
|
|
|
|
// Connect.
|
|
///////////
|
|
m_connectSuccess = false;
|
|
m_nick = m_baseNick.get();
|
|
peerConnect(m_peer, m_baseNick.get(), m_profileID, NickErrorCallback, ConnectCallback, this, PEERTrue);
|
|
if(!m_connectSuccess)
|
|
{
|
|
peerShutdown(m_peer);
|
|
ERRMSG("Failed to connect" << endl);
|
|
return;
|
|
}
|
|
|
|
bool cdOk = false;
|
|
peerAuthenticateCDKey(m_peer, serialNo.get(), AuthenticateCDKeyCallback, &cdOk, PEERTrue);
|
|
if (!cdOk)
|
|
{
|
|
peerShutdown(m_peer);
|
|
ERRMSG("Failed to auth CDKey " << serialNo.get() << endl);
|
|
return;
|
|
}
|
|
|
|
m_groupID = 0;
|
|
peerListGroupRooms(m_peer, NULL, ListGroupRoomsCallback, &m_groupID, PEERTrue);
|
|
m_groupID = s_groupID;
|
|
|
|
DBGMSG("QuickMatch room is " << m_groupID);
|
|
|
|
// Join the title room.
|
|
///////////////////////
|
|
peerJoinGroupRoom(m_peer, m_groupID, JoinCallback, this, PEERTrue);
|
|
if(!m_joinSuccess)
|
|
{
|
|
peerDisconnect(m_peer);
|
|
peerShutdown(m_peer);
|
|
ERRMSG("Failed to join the title room" << endl);
|
|
return;
|
|
}
|
|
|
|
// Connected, so lets do our thing
|
|
readLoop();
|
|
|
|
peerDisconnect(m_peer);
|
|
peerShutdown(m_peer);
|
|
}
|
|
|