Initial commit of Command & Conquer Generals and Command & Conquer Generals Zero Hour source code.

This commit is contained in:
LFeenanEA
2025-02-27 17:34:39 +00:00
parent 2e338c00cb
commit 3d0ee53a05
6072 changed files with 2283311 additions and 0 deletions

View File

@@ -0,0 +1,424 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameNetwork/Connection.h"
#include "GameNetwork/NetworkUtil.h"
#include "GameLogic/GameLogic.h"
enum { MaxQuitFlushTime = 30000 }; // wait this many milliseconds at most to retry things before quitting
/**
* The constructor.
*/
Connection::Connection() {
m_transport = NULL;
m_user = NULL;
m_netCommandList = NULL;
m_retryTime = 2000; // set retry time to 2 seconds.
m_lastTimeSent = 0;
m_frameGrouping = 1;
m_isQuitting = false;
m_quitTime = 0;
// Added By Sadullah Nader
// clearing out the latency tracker
m_averageLatency = 0.0f;
Int i;
for(i = 0; i < CONNECTION_LATENCY_HISTORY_LENGTH; i++)
{
m_latencies[i] = 0.0f;
}
// End Add
}
/**
* The destructor.
*/
Connection::~Connection() {
if (m_user != NULL) {
m_user->deleteInstance();
m_user = NULL;
}
if (m_netCommandList != NULL) {
m_netCommandList->deleteInstance();
m_netCommandList = NULL;
}
}
/**
* Initialize the connection and any subsystems.
*/
void Connection::init() {
m_transport = NULL;
if (m_user != NULL) {
m_user->deleteInstance();
m_user = NULL;
}
if (m_netCommandList == NULL) {
m_netCommandList = newInstance(NetCommandList);
m_netCommandList->init();
}
m_netCommandList->reset();
m_lastTimeSent = 0;
m_frameGrouping = 1;
m_numRetries = 0;
m_retryMetricsTime = 0;
for (Int i = 0; i < CONNECTION_LATENCY_HISTORY_LENGTH; ++i) {
m_latencies[i] = 0;
}
m_averageLatency = 0;
m_isQuitting = FALSE;
m_quitTime = 0;
}
/**
* Take the connection back to the initial state.
*/
void Connection::reset() {
init();
}
/**
* Doesn't really do anything.
*/
void Connection::update() {
}
/**
* Attach the transport object that this connection should use.
*/
void Connection::attachTransport(Transport *transport) {
m_transport = transport;
}
/**
* Assign this connection a user. This is the user to whome we send all our packetized goodies.
*/
void Connection::setUser(User *user) {
if (m_user != NULL) {
m_user->deleteInstance();
}
m_user = user;
}
/**
* Return the user object.
*/
User * Connection::getUser() {
return m_user;
}
/**
* Add this network command to the send queue for this connection.
* The relay is the mask specifying the people the person we are sending to should send to.
* The relay mostly has to do with the packet router.
*/
void Connection::sendNetCommandMsg(NetCommandMsg *msg, UnsignedByte relay) {
static NetPacket *packet = NULL;
// this is done so we don't have to allocate and delete a packet every time we send a message.
if (packet == NULL) {
packet = newInstance(NetPacket);
}
if (m_isQuitting)
return;
if (m_netCommandList != NULL) {
// check to see if this command will fit in a packet. If not, we need to split it up.
// we are splitting up the command here so that the retry logic will not try to
// resend the ENTIRE command (i.e. multiple packets work of data) and only do the retry
// one wrapper command at a time.
packet->reset();
NetCommandRef *tempref = NEW_NETCOMMANDREF(msg);
Bool msgFits = packet->addCommand(tempref);
tempref->deleteInstance(); // delete the temporary reference.
tempref = NULL;
if (!msgFits) {
NetCommandRef *origref = NEW_NETCOMMANDREF(msg);
origref->setRelay(relay);
// the message doesn't fit in a single packet, need to split it up.
NetPacketList packetList = NetPacket::ConstructBigCommandPacketList(origref);
NetPacketListIter tempPacketPtr = packetList.begin();
while (tempPacketPtr != packetList.end()) {
NetPacket *tempPacket = (*tempPacketPtr);
NetCommandList *list = tempPacket->getCommandList();
NetCommandRef *ref1 = list->getFirstMessage();
while (ref1 != NULL) {
NetCommandRef *ref2 = m_netCommandList->addMessage(ref1->getCommand());
ref2->setRelay(relay);
ref1 = ref1->getNext();
}
tempPacket->deleteInstance();
tempPacket = NULL;
++tempPacketPtr;
list->deleteInstance();
list = NULL;
}
origref->deleteInstance();
origref = NULL;
return;
}
// the message fits in a packet, add to the command list normally.
NetCommandRef *ref = m_netCommandList->addMessage(msg);
if (ref != NULL) {
/*
#if ((defined(_DEBUG)) || (defined(_INTERNAL)))
if (msg->getNetCommandType() == NETCOMMANDTYPE_GAMECOMMAND) {
DEBUG_LOG(("Connection::sendNetCommandMsg - added game command %d to net command list for frame %d.\n",
msg->getID(), msg->getExecutionFrame()));
} else if (msg->getNetCommandType() == NETCOMMANDTYPE_FRAMEINFO) {
DEBUG_LOG(("Connection::sendNetCommandMsg - added frame info for frame %d\n", msg->getExecutionFrame()));
}
#endif // _DEBUG || _INTERNAL
*/
ref->setRelay(relay);
}
}
}
void Connection::clearCommandsExceptFrom( Int playerIndex )
{
NetCommandRef *tmp = m_netCommandList->getFirstMessage();
while (tmp)
{
NetCommandMsg *msg = tmp->getCommand();
if (msg->getPlayerID() != playerIndex)
{
DEBUG_LOG(("Connection::clearCommandsExceptFrom(%d) - clearing a command from %d for frame %d\n",
playerIndex, tmp->getCommand()->getPlayerID(), tmp->getCommand()->getExecutionFrame()));
m_netCommandList->removeMessage(tmp);
NetCommandRef *toDelete = tmp;
tmp = tmp->getNext();
toDelete->deleteInstance();
} else {
tmp = tmp->getNext();
}
}
}
Bool Connection::isQueueEmpty() {
if (m_netCommandList->getFirstMessage() == NULL) {
return TRUE;
}
return FALSE;
}
void Connection::setQuitting( void )
{
m_isQuitting = TRUE;
m_quitTime = timeGetTime();
DEBUG_LOG(("Connection::setQuitting() at time %d\n", m_quitTime));
}
/**
* This is the good part. We take all the network commands queued up for this connection,
* packetize them and put them on the transport's send queue for actual sending.
*/
UnsignedInt Connection::doSend() {
Int numpackets = 0;
time_t curtime = timeGetTime();
Bool couldQueue = TRUE;
// Do this check first, since it's an important fail-safe
if (m_isQuitting && curtime > m_quitTime + MaxQuitFlushTime)
{
DEBUG_LOG(("Timed out a quitting connection. Deleting all %d messages\n", m_netCommandList->length()));
m_netCommandList->reset();
return 0;
}
if ((curtime - m_lastTimeSent) < m_frameGrouping) {
// DEBUG_LOG(("not sending packet, time = %d, m_lastFrameSent = %d, m_frameGrouping = %d\n", curtime, m_lastTimeSent, m_frameGrouping));
return 0;
}
// iterate through all the messages and put them into a packet(s).
NetCommandRef *msg = m_netCommandList->getFirstMessage();
while ((msg != NULL) && couldQueue) {
NetPacket *packet = newInstance(NetPacket);
packet->init();
packet->setAddress(m_user->GetIPAddr(), m_user->GetPort());
Bool notDone = TRUE;
// add the command messages until either we run out of messages or the packet is full.
while ((msg != NULL) && notDone) {
NetCommandRef *next = msg->getNext(); // Need this since msg could be deleted
time_t timeLastSent = msg->getTimeLastSent();
if (((curtime - timeLastSent) > m_retryTime) || (timeLastSent == -1)) {
notDone = packet->addCommand(msg);
if (notDone) {
// the msg command was added to the packet.
if (CommandRequiresAck(msg->getCommand())) {
if (timeLastSent != -1) {
++m_numRetries;
}
doRetryMetrics();
msg->setTimeLastSent(curtime);
} else {
m_netCommandList->removeMessage(msg);
msg->deleteInstance();
}
}
}
msg = next;
}
if (msg != NULL) {
DEBUG_LOG(("didn't finish sending all commands in connection\n"));
}
++numpackets;
/// @todo Make the act of giving the transport object a packet to send more efficient. Make the transport take a NetPacket object rather than the raw data, thus avoiding an extra memcpy.
if (packet->getNumCommands() > 0) {
// If the packet actually has any information to give, give it to the transport object
// for transmission.
couldQueue = m_transport->queueSend(packet->getAddr(), packet->getPort(), packet->getData(), packet->getLength());
m_lastTimeSent = curtime;
}
if (packet != NULL) {
packet->deleteInstance(); // delete the packet now that we're done with it.
}
}
return numpackets;
}
NetCommandRef * Connection::processAck(NetAckStage1CommandMsg *msg) {
return processAck(msg->getCommandID(), msg->getOriginalPlayerID());
}
NetCommandRef * Connection::processAck(NetAckBothCommandMsg *msg) {
return processAck(msg->getCommandID(), msg->getOriginalPlayerID());
}
NetCommandRef * Connection::processAck(NetCommandMsg *msg) {
if (msg->getNetCommandType() == NETCOMMANDTYPE_ACKSTAGE1) {
NetAckStage1CommandMsg *ackmsg = (NetAckStage1CommandMsg *)msg;
return processAck(ackmsg);
}
if (msg->getNetCommandType() == NETCOMMANDTYPE_ACKBOTH) {
NetAckBothCommandMsg *ackmsg = (NetAckBothCommandMsg *)msg;
return processAck(ackmsg);
}
return NULL;
}
/**
* The person we are sending to has ack'd one of the messages we sent him.
* Take that message off the list of commands to send.
*/
NetCommandRef * Connection::processAck(UnsignedShort commandID, UnsignedByte originalPlayerID) {
NetCommandRef *temp = m_netCommandList->getFirstMessage();
while ((temp != NULL) && ((temp->getCommand()->getID() != commandID) || (temp->getCommand()->getPlayerID() != originalPlayerID))) {
// cycle through the commands till we find the one we need to remove.
// Need to check for both the command ID and the player ID.
temp = temp->getNext();
}
if (temp == NULL) {
return NULL;
}
#if defined(_DEBUG) || defined(_INTERNAL)
Bool doDebug = FALSE;
if (temp->getCommand()->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTFRAME) {
doDebug = TRUE;
}
#endif
Int index = temp->getCommand()->getID() % CONNECTION_LATENCY_HISTORY_LENGTH;
m_averageLatency -= ((Real)(m_latencies[index])) / CONNECTION_LATENCY_HISTORY_LENGTH;
Real lat = timeGetTime() - temp->getTimeLastSent();
m_averageLatency += lat / CONNECTION_LATENCY_HISTORY_LENGTH;
m_latencies[index] = lat;
#if defined(_DEBUG) || defined(_INTERNAL)
if (doDebug == TRUE) {
DEBUG_LOG(("Connection::processAck - disconnect frame command %d found, removing from command list.\n", commandID));
}
#endif
m_netCommandList->removeMessage(temp);
return temp;
}
void Connection::setFrameGrouping(time_t frameGrouping) {
m_frameGrouping = frameGrouping;
// m_retryTime = frameGrouping * 4;
}
void Connection::doRetryMetrics() {
static Int numSeconds = 0;
time_t curTime = timeGetTime();
if ((curTime - m_retryMetricsTime) > 10000) {
m_retryMetricsTime = curTime;
++numSeconds;
// DEBUG_LOG(("Retries in the last 10 seconds = %d, average latency = %fms\n", m_numRetries, m_averageLatency));
m_numRetries = 0;
// m_retryTime = m_averageLatency * 1.5;
}
}
#if defined(_DEBUG) || (_INTERNAL)
void Connection::debugPrintCommands() {
NetCommandRef *ref = m_netCommandList->getFirstMessage();
while (ref != NULL) {
DEBUG_LOG(("Connection::debugPrintCommands - ID: %d\tType: %s\tRelay: 0x%X for frame %d\n",
ref->getCommand()->getID(), GetAsciiNetCommandType(ref->getCommand()->getNetCommandType()).str(),
ref->getRelay(), ref->getCommand()->getExecutionFrame()));
ref = ref->getNext();
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,809 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Recorder.h"
#include "GameClient/DisconnectMenu.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/GameLogic.h"
#include "GameNetwork/DisconnectManager.h"
#include "GameNetwork/NetworkInterface.h"
#include "GameNetwork/NetworkUtil.h"
#include "GameNetwork/GameSpy/PingThread.h"
#include "GameNetwork/GameSpy/GSConfig.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
DisconnectManager::DisconnectManager()
{
// Added By Sadullah Nader
// Initializations missing and needed
Int i;
m_currentPacketRouterIndex = 0;
m_lastFrame = 0;
m_lastFrameTime = 0;
m_lastKeepAliveSendTime = 0;
m_haveNotifiedOtherPlayersOfCurrentFrame = FALSE;
m_timeOfDisconnectScreenOn = 0;
for( i = 0; i < MAX_SLOTS; ++i) {
m_packetRouterFallback[i] = 0;
}
m_packetRouterTimeout = 0;
for( i = 0; i < MAX_SLOTS -1; ++i) {
m_playerTimeouts[i] = 0;
}
for( i = 0; i < MAX_SLOTS; ++i) {
for (Int j = 0; j < MAX_SLOTS; ++j) {
m_playerVotes[i][j].vote = FALSE;
m_playerVotes[i][j].frame = 0;
}
}
}
DisconnectManager::~DisconnectManager() {
}
void DisconnectManager::init() {
TheDisconnectMenu->hideScreen(); // make sure the screen starts out hidden.
m_lastFrame = 0;
m_lastFrameTime = -1;
m_lastKeepAliveSendTime = -1;
m_disconnectState = DISCONNECTSTATETYPE_SCREENOFF;
m_currentPacketRouterIndex = 0;
m_timeOfDisconnectScreenOn = 0;
for (Int i = 0; i < MAX_SLOTS; ++i) {
for (Int j = 0; j < MAX_SLOTS; ++j) {
m_playerVotes[i][j].vote = FALSE;
m_playerVotes[i][j].frame = 0;
}
}
for (i = 0; i < MAX_SLOTS; ++i) {
m_disconnectFrames[i] = 0;
m_disconnectFramesReceived[i] = FALSE;
}
m_pingFrame = 0;
m_pingsSent = 0;
m_pingsRecieved = 0;
}
void DisconnectManager::update(ConnectionManager *conMgr) {
if (m_lastFrameTime == -1) {
m_lastFrameTime = timeGetTime();
}
// The game logic stalls on the frame we are currently waiting for commands on,
// so we have to check for the current logic frame being one higher than
// the last one we had the commands ready for.
if (TheGameLogic->getFrame() == m_lastFrame) {
time_t curTime = timeGetTime();
if ((curTime - m_lastFrameTime) > TheGlobalData->m_networkDisconnectTime) {
if (m_disconnectState == DISCONNECTSTATETYPE_SCREENOFF) {
turnOnScreen(conMgr);
}
sendKeepAlive(conMgr);
}
} else {
nextFrame(TheGameLogic->getFrame(), conMgr);
}
if (m_disconnectState != DISCONNECTSTATETYPE_SCREENOFF) {
updateDisconnectStatus(conMgr);
// check to see if we need to send pings
if (m_pingFrame < TheGameLogic->getFrame())
{
time_t curTime = timeGetTime();
if ((curTime - m_lastFrameTime) > 10000) /// @todo: plug in some better measure here
{
m_pingFrame = TheGameLogic->getFrame();
m_pingsSent = 0;
m_pingsRecieved = 0;
// Send the pings
if (ThePinger)
{
//use next ping server
static int serverIndex = 0;
serverIndex++;
if( serverIndex >= TheGameSpyConfig->getPingServers().size() )
serverIndex = 0; //wrap back to first ping server
std::list<AsciiString>::iterator it = TheGameSpyConfig->getPingServers().begin();
for( int i = 0; i < serverIndex; i++ )
it++;
PingRequest req;
req.hostname = it->str();
req.repetitions = 5;
req.timeout = 2000;
m_pingsSent = req.repetitions;
ThePinger->addRequest(req);
DEBUG_LOG(("DisconnectManager::update() - requesting %d pings of %d from %s\n",
req.repetitions, req.timeout, req.hostname.c_str()));
}
}
}
// update the ping thread, tracking pings if we are on the same frame
if (ThePinger)
{
PingResponse resp;
while (ThePinger->getResponse(resp))
{
if (m_pingFrame != TheGameLogic->getFrame())
{
// wrong frame - we're not pinging yet
DEBUG_LOG(("DisconnectManager::update() - discarding ping of %d from %s (%d reps)\n",
resp.avgPing, resp.hostname.c_str(), resp.repetitions));
}
else
{
// right frame
DEBUG_LOG(("DisconnectManager::update() - keeping ping of %d from %s (%d reps)\n",
resp.avgPing, resp.hostname.c_str(), resp.repetitions));
if (resp.avgPing < 2000)
{
m_pingsRecieved += resp.repetitions;
}
}
}
}
}
}
UnsignedInt DisconnectManager::getPingFrame()
{
return m_pingFrame;
}
Int DisconnectManager::getPingsSent()
{
return m_pingsSent;
}
Int DisconnectManager::getPingsRecieved()
{
return m_pingsRecieved;
}
void DisconnectManager::updateDisconnectStatus(ConnectionManager *conMgr) {
for (Int i = 0; i < MAX_SLOTS; ++i) {
if (conMgr->isPlayerConnected(i)) {
Int slot = translatedSlotPosition(i, conMgr->getLocalPlayerID());
if (slot != -1) {
time_t curTime = timeGetTime();
time_t newTime = TheGlobalData->m_networkPlayerTimeoutTime - (curTime - m_playerTimeouts[slot]);
// if someone is more than 2/3 timed out, lets get our frame numbers sync'd up. Also if someone is voted out
// lets do the same thing.
if (m_haveNotifiedOtherPlayersOfCurrentFrame == FALSE) {
if ((newTime < TheGlobalData->m_networkPlayerTimeoutTime / 3) || (isPlayerVotedOut(slot, conMgr) == TRUE)) {
TheNetwork->notifyOthersOfCurrentFrame();
m_haveNotifiedOtherPlayersOfCurrentFrame = TRUE;
}
DEBUG_LOG(("DisconnectManager::updateDisconnectStatus - curTime = %d, m_timeOfDisconnectScreenOn = %d, curTime - m_timeOfDisconnectScreenOn = %d\n", curTime, m_timeOfDisconnectScreenOn, curTime - m_timeOfDisconnectScreenOn));
if (m_timeOfDisconnectScreenOn != 0) {
if ((curTime - m_timeOfDisconnectScreenOn) > TheGlobalData->m_networkDisconnectScreenNotifyTime) {
TheNetwork->notifyOthersOfCurrentFrame();
m_haveNotifiedOtherPlayersOfCurrentFrame = TRUE;
}
}
}
if ((newTime < 0) || (isPlayerVotedOut(slot, conMgr) == TRUE)) {
newTime = 0;
DEBUG_LOG(("DisconnectManager::updateDisconnectStatus - player %d(translated slot %d) has been voted out or timed out\n", i, slot));
if (allOnSameFrame(conMgr) == TRUE) {
DEBUG_LOG(("DisconnectManager::updateDisconnectStatus - all on same frame\n"));
if (isLocalPlayerNextPacketRouter(conMgr) == TRUE) {
DEBUG_LOG(("DisconnectManager::updateDisconnectStatus - local player is next packet router\n"));
DEBUG_LOG(("DisconnectManager::updateDisconnectStatus - about to do the disconnect procedure for player %d\n", i));
sendDisconnectCommand(i, conMgr);
disconnectPlayer(i, conMgr);
sendPlayerDestruct(i, conMgr);
} else {
DEBUG_LOG(("DisconnectManager::updateDisconnectStatus - local player is not the next packet router\n"));
}
} else {
DEBUG_LOG(("DisconnectManager::updateDisconnectStatus - not all on same frame\n"));
}
}
TheDisconnectMenu->setPlayerTimeoutTime(slot, newTime);
}
}
}
}
void DisconnectManager::updateWaitForPacketRouter(ConnectionManager *conMgr) {
/*
time_t curTime = timeGetTime();
time_t newTime = TheGlobalData->m_networkPlayerTimeoutTime - (curTime - m_packetRouterTimeout);
if (newTime < 0) {
newTime = 0;
// The guy that we were hoping would be the new packet router isn't. We're screwed, get out of the game.
DEBUG_LOG(("DisconnectManager::updateWaitForPacketRouter - timed out waiting for new packet router, quitting game\n"));
TheNetwork->quitGame();
}
TheDisconnectMenu->setPacketRouterTimeoutTime(newTime);
*/
}
void DisconnectManager::processDisconnectCommand(NetCommandRef *ref, ConnectionManager *conMgr) {
NetCommandMsg *msg = ref->getCommand();
if (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTKEEPALIVE) {
processDisconnectKeepAlive(msg, conMgr);
} else if (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTPLAYER) {
processDisconnectPlayer(msg, conMgr);
} else if (msg->getNetCommandType() == NETCOMMANDTYPE_PACKETROUTERQUERY) {
processPacketRouterQuery(msg, conMgr);
} else if (msg->getNetCommandType() == NETCOMMANDTYPE_PACKETROUTERACK) {
processPacketRouterAck(msg, conMgr);
} else if (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTVOTE) {
processDisconnectVote(msg, conMgr);
} else if (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTFRAME) {
processDisconnectFrame(msg, conMgr);
} else if (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTSCREENOFF) {
processDisconnectScreenOff(msg, conMgr);
}
}
void DisconnectManager::processDisconnectKeepAlive(NetCommandMsg *msg, ConnectionManager *conMgr) {
NetDisconnectKeepAliveCommandMsg *cmdMsg = (NetDisconnectKeepAliveCommandMsg *)msg;
Int slot = translatedSlotPosition(cmdMsg->getPlayerID(), conMgr->getLocalPlayerID());
if (slot != -1) {
resetPlayerTimeout(slot);
}
}
void DisconnectManager::processDisconnectPlayer(NetCommandMsg *msg, ConnectionManager *conMgr) {
NetDisconnectPlayerCommandMsg *cmdMsg = (NetDisconnectPlayerCommandMsg *)msg;
DEBUG_LOG(("DisconnectManager::processDisconnectPlayer - Got disconnect player command from player %d. Disconnecting player %d on frame %d\n", msg->getPlayerID(), cmdMsg->getDisconnectSlot(), cmdMsg->getDisconnectFrame()));
DEBUG_ASSERTCRASH(TheGameLogic->getFrame() == cmdMsg->getDisconnectFrame(), ("disconnecting player on the wrong frame!!!"));
disconnectPlayer(cmdMsg->getDisconnectSlot(), conMgr);
}
void DisconnectManager::processPacketRouterQuery(NetCommandMsg *msg, ConnectionManager *conMgr) {
NetPacketRouterQueryCommandMsg *cmdMsg = (NetPacketRouterQueryCommandMsg *)msg;
DEBUG_LOG(("DisconnectManager::processPacketRouterQuery - got a packet router query command from player %d\n", msg->getPlayerID()));
if (conMgr->getPacketRouterSlot() == conMgr->getLocalPlayerID()) {
NetPacketRouterAckCommandMsg *ackmsg = newInstance(NetPacketRouterAckCommandMsg);
ackmsg->setPlayerID(conMgr->getLocalPlayerID());
if (DoesCommandRequireACommandID(ackmsg->getNetCommandType()) == TRUE) {
ackmsg->setID(GenerateNextCommandID());
}
DEBUG_LOG(("DisconnectManager::processPacketRouterQuery - We are the new packet router, responding with an packet router ack. Local player is %d\n", ackmsg->getPlayerID()));
conMgr->sendLocalCommandDirect(ackmsg, 1 << cmdMsg->getPlayerID());
ackmsg->detach();
} else {
DEBUG_LOG(("DisconnectManager::processPacketRouterQuery - We are NOT the new packet router, these are not the droids you're looking for.\n"));
}
}
void DisconnectManager::processPacketRouterAck(NetCommandMsg *msg, ConnectionManager *conMgr) {
NetPacketRouterAckCommandMsg *cmdMsg = (NetPacketRouterAckCommandMsg *)msg;
DEBUG_LOG(("DisconnectManager::processPacketRouterAck - got packet router ack command from player %d\n", msg->getPlayerID()));
if (conMgr->getPacketRouterSlot() == cmdMsg->getPlayerID()) {
DEBUG_LOG(("DisconnectManager::processPacketRouterAck - packet router command is from who it should be.\n"));
resetPacketRouterTimeout();
Int currentPacketRouterSlot = conMgr->getPacketRouterSlot();
Int currentPacketRouterIndex = 0;
while ((currentPacketRouterSlot != conMgr->getPacketRouterFallbackSlot(currentPacketRouterIndex)) && (currentPacketRouterIndex < MAX_SLOTS)) {
++currentPacketRouterIndex;
}
DEBUG_ASSERTCRASH((currentPacketRouterIndex < MAX_SLOTS), ("Invalid packet router index"));
DEBUG_LOG(("DisconnectManager::processPacketRouterAck - New packet router confirmed, resending pending commands\n"));
conMgr->resendPendingCommands();
m_currentPacketRouterIndex = currentPacketRouterIndex;
DEBUG_LOG(("DisconnectManager::processPacketRouterAck - Setting disconnect state to screen on.\n"));
m_disconnectState = DISCONNECTSTATETYPE_SCREENON; ///< set it to screen on so that the next call to AllCommandsReady can set up everything for the next frame properly.
}
}
void DisconnectManager::processDisconnectVote(NetCommandMsg *msg, ConnectionManager *conMgr) {
NetDisconnectVoteCommandMsg *cmdMsg = (NetDisconnectVoteCommandMsg *)msg;
DEBUG_LOG(("DisconnectManager::processDisconnectVote - Got a disconnect vote for player %d command from player %d\n", cmdMsg->getSlot(), cmdMsg->getPlayerID()));
Int transSlot = translatedSlotPosition(msg->getPlayerID(), conMgr->getLocalPlayerID());
if (isPlayerInGame(transSlot, conMgr) == FALSE) {
// if they've been timed out, voted out, disconnected, don't count their vote.
return;
}
applyDisconnectVote(cmdMsg->getSlot(), cmdMsg->getVoteFrame(), cmdMsg->getPlayerID(), conMgr);
}
void DisconnectManager::processDisconnectFrame(NetCommandMsg *msg, ConnectionManager *conMgr) {
NetDisconnectFrameCommandMsg *cmdMsg = (NetDisconnectFrameCommandMsg *)msg;
UnsignedInt playerID = cmdMsg->getPlayerID();
if (m_disconnectFrames[playerID] >= cmdMsg->getDisconnectFrame()) {
// this message isn't valid, we have a disconnect frame that is later than this already.
return;
}
if (m_disconnectFramesReceived[playerID] == TRUE) {
DEBUG_LOG(("DisconnectManager::processDisconnectFrame - Got two disconnect frames without an intervening disconnect screen off command from player %d. Frames are %d and %d\n", playerID, m_disconnectFrames[playerID], cmdMsg->getDisconnectFrame()));
}
DEBUG_LOG(("DisconnectManager::processDisconnectFrame - about to call resetPlayersVotes for player %d\n", playerID));
resetPlayersVotes(playerID, cmdMsg->getDisconnectFrame()-1, conMgr);
m_disconnectFrames[playerID] = cmdMsg->getDisconnectFrame();
m_disconnectFramesReceived[playerID] = TRUE;
DEBUG_LOG(("DisconnectManager::processDisconnectFrame - Got a disconnect frame for player %d, frame = %d, local player is %d, local disconnect frame = %d, command id = %d\n", cmdMsg->getPlayerID(), cmdMsg->getDisconnectFrame(), conMgr->getLocalPlayerID(), m_disconnectFrames[conMgr->getLocalPlayerID()], cmdMsg->getID()));
if (playerID == conMgr->getLocalPlayerID()) {
DEBUG_LOG(("DisconnectManager::processDisconnectFrame - player %d is the local player\n", playerID));
// we just got the message from the local player, check to see if we need to send
// commands to anyone we already have heard from.
for (Int i = 0; i < MAX_SLOTS; ++i) {
if (i != playerID) {
Int transSlot = translatedSlotPosition(i, conMgr->getLocalPlayerID());
if (isPlayerInGame(transSlot, conMgr) == TRUE) {
if ((m_disconnectFrames[i] < m_disconnectFrames[playerID]) && (m_disconnectFramesReceived[i] == TRUE)) {
DEBUG_LOG(("DisconnectManager::processDisconnectFrame - I have more frames than player %d, my frame = %d, their frame = %d\n", i, m_disconnectFrames[conMgr->getLocalPlayerID()], m_disconnectFrames[i]));
conMgr->sendFrameDataToPlayer(i, m_disconnectFrames[i]);
}
}
}
}
} else if ((m_disconnectFrames[playerID] < m_disconnectFrames[conMgr->getLocalPlayerID()]) && (m_disconnectFramesReceived[playerID] == TRUE)) {
DEBUG_LOG(("DisconnectManager::processDisconnectFrame - I have more frames than player %d, my frame = %d, their frame = %d\n", playerID, m_disconnectFrames[conMgr->getLocalPlayerID()], m_disconnectFrames[playerID]));
conMgr->sendFrameDataToPlayer(playerID, m_disconnectFrames[playerID]);
}
}
void DisconnectManager::processDisconnectScreenOff(NetCommandMsg *msg, ConnectionManager *conMgr) {
NetDisconnectScreenOffCommandMsg *cmdMsg = (NetDisconnectScreenOffCommandMsg *)msg;
UnsignedInt playerID = cmdMsg->getPlayerID();
DEBUG_LOG(("DisconnectManager::processDisconnectScreenOff - got a screen off command from player %d for frame %d\n", cmdMsg->getPlayerID(), cmdMsg->getNewFrame()));
if ((playerID < 0) || (playerID >= MAX_SLOTS)) {
return;
}
UnsignedInt newFrame = cmdMsg->getNewFrame();
if (newFrame >= m_disconnectFrames[playerID]) {
DEBUG_LOG(("DisconnectManager::processDisconnectScreenOff - resetting the disconnect screen status for player %d\n", playerID));
m_disconnectFramesReceived[playerID] = FALSE;
m_disconnectFrames[playerID] = newFrame; // just in case we get packets out of order and the disconnect screen off message gets here before the disconnect frame message.
DEBUG_LOG(("DisconnectManager::processDisconnectScreenOff - about to call resetPlayersVotes for player %d\n", playerID));
resetPlayersVotes(playerID, cmdMsg->getNewFrame(), conMgr);
}
}
void DisconnectManager::applyDisconnectVote(Int slot, UnsignedInt frame, Int fromSlot, ConnectionManager *conMgr) {
m_playerVotes[slot][fromSlot].vote = TRUE;
m_playerVotes[slot][fromSlot].frame = frame;
Int numVotes = countVotesForPlayer(slot);
DEBUG_LOG(("DisconnectManager::applyDisconnectVote - added a vote to disconnect slot %d, from slot %d, for frame %d, current votes are %d\n", slot, fromSlot, frame, numVotes));
Int transSlot = translatedSlotPosition(slot, conMgr->getLocalPlayerID());
if (transSlot != -1) {
TheDisconnectMenu->updateVotes(transSlot, numVotes);
}
}
void DisconnectManager::nextFrame(UnsignedInt frame, ConnectionManager *conMgr) {
m_lastFrame = frame;
m_lastFrameTime = timeGetTime();
resetPlayerTimeouts(conMgr);
}
void DisconnectManager::allCommandsReady(UnsignedInt frame, ConnectionManager *conMgr, Bool waitForPacketRouter) {
if (m_disconnectState != DISCONNECTSTATETYPE_SCREENOFF) {
DEBUG_LOG(("DisconnectManager::allCommandsReady - setting screen state to off.\n"));
TheDisconnectMenu->hideScreen();
m_disconnectState = DISCONNECTSTATETYPE_SCREENOFF;
TheNetwork->notifyOthersOfNewFrame(frame);
// reset the votes since we're moving to a new frame.
for (Int i = 0; i < MAX_SLOTS; ++i) {
m_playerVotes[i][conMgr->getLocalPlayerID()].vote = FALSE;
}
DEBUG_LOG(("DisconnectManager::allCommandsReady - resetting m_timeOfDisconnectScreenOn\n"));
m_timeOfDisconnectScreenOn = 0;
}
}
Bool DisconnectManager::allowedToContinue() {
if (m_disconnectState != DISCONNECTSTATETYPE_SCREENOFF) {
return FALSE;
}
return TRUE;
}
void DisconnectManager::sendKeepAlive(ConnectionManager *conMgr) {
time_t curTime = timeGetTime();
if (((curTime - m_lastKeepAliveSendTime) > 500) || (m_lastKeepAliveSendTime == -1)) {
NetDisconnectKeepAliveCommandMsg *msg = newInstance(NetDisconnectKeepAliveCommandMsg);
msg->setPlayerID(conMgr->getLocalPlayerID());
if (DoesCommandRequireACommandID(msg->getNetCommandType()) == TRUE) {
msg->setID(GenerateNextCommandID());
}
conMgr->sendLocalCommandDirect(msg, 0xff ^ (1 << msg->getPlayerID()));
msg->detach();
m_lastKeepAliveSendTime = curTime;
}
}
void DisconnectManager::populateDisconnectScreen(ConnectionManager *conMgr) {
for (Int i = 0; i < MAX_SLOTS; ++i) {
UnicodeString name = conMgr->getPlayerName(i);
Int slot = translatedSlotPosition(i, conMgr->getLocalPlayerID());
if (slot != -1) {
TheDisconnectMenu->setPlayerName(slot, name);
Int numVotes = countVotesForPlayer(i);
TheDisconnectMenu->updateVotes(slot, numVotes);
}
}
}
Int DisconnectManager::translatedSlotPosition(Int slot, Int localSlot) {
if (slot < localSlot) {
return slot;
}
if (slot == localSlot) {
return -1;
}
return (slot - 1);
}
Int DisconnectManager::untranslatedSlotPosition(Int slot, Int localSlot) {
if (slot == -1) {
return localSlot;
}
if (slot < localSlot) {
return slot;
}
return (slot + 1);
}
void DisconnectManager::resetPlayerTimeouts(ConnectionManager *conMgr) {
// reset the player timeouts.
for (Int i = 0; i < MAX_SLOTS; ++i) {
Int slot = translatedSlotPosition(i, conMgr->getLocalPlayerID());
if (slot != -1) {
resetPlayerTimeout(slot);
}
}
}
void DisconnectManager::resetPlayerTimeout(Int slot) {
m_playerTimeouts[slot] = timeGetTime();
}
void DisconnectManager::resetPacketRouterTimeout() {
m_packetRouterTimeout = timeGetTime();
}
void DisconnectManager::turnOnScreen(ConnectionManager *conMgr) {
TheDisconnectMenu->showScreen();
DEBUG_LOG(("DisconnectManager::turnOnScreen - turning on screen on frame %d\n", TheGameLogic->getFrame()));
m_disconnectState = DISCONNECTSTATETYPE_SCREENON;
m_lastKeepAliveSendTime = -1;
populateDisconnectScreen(conMgr);
resetPlayerTimeouts(conMgr);
TheDisconnectMenu->hidePacketRouterTimeout();
m_haveNotifiedOtherPlayersOfCurrentFrame = FALSE;
m_timeOfDisconnectScreenOn = timeGetTime();
DEBUG_LOG(("DisconnectManager::turnOnScreen - turned on screen at time %d\n", m_timeOfDisconnectScreenOn));
}
void DisconnectManager::disconnectPlayer(Int slot, ConnectionManager *conMgr) {
DEBUG_LOG(("DisconnectManager::disconnectPlayer - Disconnecting slot number %d on frame %d\n", slot, TheGameLogic->getFrame()));
DEBUG_ASSERTCRASH((slot >= 0) && (slot < MAX_SLOTS), ("Attempting to disconnect an invalid slot number"));
if ((slot < 0) || (slot >= (MAX_SLOTS))) {
return;
}
if (TheGameInfo)
{
GameSlot *gSlot = TheGameInfo->getSlot( slot );
if (gSlot)
{
gSlot->markAsDisconnected();
}
}
Int transSlot = translatedSlotPosition(slot, conMgr->getLocalPlayerID());
if (transSlot != -1) {
// Ignore any disconnect commands that tell us to disconnect ourselves.
// Get the disconnecting player off the disconnect window.
UnicodeString uname = conMgr->getPlayerName(slot);
TheRecorder->logPlayerDisconnect(uname, slot);
TheDisconnectMenu->removePlayer(transSlot, uname);
PlayerLeaveCode retcode = conMgr->disconnectPlayer(slot);
DEBUG_ASSERTCRASH((retcode != PLAYERLEAVECODE_UNKNOWN), ("Invalid player leave code"));
if (retcode == PLAYERLEAVECODE_PACKETROUTER) {
DEBUG_LOG(("DisconnectManager::disconnectPlayer - disconnecting player was packet router.\n"));
conMgr->resendPendingCommands();
}
}
}
void DisconnectManager::sendDisconnectCommand(Int slot, ConnectionManager *conMgr) {
DEBUG_LOG(("DisconnectManager::sendDisconnectCommand - Sending disconnect command for slot number %d\n", slot));
DEBUG_ASSERTCRASH((slot >= 0) && (slot < MAX_SLOTS), ("Attempting to send a disconnect command for an invalid slot number"));
if ((slot < 0) || (slot >= (MAX_SLOTS))) {
return;
}
UnsignedInt disconnectFrame = getMaxDisconnectFrame();
// Need to do the NetDisconnectPlayerCommandMsg creation and sending here.
NetDisconnectPlayerCommandMsg *msg = newInstance(NetDisconnectPlayerCommandMsg);
msg->setDisconnectSlot(slot);
msg->setDisconnectFrame(disconnectFrame);
msg->setPlayerID(conMgr->getLocalPlayerID());
if (DoesCommandRequireACommandID(msg->getNetCommandType())) {
msg->setID(GenerateNextCommandID());
}
conMgr->sendLocalCommand(msg);
DEBUG_LOG(("DisconnectManager::sendDisconnectCommand - Sending disconnect command for slot number %d for frame %d\n", slot, disconnectFrame));
msg->detach();
}
void DisconnectManager::sendVoteCommand(Int slot, ConnectionManager *conMgr) {
NetDisconnectVoteCommandMsg *msg = newInstance(NetDisconnectVoteCommandMsg);
msg->setPlayerID(conMgr->getLocalPlayerID());
msg->setSlot(slot);
msg->setVoteFrame(TheGameLogic->getFrame());
if (DoesCommandRequireACommandID(msg->getNetCommandType()) == TRUE) {
msg->setID(GenerateNextCommandID());
}
conMgr->sendLocalCommandDirect(msg, 0xff & ~(1 << conMgr->getLocalPlayerID()));
msg->detach();
}
void DisconnectManager::voteForPlayerDisconnect(Int slot, ConnectionManager *conMgr) {
Int transSlot = untranslatedSlotPosition(slot, conMgr->getLocalPlayerID());
if (m_playerVotes[transSlot][conMgr->getLocalPlayerID()].vote == FALSE) {
m_playerVotes[transSlot][conMgr->getLocalPlayerID()].vote = TRUE;
sendVoteCommand(transSlot, conMgr);
// we use the game logic frame cause we might not have sent out our own disconnect frame yet.
applyDisconnectVote(transSlot, TheGameLogic->getFrame(), conMgr->getLocalPlayerID(), conMgr);
}
}
void DisconnectManager::recalculatePacketRouterIndex(ConnectionManager *conMgr) {
Int currentPacketRouterSlot = conMgr->getPacketRouterSlot();
m_currentPacketRouterIndex = 0;
while ((currentPacketRouterSlot != conMgr->getPacketRouterFallbackSlot(m_currentPacketRouterIndex)) && (m_currentPacketRouterIndex < MAX_SLOTS)) {
++m_currentPacketRouterIndex;
}
DEBUG_ASSERTCRASH((m_currentPacketRouterIndex < MAX_SLOTS), ("Invalid packet router index"));
}
Bool DisconnectManager::allOnSameFrame(ConnectionManager *conMgr) {
Bool retval = TRUE;
for (Int i = 0; (i < MAX_SLOTS) && (retval == TRUE); ++i) {
Int transSlot = translatedSlotPosition(i, conMgr->getLocalPlayerID());
if (transSlot == -1) {
continue;
}
if ((conMgr->isPlayerConnected(i) == TRUE) && (isPlayerInGame(transSlot, conMgr) == TRUE)) {
// ok, i is someone who is in the game and hasn't timed out yet or been voted out.
if (m_disconnectFramesReceived[i] == FALSE) {
// we don't know what frame they are on yet.
retval = FALSE;
}
if ((m_disconnectFramesReceived[i] == TRUE) && (m_disconnectFrames[conMgr->getLocalPlayerID()] != m_disconnectFrames[i])) {
// We know their frame, but they aren't on the same frame as us.
retval = FALSE;
}
}
}
return retval;
}
Bool DisconnectManager::isLocalPlayerNextPacketRouter(ConnectionManager *conMgr) {
UnsignedInt localSlot = conMgr->getLocalPlayerID();
UnsignedInt packetRouterSlot = conMgr->getPacketRouterSlot();
Int transSlot = translatedSlotPosition(packetRouterSlot, localSlot);
// stop when we have found a packet router that is connected
while ((transSlot != -1) && (isPlayerInGame(transSlot, conMgr) == FALSE)) {
packetRouterSlot = conMgr->getNextPacketRouterSlot(packetRouterSlot);
if ((packetRouterSlot >= MAX_SLOTS) || (packetRouterSlot < 0)) {
// don't know who the next packet router is going to be,
// so this game is not going to go anywhere anymore.
DEBUG_CRASH(("no more players left to be the packet router, this shouldn't happen."));
return FALSE;
}
transSlot = translatedSlotPosition(packetRouterSlot, localSlot);
}
if (packetRouterSlot == localSlot) {
return TRUE;
}
return FALSE;
}
Bool DisconnectManager::hasPlayerTimedOut(Int slot) {
if (slot == -1) {
return FALSE;
}
time_t newTime = TheGlobalData->m_networkPlayerTimeoutTime - (timeGetTime() - m_playerTimeouts[slot]);
if (newTime <= 0) {
return TRUE;
}
return FALSE;
}
// this function assumes that we are the packet router. (or at least that
// we will be after everyone is getting disconnected)
void DisconnectManager::sendPlayerDestruct(Int slot, ConnectionManager *conMgr) {
UnsignedShort currentID = 0;
if (DoesCommandRequireACommandID(NETCOMMANDTYPE_DESTROYPLAYER))
{
currentID = GenerateNextCommandID();
}
DEBUG_LOG(("Queueing DestroyPlayer %d for frame %d on frame %d as command %d\n",
slot, TheNetwork->getExecutionFrame()+1, TheGameLogic->getFrame(), currentID));
NetDestroyPlayerCommandMsg *netmsg = newInstance(NetDestroyPlayerCommandMsg);
netmsg->setExecutionFrame(TheNetwork->getExecutionFrame()+1);
netmsg->setPlayerID(conMgr->getLocalPlayerID());
netmsg->setID(currentID);
netmsg->setPlayerIndex(slot);
conMgr->sendLocalCommand(netmsg);
netmsg->detach();
}
// the 'slot' variable is supposed to be a translated slot position. (translated slot meaning
// that it is the player's position in the disconnect menu)
Bool DisconnectManager::isPlayerVotedOut(Int slot, ConnectionManager *conMgr) {
if (slot == -1) {
// we can't vote out ourselves.
return FALSE;
}
Int transSlot = untranslatedSlotPosition(slot, conMgr->getLocalPlayerID());
Int numVotes = countVotesForPlayer(transSlot);
if (numVotes >= (conMgr->getNumPlayers() - 1)) {
return TRUE;
}
return FALSE;
}
UnsignedInt DisconnectManager::getMaxDisconnectFrame() {
UnsignedInt retval = 0;
for (Int i = 0; i < MAX_SLOTS; ++i) {
if (m_disconnectFrames[i] > retval) {
retval = m_disconnectFrames[i];
}
}
return retval;
}
Bool DisconnectManager::isPlayerInGame(Int slot, ConnectionManager *conMgr) {
Int transSlot = untranslatedSlotPosition(slot, conMgr->getLocalPlayerID());
DEBUG_ASSERTCRASH((transSlot >= 0) && (transSlot < MAX_SLOTS), ("invalid slot number"));
if (((transSlot < 0) || (transSlot >= MAX_SLOTS)) || conMgr->isPlayerConnected(transSlot) == FALSE) {
return FALSE;
}
if (isPlayerVotedOut(slot, conMgr) == TRUE) {
return FALSE;
}
if (hasPlayerTimedOut(slot) == TRUE) {
return FALSE;
}
return TRUE;
}
void DisconnectManager::playerHasAdvancedAFrame(Int slot, UnsignedInt frame) {
// if they have advanced beyond the frame they had been previously disconnecting on.
if (frame >= m_disconnectFrames[slot]) {
m_disconnectFrames[slot] = frame; // just in case we get a disconnect frame command after this is called.
m_disconnectFramesReceived[slot] = FALSE;
}
}
Int DisconnectManager::countVotesForPlayer(Int slot) {
if ((slot < 0) || (slot >= MAX_SLOTS)) {
return 0;
}
Int retval = 0;
for (Int i = 0; i < MAX_SLOTS; ++i) {
// using TheGameLogic->getFrame() cause we might not have sent our disconnect frame yet.
if ((m_playerVotes[slot][i].vote == TRUE) && (m_playerVotes[slot][i].frame == TheGameLogic->getFrame())) {
++retval;
}
}
return retval;
}
void DisconnectManager::resetPlayersVotes(Int playerID, UnsignedInt frame, ConnectionManager *conMgr) {
DEBUG_LOG(("DisconnectManager::resetPlayersVotes - resetting player %d's votes on frame %d\n", playerID, frame));
// we need to reset this player's votes that happened before or on the given frame.
for(Int i = 0; i < MAX_SLOTS; ++i) {
if (m_playerVotes[i][playerID].frame <= frame) {
DEBUG_LOG(("DisconnectManager::resetPlayersVotes - resetting player %d's vote for player %d from frame %d on frame %d\n", playerID, i, m_playerVotes[i][playerID].frame, frame));
m_playerVotes[i][playerID].vote = FALSE;
}
}
Int numVotes = countVotesForPlayer(playerID);
DEBUG_LOG(("DisconnectManager::resetPlayersVotes - after adjusting votes, player %d has %d votes\n", playerID, numVotes));
Int transSlot = translatedSlotPosition(playerID, conMgr->getLocalPlayerID());
if (transSlot != -1) {
TheDisconnectMenu->updateVotes(transSlot, numVotes);
}
}

View File

@@ -0,0 +1,224 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: DownloadManager.cpp //////////////////////////////////////////////////////
// Generals download manager code
// Author: Matthew D. Campbell, July 2002
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameClient/GameText.h"
#include "GameNetwork/DownloadManager.h"
DownloadManager *TheDownloadManager;
DownloadManager::DownloadManager()
{
m_download = NEW CDownload(this);
m_wasError = m_sawEnd = false;
//Added By Sadullah Nader
//Initializations missing and needed
m_queuedDownloads.clear();
//
m_statusString = TheGameText->fetch("FTP:StatusIdle");
// ----- Initialize Winsock -----
m_winsockInit = true;
WORD verReq = MAKEWORD(2, 2);
WSADATA wsadata;
int err = WSAStartup(verReq, &wsadata);
if (err != 0)
{
m_winsockInit = false;
}
else
{
if ((LOBYTE(wsadata.wVersion) != 2) || (HIBYTE(wsadata.wVersion) !=2))
{
WSACleanup();
m_winsockInit = false;
}
}
}
DownloadManager::~DownloadManager()
{
delete m_download;
if (m_winsockInit)
{
WSACleanup();
m_winsockInit = false;
}
}
void DownloadManager::init( void )
{
}
void DownloadManager::reset( void )
{
}
HRESULT DownloadManager::update( void )
{
return m_download->PumpMessages();
}
HRESULT DownloadManager::downloadFile( AsciiString server, AsciiString username, AsciiString password, AsciiString file, AsciiString localfile, AsciiString regkey, Bool tryResume )
{
return m_download->DownloadFile( server.str(), username.str(), password.str(), file.str(), localfile.str(), regkey.str(), tryResume );
}
void DownloadManager::queueFileForDownload( AsciiString server, AsciiString username, AsciiString password, AsciiString file, AsciiString localfile, AsciiString regkey, Bool tryResume )
{
QueuedDownload q;
q.file = file;
q.localFile = localfile;
q.password = password;
q.regKey = regkey;
q.server = server;
q.tryResume = tryResume;
q.userName = username;
m_queuedDownloads.push_back(q);
}
HRESULT DownloadManager::downloadNextQueuedFile( void )
{
QueuedDownload q;
std::list<QueuedDownload>::iterator it = m_queuedDownloads.begin();
if (it != m_queuedDownloads.end())
{
q = *it;
m_queuedDownloads.pop_front();
m_wasError = m_sawEnd = false;
return downloadFile( q.server, q.userName, q.password, q.file, q.localFile, q.regKey, q.tryResume );
}
else
{
DEBUG_CRASH(("Starting non-existent download!"));
return S_OK;
}
}
AsciiString DownloadManager::getLastLocalFile( void )
{
char buf[256] = "";
m_download->GetLastLocalFile(buf, 256);
return buf;
}
HRESULT DownloadManager::OnError( Int error )
{
m_wasError = true;
AsciiString s = "FTP:UnknownError";
switch (error)
{
case DOWNLOADEVENT_NOSUCHSERVER:
s = "FTP:NoSuchServer";
break;
case DOWNLOADEVENT_COULDNOTCONNECT:
s = "FTP:CouldNotConnect";
break;
case DOWNLOADEVENT_LOGINFAILED:
s = "FTP:LoginFailed";
break;
case DOWNLOADEVENT_NOSUCHFILE:
s = "FTP:NoSuchFile";
break;
case DOWNLOADEVENT_LOCALFILEOPENFAILED:
s = "FTP:LocalFileOpenFailed";
break;
case DOWNLOADEVENT_TCPERROR:
s = "FTP:TCPError";
break;
case DOWNLOADEVENT_DISCONNECTERROR:
s = "FTP:DisconnectError";
break;
}
m_errorString = TheGameText->fetch(s);
DEBUG_LOG(("DownloadManager::OnError(): %s(%d)\n", s.str(), error));
return S_OK;
}
HRESULT DownloadManager::OnEnd()
{
m_sawEnd = true;
DEBUG_LOG(("DownloadManager::OnEnd()\n"));
return S_OK;
}
HRESULT DownloadManager::OnQueryResume()
{
DEBUG_LOG(("DownloadManager::OnQueryResume()\n"));
//return DOWNLOADEVENT_DONOTRESUME;
return DOWNLOADEVENT_RESUME;
}
HRESULT DownloadManager::OnProgressUpdate( Int bytesread, Int totalsize, Int timetaken, Int timeleft )
{
DEBUG_LOG(("DownloadManager::OnProgressUpdate(): %d/%d %d/%d\n", bytesread, totalsize, timetaken, timeleft));
return S_OK;
}
HRESULT DownloadManager::OnStatusUpdate( Int status )
{
AsciiString s = "FTP:StatusNone";
switch (status)
{
case DOWNLOADSTATUS_CONNECTING:
s = "FTP:StatusConnecting";
break;
case DOWNLOADSTATUS_LOGGINGIN:
s = "FTP:StatusLoggingIn";
break;
case DOWNLOADSTATUS_FINDINGFILE:
s = "FTP:StatusFindingFile";
break;
case DOWNLOADSTATUS_QUERYINGRESUME:
s = "FTP:StatusQueryingResume";
break;
case DOWNLOADSTATUS_DOWNLOADING:
s = "FTP:StatusDownloading";
break;
case DOWNLOADSTATUS_DISCONNECTING:
s = "FTP:StatusDisconnecting";
break;
case DOWNLOADSTATUS_FINISHING:
s = "FTP:StatusFinishing";
break;
case DOWNLOADSTATUS_DONE:
s = "FTP:StatusDone";
break;
}
m_statusString = TheGameText->fetch(s);
DEBUG_LOG(("DownloadManager::OnStatusUpdate(): %s(%d)\n", s.str(), status));
return S_OK;
}

View File

@@ -0,0 +1,283 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
// FILE: FileTransfer.cpp
// Author: Matthew D. Campbell, December 2002
// Description: File Transfer wrapper using TheNetwork
///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameClient/LoadScreen.h"
#include "GameClient/Shell.h"
#include "GameNetwork/FileTransfer.h"
#include "GameNetwork/NetworkUtil.h"
//-------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------
static Bool doFileTransfer( AsciiString filename, MapTransferLoadScreen *ls, Int mask )
{
Bool fileTransferDone = FALSE;
Int fileTransferPercent = 0;
Int i;
if (mask)
{
ls->setCurrentFilename(filename);
UnsignedInt startTime = timeGetTime();
const Int timeoutPeriod = 2*60*1000;
ls->processTimeout(timeoutPeriod/1000);
ls->update(0);
fileTransferDone = FALSE;
fileTransferPercent = 0;
UnsignedShort fileCommandID = 0;
Bool sentFile = FALSE;
if (TheGameInfo->amIHost())
{
Sleep(500);
fileCommandID = TheNetwork->sendFileAnnounce(filename, mask);
}
else
{
sentFile = TRUE;
}
DEBUG_LOG(("Starting file transfer loop\n"));
while (!fileTransferDone)
{
if (!sentFile && TheNetwork->areAllQueuesEmpty())
{
TheNetwork->sendFile(filename, mask, fileCommandID);
sentFile = TRUE;
}
// get the progress for each player, and take the min for our overall progress
fileTransferDone = TRUE;
fileTransferPercent = 100;
for (i=1; i<MAX_SLOTS; ++i)
{
if (TheGameInfo->getConstSlot(i)->isHuman() && !TheGameInfo->getConstSlot(i)->hasMap())
{
Int slotTransferPercent = TheNetwork->getFileTransferProgress(i, filename);
fileTransferPercent = min(fileTransferPercent, slotTransferPercent);
if (slotTransferPercent == 0)
ls->processProgress(i, slotTransferPercent, "MapTransfer:Preparing");
else if (slotTransferPercent < 100)
ls->processProgress(i, slotTransferPercent, "MapTransfer:Recieving");
else
ls->processProgress(i, slotTransferPercent, "MapTransfer:Done");
}
}
if (fileTransferPercent < 100)
{
fileTransferDone = FALSE;
if (fileTransferPercent == 0)
ls->processProgress(0, fileTransferPercent, "MapTransfer:Preparing");
else
ls->processProgress(0, fileTransferPercent, "MapTransfer:Sending");
}
else
{
DEBUG_LOG(("File transfer is 100%%!\n"));
ls->processProgress(0, fileTransferPercent, "MapTransfer:Done");
}
Int now = timeGetTime();
if (now > startTime + timeoutPeriod) // bail if we don't finish in a reasonable amount of time
{
DEBUG_LOG(("Timing out file transfer\n"));
break;
}
else
{
ls->processTimeout((startTime + timeoutPeriod - now)/1000);
}
ls->update(fileTransferPercent);
}
if (!fileTransferDone)
{
return FALSE;
}
}
return TRUE;
}
//-------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------
AsciiString GetBasePathFromPath( AsciiString path )
{
const char *s = path.reverseFind('\\');
if (s)
{
Int len = s - path.str();
AsciiString base;
char *buf = base.getBufferForRead(len + 1);
memcpy(buf, path.str(), len);
buf[len] = 0;
return buf;
}
return AsciiString::TheEmptyString;
}
AsciiString GetFileFromPath( AsciiString path )
{
const char *s = path.reverseFind('\\');
if (s)
return s+1;
return path;
}
AsciiString GetExtensionFromFile( AsciiString fname )
{
const char *s = fname.reverseFind('.');
if (s)
return s+1;
return fname;
}
AsciiString GetBaseFileFromFile( AsciiString fname )
{
const char *s = fname.reverseFind('.');
if (s)
{
Int len = s - fname.str();
AsciiString base;
char *buf = base.getBufferForRead(len + 1);
memcpy(buf, fname.str(), len);
buf[len] = 0;
return buf;
}
return AsciiString::TheEmptyString;
}
AsciiString GetPreviewFromMap( AsciiString path )
{
AsciiString fname = GetBaseFileFromFile(GetFileFromPath(path));
AsciiString base = GetBasePathFromPath(path);
AsciiString out;
out.format("%s\\%s.tga", base.str(), fname.str());
return out;
}
AsciiString GetINIFromMap( AsciiString path )
{
AsciiString base = GetBasePathFromPath(path);
AsciiString out;
out.format("%s\\map.ini", base.str());
return out;
}
AsciiString GetStrFileFromMap( AsciiString path )
{
AsciiString base = GetBasePathFromPath(path);
AsciiString out;
out.format("%s\\map.str", base.str());
return out;
}
AsciiString GetSoloINIFromMap( AsciiString path )
{
AsciiString base = GetBasePathFromPath(path);
AsciiString out;
out.format("%s\\solo.ini", base.str());
return out;
}
AsciiString GetAssetUsageFromMap( AsciiString path )
{
AsciiString base = GetBasePathFromPath(path);
AsciiString out;
out.format("%s\\assetusage.txt", base.str());
return out;
}
AsciiString GetReadmeFromMap( AsciiString path )
{
AsciiString base = GetBasePathFromPath(path);
AsciiString out;
out.format("%s\\readme.txt", base.str());
return out;
}
//-------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------
Bool DoAnyMapTransfers(GameInfo *game)
{
TheGameInfo = game;
Int mask = 0;
Int i=0;
for (i=1; i<MAX_SLOTS; ++i)
{
if (TheGameInfo->getConstSlot(i)->isHuman() && !TheGameInfo->getConstSlot(i)->hasMap())
{
DEBUG_LOG(("Adding player %d to transfer mask\n", i));
mask |= (1<<i);
}
}
if (!mask)
return TRUE;
TheShell->hideShell();
MapTransferLoadScreen *ls = NEW MapTransferLoadScreen;
ls->init(TheGameInfo);
Bool ok = TRUE;
if (TheGameInfo->getMapContentsMask() & 2)
ok = doFileTransfer(GetPreviewFromMap(game->getMap()), ls, mask);
if (ok && TheGameInfo->getMapContentsMask() & 4)
ok = doFileTransfer(GetINIFromMap(game->getMap()), ls, mask);
if (ok && TheGameInfo->getMapContentsMask() & 8)
ok = doFileTransfer(GetStrFileFromMap(game->getMap()), ls, mask);
if (ok && TheGameInfo->getMapContentsMask() & 16)
ok = doFileTransfer(GetSoloINIFromMap(game->getMap()), ls, mask);
if (ok && TheGameInfo->getMapContentsMask() & 32)
ok = doFileTransfer(GetAssetUsageFromMap(game->getMap()), ls, mask);
if (ok && TheGameInfo->getMapContentsMask() & 64)
ok = doFileTransfer(GetReadmeFromMap(game->getMap()), ls, mask);
if (ok)
ok = doFileTransfer(game->getMap(), ls, mask);
delete ls;
ls = NULL;
if (!ok)
TheShell->showShell();
return ok;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,205 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameNetwork/FrameData.h"
#include "GameNetwork/NetworkUtil.h"
/**
* Constructor
*/
FrameData::FrameData()
{
m_frame = 0;
m_commandList = NULL;
m_commandCount = 0;
m_frameCommandCount = -1;
//Added By Sadullah Nader
//Initializations missing and needed
m_lastFailedCC = 0;
m_lastFailedFrameCC = 0;
//
}
/**
* Destructor
*/
FrameData::~FrameData()
{
if (m_commandList != NULL) {
m_commandList->deleteInstance();
m_commandList = NULL;
}
}
/**
* Initialize this thing.
*/
void FrameData::init()
{
m_frame = 0;
if (m_commandList == NULL) {
m_commandList = newInstance(NetCommandList);
m_commandList->init();
}
m_commandList->reset();
m_frameCommandCount = -1;
//DEBUG_LOG(("FrameData::init\n"));
m_commandCount = 0;
m_lastFailedCC = -2;
m_lastFailedFrameCC = -2;
}
/**
* Reset this thing.
*/
void FrameData::reset() {
init();
}
/**
* update the thing, doesn't do anything at the moment.
*/
void FrameData::update() {
}
/**
* return the frame number this frame data is associated with.
*/
UnsignedInt FrameData::getFrame() {
return m_frame;
}
/**
* Assign the frame number this frame data is associated with.
*/
void FrameData::setFrame(UnsignedInt frame) {
m_frame = frame;
}
/**
* Returns true if all the frame command count is equal to the number of commands that have been received.
*/
FrameDataReturnType FrameData::allCommandsReady(Bool debugSpewage) {
if (m_frameCommandCount == m_commandCount) {
m_lastFailedFrameCC = -2;
m_lastFailedCC = -2;
return FRAMEDATA_READY;
}
if (debugSpewage) {
if ((m_lastFailedFrameCC != m_frameCommandCount) || (m_lastFailedCC != m_commandCount)) {
DEBUG_LOG(("FrameData::allCommandsReady - failed, frame command count = %d, command count = %d\n", m_frameCommandCount, m_commandCount));
m_lastFailedFrameCC = m_frameCommandCount;
m_lastFailedCC = m_commandCount;
}
}
if (m_commandCount > m_frameCommandCount) {
DEBUG_LOG(("FrameData::allCommandsReady - There are more commands than there should be (%d, should be %d). Commands in command list are...\n", m_commandCount, m_frameCommandCount));
NetCommandRef *ref = m_commandList->getFirstMessage();
while (ref != NULL) {
DEBUG_LOG(("%s, frame = %d, id = %d\n", GetAsciiNetCommandType(ref->getCommand()->getNetCommandType()).str(), ref->getCommand()->getExecutionFrame(), ref->getCommand()->getID()));
ref = ref->getNext();
}
DEBUG_LOG(("FrameData::allCommandsReady - End of command list.\n"));
DEBUG_LOG(("FrameData::allCommandsReady - about to clear the command list\n"));
reset();
DEBUG_LOG(("FrameData::allCommandsReady - command list cleared. command list length = %d, command count = %d, frame command count = %d\n", m_commandList->length(), m_commandCount, m_frameCommandCount));
return FRAMEDATA_RESEND;
}
return FRAMEDATA_NOTREADY;
}
/**
* Set the command count for this frame
*/
void FrameData::setFrameCommandCount(UnsignedInt frameCommandCount) {
//DEBUG_LOG(("setFrameCommandCount to %d for frame %d\n", frameCommandCount, m_frame));
m_frameCommandCount = frameCommandCount;
}
/**
* Get the command count for this frame.
*/
UnsignedInt FrameData::getFrameCommandCount() {
return m_frameCommandCount;
}
/**
* return the number of commands received so far.
*/
UnsignedInt FrameData::getCommandCount() {
return m_commandCount;
}
/**
* Add a command to this frame
*/
void FrameData::addCommand(NetCommandMsg *msg) {
// need to add the message in order of command ID
if (m_commandList == NULL) {
init();
}
// We don't need to worry about setting the relay since its not getting sent anywhere.
if (m_commandList->findMessage(msg) != NULL) {
// We don't want to add the same command twice.
return;
}
m_commandList->addMessage(msg);
++m_commandCount;
//DEBUG_LOG(("added command %d, type = %d(%s), command count = %d, frame command count = %d\n", msg->getID(), msg->getNetCommandType(), GetAsciiNetCommandType(msg->getNetCommandType()).str(), m_commandCount, m_frameCommandCount));
}
/**
* Return the list of commands for this frame
*/
NetCommandList * FrameData::getCommandList() {
return m_commandList;
}
/**
* Set both the command count and the frame command count to 0.
*/
void FrameData::zeroFrame() {
m_commandCount = 0;
m_frameCommandCount = 0;
}
/**
* destroy all the commands in this frame.
*/
void FrameData::destroyGameMessages() {
if (m_commandList == NULL) {
return;
}
m_commandList->reset();
m_commandCount = 0;
}

View File

@@ -0,0 +1,207 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameNetwork/FrameDataManager.h"
#include "GameNetwork/NetworkUtil.h"
/**
* Constructor. isLocal tells it whether its the frame data manager for the local player or not.
*/
FrameDataManager::FrameDataManager(Bool isLocal) {
m_isLocal = isLocal;
m_frameData = NEW FrameData[FRAME_DATA_LENGTH];
m_isQuitting = FALSE;
m_quitFrame = 0;
}
/**
* destructor.
*/
FrameDataManager::~FrameDataManager() {
for (Int i = 0; i < FRAME_DATA_LENGTH; ++i) {
m_frameData[i].reset();
}
if (m_frameData)
{
delete[] m_frameData;
m_frameData = NULL;
}
}
/**
* Initialize all of the frame datas associated with this manager.
*/
void FrameDataManager::init() {
for (Int i = 0; i < FRAME_DATA_LENGTH; ++i) {
m_frameData[i].init();
if (m_isLocal) {
// If this is the local connection, adjust the frame command count.
m_frameData[i].setFrameCommandCount(m_frameData[i].getCommandCount());
}
}
m_isQuitting = FALSE;
m_quitFrame = 0;
}
/**
* Reset the state of all the frames.
*/
void FrameDataManager::reset() {
init();
}
/**
* update function. Does nothing at this time.
*/
void FrameDataManager::update() {
}
/**
* Add a network command to the appropriate frame.
*/
void FrameDataManager::addNetCommandMsg(NetCommandMsg *msg) {
UnsignedInt frame = msg->getExecutionFrame();
UnsignedInt frameindex = frame % FRAME_DATA_LENGTH;
DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("FrameDataManager::addNetCommandMsg - about to add a command of type %s for frame %d, frame index %d\n", GetAsciiNetCommandType(msg->getNetCommandType()).str(), frame, frameindex));
m_frameData[frameindex].addCommand(msg);
if (m_isLocal) {
// If this is the local connection, adjust the frame command count.
m_frameData[frameindex].setFrameCommandCount(m_frameData[frameindex].getCommandCount());
}
}
/**
* Returns true if all the commands for the given frame are ready.
*/
FrameDataReturnType FrameDataManager::allCommandsReady(UnsignedInt frame, Bool debugSpewage) {
UnsignedInt frameindex = frame % FRAME_DATA_LENGTH;
//DEBUG_ASSERTCRASH(m_frameData[frameindex].getFrame() == frame || frame == 256, ("Looking at old commands!"));
return m_frameData[frameindex].allCommandsReady(debugSpewage);
}
/**
* Returns the command list for the given frame.
*/
NetCommandList * FrameDataManager::getFrameCommandList(UnsignedInt frame) {
UnsignedInt frameindex = frame % FRAME_DATA_LENGTH;
return m_frameData[frameindex].getCommandList();
}
/**
* Reset the contents of the given frame.
*/
void FrameDataManager::resetFrame(UnsignedInt frame, Bool isAdvancing) {
UnsignedInt frameindex = frame % FRAME_DATA_LENGTH;
m_frameData[frameindex].reset();
if (isAdvancing) {
m_frameData[frameindex].setFrame(frame + MAX_FRAMES_AHEAD);
}
if (m_isLocal) {
m_frameData[frameindex].setFrameCommandCount(m_frameData[frameindex].getCommandCount());
}
DEBUG_ASSERTCRASH(m_frameData[frameindex].getCommandCount() == 0, ("we just reset the frame data and the command count is not zero, huh?"));
}
/**
* Returns the command count for the given frame.
*/
UnsignedInt FrameDataManager::getCommandCount(UnsignedInt frame) {
UnsignedInt frameindex = frame % FRAME_DATA_LENGTH;
return m_frameData[frameindex].getCommandCount();
}
/**
* Set the frame command count for the given frame.
*/
void FrameDataManager::setFrameCommandCount(UnsignedInt frame, UnsignedInt commandCount) {
UnsignedInt frameindex = frame % FRAME_DATA_LENGTH;
m_frameData[frameindex].setFrameCommandCount(commandCount);
}
/**
*
*/
UnsignedInt FrameDataManager::getFrameCommandCount(UnsignedInt frame) {
UnsignedInt frameindex = frame % FRAME_DATA_LENGTH;
return m_frameData[frameindex].getFrameCommandCount();
}
/**
* Set both the command count and the frame command count to 0 for the given frames.
*/
void FrameDataManager::zeroFrames(UnsignedInt startingFrame, UnsignedInt numFrames) {
UnsignedInt frameIndex = startingFrame % FRAME_DATA_LENGTH;
for (UnsignedInt i = 0; i < numFrames; ++i) {
//DEBUG_LOG(("Calling zeroFrame for frame index %d\n", frameIndex));
m_frameData[frameIndex].zeroFrame();
++frameIndex;
frameIndex = frameIndex % FRAME_DATA_LENGTH;
}
}
/**
* Destroy all the commands held by this object.
*/
void FrameDataManager::destroyGameMessages() {
for (Int i = 0; i < FRAME_DATA_LENGTH; ++i) {
m_frameData[i].destroyGameMessages();
}
}
/**
* Sets the quit frame, also sets the isQuitting flag.
*/
void FrameDataManager::setQuitFrame(UnsignedInt frame) {
m_isQuitting = TRUE;
m_quitFrame = frame;
}
/**
* returns the quit frame.
*/
UnsignedInt FrameDataManager::getQuitFrame() {
return m_quitFrame;
}
/**
* returns true if this frame data manager is quitting.
*/
Bool FrameDataManager::getIsQuitting() {
return m_isQuitting;
}

View File

@@ -0,0 +1,147 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
/** FrameMetrics.cpp */
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameNetwork/FrameMetrics.h"
#include "GameClient/Display.h"
#include "GameNetwork/NetworkUtil.h"
FrameMetrics::FrameMetrics()
{
//Added By Sadullah Nader
//Initializations missing and needed
m_averageFps = 0.0f;
m_averageLatency = 0.0f;
m_cushionIndex = 0;
m_fpsListIndex = 0;
m_lastFpsTimeThing = 0;
m_minimumCushion = 0;
m_pendingLatencies = NEW time_t[MAX_FRAMES_AHEAD];
for(Int i = 0; i < MAX_FRAMES_AHEAD; i++)
m_pendingLatencies[i] = 0;
//
m_fpsList = NEW Real[TheGlobalData->m_networkFPSHistoryLength];
m_latencyList = NEW Real[TheGlobalData->m_networkLatencyHistoryLength];
}
FrameMetrics::~FrameMetrics() {
if (m_fpsList != NULL) {
delete m_fpsList;
m_fpsList = NULL;
}
if (m_latencyList != NULL) {
delete m_latencyList;
m_latencyList = NULL;
}
if (m_pendingLatencies)
{
delete[] m_pendingLatencies;
m_pendingLatencies = NULL;
}
}
void FrameMetrics::init() {
m_averageFps = 30;
m_averageLatency = (Real)0.2;
m_minimumCushion = -1;
for (Int i = 0; i < TheGlobalData->m_networkFPSHistoryLength; ++i) {
m_fpsList[i] = 30.0;
}
m_fpsListIndex = 0;
for (i = 0; i < TheGlobalData->m_networkLatencyHistoryLength; ++i) {
m_latencyList[i] = (Real)0.2;
}
m_cushionIndex = 0;
}
void FrameMetrics::reset() {
init();
}
void FrameMetrics::doPerFrameMetrics(UnsignedInt frame) {
// Do the measurement of the fps.
time_t curTime = timeGetTime();
if ((curTime - m_lastFpsTimeThing) >= 1000) {
// if ((m_fpsListIndex % 16) == 0) {
// DEBUG_LOG(("FrameMetrics::doPerFrameMetrics - adding %f to fps history. average before: %f ", m_fpsList[m_fpsListIndex], m_averageFps));
// }
m_averageFps -= ((m_fpsList[m_fpsListIndex])) / TheGlobalData->m_networkFPSHistoryLength; // subtract out the old value from the average.
m_fpsList[m_fpsListIndex] = TheDisplay->getAverageFPS();
// m_fpsList[m_fpsListIndex] = TheGameClient->getFrame() - m_fpsStartingFrame;
m_averageFps += ((Real)(m_fpsList[m_fpsListIndex])) / TheGlobalData->m_networkFPSHistoryLength; // add the new value to the average.
// DEBUG_LOG(("average after: %f\n", m_averageFps));
++m_fpsListIndex;
m_fpsListIndex %= TheGlobalData->m_networkFPSHistoryLength;
m_lastFpsTimeThing = curTime;
}
Int pendingLatenciesIndex = frame % MAX_FRAMES_AHEAD;
m_pendingLatencies[pendingLatenciesIndex] = curTime;
}
void FrameMetrics::processLatencyResponse(UnsignedInt frame) {
time_t curTime = timeGetTime();
Int pendingIndex = frame % MAX_FRAMES_AHEAD;
time_t timeDiff = curTime - m_pendingLatencies[pendingIndex];
Int latencyListIndex = frame % TheGlobalData->m_networkLatencyHistoryLength;
m_averageLatency -= m_latencyList[latencyListIndex] / TheGlobalData->m_networkLatencyHistoryLength;
m_latencyList[latencyListIndex] = (Real)timeDiff / (Real)1000; // convert to seconds from milliseconds.
m_averageLatency += m_latencyList[latencyListIndex] / TheGlobalData->m_networkLatencyHistoryLength;
if (frame % 16 == 0) {
// DEBUG_LOG(("ConnectionManager::processFrameInfoAck - average latency = %f\n", m_averageLatency));
}
}
void FrameMetrics::addCushion(Int cushion) {
++m_cushionIndex;
m_cushionIndex %= TheGlobalData->m_networkCushionHistoryLength;
if (m_cushionIndex == 0) {
m_minimumCushion = -1;
}
if ((cushion < m_minimumCushion) || (m_minimumCushion == -1)) {
m_minimumCushion = cushion;
}
}
Int FrameMetrics::getAverageFPS() {
return (Int)m_averageFps;
}
Real FrameMetrics::getAverageLatency() {
return m_averageLatency;
}
Int FrameMetrics::getMinimumCushion() {
return m_minimumCushion;
}

View File

@@ -0,0 +1,466 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: GUIUtil.cpp //////////////////////////////////////////////////////
// Author: Matthew D. Campbell, Sept 2002
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameNetwork/GUIUtil.h"
#include "GameNetwork/NetworkDefs.h"
#include "GameClient/GameWindowManager.h"
#include "GameClient/MapUtil.h"
#include "Common/NameKeyGenerator.h"
#include "Common/MultiplayerSettings.h"
#include "GameClient/GadgetListBox.h"
#include "GameClient/GadgetComboBox.h"
#include "GameClient/GadgetTextEntry.h"
#include "GameClient/GadgetStaticText.h"
#include "GameClient/GadgetPushButton.h"
#include "GameClient/GameText.h"
#include "GameNetwork/GameInfo.h"
#include "Common/PlayerTemplate.h"
#include "GameNetwork/LANAPICallbacks.h" // for acceptTrueColor, etc
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// -----------------------------------------------------------------------------
static Bool winInitialized = FALSE;
void EnableSlotListUpdates( Bool val )
{
winInitialized = val;
}
Bool AreSlotListUpdatesEnabled( void )
{
return winInitialized;
}
// -----------------------------------------------------------------------------
void EnableAcceptControls(Bool Enabled, GameInfo *myGame, GameWindow *comboPlayer[],
GameWindow *comboColor[], GameWindow *comboPlayerTemplate[],
GameWindow *comboTeam[], GameWindow *buttonAccept[], GameWindow *buttonStart,
GameWindow *buttonMapStartPosition[], Int slotNum)
{
if(slotNum == -1 || slotNum >= MAX_SLOTS )
slotNum = myGame->getLocalSlotNum();
Bool isObserver = myGame->getConstSlot(slotNum)->getPlayerTemplate() == PLAYERTEMPLATE_OBSERVER;
if( !myGame->amIHost() && (buttonStart != NULL) )
buttonStart->winEnable(Enabled);
if(comboColor[slotNum])
{
if (isObserver)
{
GadgetComboBoxHideList(comboColor[slotNum]);
}
comboColor[slotNum]->winEnable(Enabled && !isObserver);
}
if(comboPlayerTemplate[slotNum])
comboPlayerTemplate[slotNum]->winEnable(Enabled);
if(comboTeam[slotNum])
{
if (isObserver)
{
GadgetComboBoxHideList(comboTeam[slotNum]);
}
comboTeam[slotNum]->winEnable(Enabled && !isObserver);
}
Bool canChooseStartSpot = FALSE;
if (!isObserver)
canChooseStartSpot = TRUE;
for (Int i=0; i<MAX_SLOTS && !canChooseStartSpot && myGame->amIHost(); ++i)
{
if (myGame->getConstSlot(i) && myGame->getConstSlot(i)->isAI())
canChooseStartSpot = TRUE;
}
if (slotNum == myGame->getLocalSlotNum())
{
if (myGame->getConstSlot(myGame->getLocalSlotNum())->hasMap())
{
for (Int i=0; i<MAX_SLOTS; ++i)
{
if (buttonMapStartPosition[i])
{
buttonMapStartPosition[i]->winEnable(Enabled && canChooseStartSpot);
}
}
}
else
{
for (Int i=0; i<MAX_SLOTS; ++i)
{
if (buttonMapStartPosition[i])
buttonMapStartPosition[i]->winEnable(FALSE);
}
}
}
}
// -----------------------------------------------------------------------------
void ShowUnderlyingGUIElements( Bool show, const char *layoutFilename, const char *parentName,
const char **gadgetsToHide, const char **perPlayerGadgetsToHide )
{
AsciiString parentNameStr;
parentNameStr.format("%s:%s", layoutFilename, parentName);
NameKeyType parentID = NAMEKEY(parentNameStr);
GameWindow *parent = TheWindowManager->winGetWindowFromId( NULL, parentID );
if (!parent)
{
DEBUG_CRASH(("Window %s not found\n", parentNameStr.str()));
return;
}
// hide some GUI elements of the screen underneath
GameWindow *win;
Int player;
const char **text;
text = gadgetsToHide;
while (*text)
{
AsciiString gadgetName;
gadgetName.format("%s:%s", layoutFilename, *text);
win = TheWindowManager->winGetWindowFromId( parent, NAMEKEY(gadgetName) );
//DEBUG_ASSERTCRASH(win, ("Cannot find %s to show/hide it", gadgetName.str()));
if (win)
{
win->winHide( !show );
}
++text;
}
text = perPlayerGadgetsToHide;
while (*text)
{
for (player = 0; player < MAX_SLOTS; ++player)
{
AsciiString gadgetName;
gadgetName.format("%s:%s%d", layoutFilename, *text, player);
win = TheWindowManager->winGetWindowFromId( parent, NAMEKEY(gadgetName) );
//DEBUG_ASSERTCRASH(win, ("Cannot find %s to show/hide it", gadgetName.str()));
if (win)
{
win->winHide( !show );
}
}
++text;
}
}
// -----------------------------------------------------------------------------
void PopulateColorComboBox(Int comboBox, GameWindow *comboArray[], GameInfo *myGame, Bool isObserver)
{
Int numColors = TheMultiplayerSettings->getNumColors();
UnicodeString colorName;
std::vector<bool> availableColors;
for (Int i = 0; i < numColors; i++)
availableColors.push_back(true);
for (i = 0; i < MAX_SLOTS; i++)
{
GameSlot *slot = myGame->getSlot(i);
if( slot && (i != comboBox) && (slot->getColor() >= 0 )&& (slot->getColor() < numColors))
{
DEBUG_ASSERTCRASH(slot->getColor() >= 0,("We've tried to access array %d and that ain't good",slot->getColor()));
availableColors[slot->getColor()] = false;
}
}
Bool wasObserver = (GadgetComboBoxGetLength(comboArray[comboBox]) == 1);
GadgetComboBoxReset(comboArray[comboBox]);
MultiplayerColorDefinition *def = TheMultiplayerSettings->getColor(PLAYERTEMPLATE_RANDOM);
Int newIndex = GadgetComboBoxAddEntry(comboArray[comboBox],
(isObserver)?TheGameText->fetch("GUI:None"):TheGameText->fetch("GUI:???"), def->getColor());
GadgetComboBoxSetItemData(comboArray[comboBox], newIndex, (void *)-1);
if (isObserver)
{
GadgetComboBoxSetSelectedPos(comboArray[comboBox], 0);
return;
}
for (Int c=0; c<numColors; ++c)
{
def = TheMultiplayerSettings->getColor(c);
if (!def || availableColors[c] == false)
continue;
colorName = TheGameText->fetch(def->getTooltipName().str());
newIndex = GadgetComboBoxAddEntry(comboArray[comboBox], colorName, def->getColor());
GadgetComboBoxSetItemData(comboArray[comboBox], newIndex, (void *)c);
}
if (wasObserver)
GadgetComboBoxSetSelectedPos(comboArray[comboBox], 0);
}
// -----------------------------------------------------------------------------
void PopulatePlayerTemplateComboBox(Int comboBox, GameWindow *comboArray[], GameInfo *myGame, Bool allowObservers)
{
Int numPlayerTemplates = ThePlayerTemplateStore->getPlayerTemplateCount();
UnicodeString playerTemplateName;
GadgetComboBoxReset(comboArray[comboBox]);
MultiplayerColorDefinition *def = TheMultiplayerSettings->getColor(PLAYERTEMPLATE_RANDOM);
Int newIndex = GadgetComboBoxAddEntry(comboArray[comboBox], TheGameText->fetch("GUI:Random"), def->getColor());
GadgetComboBoxSetItemData(comboArray[comboBox], newIndex, (void *)PLAYERTEMPLATE_RANDOM);
std::set<AsciiString> seenSides;
for (Int c=0; c<numPlayerTemplates; ++c)
{
const PlayerTemplate *fac = ThePlayerTemplateStore->getNthPlayerTemplate(c);
if (!fac)
continue;
if (fac->getStartingBuilding().isEmpty())
continue;
AsciiString side;
side.format("SIDE:%s", fac->getSide().str());
if (seenSides.find(side) != seenSides.end())
continue;
seenSides.insert(side);
newIndex = GadgetComboBoxAddEntry(comboArray[comboBox], TheGameText->fetch(side), def->getColor());
GadgetComboBoxSetItemData(comboArray[comboBox], newIndex, (void *)c);
}
seenSides.clear();
// disabling observers for Multiplayer test
#ifndef _PLAYTEST
if (allowObservers)
{
def = TheMultiplayerSettings->getColor(PLAYERTEMPLATE_OBSERVER);
newIndex = GadgetComboBoxAddEntry(comboArray[comboBox], TheGameText->fetch("GUI:Observer"), def->getColor());
GadgetComboBoxSetItemData(comboArray[comboBox], newIndex, (void *)PLAYERTEMPLATE_OBSERVER);
}
#endif
GadgetComboBoxSetSelectedPos(comboArray[comboBox], 0);
}
// -----------------------------------------------------------------------------
void PopulateTeamComboBox(Int comboBox, GameWindow *comboArray[], GameInfo *myGame, Bool isObserver)
{
Int numTeams = MAX_SLOTS/2;
UnicodeString teamName;
GadgetComboBoxReset(comboArray[comboBox]);
MultiplayerColorDefinition *def = TheMultiplayerSettings->getColor(PLAYERTEMPLATE_RANDOM);
Int newIndex = GadgetComboBoxAddEntry(comboArray[comboBox], TheGameText->fetch("Team:0"), def->getColor());
GadgetComboBoxSetItemData(comboArray[comboBox], newIndex, (void *)-1);
if (isObserver)
{
GadgetComboBoxSetSelectedPos(comboArray[comboBox], 0);
return;
}
for (Int c=0; c<numTeams; ++c)
{
AsciiString teamStr;
teamStr.format("Team:%d", c + 1);
teamName = TheGameText->fetch(teamStr.str());
newIndex = GadgetComboBoxAddEntry(comboArray[comboBox], teamName, def->getColor());
GadgetComboBoxSetItemData(comboArray[comboBox], newIndex, (void *)c);
}
GadgetComboBoxSetSelectedPos(comboArray[comboBox], 0);
}
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------
// The slot list displaying function
//-------------------------------------------------------------------------------------------------
void UpdateSlotList( GameInfo *myGame, GameWindow *comboPlayer[],
GameWindow *comboColor[], GameWindow *comboPlayerTemplate[],
GameWindow *comboTeam[], GameWindow *buttonAccept[],
GameWindow *buttonStart, GameWindow *buttonMapStartPosition[] )
{
if(!AreSlotListUpdatesEnabled())
return;
//LANGameInfo *myGame = TheLAN->GetMyGame();
const MapMetaData *mapData = TheMapCache->findMap( myGame->getMap() );
Bool willTransfer = TRUE;
if (mapData)
{
willTransfer = !mapData->m_isOfficial;
}
else
{
willTransfer = WouldMapTransfer(myGame->getMap());
}
if (myGame)
{
for( int i =0; i < MAX_SLOTS; i++ )
{
GameSlot * slot = myGame->getSlot(i);
// if i'm host, enable the controls for AI
if(myGame->amIHost() && slot && slot->isAI())
{
EnableAcceptControls(TRUE, myGame, comboPlayer, comboColor, comboPlayerTemplate,
comboTeam, buttonAccept, buttonStart, buttonMapStartPosition, i);
}
else if (slot && myGame->getLocalSlotNum() == i)
{
if(slot->isAccepted() && !myGame->amIHost())
{
EnableAcceptControls(FALSE, myGame, comboPlayer, comboColor, comboPlayerTemplate,
comboTeam, buttonAccept, buttonStart, buttonMapStartPosition);
}
else
{
if (slot->hasMap()) {
EnableAcceptControls(TRUE, myGame, comboPlayer, comboColor, comboPlayerTemplate,
comboTeam, buttonAccept, buttonStart, buttonMapStartPosition);
}
else
{
EnableAcceptControls(willTransfer, myGame, comboPlayer, comboColor, comboPlayerTemplate,
comboTeam, buttonAccept, buttonStart, buttonMapStartPosition);
}
}
}
else if(myGame->amIHost())
{
EnableAcceptControls(FALSE, myGame, comboPlayer, comboColor, comboPlayerTemplate,
comboTeam, buttonAccept, buttonStart, buttonMapStartPosition, i);
}
if(slot && slot->isHuman())
{
UnicodeString newName = slot->getName();
UnicodeString oldName = GadgetComboBoxGetText(comboPlayer[i]);
if (comboPlayer[i] && newName.compare(oldName))
{
GadgetComboBoxSetText(comboPlayer[i], newName);
}
if(i!= 0 && buttonAccept && buttonAccept[i])
{
buttonAccept[i]->winHide(FALSE);
//Color In the little accepted boxes
if(slot->isAccepted())
{
if(BitTest(buttonAccept[i]->winGetStatus(), WIN_STATUS_IMAGE ))
buttonAccept[i]->winEnable(TRUE);
else
GadgetButtonSetEnabledColor(buttonAccept[i], acceptTrueColor );
}
else
{
if(BitTest(buttonAccept[i]->winGetStatus(), WIN_STATUS_IMAGE ))
buttonAccept[i]->winEnable(FALSE);
else
GadgetButtonSetEnabledColor(buttonAccept[i], acceptFalseColor );
}
}
}
else
{
GadgetComboBoxSetSelectedPos(comboPlayer[i], slot->getState(), TRUE);
if( buttonAccept && buttonAccept[i] )
buttonAccept[i]->winHide(TRUE);
}
/*
if (myGame->getLocalSlotNum() == i && i!=0)
{
if (comboPlayer[i])
comboPlayer[i]->winEnable( TRUE );
}
else*/ if (!myGame->amIHost())
{
if (comboPlayer[i])
comboPlayer[i]->winEnable( FALSE );
}
//if( i == myGame->getLocalSlotNum())
if((comboColor[i] != NULL) && BitTest(comboColor[i]->winGetStatus(), WIN_STATUS_ENABLED))
PopulateColorComboBox(i, comboColor, myGame, myGame->getConstSlot(i)->getPlayerTemplate() == PLAYERTEMPLATE_OBSERVER);
Int max, idx;
if (comboColor[i] != NULL) {
max = GadgetComboBoxGetLength(comboColor[i]);
for (idx=0; idx<max; ++idx)
{
Int color = (Int)GadgetComboBoxGetItemData(comboColor[i], idx);
if (color == slot->getColor())
{
GadgetComboBoxSetSelectedPos(comboColor[i], idx, TRUE);
break;
}
}
}
if (comboTeam[i] != NULL) {
max = GadgetComboBoxGetLength(comboTeam[i]);
for (idx=0; idx<max; ++idx)
{
Int team = (Int)GadgetComboBoxGetItemData(comboTeam[i], idx);
if (team == slot->getTeamNumber())
{
GadgetComboBoxSetSelectedPos(comboTeam[i], idx, TRUE);
break;
}
}
}
if (comboPlayerTemplate[i] != NULL) {
max = GadgetComboBoxGetLength(comboPlayerTemplate[i]);
for (idx=0; idx<max; ++idx)
{
Int playerTemplate = (Int)GadgetComboBoxGetItemData(comboPlayerTemplate[i], idx);
if (playerTemplate == slot->getPlayerTemplate())
{
GadgetComboBoxSetSelectedPos(comboPlayerTemplate[i], idx, TRUE);
break;
}
}
}
}
}
}
// -----------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,101 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameNetwork/GameMessageParser.h"
//----------------------------------------------------------------------------
GameMessageParser::GameMessageParser()
{
m_first = NULL;
m_argTypeCount = 0;
}
//----------------------------------------------------------------------------
GameMessageParser::GameMessageParser(GameMessage *msg)
{
m_first = NULL;
m_argTypeCount = 0;
UnsignedByte argCount = msg->getArgumentCount();
GameMessageArgumentDataType lasttype = ARGUMENTDATATYPE_UNKNOWN;
Int thisTypeCount = 0;
for (UnsignedByte i = 0; i < argCount; ++i) {
GameMessageArgumentDataType type = msg->getArgumentDataType(i);
if (type != lasttype) {
if (thisTypeCount > 0) {
addArgType(lasttype, thisTypeCount);
++m_argTypeCount;
}
lasttype = type;
thisTypeCount = 0;
}
++thisTypeCount;
}
if (thisTypeCount > 0) {
addArgType(lasttype, thisTypeCount);
++m_argTypeCount;
}
}
//----------------------------------------------------------------------------
GameMessageParser::~GameMessageParser()
{
GameMessageParserArgumentType *temp = NULL;
while (m_first != NULL) {
temp = m_first->getNext();
m_first->deleteInstance();
m_first = temp;
}
}
//----------------------------------------------------------------------------
void GameMessageParser::addArgType(GameMessageArgumentDataType type, Int argCount)
{
if (m_first == NULL) {
m_first = newInstance(GameMessageParserArgumentType)(type, argCount);
m_last = m_first;
return;
}
m_last->setNext(newInstance(GameMessageParserArgumentType)(type, argCount));
m_last = m_last->getNext();
}
//----------------------------------------------------------------------------
GameMessageParserArgumentType::GameMessageParserArgumentType(GameMessageArgumentDataType type, Int argCount)
{
m_next = NULL;
m_type = type;
m_argCount = argCount;
}
//----------------------------------------------------------------------------
GameMessageParserArgumentType::~GameMessageParserArgumentType()
{
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,339 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: Chat.cpp //////////////////////////////////////////////////////
// Generals GameSpy chat-related code
// Author: Matthew D. Campbell, July 2002
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/AudioEventRTS.h"
#include "Common/INI.h"
#include "GameClient/GameText.h"
#include "GameClient/GadgetListBox.h"
#include "GameClient/LanguageFilter.h"
#include "GameClient/GameWindowManager.h"
#include "GameNetwork/GameSpy/PeerDefsImplementation.h"
#include "GameNetwork/GameSpy/PeerThread.h"
#include "GameClient/InGameUI.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
#define OFFSET(x) (sizeof(Int) * (x))
static const FieldParse GameSpyColorFieldParse[] =
{
{ "Default", INI::parseColorInt, NULL, OFFSET(GSCOLOR_DEFAULT) },
{ "CurrentRoom", INI::parseColorInt, NULL, OFFSET(GSCOLOR_CURRENTROOM) },
{ "ChatRoom", INI::parseColorInt, NULL, OFFSET(GSCOLOR_ROOM) },
{ "Game", INI::parseColorInt, NULL, OFFSET(GSCOLOR_GAME) },
{ "GameFull", INI::parseColorInt, NULL, OFFSET(GSCOLOR_GAME_FULL) },
{ "GameCRCMismatch", INI::parseColorInt, NULL, OFFSET(GSCOLOR_GAME_CRCMISMATCH) },
{ "PlayerNormal", INI::parseColorInt, NULL, OFFSET(GSCOLOR_PLAYER_NORMAL) },
{ "PlayerOwner", INI::parseColorInt, NULL, OFFSET(GSCOLOR_PLAYER_OWNER) },
{ "PlayerBuddy", INI::parseColorInt, NULL, OFFSET(GSCOLOR_PLAYER_BUDDY) },
{ "PlayerSelf", INI::parseColorInt, NULL, OFFSET(GSCOLOR_PLAYER_SELF) },
{ "PlayerIgnored", INI::parseColorInt, NULL, OFFSET(GSCOLOR_PLAYER_IGNORED) },
{ "ChatNormal", INI::parseColorInt, NULL, OFFSET(GSCOLOR_CHAT_NORMAL) },
{ "ChatEmote", INI::parseColorInt, NULL, OFFSET(GSCOLOR_CHAT_EMOTE) },
{ "ChatOwner", INI::parseColorInt, NULL, OFFSET(GSCOLOR_CHAT_OWNER) },
{ "ChatOwnerEmote", INI::parseColorInt, NULL, OFFSET(GSCOLOR_CHAT_OWNER_EMOTE) },
{ "ChatPriv", INI::parseColorInt, NULL, OFFSET(GSCOLOR_CHAT_PRIVATE) },
{ "ChatPrivEmote", INI::parseColorInt, NULL, OFFSET(GSCOLOR_CHAT_PRIVATE_EMOTE) },
{ "ChatPrivOwner", INI::parseColorInt, NULL, OFFSET(GSCOLOR_CHAT_PRIVATE_OWNER) },
{ "ChatPrivOwnerEmote", INI::parseColorInt, NULL, OFFSET(GSCOLOR_CHAT_PRIVATE_OWNER_EMOTE) },
{ "ChatBuddy", INI::parseColorInt, NULL, OFFSET(GSCOLOR_CHAT_BUDDY) },
{ "ChatSelf", INI::parseColorInt, NULL, OFFSET(GSCOLOR_CHAT_SELF) },
{ "AcceptTrue", INI::parseColorInt, NULL, OFFSET(GSCOLOR_ACCEPT_TRUE) },
{ "AcceptFalse", INI::parseColorInt, NULL, OFFSET(GSCOLOR_ACCEPT_FALSE) },
{ "MapSelected", INI::parseColorInt, NULL, OFFSET(GSCOLOR_MAP_SELECTED) },
{ "MapUnselected", INI::parseColorInt, NULL, OFFSET(GSCOLOR_MAP_UNSELECTED) },
{ "MOTD", INI::parseColorInt, NULL, OFFSET(GSCOLOR_MOTD) },
{ "MOTDHeading", INI::parseColorInt, NULL, OFFSET(GSCOLOR_MOTD_HEADING) },
{ NULL, NULL, NULL, 0 } // keep this last
};
void INI::parseOnlineChatColorDefinition( INI* ini )
{
// parse the ini definition
ini->initFromINI( GameSpyColor, GameSpyColorFieldParse );
}
Color GameSpyColor[GSCOLOR_MAX] =
{
GameMakeColor(255,255,255,255), // GSCOLOR_DEFAULT
GameMakeColor(255,255, 0,255), // GSCOLOR_CURRENTROOM
GameMakeColor(255,255,255,255), // GSCOLOR_ROOM
GameMakeColor(128,128,0,255), // GSCOLOR_GAME
GameMakeColor(128,128,128,255), // GSCOLOR_GAME_FULL
GameMakeColor(128,128,128,255), // GSCOLOR_GAME_CRCMISMATCH
GameMakeColor(255, 0, 0,255), // GSCOLOR_PLAYER_NORMAL
GameMakeColor(255, 0,255,255), // GSCOLOR_PLAYER_OWNER
GameMakeColor(255, 0,128,255), // GSCOLOR_PLAYER_BUDDY
GameMakeColor(255, 0, 0,255), // GSCOLOR_PLAYER_SELF
GameMakeColor(128,128,128,255), // GSCOLOR_PLAYER_IGNORED
GameMakeColor(255,0,0,255), // GSCOLOR_CHAT_NORMAL
GameMakeColor(255,128,0,255), // GSCOLOR_CHAT_EMOTE,
GameMakeColor(255,255,0,255), // GSCOLOR_CHAT_OWNER,
GameMakeColor(128,255,0,255), // GSCOLOR_CHAT_OWNER_EMOTE,
GameMakeColor(0,0,255,255), // GSCOLOR_CHAT_PRIVATE,
GameMakeColor(0,255,255,255), // GSCOLOR_CHAT_PRIVATE_EMOTE,
GameMakeColor(255,0,255,255), // GSCOLOR_CHAT_PRIVATE_OWNER,
GameMakeColor(255,128,255,255), // GSCOLOR_CHAT_PRIVATE_OWNER_EMOTE,
GameMakeColor(255, 0,255,255), // GSCOLOR_CHAT_BUDDY,
GameMakeColor(255, 0,128,255), // GSCOLOR_CHAT_SELF,
GameMakeColor( 0,255, 0,255), // GSCOLOR_ACCEPT_TRUE,
GameMakeColor(255, 0, 0,255), // GSCOLOR_ACCEPT_FALSE,
GameMakeColor(255,255, 0,255), // GSCOLOR_MAP_SELECTED,
GameMakeColor(255,255,255,255), // GSCOLOR_MAP_UNSELECTED,
GameMakeColor(255,255,255,255), // GSCOLOR_MOTD,
GameMakeColor(255,255, 0,255), // GSCOLOR_MOTD_HEADING,
};
Bool GameSpyInfo::sendChat( UnicodeString message, Bool isAction, GameWindow *playerListbox )
{
RoomType roomType = StagingRoom;
if (getCurrentGroupRoom())
roomType = GroupRoom;
PeerRequest req;
req.text = message.str();
message.trim();
// Echo the user's input to the chat window
if (!message.isEmpty())
{
if (!playerListbox)
{
// Public message
req.message.isAction = isAction;
req.peerRequestType = PeerRequest::PEERREQUEST_MESSAGEROOM;
TheGameSpyPeerMessageQueue->addRequest(req);
return false;
}
// Get the selections (is this a private message?)
Int maxSel = GadgetListBoxGetListLength(playerListbox);
Int *selections;
GadgetListBoxGetSelected(playerListbox, (Int *)&selections);
if (selections[0] == -1)
{
// Public message
req.message.isAction = isAction;
req.peerRequestType = PeerRequest::PEERREQUEST_MESSAGEROOM;
TheGameSpyPeerMessageQueue->addRequest(req);
return false;
}
else
{
// Private message
// Construct a list
AsciiString names = AsciiString::TheEmptyString;
AsciiString tmp = AsciiString::TheEmptyString;
AsciiString aStr; // AsciiString buf for translating Unicode entries
names.format("%s", TheGameSpyInfo->getLocalName().str());
for (int i=0; i<maxSel; i++)
{
if (selections[i] != -1)
{
aStr.translate(GadgetListBoxGetText(playerListbox, selections[i], GadgetListBoxGetNumColumns(playerListbox)-1));
if (aStr.compareNoCase(TheGameSpyInfo->getLocalName()))
{
tmp.format(",%s", aStr.str());
names.concat(tmp);
}
}
else
{
break;
}
}
if (!names.isEmpty())
{
req.nick = names.str();
req.message.isAction = isAction;
req.peerRequestType = PeerRequest::PEERREQUEST_MESSAGEPLAYER;
TheGameSpyPeerMessageQueue->addRequest(req);
}
return true;
}
}
return false;
}
void GameSpyInfo::addChat( AsciiString nick, Int profileID, UnicodeString msg, Bool isPublic, Bool isAction, GameWindow *win )
{
PlayerInfoMap::iterator it = getPlayerInfoMap()->find(nick);
if (it != getPlayerInfoMap()->end())
{
addChat( it->second, msg, isPublic, isAction, win );
}
else
{
}
}
void GameSpyInfo::addChat( PlayerInfo p, UnicodeString msg, Bool isPublic, Bool isAction, GameWindow *win )
{
Int style;
if(isSavedIgnored(p.m_profileID) || isIgnored(p.m_name))
return;
Bool isOwner = p.m_flags & PEER_FLAG_OP;
Bool isBuddy = getBuddyMap()->find(p.m_profileID) != getBuddyMap()->end();
Bool isMe = p.m_name.compare(TheGameSpyInfo->getLocalName()) == 0;
if(!isMe)
{
if(m_disallowAsainText)
{
const WideChar *buff = msg.str();
Int length = msg.getLength();
for(Int i = 0; i < length; ++i)
{
if(buff[i] >= 256)
return;
}
}
else if(m_disallowNonAsianText)
{
const WideChar *buff = msg.str();
Int length = msg.getLength();
Bool hasUnicode = FALSE;
for(Int i = 0; i < length; ++i)
{
if(buff[i] >= 256)
{
hasUnicode = TRUE;
break;
}
}
if(!hasUnicode)
return;
}
if (!isPublic)
{
AudioEventRTS privMsgAudio("GUIMessageReceived");
if( TheAudio )
{
TheAudio->addAudioEvent( &privMsgAudio );
} // end if
}
}
if (isBuddy)
{
style = GSCOLOR_CHAT_BUDDY;
}
else if (isPublic && isAction)
{
style = (isOwner)?GSCOLOR_CHAT_OWNER_EMOTE:GSCOLOR_CHAT_EMOTE;
}
else if (isPublic)
{
style = (isOwner)?GSCOLOR_CHAT_OWNER:GSCOLOR_CHAT_NORMAL;
}
else if (isAction)
{
style = (isOwner)?GSCOLOR_CHAT_PRIVATE_OWNER_EMOTE:GSCOLOR_CHAT_PRIVATE_EMOTE;
}
else
{
style = (isOwner)?GSCOLOR_CHAT_PRIVATE_OWNER:GSCOLOR_CHAT_PRIVATE;
}
UnicodeString name;
name.translate(p.m_name);
// filters language
// if( TheGlobalData->m_languageFilterPref )
// {
TheLanguageFilter->filterLine(msg);
// }
UnicodeString fullMsg;
if (isAction)
{
fullMsg.format( L"%ls %ls", name.str(), msg.str() );
}
else
{
fullMsg.format( L"[%ls] %ls", name.str(), msg.str() );
}
Int index = addText(fullMsg, GameSpyColor[style], win);
if (index >= 0)
{
GadgetListBoxSetItemData(win, (void *)p.m_profileID, index);
}
}
Int GameSpyInfo::addText( UnicodeString message, Color c, GameWindow *win )
{
if (TheGameSpyGame && TheGameSpyGame->isInGame() && TheGameSpyGame->isGameInProgress())
{
static AudioEventRTS messageFromChatSound("GUIMessageReceived");
TheAudio->addAudioEvent(&messageFromChatSound);
TheInGameUI->message(message);
}
if (!win)
{
// try to pick up a registered text window
if (m_textWindows.empty())
return -1;
win = *(m_textWindows.begin());
}
Int index = GadgetListBoxAddEntryText(win, message, c, -1, -1);
GadgetListBoxSetItemData(win, (void *)-1, index);
return index;
}
void GameSpyInfo::registerTextWindow( GameWindow *win )
{
m_textWindows.insert(win);
}
void GameSpyInfo::unregisterTextWindow( GameWindow *win )
{
m_textWindows.erase(win);
}

View File

@@ -0,0 +1,487 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
// FILE: GSConfig.cpp
// Author: Matthew D. Campbell, Sept 2002
// Description: GameSpy online config
///////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameState.h"
#include "GameClient/MapUtil.h"
#include "GameNetwork/GameSpy/GSConfig.h"
#include "GameNetwork/RankPointValue.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
///////////////////////////////////////////////////////////////////////////////////////
GameSpyConfigInterface *TheGameSpyConfig = NULL;
class GameSpyConfig : public GameSpyConfigInterface
{
public:
GameSpyConfig( AsciiString config );
~GameSpyConfig() {}
// Pings
std::list<AsciiString> getPingServers(void) { return m_pingServers; }
Int getNumPingRepetitions(void) { return m_pingReps; }
Int getPingTimeoutInMs(void) { return m_pingTimeout; }
virtual Int getPingCutoffGood( void ) { return m_pingCutoffGood; }
virtual Int getPingCutoffBad( void ) { return m_pingCutoffBad; }
// QM
std::list<AsciiString> getQMMaps(void) { return m_qmMaps; }
Int getQMBotID(void) { return m_qmBotID; }
Int getQMChannel(void) { return m_qmChannel; }
void setQMChannel(Int channel) { m_qmChannel = channel; }
// Player Info
Int getPointsForRank(Int rank);
virtual Bool isPlayerVIP(Int id);
virtual Bool getManglerLocation(Int index, AsciiString& host, UnsignedShort& port);
// Ladder / Any other external parsing
AsciiString getLeftoverConfig(void) { return m_leftoverConfig; }
// NAT Timeouts
virtual Int getTimeBetweenRetries() { return m_natRetryInterval; }
virtual Int getMaxManglerRetries() { return m_natMaxManglerRetries; }
virtual time_t getRetryInterval() { return m_natManglerRetryInterval; }
virtual time_t getKeepaliveInterval() { return m_natKeepaliveInterval; }
virtual time_t getPortTimeout() { return m_natPortTimeout; }
virtual time_t getRoundTimeout() { return m_natRoundTimeout; }
// Custom match
virtual Bool restrictGamesToLobby() { return m_restrictGamesToLobby; }
protected:
std::list<AsciiString> m_pingServers;
Int m_pingReps;
Int m_pingTimeout;
Int m_pingCutoffGood;
Int m_pingCutoffBad;
Int m_natRetryInterval;
Int m_natMaxManglerRetries;
time_t m_natManglerRetryInterval;
time_t m_natKeepaliveInterval;
time_t m_natPortTimeout;
time_t m_natRoundTimeout;
std::vector<AsciiString> m_manglerHosts;
std::vector<UnsignedShort> m_manglerPorts;
std::list<AsciiString> m_qmMaps;
Int m_qmBotID;
Int m_qmChannel;
Bool m_restrictGamesToLobby;
std::set<Int> m_vip; // VIP people
Int m_rankPoints[MAX_RANKS];
AsciiString m_leftoverConfig;
};
///////////////////////////////////////////////////////////////////////////////////////
GameSpyConfigInterface* GameSpyConfigInterface::create(AsciiString config)
{
return NEW GameSpyConfig(config);
}
///////////////////////////////////////////////////////////////////////////////////////
class SectionChecker
{
public:
typedef std::list<const Bool *> SectionList;
void addVar(const Bool *var) { m_bools.push_back(var); }
Bool isInSection();
protected:
SectionList m_bools;
};
Bool SectionChecker::isInSection() {
Bool ret = FALSE;
for (SectionList::const_iterator it = m_bools.begin(); it != m_bools.end(); ++it)
{
ret = ret || **it;
}
return ret;
}
///////////////////////////////////////////////////////////////////////////////////////
GameSpyConfig::GameSpyConfig( AsciiString config ) :
m_natRetryInterval(1000),
m_natMaxManglerRetries(25),
m_natManglerRetryInterval(300),
m_natKeepaliveInterval(15000),
m_natPortTimeout(10000),
m_natRoundTimeout(10000),
m_pingReps(1),
m_pingTimeout(1000),
m_pingCutoffGood(300),
m_pingCutoffBad(600),
m_restrictGamesToLobby(FALSE),
m_qmBotID(0),
m_qmChannel(0)
{
m_rankPoints[0] = 0;
m_rankPoints[1] = 5;
m_rankPoints[2] = 10;
m_rankPoints[3] = 20;
m_rankPoints[4] = 50;
m_rankPoints[5] = 100;
m_rankPoints[6] = 200;
m_rankPoints[7] = 500;
m_rankPoints[8] = 1000;
m_rankPoints[9] = 2000;
AsciiString line;
Bool inPingServers = FALSE;
Bool inPingDuration = FALSE;
Bool inQMMaps = FALSE;
Bool inQMBot = FALSE;
Bool inManglers = FALSE;
Bool inVIP = FALSE;
Bool inNAT = FALSE;
Bool inCustom = FALSE;
SectionChecker sections;
sections.addVar(&inPingServers);
sections.addVar(&inPingDuration);
sections.addVar(&inQMMaps);
sections.addVar(&inQMBot);
sections.addVar(&inManglers);
sections.addVar(&inVIP);
sections.addVar(&inNAT);
sections.addVar(&inCustom);
while (config.nextToken(&line, "\n"))
{
if (line.getCharAt(line.getLength()-1) == '\r')
line.removeLastChar(); // there is a trailing '\r'
line.trim();
if (line.isEmpty())
continue;
if (!sections.isInSection() && line.compare("<PingServers>") == 0)
{
inPingServers = TRUE;
}
else if (inPingServers && line.compare("</PingServers>") == 0)
{
inPingServers = FALSE;
}
else if (!sections.isInSection() && line.compare("<PingDuration>") == 0)
{
inPingDuration = TRUE;
}
else if (inPingDuration && line.compare("</PingDuration>") == 0)
{
inPingDuration = FALSE;
}
else if (!sections.isInSection() && line.compare("<QMMaps>") == 0)
{
inQMMaps = TRUE;
}
else if (inQMMaps && line.compare("</QMMaps>") == 0)
{
inQMMaps = FALSE;
}
else if (!sections.isInSection() && line.compare("<Manglers>") == 0)
{
inManglers = TRUE;
}
else if (inManglers && line.compare("</Manglers>") == 0)
{
inManglers = FALSE;
}
else if (!sections.isInSection() && line.compare("<QMBot>") == 0)
{
inQMBot = TRUE;
}
else if (inQMBot && line.compare("</QMBot>") == 0)
{
inQMBot = FALSE;
}
else if (!sections.isInSection() && line.compare("<VIP>") == 0)
{
inVIP = TRUE;
}
else if (inVIP && line.compare("</VIP>") == 0)
{
inVIP = FALSE;
}
else if (!sections.isInSection() && line.compare("<NAT>") == 0)
{
inNAT = TRUE;
}
else if (inNAT && line.compare("</NAT>") == 0)
{
inNAT = FALSE;
}
else if (!sections.isInSection() && line.compare("<Custom>") == 0)
{
inCustom = TRUE;
}
else if (inCustom && line.compare("</Custom>") == 0)
{
inCustom = FALSE;
}
else if (inVIP)
{
line.toLower();
if (line.getLength())
{
Int val = atoi(line.str());
if (val > 0)
m_vip.insert(val);
}
}
else if (inPingServers)
{
line.toLower();
m_pingServers.push_back(line);
}
else if (inPingDuration)
{
line.toLower();
AsciiString key, val;
if (line.nextToken(&key, " ="))
{
if (key == "reps")
{
if (line.nextToken(&val, " ="))
{
m_pingReps = atoi(val.str());
}
}
else if (key == "timeout")
{
if (line.nextToken(&val, " ="))
{
m_pingTimeout = atoi(val.str());
}
}
else if (key == "low")
{
if (line.nextToken(&val, " ="))
{
m_pingCutoffGood = atoi(val.str());
}
}
else if (key == "med")
{
if (line.nextToken(&val, " ="))
{
m_pingCutoffBad = atoi(val.str());
}
}
}
}
else if (inManglers)
{
line.trim();
line.toLower();
AsciiString hostStr;
AsciiString portStr;
line.nextToken(&hostStr, ":");
line.nextToken(&portStr, ":\n\r");
if (hostStr.isNotEmpty() && portStr.isNotEmpty())
{
m_manglerHosts.push_back(hostStr);
m_manglerPorts.push_back(atoi(portStr.str()));
}
}
else if (inQMMaps)
{
line.toLower();
AsciiString mapName;
mapName.format("%s\\%s\\%s.map", TheMapCache->getMapDir().str(), line.str(), line.str());
mapName = TheGameState->portableMapPathToRealMapPath(TheGameState->realMapPathToPortableMapPath(mapName));
mapName.toLower();
// [SKB: Jul 01 2003 @ 6:43pm] :
// German2 is missing some maps because of content. But, we need the m_qmMaps
// to contain same number of strings as the Retail version so that the
// QM Bot thinks that they have the same number of maps.
#if 1
m_qmMaps.push_back(mapName);
#else
const MapMetaData *md = TheMapCache->findMap(mapName);
if (md)
{
m_qmMaps.push_back(mapName);
}
#endif
}
else if (inQMBot)
{
line.toLower();
AsciiString key, val;
if (line.nextToken(&key, " ="))
{
if (key == "id")
{
if (line.nextToken(&val, " ="))
{
m_qmBotID = atoi(val.str());
}
}
}
}
else if (inNAT)
{
line.toLower();
AsciiString key, val;
if (line.nextToken(&key, " ="))
{
if (key == "retryinterval")
{
if (line.nextToken(&val, " ="))
{
m_natRetryInterval = atoi(val.str());
}
}
else if (key == "manglerretries")
{
if (line.nextToken(&val, " ="))
{
m_natMaxManglerRetries = atoi(val.str());
}
}
else if (key == "manglerinterval")
{
if (line.nextToken(&val, " ="))
{
m_natManglerRetryInterval = atoi(val.str());
}
}
else if (key == "keepaliveinterval")
{
if (line.nextToken(&val, " ="))
{
m_natKeepaliveInterval = atoi(val.str());
}
}
else if (key == "porttimeout")
{
if (line.nextToken(&val, " ="))
{
m_natPortTimeout = atoi(val.str());
}
}
else if (key == "roundtimeout")
{
if (line.nextToken(&val, " ="))
{
m_natRoundTimeout = atoi(val.str());
}
}
else
{
DEBUG_LOG(("Unknown key '%s' = '%s' in NAT block of GameSpy Config\n", key.str(), val.str()));
}
}
else
{
DEBUG_LOG(("Key '%s' missing val in NAT block of GameSpy Config\n", key.str()));
}
}
else if (inCustom)
{
line.toLower();
AsciiString key, val;
if (line.nextToken(&key, " =") && line.nextToken(&val, " ="))
{
if (key == "restricted")
{
m_restrictGamesToLobby = atoi(val.str());
}
else
{
DEBUG_LOG(("Unknown key '%s' = '%s' in Custom block of GameSpy Config\n", key.str(), val.str()));
}
}
else
{
DEBUG_LOG(("Key '%s' missing val in Custom block of GameSpy Config\n", key.str()));
}
}
else
{
m_leftoverConfig.concat(line);
m_leftoverConfig.concat('\n');
}
}
}
///////////////////////////////////////////////////////////////////////////////////////
Int GameSpyConfig::getPointsForRank(Int rank)
{
if (rank >= MAX_RANKS) rank = MAX_RANKS-1;
if (rank < 0) rank = 0;
return m_rankPoints[rank];
}
///////////////////////////////////////////////////////////////////////////////////////
Bool GameSpyConfig::getManglerLocation(Int index, AsciiString& host, UnsignedShort& port)
{
if (index < 0 || index >= m_manglerHosts.size())
{
return FALSE;
}
host = m_manglerHosts[index];
port = m_manglerPorts[index];
return TRUE;
}
///////////////////////////////////////////////////////////////////////////////////////
Bool GameSpyConfig::isPlayerVIP(Int id)
{
std::set<Int>::const_iterator it = std::find(m_vip.begin(), m_vip.end(), id);
return it != m_vip.end();
}
///////////////////////////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,527 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: LadderDefs.cpp //////////////////////////////////////////////////////
// Generals ladder code
// Author: Matthew D. Campbell, August 2002
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameNetwork/GameSpy/ThreadUtils.h"
#include "GameNetwork/GameSpy/LadderDefs.h"
#include "GameNetwork/GameSpy/PeerDefs.h"
#include "GameNetwork/GameSpy/GSConfig.h"
#include "Common/GameState.h"
#include "Common/File.h"
#include "Common/FileSystem.h"
#include "Common/PlayerTemplate.h"
#include "GameClient/GameText.h"
#include "GameClient/MapUtil.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
LadderList *TheLadderList = NULL;
LadderInfo::LadderInfo()
{
playersPerTeam = 1;
minWins = 0;
maxWins = 0;
randomMaps = TRUE;
randomFactions = TRUE;
validQM = TRUE;
validCustom = FALSE;
port = 0;
submitReplay = FALSE;
index = -1;
}
static LadderInfo *parseLadder(AsciiString raw)
{
DEBUG_LOG(("Looking at ladder:\n%s\n", raw.str()));
LadderInfo *lad = NULL;
AsciiString line;
while (raw.nextToken(&line, "\n"))
{
if (line.getCharAt(line.getLength()-1) == '\r')
line.removeLastChar(); // there is a trailing '\r'
line.trim();
if (line.isEmpty())
continue;
// woohoo! got a line!
line.trim();
if ( !lad && line.startsWith("<Ladder ") )
{
// start of a ladder def
lad = NEW LadderInfo;
// fill in some info
AsciiString tokenName, tokenAddr, tokenPort, tokenHomepage;
line.removeLastChar(); // the '>'
line = line.str() + 7; // the "<Ladder "
line.nextToken(&tokenAddr, "\" ");
line.nextToken(&tokenPort, " ");
line.nextToken(&tokenHomepage, " ");
lad->name = MultiByteToWideCharSingleLine(tokenName.str()).c_str();
while (lad->name.getLength() > 20)
lad->name.removeLastChar(); // Per Harvard's request, ladder names are limited to 20 chars
lad->address = tokenAddr;
lad->port = atoi(tokenPort.str());
lad->homepageURL = tokenHomepage;
}
else if ( lad && line.startsWith("Name ") )
{
lad->name = MultiByteToWideCharSingleLine(line.str() + 5).c_str();
}
else if ( lad && line.startsWith("Desc ") )
{
lad->description = MultiByteToWideCharSingleLine(line.str() + 5).c_str();
}
else if ( lad && line.startsWith("Loc ") )
{
lad->location = MultiByteToWideCharSingleLine(line.str() + 4).c_str();
}
else if ( lad && line.startsWith("TeamSize ") )
{
lad->playersPerTeam = atoi(line.str() + 9);
}
else if ( lad && line.startsWith("RandomMaps ") )
{
lad->randomMaps = atoi(line.str() + 11);
}
else if ( lad && line.startsWith("RandomFactions ") )
{
lad->randomFactions = atoi(line.str() + 15);
}
else if ( lad && line.startsWith("Faction ") )
{
AsciiString faction = line.str() + 8;
AsciiStringList outStringList;
ThePlayerTemplateStore->getAllSideStrings(&outStringList);
AsciiStringList::iterator aIt = std::find(outStringList.begin(), outStringList.end(), faction);
if (aIt != outStringList.end())
{
// valid faction - now check for dupes
aIt = std::find(lad->validFactions.begin(), lad->validFactions.end(), faction);
if (aIt == lad->validFactions.end())
{
lad->validFactions.push_back(faction);
}
}
}
/*
else if ( lad && line.startsWith("QM ") )
{
lad->validQM = atoi(line.str() + 3);
}
else if ( lad && line.startsWith("Custom ") )
{
lad->validCustom = atoi(line.str() + 7);
}
*/
else if ( lad && line.startsWith("MinWins ") )
{
lad->minWins = atoi(line.str() + 8);
}
else if ( lad && line.startsWith("MaxWins ") )
{
lad->maxWins = atoi(line.str() + 8);
}
else if ( lad && line.startsWith("CryptedPass ") )
{
lad->cryptedPassword = line.str() + 12;
}
else if ( lad && line.compare("</Ladder>") == 0 )
{
DEBUG_LOG(("Saw a ladder: name=%ls, addr=%s:%d, players=%dv%d, pass=%s, replay=%d, homepage=%s\n",
lad->name.str(), lad->address.str(), lad->port, lad->playersPerTeam, lad->playersPerTeam, lad->cryptedPassword.str(),
lad->submitReplay, lad->homepageURL.str()));
// end of a ladder
if (lad->playersPerTeam >= 1 && lad->playersPerTeam <= MAX_SLOTS/2)
{
if (lad->validFactions.size() == 0)
{
DEBUG_LOG(("No factions specified. Using all.\n"));
lad->validFactions.push_back("America");
lad->validFactions.push_back("China");
lad->validFactions.push_back("GLA");
}
else
{
AsciiStringList validFactions = lad->validFactions;
for (AsciiStringListIterator it = validFactions.begin(); it != validFactions.end(); ++it)
{
AsciiString faction = *it;
AsciiString marker;
marker.format("INI:Faction%s", faction.str());
DEBUG_LOG(("Faction %s has marker %s corresponding to str %ls\n", faction.str(), marker.str(), TheGameText->fetch(marker).str()));
}
}
if (lad->validMaps.size() == 0)
{
DEBUG_LOG(("No maps specified. Using all.\n"));
std::list<AsciiString> qmMaps = TheGameSpyConfig->getQMMaps();
for (std::list<AsciiString>::const_iterator it = qmMaps.begin(); it != qmMaps.end(); ++it)
{
AsciiString mapName = *it;
// check sizes on the maps before allowing them
const MapMetaData *md = TheMapCache->findMap(mapName);
if (md && md->m_numPlayers >= lad->playersPerTeam*2)
{
lad->validMaps.push_back(mapName);
}
}
}
return lad;
}
else
{
// no maps? don't play on it!
delete lad;
lad = NULL;
return NULL;
}
}
else if ( lad && line.startsWith("Map ") )
{
// valid map
AsciiString mapName = line.str() + 4;
mapName.trim();
if (mapName.isNotEmpty())
{
mapName.format("%s\\%s\\%s.map", TheMapCache->getMapDir().str(), mapName.str(), mapName.str());
mapName = TheGameState->portableMapPathToRealMapPath(TheGameState->realMapPathToPortableMapPath(mapName));
mapName.toLower();
std::list<AsciiString> qmMaps = TheGameSpyConfig->getQMMaps();
if (std::find(qmMaps.begin(), qmMaps.end(), mapName) != qmMaps.end())
{
// check sizes on the maps before allowing them
const MapMetaData *md = TheMapCache->findMap(mapName);
if (md && md->m_numPlayers >= lad->playersPerTeam*2)
lad->validMaps.push_back(mapName);
}
}
}
else
{
// bad ladder - kill it
delete lad;
lad = NULL;
}
}
if (lad)
{
delete lad;
lad = NULL;
}
return NULL;
}
LadderList::LadderList()
{
//Int profile = TheGameSpyInfo->getLocalProfileID();
AsciiString rawMotd = TheGameSpyConfig->getLeftoverConfig();
AsciiString line;
Bool inLadders = FALSE;
Bool inSpecialLadders = FALSE;
Bool inLadder = FALSE;
LadderInfo *lad = NULL;
Int index = 1;
AsciiString rawLadder;
while (rawMotd.nextToken(&line, "\n"))
{
if (line.getCharAt(line.getLength()-1) == '\r')
line.removeLastChar(); // there is a trailing '\r'
line.trim();
if (line.isEmpty())
continue;
if (!inLadders && line.compare("<Ladders>") == 0)
{
inLadders = TRUE;
rawLadder.clear();
}
else if (inLadders && line.compare("</Ladders>") == 0)
{
inLadders = FALSE;
}
else if (!inSpecialLadders && line.compare("<SpecialLadders>") == 0)
{
inSpecialLadders = TRUE;
rawLadder.clear();
}
else if (inSpecialLadders && line.compare("</SpecialLadders>") == 0)
{
inSpecialLadders = FALSE;
}
else if (inLadders || inSpecialLadders)
{
if (line.startsWith("<Ladder ") && !inLadder)
{
inLadder = TRUE;
rawLadder.clear();
rawLadder.concat(line);
rawLadder.concat('\n');
}
else if (line.compare("</Ladder>") == 0 && inLadder)
{
inLadder = FALSE;
rawLadder.concat(line);
rawLadder.concat('\n');
if ((lad = parseLadder(rawLadder)) != NULL)
{
lad->index = index++;
if (inLadders)
{
DEBUG_LOG(("Adding to standard ladders\n"));
m_standardLadders.push_back(lad);
}
else
{
DEBUG_LOG(("Adding to special ladders\n"));
m_specialLadders.push_back(lad);
}
}
rawLadder.clear();
}
else if (inLadder)
{
rawLadder.concat(line);
rawLadder.concat('\n');
}
}
}
// look for local ladders
loadLocalLadders();
DEBUG_LOG(("After looking for ladders, we have %d local, %d special && %d normal\n", m_localLadders.size(), m_specialLadders.size(), m_standardLadders.size()));
}
LadderList::~LadderList()
{
LadderInfoList::iterator it;
for (it = m_specialLadders.begin(); it != m_specialLadders.end(); it = m_specialLadders.begin())
{
delete *it;
m_specialLadders.pop_front();
}
for (it = m_standardLadders.begin(); it != m_standardLadders.end(); it = m_standardLadders.begin())
{
delete *it;
m_standardLadders.pop_front();
}
for (it = m_localLadders.begin(); it != m_localLadders.end(); it = m_localLadders.begin())
{
delete *it;
m_localLadders.pop_front();
}
}
const LadderInfo* LadderList::findLadder( const AsciiString& addr, UnsignedShort port )
{
LadderInfoList::const_iterator cit;
for (cit = m_specialLadders.begin(); cit != m_specialLadders.end(); ++cit)
{
const LadderInfo *li = *cit;
if (li->address == addr && li->port == port)
{
return li;
}
}
for (cit = m_standardLadders.begin(); cit != m_standardLadders.end(); ++cit)
{
const LadderInfo *li = *cit;
if (li->address == addr && li->port == port)
{
return li;
}
}
for (cit = m_localLadders.begin(); cit != m_localLadders.end(); ++cit)
{
const LadderInfo *li = *cit;
if (li->address == addr && li->port == port)
{
return li;
}
}
return NULL;
}
const LadderInfo* LadderList::findLadderByIndex( Int index )
{
if (index == 0)
return NULL;
LadderInfoList::const_iterator cit;
for (cit = m_specialLadders.begin(); cit != m_specialLadders.end(); ++cit)
{
const LadderInfo *li = *cit;
if (li->index == index)
{
return li;
}
}
for (cit = m_standardLadders.begin(); cit != m_standardLadders.end(); ++cit)
{
const LadderInfo *li = *cit;
if (li->index == index)
{
return li;
}
}
for (cit = m_localLadders.begin(); cit != m_localLadders.end(); ++cit)
{
const LadderInfo *li = *cit;
if (li->index == index)
{
return li;
}
}
return NULL;
}
const LadderInfoList* LadderList::getSpecialLadders( void )
{
return &m_specialLadders;
}
const LadderInfoList* LadderList::getStandardLadders( void )
{
return &m_standardLadders;
}
const LadderInfoList* LadderList::getLocalLadders( void )
{
return &m_localLadders;
}
void LadderList::loadLocalLadders( void )
{
AsciiString dirname;
dirname.format("%sGeneralsOnline\\Ladders\\", TheGlobalData->getPath_UserData().str());
FilenameList filenameList;
TheFileSystem->getFileListInDirectory(dirname, AsciiString("*.ini"), filenameList, TRUE);
Int index = -1;
FilenameList::iterator it = filenameList.begin();
while (it != filenameList.end())
{
AsciiString filename = *it;
DEBUG_LOG(("Looking at possible ladder info file '%s'\n", filename.str()));
filename.toLower();
checkLadder( filename, index-- );
++it;
}
}
void LadderList::checkLadder( AsciiString fname, Int index )
{
File *fp = TheFileSystem->openFile(fname.str(), File::READ | File::TEXT);
char buf[1024];
AsciiString rawData;
if (fp)
{
Int len;
while (!fp->eof())
{
len = fp->read(buf, 1023);
buf[len] = 0;
buf[1023] = 0;
rawData.concat(buf);
}
fp->close();
fp = NULL;
}
DEBUG_LOG(("Read %d bytes from '%s'\n", rawData.getLength(), fname.str()));
if (rawData.isEmpty())
return;
LadderInfo *li = parseLadder(rawData);
if (!li)
{
return;
}
// sanity check
if (li->address.isEmpty())
{
DEBUG_LOG(("Bailing because of li->address.isEmpty()\n"));
delete li;
return;
}
if (!li->port)
{
DEBUG_LOG(("Bailing because of !li->port\n"));
delete li;
return;
}
if (li->validMaps.size() == 0)
{
DEBUG_LOG(("Bailing because of li->validMaps.size() == 0\n"));
delete li;
return;
}
li->index = index;
// ladders are QM-only at this point, which kinda invalidates the whole concept of local ladders. Oh well.
li->validQM = FALSE; // no local ladders in QM
li->validCustom = FALSE;
//for (Int i=0; i<4; ++i)
// fname.removeLastChar(); // remove .lad
//li->name = UnicodeString(MultiByteToWideCharSingleLine(fname.reverseFind('\\')+1).c_str());
DEBUG_LOG(("Adding local ladder %ls\n", li->name.str()));
m_localLadders.push_back(li);
}

View File

@@ -0,0 +1,859 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
// FILE: LobbyUtils.cpp
// Author: Matthew D. Campbell, Sept 2002
// Description: GameSpy lobby utils
///////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameEngine.h"
#include "Common/MultiplayerSettings.h"
#include "Common/PlayerTemplate.h"
#include "Common/Version.h"
#include "GameClient/AnimateWindowManager.h"
#include "GameClient/WindowLayout.h"
#include "GameClient/Gadget.h"
#include "GameClient/Image.h"
#include "GameClient/Shell.h"
#include "GameClient/KeyDefs.h"
#include "GameClient/GameWindowManager.h"
#include "GameClient/GadgetComboBox.h"
#include "GameClient/GadgetListBox.h"
#include "GameClient/GadgetTextEntry.h"
#include "GameClient/GameText.h"
#include "GameClient/MapUtil.h"
#include "GameClient/MessageBox.h"
#include "GameClient/Mouse.h"
#include "GameNetwork/GameSpyOverlay.h"
#include "GameClient/LanguageFilter.h"
#include "GameNetwork/GameSpy/BuddyDefs.h"
#include "GameNetwork/GameSpy/LadderDefs.h"
#include "GameNetwork/GameSpy/LobbyUtils.h"
#include "GameNetwork/GameSpy/PeerDefs.h"
#include "GameNetwork/GameSpy/PeerThread.h"
#include "GameNetwork/GameSpy/PersistentStorageDefs.h"
#include "GameNetwork/GameSpy/GSConfig.h"
#include "Common/STLTypedefs.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// PRIVATE DATA ///////////////////////////////////////////////////////////////////////////////////
static enum {
COLUMN_NAME = 0,
COLUMN_MAP,
COLUMN_LADDER,
COLUMN_NUMPLAYERS,
COLUMN_PASSWORD,
COLUMN_OBSERVER,
COLUMN_PING,
};
static NameKeyType buttonSortAlphaID = NAMEKEY_INVALID;
static NameKeyType buttonSortPingID = NAMEKEY_INVALID;
static NameKeyType buttonSortBuddiesID = NAMEKEY_INVALID;
static NameKeyType windowSortAlphaID = NAMEKEY_INVALID;
static NameKeyType windowSortPingID = NAMEKEY_INVALID;
static NameKeyType windowSortBuddiesID = NAMEKEY_INVALID;
static GameWindow *buttonSortAlpha = NULL;
static GameWindow *buttonSortPing = NULL;
static GameWindow *buttonSortBuddies = NULL;
static GameWindow *windowSortAlpha = NULL;
static GameWindow *windowSortPing = NULL;
static GameWindow *windowSortBuddies = NULL;
static GameSortType theGameSortType = GAMESORT_ALPHA_ASCENDING;
static Bool sortBuddies = TRUE;
static void showSortIcons(void)
{
if (windowSortAlpha && windowSortPing)
{
switch(theGameSortType)
{
case GAMESORT_ALPHA_ASCENDING:
windowSortAlpha->winHide(FALSE);
windowSortAlpha->winEnable(TRUE);
windowSortPing->winHide(TRUE);
break;
case GAMESORT_ALPHA_DESCENDING:
windowSortAlpha->winHide(FALSE);
windowSortAlpha->winEnable(FALSE);
windowSortPing->winHide(TRUE);
break;
case GAMESORT_PING_ASCENDING:
windowSortPing->winHide(FALSE);
windowSortPing->winEnable(TRUE);
windowSortAlpha->winHide(TRUE);
break;
case GAMESORT_PING_DESCENDING:
windowSortPing->winHide(FALSE);
windowSortPing->winEnable(FALSE);
windowSortAlpha->winHide(TRUE);
break;
}
}
if (sortBuddies)
{
if (windowSortBuddies)
{
windowSortBuddies->winHide(FALSE);
}
}
else
{
if (windowSortBuddies)
{
windowSortBuddies->winHide(TRUE);
}
}
}
void setSortMode( GameSortType sortType ) { theGameSortType = sortType; showSortIcons(); RefreshGameListBoxes(); }
void sortByBuddies( Bool doSort ) { sortBuddies = doSort; showSortIcons(); RefreshGameListBoxes(); }
Bool HandleSortButton( NameKeyType sortButton )
{
if (sortButton == buttonSortBuddiesID)
{
sortByBuddies( !sortBuddies );
return TRUE;
}
else if (sortButton == buttonSortAlphaID)
{
if (theGameSortType == GAMESORT_ALPHA_ASCENDING)
{
setSortMode(GAMESORT_ALPHA_DESCENDING);
}
else
{
setSortMode(GAMESORT_ALPHA_ASCENDING);
}
return TRUE;
}
else if (sortButton == buttonSortPingID)
{
if (theGameSortType == GAMESORT_PING_ASCENDING)
{
setSortMode(GAMESORT_PING_DESCENDING);
}
else
{
setSortMode(GAMESORT_PING_ASCENDING);
}
return TRUE;
}
return FALSE;
}
// window ids ------------------------------------------------------------------------------
static NameKeyType parentID = NAMEKEY_INVALID;
//static NameKeyType parentGameListSmallID = NAMEKEY_INVALID;
static NameKeyType parentGameListLargeID = NAMEKEY_INVALID;
static NameKeyType listboxLobbyGamesSmallID = NAMEKEY_INVALID;
static NameKeyType listboxLobbyGamesLargeID = NAMEKEY_INVALID;
//static NameKeyType listboxLobbyGameInfoID = NAMEKEY_INVALID;
// Window Pointers ------------------------------------------------------------------------
static GameWindow *parent = NULL;
//static GameWindow *parentGameListSmall = NULL;
static GameWindow *parentGameListLarge = NULL;
//GameWindow *listboxLobbyGamesSmall = NULL;
GameWindow *listboxLobbyGamesLarge = NULL;
//GameWindow *listboxLobbyGameInfo = NULL;
static const Image *pingImages[3] = { NULL, NULL, NULL };
static void gameTooltip(GameWindow *window,
WinInstanceData *instData,
UnsignedInt mouse)
{
Int x, y, row, col;
x = LOLONGTOSHORT(mouse);
y = HILONGTOSHORT(mouse);
GadgetListBoxGetEntryBasedOnXY(window, x, y, row, col);
if (row == -1 || col == -1)
{
TheMouse->setCursorTooltip( UnicodeString::TheEmptyString);//TheGameText->fetch("TOOLTIP:GamesBeingFormed") );
return;
}
Int gameID = (Int)GadgetListBoxGetItemData(window, row, 0);
GameSpyStagingRoom *room = TheGameSpyInfo->findStagingRoomByID(gameID);
if (!room)
{
TheMouse->setCursorTooltip( TheGameText->fetch("TOOLTIP:UnknownGame") );
return;
}
if (col == COLUMN_PING)
{
#ifdef DEBUG_LOGGING
UnicodeString s;
s.format(L"Ping is %d ms (cutoffs are %d ms and %d ms\n%hs local pings\n%hs remote pings",
room->getPingAsInt(), TheGameSpyConfig->getPingCutoffGood(), TheGameSpyConfig->getPingCutoffBad(),
TheGameSpyInfo->getPingString().str(), room->getPingString().str()
);
TheMouse->setCursorTooltip( s, 10, NULL, 2.0f ); // the text and width are the only params used. the others are the default values.
#else
TheMouse->setCursorTooltip( TheGameText->fetch("TOOLTIP:PingInfo"), 10, NULL, 2.0f ); // the text and width are the only params used. the others are the default values.
#endif
return;
}
if (col == COLUMN_NUMPLAYERS)
{
TheMouse->setCursorTooltip( TheGameText->fetch("TOOLTIP:NumberOfPlayers"), 10, NULL, 2.0f ); // the text and width are the only params used. the others are the default values.
return;
}
if (col == COLUMN_PASSWORD)
{
if (room->getHasPassword())
{
UnicodeString checkTooltip =TheGameText->fetch("TOOTIP:Password");
if(!checkTooltip.compare(L"Password required to joing game"))
checkTooltip.set(L"Password required to join game");
TheMouse->setCursorTooltip( checkTooltip, 10, NULL, 2.0f ); // the text and width are the only params used. the others are the default values.
}
else
TheMouse->setCursorTooltip( UnicodeString::TheEmptyString );
return;
}
UnicodeString tooltip;
UnicodeString mapName;
const MapMetaData *md = TheMapCache->findMap(room->getMap());
if (md)
{
mapName = md->m_displayName;
}
else
{
const char *start = room->getMap().reverseFind('\\');
if (start)
{
++start;
}
else
{
start = room->getMap().str();
}
mapName.translate( start );
}
UnicodeString tmp;
tooltip.format(TheGameText->fetch("TOOLTIP:GameInfoGameName"), room->getGameName().str());
if (room->getLadderPort() != 0)
{
const LadderInfo *linfo = TheLadderList->findLadder(room->getLadderIP(), room->getLadderPort());
if (linfo)
{
tmp.format(TheGameText->fetch("TOOLTIP:GameInfoLadderName"), linfo->name.str());
tooltip.concat(tmp);
}
}
if (room->getExeCRC() != TheGlobalData->m_exeCRC || room->getIniCRC() != TheGlobalData->m_iniCRC)
{
tmp.format(TheGameText->fetch("TOOLTIP:InvalidGameVersion"), mapName.str());
tooltip.concat(tmp);
}
tmp.format(TheGameText->fetch("TOOLTIP:GameInfoMap"), mapName.str());
tooltip.concat(tmp);
AsciiString aPlayer;
UnicodeString player;
Int numPlayers = 0;
for (Int i=0; i<MAX_SLOTS; ++i)
{
GameSpyGameSlot *slot = room->getGameSpySlot(i);
if (i == 0 && (!slot || !slot->isHuman()))
{
DEBUG_CRASH(("About to tooltip a non-hosted game!\n"));
}
if (slot && slot->isHuman())
{
tmp.format(TheGameText->fetch("TOOLTIP:GameInfoPlayer"), slot->getName().str(), slot->getWins(), slot->getLosses());
tooltip.concat(tmp);
++numPlayers;
}
else if (slot && slot->isAI())
{
++numPlayers;
switch(slot->getState())
{
case SLOT_EASY_AI:
tooltip.concat(L'\n');
tooltip.concat(TheGameText->fetch("GUI:EasyAI"));
break;
case SLOT_MED_AI:
tooltip.concat(L'\n');
tooltip.concat(TheGameText->fetch("GUI:MediumAI"));
break;
case SLOT_BRUTAL_AI:
tooltip.concat(L'\n');
tooltip.concat(TheGameText->fetch("GUI:HardAI"));
break;
}
}
}
DEBUG_ASSERTCRASH(numPlayers, ("Tooltipping a 0-player game!\n"));
TheMouse->setCursorTooltip( tooltip, 10, NULL, 2.0f ); // the text and width are the only params used. the others are the default values.
}
static Bool isSmall = TRUE;
GameWindow *GetGameListBox( void )
{
return listboxLobbyGamesLarge;
}
GameWindow *GetGameInfoListBox( void )
{
return NULL;
}
NameKeyType GetGameListBoxID( void )
{
return listboxLobbyGamesLargeID;
}
NameKeyType GetGameInfoListBoxID( void )
{
return NAMEKEY_INVALID;
}
void GrabWindowInfo( void )
{
isSmall = TRUE;
parentID = NAMEKEY( "WOLCustomLobby.wnd:WOLLobbyMenuParent" );
parent = TheWindowManager->winGetWindowFromId(NULL, parentID);
pingImages[0] = TheMappedImageCollection->findImageByName("Ping03");
pingImages[1] = TheMappedImageCollection->findImageByName("Ping02");
pingImages[2] = TheMappedImageCollection->findImageByName("Ping01");
DEBUG_ASSERTCRASH(pingImages[0], ("Can't find ping image!"));
DEBUG_ASSERTCRASH(pingImages[1], ("Can't find ping image!"));
DEBUG_ASSERTCRASH(pingImages[2], ("Can't find ping image!"));
// parentGameListSmallID = NAMEKEY( "WOLCustomLobby.wnd:ParentGameListSmall" );
// parentGameListSmall = TheWindowManager->winGetWindowFromId(NULL, parentGameListSmallID);
parentGameListLargeID = NAMEKEY( "WOLCustomLobby.wnd:ParentGameListLarge" );
parentGameListLarge = TheWindowManager->winGetWindowFromId(NULL, parentGameListLargeID);
listboxLobbyGamesSmallID = NAMEKEY( "WOLCustomLobby.wnd:ListboxGames" );
// listboxLobbyGamesSmall = TheWindowManager->winGetWindowFromId(NULL, listboxLobbyGamesSmallID);
// listboxLobbyGamesSmall->winSetTooltipFunc(gameTooltip);
listboxLobbyGamesLargeID = NAMEKEY( "WOLCustomLobby.wnd:ListboxGamesLarge" );
listboxLobbyGamesLarge = TheWindowManager->winGetWindowFromId(NULL, listboxLobbyGamesLargeID);
listboxLobbyGamesLarge->winSetTooltipFunc(gameTooltip);
//
// listboxLobbyGameInfoID = NAMEKEY( "WOLCustomLobby.wnd:ListboxGameInfo" );
// listboxLobbyGameInfo = TheWindowManager->winGetWindowFromId(NULL, listboxLobbyGameInfoID);
buttonSortAlphaID = NAMEKEY("WOLCustomLobby.wnd:ButtonSortAlpha");
buttonSortPingID = NAMEKEY("WOLCustomLobby.wnd:ButtonSortPing");
buttonSortBuddiesID = NAMEKEY("WOLCustomLobby.wnd:ButtonSortBuddies");
windowSortAlphaID = NAMEKEY("WOLCustomLobby.wnd:WindowSortAlpha");
windowSortPingID = NAMEKEY("WOLCustomLobby.wnd:WindowSortPing");
windowSortBuddiesID = NAMEKEY("WOLCustomLobby.wnd:WindowSortBuddies");
buttonSortAlpha = TheWindowManager->winGetWindowFromId(parent, buttonSortAlphaID);
buttonSortPing = TheWindowManager->winGetWindowFromId(parent, buttonSortPingID);
buttonSortBuddies = TheWindowManager->winGetWindowFromId(parent, buttonSortBuddiesID);
windowSortAlpha = TheWindowManager->winGetWindowFromId(parent, windowSortAlphaID);
windowSortPing = TheWindowManager->winGetWindowFromId(parent, windowSortPingID);
windowSortBuddies = TheWindowManager->winGetWindowFromId(parent, windowSortBuddiesID);
showSortIcons();
}
void ReleaseWindowInfo( void )
{
isSmall = TRUE;
parent = NULL;
// parentGameListSmall = NULL;
parentGameListLarge = NULL;
// listboxLobbyGamesSmall = NULL;
listboxLobbyGamesLarge = NULL;
// listboxLobbyGameInfo = NULL;
buttonSortAlpha = NULL;
buttonSortPing = NULL;
buttonSortBuddies = NULL;
windowSortAlpha = NULL;
windowSortPing = NULL;
windowSortBuddies = NULL;
}
typedef std::set<GameSpyStagingRoom *> BuddyGameSet;
static BuddyGameSet *theBuddyGames = NULL;
static void populateBuddyGames(void)
{
BuddyInfoMap *m = TheGameSpyInfo->getBuddyMap();
theBuddyGames = NEW BuddyGameSet;
if (!m)
{
return;
}
for (BuddyInfoMap::const_iterator bit = m->begin(); bit != m->end(); ++bit)
{
BuddyInfo info = bit->second;
if (info.m_status == GP_STAGING)
{
StagingRoomMap *srm = TheGameSpyInfo->getStagingRoomList();
for (StagingRoomMap::iterator srmIt = srm->begin(); srmIt != srm->end(); ++srmIt)
{
GameSpyStagingRoom *game = srmIt->second;
game->cleanUpSlotPointers();
const GameSpyGameSlot *slot = game->getGameSpySlot(0);
if (slot && slot->getName() == info.m_locationString)
{
theBuddyGames->insert(game);
break;
}
}
}
}
}
static void clearBuddyGames(void)
{
if (theBuddyGames)
delete theBuddyGames;
theBuddyGames = NULL;
}
struct GameSortStruct
{
bool operator()(GameSpyStagingRoom *g1, GameSpyStagingRoom *g2)
{
// sort CRC mismatches to the bottom
Bool g1Good = (g1->getExeCRC() != TheGlobalData->m_exeCRC || g1->getIniCRC() != TheGlobalData->m_iniCRC);
Bool g2Good = (g1->getExeCRC() != TheGlobalData->m_exeCRC || g1->getIniCRC() != TheGlobalData->m_iniCRC);
if ( g1Good ^ g2Good )
{
return g1Good;
}
// sort games with private ladders to the bottom
Bool g1UnknownLadder = (g1->getLadderPort() && TheLadderList->findLadder(g1->getLadderIP(), g1->getLadderPort()) == NULL);
Bool g2UnknownLadder = (g2->getLadderPort() && TheLadderList->findLadder(g2->getLadderIP(), g2->getLadderPort()) == NULL);
if ( g1UnknownLadder ^ g2UnknownLadder )
{
return g2UnknownLadder;
}
// sort full games to the bottom
Bool g1Full = (g1->getNumNonObserverPlayers() == g1->getMaxPlayers() || g1->getNumPlayers() == MAX_SLOTS);
Bool g2Full = (g2->getNumNonObserverPlayers() == g2->getMaxPlayers() || g2->getNumPlayers() == MAX_SLOTS);
if ( g1Full ^ g2Full )
{
return g2Full;
}
if (sortBuddies)
{
Bool g1HasBuddies = (theBuddyGames->find(g1) != theBuddyGames->end());
Bool g2HasBuddies = (theBuddyGames->find(g2) != theBuddyGames->end());
if ( g1HasBuddies ^ g2HasBuddies )
{
return g1HasBuddies;
}
}
switch(theGameSortType)
{
case GAMESORT_ALPHA_ASCENDING:
return wcsicmp(g1->getGameName().str(), g2->getGameName().str()) < 0;
break;
case GAMESORT_ALPHA_DESCENDING:
return wcsicmp(g1->getGameName().str(),g2->getGameName().str()) > 0;
break;
case GAMESORT_PING_ASCENDING:
return g1->getPingAsInt() < g2->getPingAsInt();
break;
case GAMESORT_PING_DESCENDING:
return g1->getPingAsInt() > g2->getPingAsInt();
break;
}
return false;
}
};
static Int insertGame( GameWindow *win, GameSpyStagingRoom *game, Bool showMap )
{
game->cleanUpSlotPointers();
Color gameColor = GameSpyColor[GSCOLOR_GAME];
if (game->getNumNonObserverPlayers() == game->getMaxPlayers() || game->getNumPlayers() == MAX_SLOTS)
{
gameColor = GameSpyColor[GSCOLOR_GAME_FULL];
}
if (game->getExeCRC() != TheGlobalData->m_exeCRC || game->getIniCRC() != TheGlobalData->m_iniCRC)
{
gameColor = GameSpyColor[GSCOLOR_GAME_CRCMISMATCH];
}
UnicodeString gameName = game->getGameName();
if(TheGameSpyInfo->getDisallowAsianText())
{
const WideChar *buff = gameName.str();
Int length = gameName.getLength();
for(Int i = 0; i < length; ++i)
{
if(buff[i] >= 256)
return -1;
}
}
else if(TheGameSpyInfo->getDisallowNonAsianText())
{
const WideChar *buff = gameName.str();
Int length = gameName.getLength();
Bool hasUnicode = FALSE;
for(Int i = 0; i < length; ++i)
{
if(buff[i] >= 256)
{
hasUnicode = TRUE;
break;
}
}
if(!hasUnicode)
return -1;
}
Int index = GadgetListBoxAddEntryText(win, game->getGameName(), gameColor, -1, COLUMN_NAME);
GadgetListBoxSetItemData(win, (void *)game->getID(), index);
UnicodeString s;
if (showMap)
{
UnicodeString mapName;
const MapMetaData *md = TheMapCache->findMap(game->getMap());
if (md)
{
mapName = md->m_displayName;
}
else
{
const char *start = game->getMap().reverseFind('\\');
if (start)
{
++start;
}
else
{
start = game->getMap().str();
}
mapName.translate( start );
}
GadgetListBoxAddEntryText(win, mapName, gameColor, index, COLUMN_MAP);
const LadderInfo * li = TheLadderList->findLadder(game->getLadderIP(), game->getLadderPort());
if (li)
{
GadgetListBoxAddEntryText(win, li->name, gameColor, index, COLUMN_LADDER);
}
else if (game->getLadderPort())
{
GadgetListBoxAddEntryText(win, TheGameText->fetch("GUI:UnknownLadder"), gameColor, index, COLUMN_LADDER);
}
else
{
GadgetListBoxAddEntryText(win, TheGameText->fetch("GUI:NoLadder"), gameColor, index, COLUMN_LADDER);
}
}
else
{
GadgetListBoxAddEntryText(win, UnicodeString(L" "), gameColor, index, COLUMN_MAP);
GadgetListBoxAddEntryText(win, UnicodeString(L" "), gameColor, index, COLUMN_LADDER);
}
s.format(L"%d/%d", game->getReportedNumPlayers(), game->getReportedMaxPlayers());
GadgetListBoxAddEntryText(win, s, gameColor, index, COLUMN_NUMPLAYERS);
if (game->getHasPassword())
{
const Image *img = TheMappedImageCollection->findImageByName("Password");
Int width = 10, height = 10;
if (img)
{
width = img->getImageWidth();
height = img->getImageHeight();
}
GadgetListBoxAddEntryImage(win, img, index, COLUMN_PASSWORD, width, height);
}
else
{
GadgetListBoxAddEntryText(win, UnicodeString(L" "), gameColor, index, COLUMN_PASSWORD);
}
if (game->getAllowObservers())
{
const Image *img = TheMappedImageCollection->findImageByName("Observer");
GadgetListBoxAddEntryImage(win, img, index, COLUMN_OBSERVER);
}
else
{
GadgetListBoxAddEntryText(win, UnicodeString(L" "), gameColor, index, COLUMN_OBSERVER);
}
s.format(L"%d", game->getPingAsInt());
GadgetListBoxAddEntryText(win, s, gameColor, index, COLUMN_PING);
Int ping = game->getPingAsInt();
Int width = 10, height = 10;
if (pingImages[0])
{
width = pingImages[0]->getImageWidth();
height = pingImages[0]->getImageHeight();
}
// CLH picking an arbitrary number for our ping display
if (ping < TheGameSpyConfig->getPingCutoffGood())
{
GadgetListBoxAddEntryImage(win, pingImages[0], index, COLUMN_PING, width, height);
}
else if (ping < TheGameSpyConfig->getPingCutoffBad())
{
GadgetListBoxAddEntryImage(win, pingImages[1], index, COLUMN_PING, width, height);
}
else
{
GadgetListBoxAddEntryImage(win, pingImages[2], index, COLUMN_PING, width, height);
}
return index;
}
void RefreshGameListBox( GameWindow *win, Bool showMap )
{
if (!win)
return;
// save off selection
Int selectedIndex = -1;
Int indexToSelect = -1;
Int selectedID = 0;
GadgetListBoxGetSelected(win, &selectedIndex);
if (selectedIndex != -1 )
{
selectedID = (Int)GadgetListBoxGetItemData(win, selectedIndex);
}
int prevPos = GadgetListBoxGetTopVisibleEntry( win );
// empty listbox
GadgetListBoxReset(win);
// sort our games
typedef std::multiset<GameSpyStagingRoom *, GameSortStruct> SortedGameList;
SortedGameList sgl;
StagingRoomMap *srm = TheGameSpyInfo->getStagingRoomList();
populateBuddyGames();
for (StagingRoomMap::iterator srmIt = srm->begin(); srmIt != srm->end(); ++srmIt)
{
sgl.insert(srmIt->second);
}
// populate listbox
for (SortedGameList::iterator sglIt = sgl.begin(); sglIt != sgl.end(); ++sglIt)
{
GameSpyStagingRoom *game = *sglIt;
if (game)
{
Int index = insertGame(win, game, showMap);
if (game->getID() == selectedID)
{
indexToSelect = index;
}
}
}
clearBuddyGames();
// restore selection
GadgetListBoxSetSelected(win, indexToSelect); // even for -1, so we can disable the 'Join Game' button
// if(prevPos > 10)
GadgetListBoxSetTopVisibleEntry( win, prevPos );//+ 1
if (indexToSelect < 0 && selectedID)
{
TheWindowManager->winSetLoneWindow(NULL);
}
}
void RefreshGameInfoListBox( GameWindow *mainWin, GameWindow *win )
{
// if (!mainWin || !win)
// return;
//
// GadgetListBoxReset(win);
//
// Int selected = -1;
// GadgetListBoxGetSelected(mainWin, &selected);
// if (selected < 0)
// {
// return;
// }
//
// Int selectedID = (Int)GadgetListBoxGetItemData(mainWin, selected);
// if (selectedID < 0)
// {
// return;
// }
//
// StagingRoomMap *srm = TheGameSpyInfo->getStagingRoomList();
// StagingRoomMap::iterator srmIt = srm->find(selectedID);
// if (srmIt != srm->end())
// {
// GameSpyStagingRoom *theRoom = srmIt->second;
// theRoom->cleanUpSlotPointers();
//
// // game name
//// GadgetListBoxAddEntryText(listboxLobbyGameInfo, theRoom->getGameName(), GameSpyColor[GSCOLOR_DEFAULT], -1);
//
// const LadderInfo * li = TheLadderList->findLadder(theRoom->getLadderIP(), theRoom->getLadderPort());
// if (li)
// {
// UnicodeString tmp;
// tmp.format(TheGameText->fetch("TOOLTIP:LadderName"), li->name.str());
// GadgetListBoxAddEntryText(listboxLobbyGameInfo, tmp, GameSpyColor[GSCOLOR_DEFAULT], -1);
// }
// else if (theRoom->getLadderPort())
// {
// GadgetListBoxAddEntryText(listboxLobbyGameInfo, TheGameText->fetch("TOOLTIP:UnknownLadder"), GameSpyColor[GSCOLOR_DEFAULT], -1);
// }
// else
// {
// GadgetListBoxAddEntryText(listboxLobbyGameInfo, TheGameText->fetch("TOOLTIP:NoLadder"), GameSpyColor[GSCOLOR_DEFAULT], -1);
// }
//
// if (theRoom->getExeCRC() != TheGlobalData->m_exeCRC || theRoom->getIniCRC() != TheGlobalData->m_iniCRC)
// {
// GadgetListBoxAddEntryText(listboxLobbyGameInfo, TheGameText->fetch("TOOLTIP:InvalidGameVersionSingleLine"), GameSpyColor[GSCOLOR_DEFAULT], -1);
// }
//
// // map name
// UnicodeString mapName;
// const MapMetaData *md = TheMapCache->findMap(theRoom->getMap());
// if (md)
// {
// mapName = md->m_displayName;
// }
// else
// {
// const char *start = theRoom->getMap().reverseFind('\\');
// if (start)
// {
// ++start;
// }
// else
// {
// start = theRoom->getMap().str();
// }
// mapName.translate( start );
// }
//
// GadgetListBoxAddEntryText(listboxLobbyGameInfo, mapName, GameSpyColor[GSCOLOR_DEFAULT], -1);
//
// // player list (rank, win/loss, side)
// for (Int i=0; i<MAX_SLOTS; ++i)
// {
// const GameSpyGameSlot *slot = theRoom->getGameSpySlot(i);
// if (slot && slot->isHuman())
// {
// UnicodeString theName, theRating, thePlayerTemplate;
// Int colorIdx = slot->getColor();
// theName = slot->getName();
// theRating.format(L" (%d-%d)", slot->getWins(), slot->getLosses());
// const PlayerTemplate * pt = ThePlayerTemplateStore->getNthPlayerTemplate(slot->getPlayerTemplate());
// if (pt)
// {
// thePlayerTemplate = pt->getDisplayName();
// }
// else
// {
// thePlayerTemplate = TheGameText->fetch("GUI:Random");
// }
//
// UnicodeString theText;
// theText.format(L"%ls - %ls - %ls", theName.str(), thePlayerTemplate.str(), theRating.str());
//
// Int theColor = GameSpyColor[GSCOLOR_DEFAULT];
// const MultiplayerColorDefinition *mcd = TheMultiplayerSettings->getColor(colorIdx);
// if (mcd)
// {
// theColor = mcd->getColor();
// }
//
// GadgetListBoxAddEntryText(listboxLobbyGameInfo, theText, theColor, -1);
// }
// }
// }
}
void RefreshGameListBoxes( void )
{
GameWindow *main = GetGameListBox();
GameWindow *info = GetGameInfoListBox();
RefreshGameListBox( main, (info == NULL) );
if (info)
{
RefreshGameInfoListBox( main, info );
}
}
void ToggleGameListType( void )
{
isSmall = !isSmall;
if(isSmall)
{
parentGameListLarge->winHide(TRUE);
// parentGameListSmall->winHide(FALSE);
}
else
{
parentGameListLarge->winHide(FALSE);
// parentGameListSmall->winHide(TRUE);
}
RefreshGameListBoxes();
}

View File

@@ -0,0 +1,895 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
// FILE: MainMenuUtils.cpp
// Author: Matthew D. Campbell, Sept 2002
// Description: GameSpy version check, patch download, etc utils
///////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include <fcntl.h>
//#include "Common/Registry.h"
#include "Common/UserPreferences.h"
#include "Common/Version.h"
#include "GameClient/GameText.h"
#include "GameClient/MessageBox.h"
#include "GameClient/Shell.h"
#include "GameLogic/ScriptEngine.h"
#include "GameClient/ShellHooks.h"
#include "GameSpy/ghttp/ghttp.h"
#include "GameNetwork/DownloadManager.h"
#include "GameNetwork/GameSpy/BuddyThread.h"
#include "GameNetwork/GameSpy/MainMenuUtils.h"
#include "GameNetwork/GameSpy/PeerDefs.h"
#include "GameNetwork/GameSpy/PeerThread.h"
#include "WWDownload/Registry.h"
#include "WWDownload/URLBuilder.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
///////////////////////////////////////////////////////////////////////////////////////
static Bool checkingForPatchBeforeGameSpy = FALSE;
static Int checksLeftBeforeOnline = 0;
static Int timeThroughOnline = 0; // used to avoid having old callbacks cause problems
static Bool mustDownloadPatch = FALSE;
static Bool cantConnectBeforeOnline = FALSE;
static std::list<QueuedDownload> queuedDownloads;
static char *MOTDBuffer = NULL;
static char *configBuffer = NULL;
GameWindow *onlineCancelWindow = NULL;
static Bool s_asyncDNSThreadDone = TRUE;
static Bool s_asyncDNSThreadSucceeded = FALSE;
static Bool s_asyncDNSLookupInProgress = FALSE;
static HANDLE s_asyncDNSThreadHandle = NULL;
enum {
LOOKUP_INPROGRESS,
LOOKUP_FAILED,
LOOKUP_SUCCEEDED,
};
///////////////////////////////////////////////////////////////////////////////////////
static void startOnline( void );
static void reallyStartPatchCheck( void );
///////////////////////////////////////////////////////////////////////////////////////
// someone has hit a button allowing downloads to start
void StartDownloadingPatches( void )
{
if (queuedDownloads.empty())
{
HandleCanceledDownload();
return;
}
WindowLayout *layout;
layout = TheWindowManager->winCreateLayout( AsciiString( "Menus/DownloadMenu.wnd" ) );
layout->runInit();
layout->hide( FALSE );
layout->bringForward();
HandleCanceledDownload(FALSE);
DEBUG_ASSERTCRASH(TheDownloadManager, ("No download manager!"));
if (TheDownloadManager)
{
std::list<QueuedDownload>::iterator it = queuedDownloads.begin();
while (it != queuedDownloads.end())
{
QueuedDownload q = *it;
TheDownloadManager->queueFileForDownload(q.server, q.userName, q.password,
q.file, q.localFile, q.regKey, q.tryResume);
queuedDownloads.pop_front();
it = queuedDownloads.begin();
}
TheDownloadManager->downloadNextQueuedFile();
}
}
///////////////////////////////////////////////////////////////////////////////////////
// user agrees to patch before going online
static void patchBeforeOnlineCallback( void )
{
StartDownloadingPatches();
}
// user doesn't want to patch before going online
static void noPatchBeforeOnlineCallback( void )
{
queuedDownloads.clear();
if (mustDownloadPatch || cantConnectBeforeOnline)
{
// go back to normal
HandleCanceledDownload();
}
else
{
// clear out unneeded downloads and go on
startOnline();
}
}
///////////////////////////////////////////////////////////////////////////////////////
static Bool hasWriteAccess()
{
const char* filename = "PatchAccessTest.txt";
remove(filename);
int handle = _open( filename, _O_CREAT | _O_RDWR, _S_IREAD | _S_IWRITE);
if (handle == -1)
{
return false;
}
_close(handle);
remove(filename);
unsigned int val;
if (!GetUnsignedIntFromRegistry("", "Version", val))
{
return false;
}
if (!SetUnsignedIntInRegistry("", "Version", val))
{
return false;
}
return true;
}
///////////////////////////////////////////////////////////////////////////////////////
static void startOnline( void )
{
checkingForPatchBeforeGameSpy = FALSE;
DEBUG_ASSERTCRASH(checksLeftBeforeOnline==0, ("starting online with pending callbacks"));
if (onlineCancelWindow)
{
TheWindowManager->winDestroy(onlineCancelWindow);
onlineCancelWindow = NULL;
}
if (cantConnectBeforeOnline)
{
MessageBoxOk(TheGameText->fetch("GUI:CannotConnectToServservTitle"),
TheGameText->fetch("GUI:CannotConnectToServserv"),
noPatchBeforeOnlineCallback);
return;
}
if (queuedDownloads.size())
{
if (!hasWriteAccess())
{
MessageBoxOk(TheGameText->fetch("GUI:Error"),
TheGameText->fetch("GUI:MustHaveAdminRights"),
noPatchBeforeOnlineCallback);
}
else if (mustDownloadPatch)
{
MessageBoxOkCancel(TheGameText->fetch("GUI:PatchAvailable"),
TheGameText->fetch("GUI:MustPatchForOnline"),
patchBeforeOnlineCallback, noPatchBeforeOnlineCallback);
}
else
{
MessageBoxYesNo(TheGameText->fetch("GUI:PatchAvailable"),
TheGameText->fetch("GUI:CanPatchForOnline"),
patchBeforeOnlineCallback, noPatchBeforeOnlineCallback);
}
return;
}
TheScriptEngine->signalUIInteract(TheShellHookNames[SHELL_SCRIPT_HOOK_MAIN_MENU_ONLINE_SELECTED]);
DEBUG_ASSERTCRASH( !TheGameSpyBuddyMessageQueue, ("TheGameSpyBuddyMessageQueue exists!") );
DEBUG_ASSERTCRASH( !TheGameSpyPeerMessageQueue, ("TheGameSpyPeerMessageQueue exists!") );
DEBUG_ASSERTCRASH( !TheGameSpyInfo, ("TheGameSpyInfo exists!") );
SetUpGameSpy(MOTDBuffer, configBuffer);
if (MOTDBuffer)
{
delete[] MOTDBuffer;
MOTDBuffer = NULL;
}
if (configBuffer)
{
delete[] configBuffer;
configBuffer = NULL;
}
#ifdef ALLOW_NON_PROFILED_LOGIN
UserPreferences pref;
pref.load("GameSpyLogin.ini");
UserPreferences::const_iterator it = pref.find("useProfiles");
if (it != pref.end() && it->second.compareNoCase("yes") == 0)
#endif ALLOW_NON_PROFILED_LOGIN
TheShell->push( AsciiString("Menus/GameSpyLoginProfile.wnd") );
#ifdef ALLOW_NON_PROFILED_LOGIN
else
TheShell->push( AsciiString("Menus/GameSpyLoginQuick.wnd") );
#endif ALLOW_NON_PROFILED_LOGIN
}
///////////////////////////////////////////////////////////////////////////////////////
static void queuePatch(Bool mandatory, AsciiString downloadURL)
{
QueuedDownload q;
Bool success = TRUE;
AsciiString connectionType;
success &= downloadURL.nextToken(&connectionType, ":");
AsciiString server;
success &= downloadURL.nextToken(&server, ":/");
AsciiString user;
success &= downloadURL.nextToken(&user, ":@");
AsciiString pass;
success &= downloadURL.nextToken(&pass, "@/");
AsciiString filePath;
success &= downloadURL.nextToken(&filePath, "");
if (!success && user.isNotEmpty())
{
// no user/pass combo - move the file into it's proper place
filePath = user;
user = ""; // LFeenanEA - Credentials removed as per Security requirements
pass = "";
success = TRUE;
}
AsciiString fileStr = filePath;
const char *s = filePath.reverseFind('/');
if (s)
fileStr = s+1;
AsciiString fileName = "patches\\";
fileName.concat(fileStr);
DEBUG_LOG(("download URL split: %d [%s] [%s] [%s] [%s] [%s] [%s]\n",
success, connectionType.str(), server.str(), user.str(), pass.str(),
filePath.str(), fileName.str()));
if (!success)
return;
q.file = filePath;
q.localFile = fileName;
q.password = pass;
q.regKey = "";
q.server = server;
q.tryResume = TRUE;
q.userName = user;
std::list<QueuedDownload>::iterator it = queuedDownloads.begin();
while (it != queuedDownloads.end())
{
if (it->localFile == q.localFile)
return; // don't add it if it exists already (because we can check multiple times)
++it;
}
queuedDownloads.push_back(q);
}
///////////////////////////////////////////////////////////////////////////////////////
static GHTTPBool motdCallback( GHTTPRequest request, GHTTPResult result,
char * buffer, int bufferLen, void * param )
{
Int run = (Int)param;
if (run != timeThroughOnline)
{
DEBUG_CRASH(("Old callback being called!"));
return GHTTPTrue;
}
if (MOTDBuffer)
{
delete[] MOTDBuffer;
MOTDBuffer = NULL;
}
MOTDBuffer = NEW char[bufferLen];
memcpy(MOTDBuffer, buffer, bufferLen);
MOTDBuffer[bufferLen-1] = 0;
--checksLeftBeforeOnline;
DEBUG_ASSERTCRASH(checksLeftBeforeOnline>=0, ("Too many callbacks"));
if (onlineCancelWindow && !checksLeftBeforeOnline)
{
TheWindowManager->winDestroy(onlineCancelWindow);
onlineCancelWindow = NULL;
}
DEBUG_LOG(("------- Got MOTD before going online -------\n"));
DEBUG_LOG(("%s\n", (MOTDBuffer)?MOTDBuffer:""));
DEBUG_LOG(("--------------------------------------------\n"));
if (!checksLeftBeforeOnline)
startOnline();
return GHTTPTrue;
}
///////////////////////////////////////////////////////////////////////////////////////
static GHTTPBool configCallback( GHTTPRequest request, GHTTPResult result,
char * buffer, int bufferLen, void * param )
{
Int run = (Int)param;
if (run != timeThroughOnline)
{
DEBUG_CRASH(("Old callback being called!"));
return GHTTPTrue;
}
if (configBuffer)
{
delete[] configBuffer;
configBuffer = NULL;
}
if (result != GHTTPSuccess || bufferLen < 100)
{
if (!checkingForPatchBeforeGameSpy)
return GHTTPTrue;
--checksLeftBeforeOnline;
if (onlineCancelWindow && !checksLeftBeforeOnline)
{
TheWindowManager->winDestroy(onlineCancelWindow);
onlineCancelWindow = NULL;
}
cantConnectBeforeOnline = TRUE;
if (!checksLeftBeforeOnline)
{
startOnline();
}
return GHTTPTrue;
}
configBuffer = NEW char[bufferLen];
memcpy(configBuffer, buffer, bufferLen);
configBuffer[bufferLen-1] = 0;
AsciiString fname;
fname.format("%sGeneralsOnline\\Config.txt", TheGlobalData->getPath_UserData().str());
FILE *fp = fopen(fname.str(), "wb");
if (fp)
{
fwrite(configBuffer, bufferLen, 1, fp);
fclose(fp);
}
--checksLeftBeforeOnline;
DEBUG_ASSERTCRASH(checksLeftBeforeOnline>=0, ("Too many callbacks"));
if (onlineCancelWindow && !checksLeftBeforeOnline)
{
TheWindowManager->winDestroy(onlineCancelWindow);
onlineCancelWindow = NULL;
}
DEBUG_LOG(("Got Config before going online\n"));
if (!checksLeftBeforeOnline)
startOnline();
return GHTTPTrue;
}
///////////////////////////////////////////////////////////////////////////////////////
static GHTTPBool configHeadCallback( GHTTPRequest request, GHTTPResult result,
char * buffer, int bufferLen, void * param )
{
Int run = (Int)param;
if (run != timeThroughOnline)
{
DEBUG_CRASH(("Old callback being called!"));
return GHTTPTrue;
}
DEBUG_LOG(("HTTP head resp: res=%d, len=%d, buf=[%s]\n", result, bufferLen, buffer));
if (result == GHTTPSuccess)
{
DEBUG_LOG(("Headers are [%s]\n", ghttpGetHeaders( request )));
AsciiString headers(ghttpGetHeaders( request ));
AsciiString line;
while (headers.nextToken(&line, "\n\r"))
{
AsciiString key, val;
line.nextToken(&key, ": ");
line.nextToken(&val, ": \r\n");
if (key.compare("Content-Length") == 0 && val.isNotEmpty())
{
Int serverLen = atoi(val.str());
Int fileLen = 0;
AsciiString fname;
fname.format("%sGeneralsOnline\\Config.txt", TheGlobalData->getPath_UserData().str());
FILE *fp = fopen(fname.str(), "rb");
if (fp)
{
fseek(fp, 0, SEEK_END);
fileLen = ftell(fp);
fclose(fp);
}
if (serverLen == fileLen)
{
// we don't need to download the MOTD again
--checksLeftBeforeOnline;
DEBUG_ASSERTCRASH(checksLeftBeforeOnline>=0, ("Too many callbacks"));
if (onlineCancelWindow && !checksLeftBeforeOnline)
{
TheWindowManager->winDestroy(onlineCancelWindow);
onlineCancelWindow = NULL;
}
if (configBuffer)
{
delete[] configBuffer;
configBuffer = NULL;
}
AsciiString fname;
fname.format("%sGeneralsOnline\\Config.txt", TheGlobalData->getPath_UserData().str());
FILE *fp = fopen(fname.str(), "rb");
if (fp)
{
configBuffer = NEW char[fileLen];
fread(configBuffer, fileLen, 1, fp);
configBuffer[fileLen-1] = 0;
fclose(fp);
DEBUG_LOG(("Got Config before going online\n"));
if (!checksLeftBeforeOnline)
startOnline();
return GHTTPTrue;
}
}
}
}
}
// we need to download the MOTD again
std::string gameURL, mapURL;
std::string configURL, motdURL;
FormatURLFromRegistry(gameURL, mapURL, configURL, motdURL);
ghttpGet( configURL.c_str(), GHTTPFalse, configCallback, param );
return GHTTPTrue;
}
///////////////////////////////////////////////////////////////////////////////////////
static GHTTPBool gamePatchCheckCallback( GHTTPRequest request, GHTTPResult result, char * buffer, int bufferLen, void * param )
{
Int run = (Int)param;
if (run != timeThroughOnline)
{
DEBUG_CRASH(("Old callback being called!"));
return GHTTPTrue;
}
--checksLeftBeforeOnline;
DEBUG_ASSERTCRASH(checksLeftBeforeOnline>=0, ("Too many callbacks"));
DEBUG_LOG(("Result=%d, buffer=[%s], len=%d\n", result, buffer, bufferLen));
if (result != GHTTPSuccess)
{
if (!checkingForPatchBeforeGameSpy)
return GHTTPTrue;
cantConnectBeforeOnline = TRUE;
if (!checksLeftBeforeOnline)
{
startOnline();
}
return GHTTPTrue;
}
AsciiString message = buffer;
AsciiString line;
while (message.nextToken(&line, "\r\n"))
{
AsciiString type, req, url;
Bool ok = TRUE;
ok &= line.nextToken(&type, " ");
ok &= line.nextToken(&req, " ");
ok &= line.nextToken(&url, " ");
if (ok && type == "patch")
{
DEBUG_LOG(("Saw a patch: %d/[%s]\n", atoi(req.str()), url.str()));
queuePatch( atoi(req.str()), url );
if (atoi(req.str()))
{
mustDownloadPatch = TRUE;
}
}
else if (ok && type == "server")
{
}
}
if (!checksLeftBeforeOnline)
{
startOnline();
}
return GHTTPTrue;
}
///////////////////////////////////////////////////////////////////////////////////////
void CancelPatchCheckCallbackAndReopenDropdown( void )
{
HandleCanceledDownload();
CancelPatchCheckCallback();
}
void CancelPatchCheckCallback( void )
{
s_asyncDNSLookupInProgress = FALSE;
HandleCanceledDownload(FALSE); // don't dropdown
checkingForPatchBeforeGameSpy = FALSE;
checksLeftBeforeOnline = 0;
if (onlineCancelWindow)
{
TheWindowManager->winDestroy(onlineCancelWindow);
onlineCancelWindow = NULL;
}
queuedDownloads.clear();
if (MOTDBuffer)
{
delete[] MOTDBuffer;
MOTDBuffer = NULL;
}
if (configBuffer)
{
delete[] configBuffer;
configBuffer = NULL;
}
}
///////////////////////////////////////////////////////////////////////////////////////
static GHTTPBool overallStatsCallback( GHTTPRequest request, GHTTPResult result, char * buffer, int bufferLen, void * param )
{
DEBUG_LOG(("overallStatsCallback() - Result=%d, len=%d\n", result, bufferLen));
if (result != GHTTPSuccess)
{
return GHTTPTrue;
}
OverallStats USA, China, GLA;
AsciiString message = buffer;
Int state = STATS_MAX; // STATS_MAX == none
AsciiString line;
OverallStats *stats = NULL;
while (message.nextToken(&line, "\n"))
{
line.trim();
line.toLower();
if (strstr(line.str(), "today"))
{
state = STATS_TODAY;
}
else if (strstr(line.str(), "yesterday"))
{
state = STATS_YESTERDAY;
}
else if (strstr(line.str(), "all time"))
{
state = STATS_ALLTIME;
}
else if (strstr(line.str(), "last week"))
{
state = STATS_LASTWEEK;
}
else if (state != STATS_MAX && strstr(line.str(), "usa"))
{
stats = &USA;
}
else if (state != STATS_MAX && strstr(line.str(), "china"))
{
stats = &China;
}
else if (state != STATS_MAX && strstr(line.str(), "gla"))
{
stats = &GLA;
}
if (stats)
{
AsciiString totalLine, winsLine, lossesLine;
message.nextToken(&totalLine, "\n");
message.nextToken(&winsLine, "\n");
message.nextToken(&lossesLine, "\n");
while (totalLine.isNotEmpty() && !isdigit(totalLine.getCharAt(0)))
{
totalLine = totalLine.str()+1;
}
while (winsLine.isNotEmpty() && !isdigit(winsLine.getCharAt(0)))
{
winsLine = winsLine.str()+1;
}
while (lossesLine.isNotEmpty() && !isdigit(lossesLine.getCharAt(0)))
{
lossesLine = lossesLine.str()+1;
}
if (totalLine.isNotEmpty() && winsLine.isNotEmpty() && lossesLine.isNotEmpty())
{
stats->wins[state] = atoi(winsLine.str());
stats->losses[state] = atoi(lossesLine.str());
}
stats = NULL;
}
}
HandleOverallStats(USA, China, GLA);
return GHTTPTrue;
}
///////////////////////////////////////////////////////////////////////////////////////
static GHTTPBool numPlayersOnlineCallback( GHTTPRequest request, GHTTPResult result, char * buffer, int bufferLen, void * param )
{
DEBUG_LOG(("numPlayersOnlineCallback() - Result=%d, buffer=[%s], len=%d\n", result, buffer, bufferLen));
if (result != GHTTPSuccess)
{
return GHTTPTrue;
}
AsciiString message = buffer;
message.trim();
const char *s = message.reverseFind('\\');
if (!s)
{
return GHTTPTrue;
}
if (*s == '\\')
++s;
DEBUG_LOG(("Message was '%s', trimmed to '%s'=%d\n", buffer, s, atoi(s)));
HandleNumPlayersOnline(atoi(s));
return GHTTPTrue;
}
///////////////////////////////////////////////////////////////////////////////////////
void CheckOverallStats( void )
{
ghttpGet("http://gamestats.gamespy.com/ccgenerals/display.html",
GHTTPFalse, overallStatsCallback, NULL);
}
///////////////////////////////////////////////////////////////////////////////////////
void CheckNumPlayersOnline( void )
{
ghttpGet("http://launch.gamespyarcade.com/software/launch/arcadecount2.dll?svcname=ccgenerals",
GHTTPFalse, numPlayersOnlineCallback, NULL);
}
///////////////////////////////////////////////////////////////////////////////////////
DWORD WINAPI asyncGethostbynameThreadFunc( void * szName )
{
HOSTENT *he = gethostbyname( (const char *)szName );
if (he)
{
s_asyncDNSThreadSucceeded = TRUE;
}
else
{
s_asyncDNSThreadSucceeded = FALSE;
}
s_asyncDNSThreadDone = TRUE;
return 0;
}
///////////////////////////////////////////////////////////////////////////////////////
int asyncGethostbyname(char * szName)
{
static int stat = 0;
static unsigned long threadid;
if( stat == 0 )
{
/* Kick off gethostname thread */
s_asyncDNSThreadDone = FALSE;
s_asyncDNSThreadHandle = CreateThread( NULL, 0, asyncGethostbynameThreadFunc, szName, 0, &threadid );
if( s_asyncDNSThreadHandle == NULL )
{
return( LOOKUP_FAILED );
}
stat = 1;
}
if( stat == 1 )
{
if( s_asyncDNSThreadDone )
{
/* Thread finished */
stat = 0;
s_asyncDNSLookupInProgress = FALSE;
s_asyncDNSThreadHandle = NULL;
return( (s_asyncDNSThreadSucceeded)?LOOKUP_SUCCEEDED:LOOKUP_FAILED );
}
}
return( LOOKUP_INPROGRESS );
}
///////////////////////////////////////////////////////////////////////////////////////
// GameSpy's HTTP SDK has had at least 1 crash bug, so we're going to just bail and
// never try again if they crash us. We won't be able to get back online again (we'll
// time out) but at least we'll live.
static Bool isHttpOk = TRUE;
void HTTPThinkWrapper( void )
{
if (s_asyncDNSLookupInProgress)
{
Int ret = asyncGethostbyname("servserv.generals.ea.com");
switch(ret)
{
case LOOKUP_FAILED:
cantConnectBeforeOnline = TRUE;
startOnline();
break;
case LOOKUP_SUCCEEDED:
reallyStartPatchCheck();
break;
}
}
if (isHttpOk)
{
try
{
ghttpThink();
}
catch (...)
{
isHttpOk = FALSE; // we can't abort the login, since we might be done with the
// required checks and are fetching extras. If it is a required
// check, we'll time out normally.
}
}
}
///////////////////////////////////////////////////////////////////////////////////////
void StopAsyncDNSCheck( void )
{
if (s_asyncDNSThreadHandle)
{
#ifdef DEBUG_CRASHING
Int res =
#endif
TerminateThread(s_asyncDNSThreadHandle,0);
DEBUG_ASSERTCRASH(res, ("Could not terminate the Async DNS Lookup thread!")); // Thread still not killed!
}
s_asyncDNSThreadHandle = NULL;
s_asyncDNSLookupInProgress = FALSE;
}
///////////////////////////////////////////////////////////////////////////////////////
void StartPatchCheck( void )
{
checkingForPatchBeforeGameSpy = TRUE;
cantConnectBeforeOnline = FALSE;
timeThroughOnline++;
checksLeftBeforeOnline = 0;
onlineCancelWindow = MessageBoxCancel(TheGameText->fetch("GUI:CheckingForPatches"),
TheGameText->fetch("GUI:CheckingForPatches"), CancelPatchCheckCallbackAndReopenDropdown);
s_asyncDNSLookupInProgress = TRUE;
Int ret = asyncGethostbyname("servserv.generals.ea.com");
switch(ret)
{
case LOOKUP_FAILED:
cantConnectBeforeOnline = TRUE;
startOnline();
break;
case LOOKUP_SUCCEEDED:
reallyStartPatchCheck();
break;
}
}
///////////////////////////////////////////////////////////////////////////////////////
static void reallyStartPatchCheck( void )
{
checksLeftBeforeOnline = 4;
std::string gameURL, mapURL;
std::string configURL, motdURL;
FormatURLFromRegistry(gameURL, mapURL, configURL, motdURL);
std::string proxy;
if (GetStringFromRegistry("", "Proxy", proxy))
{
if (!proxy.empty())
{
ghttpSetProxy(proxy.c_str());
}
}
// check for a patch first
DEBUG_LOG(("Game patch check: [%s]\n", gameURL.c_str()));
DEBUG_LOG(("Map patch check: [%s]\n", mapURL.c_str()));
DEBUG_LOG(("Config: [%s]\n", configURL.c_str()));
DEBUG_LOG(("MOTD: [%s]\n", motdURL.c_str()));
ghttpGet(gameURL.c_str(), GHTTPFalse, gamePatchCheckCallback, (void *)timeThroughOnline);
ghttpGet(mapURL.c_str(), GHTTPFalse, gamePatchCheckCallback, (void *)timeThroughOnline);
ghttpHead(configURL.c_str(), GHTTPFalse, configHeadCallback, (void *)timeThroughOnline);
ghttpGet(motdURL.c_str(), GHTTPFalse, motdCallback, (void *)timeThroughOnline);
// check total game stats
CheckOverallStats();
// check the users online
CheckNumPlayersOnline();
}
///////////////////////////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,938 @@
/*
** Command & Conquer Generals(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/>.
*/
// FILE: PeerDefs.cpp //////////////////////////////////////////////////////
// Generals GameSpy Peer (chat) definitions
// Author: Matthew D. Campbell, June 2002
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include <set>
#include "Common/GameState.h"
#include "Common/RandomValue.h"
#include "Common/IgnorePreferences.h"
#include "Common/CustomMatchPreferences.h"
#include "Common/GameSpyMiscPreferences.h"
#include "Common/Recorder.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/PlayerTemplate.h"
#include "GameClient/MapUtil.h"
#include "GameClient/ShellHooks.h"
#include "GameClient/GameText.h"
#include "GameNetwork/GameSpy/LadderDefs.h"
#include "GameNetwork/GameSpy/PeerDefsImplementation.h"
#include "GameNetwork/GameSpy/BuddyThread.h"
#include "GameNetwork/GameSpy/PeerThread.h"
#include "GameNetwork/GameSpy/PingThread.h"
#include "GameNetwork/GameSpy/PersistentStorageThread.h"
#include "GameNetwork/GameSpy/GSConfig.h"
#include "GameNetwork/GameSpyOverlay.h"
#include "GameNetwork/RankPointValue.h"
#include "GameLogic/GameLogic.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
GameSpyInfoInterface *TheGameSpyInfo = NULL;
GameSpyStagingRoom *TheGameSpyGame = NULL;
void deleteNotificationBox( void );
bool AsciiComparator::operator()(AsciiString s1, AsciiString s2) const
{
return stricmp(s1.str(), s2.str()) < 0;
}
GameSpyInfo::GameSpyInfo()
{
reset();
TheGameSpyGame = &m_localStagingRoom;
m_isDisconAfterGameStart = FALSE;
}
GameSpyInfo::~GameSpyInfo()
{
TheGameSpyGame = NULL;
reset();
}
void GameSpyInfo::reset( void )
{
m_sawFullGameList = FALSE;
m_isDisconAfterGameStart = FALSE;
m_currentGroupRoomID = 0;
clearGroupRoomList();
clearStagingRoomList();
m_localStagingRoomID = 0;
m_buddyRequestMap.clear();
m_buddyMap.clear();
m_buddyMessages.clear();
m_joinedStagingRoom = 0;
m_isHosting = false;
m_localStagingRoomID = 0;
m_localStagingRoom.reset();
m_gotGroupRoomList = false;
m_localName = "";
m_localProfileID = 0;
m_maxMessagesPerUpdate = 100;
// Added By Sadullah Nader
// Initialization missing and needed
m_disallowAsainText = FALSE;
m_disallowNonAsianText = FALSE;
m_disconReason = 0;
m_localBaseName.clear();
m_localEmail.clear();
m_localPasswd.clear();
m_pingString.clear();
m_rawConfig.clear();
m_rawMotd.clear();
//
m_internalIP = m_externalIP = 0;
m_savedIgnoreMap.clear();
m_preorderPlayers.clear();
m_cachedLocalPlayerStats.reset();
m_additionalDisconnects = -1;
}
Bool GameSpyInfo::didPlayerPreorder( Int profileID ) const
{
std::set<Int>::const_iterator it = m_preorderPlayers.find(profileID);
return (it != m_preorderPlayers.end());
}
void GameSpyInfo::markPlayerAsPreorder( Int profileID )
{
m_preorderPlayers.insert(profileID);
}
void GameSpyInfo::setLocalIPs(UnsignedInt internalIP, UnsignedInt externalIP)
{
m_internalIP = internalIP;
m_externalIP = externalIP;
}
void GameSpyInfo::readAdditionalDisconnects( void )
{
m_additionalDisconnects = GetAdditionalDisconnectsFromUserFile(m_localProfileID);
DEBUG_LOG(("GameSpyInfo::readAdditionalDisconnects() found %d disconnects.\n", m_additionalDisconnects));
}
Int GameSpyInfo::getAdditionalDisconnects( void )
{
DEBUG_LOG(("GameSpyInfo::getAdditionalDisconnects() would have returned %d. Returning 0 instead.\n", m_additionalDisconnects));
return 0;
}
void GameSpyInfo::clearAdditionalDisconnects( void )
{
m_additionalDisconnects = 0;
}
GameSpyInfoInterface* GameSpyInfoInterface::createNewGameSpyInfoInterface( void )
{
return NEW GameSpyInfo;
}
Bool GameSpyInfo::amIHost( void )
{
return m_isHosting;
}
GameSpyStagingRoom* GameSpyInfo::getCurrentStagingRoom( void )
{
if (m_isHosting || m_joinedStagingRoom)
return &m_localStagingRoom;
StagingRoomMap::iterator it = m_stagingRooms.find(m_joinedStagingRoom);
if (it != m_stagingRooms.end())
return it->second;
return NULL;
}
void GameSpyInfo::setGameOptions( void )
{
if (!m_isHosting)
return;
// set options for game lists, and UTM players in-game
PeerRequest req;
req.peerRequestType = PeerRequest::PEERREQUEST_SETGAMEOPTIONS;
req.options = GameInfoToAsciiString(&m_localStagingRoom).str();
Int i;
AsciiString mapName = TheGameState->realMapPathToPortableMapPath(m_localStagingRoom.getMap());
AsciiString newMapName;
for (i=0; i<mapName.getLength(); ++i)
{
char c = mapName.getCharAt(i);
if (c != '\\')
newMapName.concat(c);
else
newMapName.concat('/');
}
req.gameOptsMapName = newMapName.str();
//const MapMetaData *md = TheMapCache->findMap(mapName);
//if (!md)
//return; // there really isn't any need to send info like this...
req.gameOptions.numPlayers = 0;
req.gameOptions.numObservers = 0;
Int numOpenSlots = 0;
AsciiString playerInfo = "";
for (i=0; i<MAX_SLOTS; ++i)
{
Int wins = 0, losses = 0, profileID = 0;
GameSpyGameSlot *slot = TheGameSpyGame->getGameSpySlot(i);
req.gameOptsPlayerNames[i] = "";
if (!slot->isOccupied())
{
if (slot->isOpen())
++numOpenSlots;
}
else
{
AsciiString playerName;
if (slot->isHuman())
{
playerName.translate(slot->getName());
req.gameOptsPlayerNames[i] = playerName.str();
PlayerInfoMap::iterator it = m_playerInfoMap.find(playerName);
if (it != m_playerInfoMap.end())
{
wins = it->second.m_wins;
losses = it->second.m_losses;
profileID = it->second.m_profileID;
}
req.gameOptions.wins[req.gameOptions.numObservers+req.gameOptions.numPlayers] = wins;
req.gameOptions.losses[req.gameOptions.numObservers+req.gameOptions.numPlayers] = losses;
req.gameOptions.profileID[req.gameOptions.numObservers+req.gameOptions.numPlayers] = profileID;
req.gameOptions.faction[req.gameOptions.numObservers+req.gameOptions.numPlayers] = slot->getPlayerTemplate();
req.gameOptions.color[req.gameOptions.numObservers+req.gameOptions.numPlayers] = slot->getColor();
if (slot->getPlayerTemplate() == PLAYERTEMPLATE_OBSERVER)
{
++req.gameOptions.numObservers;
}
else
{
++req.gameOptions.numPlayers;
}
}
else if (slot->isAI())
{
// add in AI players
switch (slot->getState())
{
case SLOT_EASY_AI:
playerName = "CE";
break;
case SLOT_MED_AI:
playerName = "CM";
break;
case SLOT_BRUTAL_AI:
playerName = "CH";
break;
}
req.gameOptsPlayerNames[i] = playerName.str(); // name is unused - we go off of the profileID
req.gameOptions.wins[req.gameOptions.numObservers+req.gameOptions.numPlayers] = 0;
req.gameOptions.losses[req.gameOptions.numObservers+req.gameOptions.numPlayers] = 0;
req.gameOptions.profileID[req.gameOptions.numObservers+req.gameOptions.numPlayers] = slot->getState();
req.gameOptions.faction[req.gameOptions.numObservers+req.gameOptions.numPlayers] = slot->getPlayerTemplate();
req.gameOptions.color[req.gameOptions.numObservers+req.gameOptions.numPlayers] = slot->getColor();
++req.gameOptions.numPlayers;
}
}
}
req.gameOptions.maxPlayers = numOpenSlots + req.gameOptions.numPlayers + req.gameOptions.numObservers;
TheGameSpyPeerMessageQueue->addRequest(req);
req.peerRequestType = PeerRequest::PEERREQUEST_UTMROOM;
req.UTM.isStagingRoom = TRUE;
req.id = "Pings/";
AsciiString pings;
for (i=0; i<MAX_SLOTS; ++i)
{
if (i!=0)
pings.concat(",");
GameSpyGameSlot *slot = TheGameSpyGame->getGameSpySlot(i);
if (slot && slot->isHuman())
{
pings.concat(slot->getPingString());
}
else
{
pings.concat("0");
}
}
req.options = pings.str();
TheGameSpyPeerMessageQueue->addRequest(req);
}
Bool GameSpyInfo::isBuddy( Int id )
{
return m_buddyMap.find(id) != m_buddyMap.end();
}
void GameSpyInfo::addGroupRoom( GameSpyGroupRoom room )
{
if (room.m_groupID == 0)
{
m_gotGroupRoomList = TRUE;
GroupRoomMap::iterator iter;
// figure out how many good strings we've got
std::vector<UnicodeString> names;
Int numRooms = 0;
for (iter = getGroupRoomList()->begin(); iter != getGroupRoomList()->end(); ++iter)
{
GameSpyGroupRoom room = iter->second;
if (room.m_groupID != TheGameSpyConfig->getQMChannel())
{
++numRooms;
AsciiString groupLabel;
groupLabel.format("GUI:%s", room.m_name.str());
Bool exists = FALSE;
UnicodeString groupName = TheGameText->fetch(groupLabel, &exists);
if (exists)
{
names.push_back(groupName);
}
}
}
if (!names.empty() && names.size() != numRooms)
{
// didn't get all names. fix up
Int nameIndex = 0;
Int timesThrough = 1; // start with USA Lobby 1
for (iter = TheGameSpyInfo->getGroupRoomList()->begin(); iter != TheGameSpyInfo->getGroupRoomList()->end(); ++iter)
{
GameSpyGroupRoom room = iter->second;
if (room.m_groupID != TheGameSpyConfig->getQMChannel())
{
room.m_translatedName.format(L"%ls %d", names[nameIndex].str(), timesThrough);
nameIndex = (nameIndex+1)%names.size();
m_groupRooms[room.m_groupID] = room;
if (!nameIndex)
{
// we've looped through the name list already. increment the timesThrough counter
++timesThrough;
}
}
}
}
}
else
{
DEBUG_LOG(("Adding group room %d (%s)\n", room.m_groupID, room.m_name.str()));
AsciiString groupLabel;
groupLabel.format("GUI:%s", room.m_name.str());
room.m_translatedName = TheGameText->fetch(groupLabel);
m_groupRooms[room.m_groupID] = room;
if ( !stricmp("quickmatch", room.m_name.str()) )
{
DEBUG_LOG(("Group room %d (%s) is the QuickMatch room\n", room.m_groupID, room.m_name.str()));
TheGameSpyConfig->setQMChannel(room.m_groupID);
}
}
}
void GameSpyInfo::joinGroupRoom( Int groupID )
{
if (groupID > 0)
{
PeerRequest req;
req.peerRequestType = PeerRequest::PEERREQUEST_JOINGROUPROOM;
req.groupRoom.id = groupID;
TheGameSpyPeerMessageQueue->addRequest(req);
m_playerInfoMap.clear();
}
}
void GameSpyInfo::leaveGroupRoom( void )
{
PeerRequest req;
req.peerRequestType = PeerRequest::PEERREQUEST_LEAVEGROUPROOM;
TheGameSpyPeerMessageQueue->addRequest(req);
setCurrentGroupRoom(0);
m_playerInfoMap.clear();
}
void GameSpyInfo::joinBestGroupRoom( void )
{
if (m_currentGroupRoomID)
{
DEBUG_LOG(("Bailing from GameSpyInfo::joinBestGroupRoom() - we were already in a room\n"));
m_currentGroupRoomID = 0;
return;
}
if (m_groupRooms.size())
{
int minID = -1;
int minPlayers = 1000;
GroupRoomMap::iterator iter = m_groupRooms.begin();
while (iter != m_groupRooms.end())
{
GameSpyGroupRoom room = iter->second;
DEBUG_LOG(("Group room %d: %s (%d, %d, %d, %d)\n", room.m_groupID, room.m_name.str(), room.m_numWaiting, room.m_maxWaiting,
room.m_numGames, room.m_numPlaying));
if (TheGameSpyConfig->getQMChannel() != room.m_groupID && minPlayers > 25 && room.m_numWaiting < minPlayers)
{
minID = room.m_groupID;
minPlayers = room.m_numWaiting;
}
++iter;
}
if (minID > 0)
{
PeerRequest req;
req.peerRequestType = PeerRequest::PEERREQUEST_JOINGROUPROOM;
req.groupRoom.id = minID;
TheGameSpyPeerMessageQueue->addRequest(req);
m_playerInfoMap.clear();
}
else
{
GSMessageBoxOk(TheGameText->fetch("GUI:Error"), TheGameText->fetch("GUI:GSGroupRoomJoinFail"), NULL);
}
}
else
{
GSMessageBoxOk(TheGameText->fetch("GUI:Error"), TheGameText->fetch("GUI:GSGroupRoomJoinFail"), NULL);
}
}
void GameSpyInfo::updatePlayerInfo( PlayerInfo pi, AsciiString oldNick )
{
if (!oldNick.isEmpty())
playerLeftGroupRoom(oldNick);
m_playerInfoMap[pi.m_name] = pi;
if (pi.m_preorder != 0)
markPlayerAsPreorder(pi.m_profileID);
}
void GameSpyInfo::playerLeftGroupRoom( AsciiString nick )
{
PlayerInfoMap::iterator it = m_playerInfoMap.find(nick);
if (it != m_playerInfoMap.end())
{
m_playerInfoMap.erase(it);
}
}
void GameSpyInfo::clearStagingRoomList( void )
{
Int numRoomsRemoved = 0;
m_sawFullGameList = FALSE;
m_stagingRoomsDirty = FALSE;
StagingRoomMap::iterator it = m_stagingRooms.begin();
while (it != m_stagingRooms.end())
{
++numRoomsRemoved;
delete it->second;
m_stagingRooms.erase(it);
it = m_stagingRooms.begin();
}
if (numRoomsRemoved > 0)
{
//m_stagingRoomsDirty = true; // only consider ourselves dirty if we actually removed some games.
}
}
void GameSpyInfo::addStagingRoom( GameSpyStagingRoom room )
{
removeStagingRoom(room);
GameSpyStagingRoom *newRoom = NEW GameSpyStagingRoom;
*newRoom = room;
newRoom->cleanUpSlotPointers();
m_stagingRooms[room.getID()] = newRoom;
m_stagingRoomsDirty = m_sawFullGameList;
}
void GameSpyInfo::updateStagingRoom( GameSpyStagingRoom room )
{
addStagingRoom(room);
}
void GameSpyInfo::removeStagingRoom( GameSpyStagingRoom room )
{
StagingRoomMap::iterator it = m_stagingRooms.find(room.getID());
if (it != m_stagingRooms.end())
{
delete it->second;
m_stagingRooms.erase(it);
m_stagingRoomsDirty = m_sawFullGameList;
}
}
Bool GameSpyInfo::hasStagingRoomListChanged( void )
{
Bool val = m_stagingRoomsDirty;
m_stagingRoomsDirty = false;
return val;
}
GameSpyStagingRoom* GameSpyInfo::findStagingRoomByID( Int id )
{
StagingRoomMap::iterator it = m_stagingRooms.find(id);
if (it != m_stagingRooms.end())
return it->second;
return NULL;
}
void GameSpyInfo::leaveStagingRoom( void )
{
m_localStagingRoomID = 0;
PeerRequest req;
req.peerRequestType = PeerRequest::PEERREQUEST_LEAVESTAGINGROOM;
TheGameSpyPeerMessageQueue->addRequest(req);
m_playerInfoMap.clear();
m_joinedStagingRoom = FALSE;
m_isHosting = FALSE;
}
void GameSpyInfo::markAsStagingRoomHost( void )
{
m_localStagingRoomID = 0;
m_joinedStagingRoom = FALSE; m_isHosting = TRUE;
m_localStagingRoom.reset();
m_localStagingRoom.enterGame();
m_localStagingRoom.setSeed(GetTickCount());
GameSlot newSlot;
UnicodeString uName;
uName.translate(m_localName);
newSlot.setState(SLOT_PLAYER, uName);
m_localStagingRoom.setLocalIP(m_externalIP);
newSlot.setIP(m_externalIP);
m_localStagingRoom.setSlot(0,newSlot);
m_localStagingRoom.setLocalName(m_localName);
TheMapCache->updateCache();
m_localStagingRoom.setMap(getDefaultMap(TRUE));
m_localStagingRoom.adjustSlotsForMap(); // close slots that the map can't hold. BGC
}
void GameSpyInfo::markAsStagingRoomJoiner( Int game )
{
m_localStagingRoomID = game;
m_joinedStagingRoom = TRUE; m_isHosting = FALSE;
m_localStagingRoom.reset();
m_localStagingRoom.enterGame();
StagingRoomMap::iterator it = m_stagingRooms.find(game);
if (it != m_stagingRooms.end())
{
GameSpyStagingRoom *info = it->second;
info->cleanUpSlotPointers();
AsciiString options = GameInfoToAsciiString(info);
#ifdef DEBUG_CRASHING
Bool res =
#endif
ParseAsciiStringToGameInfo(&m_localStagingRoom, options);
DEBUG_ASSERTCRASH(res, ("Could not parse game info \"%s\"", options.str()));
m_localStagingRoom.setInGame();
m_localStagingRoom.setLocalName(m_localName);
m_localStagingRoom.setExeCRC(info->getExeCRC());
m_localStagingRoom.setIniCRC(info->getIniCRC());
m_localStagingRoom.setAllowObservers(info->getAllowObservers());
m_localStagingRoom.setHasPassword(info->getHasPassword());
m_localStagingRoom.setGameName(info->getGameName());
DEBUG_LOG(("Joining game: host is %ls\n", m_localStagingRoom.getConstSlot(0)->getName().str()));
}
}
void GameSpyInfo::setMOTD( const AsciiString& motd )
{
m_rawMotd = motd;
}
const AsciiString& GameSpyInfo::getMOTD( void )
{
return m_rawMotd;
}
void GameSpyInfo::setConfig( const AsciiString& config )
{
m_rawConfig = config;
}
const AsciiString& GameSpyInfo::getConfig( void )
{
return m_rawConfig;
}
// --------------------------------------------------------------
void SetUpGameSpy( const char *motdBuffer, const char *configBuffer )
{
if (!motdBuffer)
motdBuffer = "";
if (!configBuffer)
configBuffer = "";
TearDownGameSpy();
AsciiString dir = TheGlobalData->getPath_UserData();
CreateDirectory(dir.str(), NULL);
dir.format("%sGeneralsOnline", TheGlobalData->getPath_UserData().str());
CreateDirectory(dir.str(), NULL);
dir.format("%sGeneralsOnline\\Ladders", TheGlobalData->getPath_UserData().str());
CreateDirectory(dir.str(), NULL);
TheGameSpyBuddyMessageQueue = GameSpyBuddyMessageQueueInterface::createNewMessageQueue();
TheGameSpyBuddyMessageQueue->startThread();
TheGameSpyPeerMessageQueue = GameSpyPeerMessageQueueInterface::createNewMessageQueue();
TheGameSpyPeerMessageQueue->startThread();
TheGameSpyPSMessageQueue = GameSpyPSMessageQueueInterface::createNewMessageQueue();
TheGameSpyPSMessageQueue->startThread();
/*
TheGameSpyGame = NEW GameSpyStagingRoom;
*/
TheGameSpyInfo = GameSpyInfoInterface::createNewGameSpyInfoInterface();
TheGameSpyInfo->setMOTD(motdBuffer);
TheGameSpyInfo->setConfig(configBuffer);
CustomMatchPreferences pref;
TheGameSpyInfo->setDisallowAsianText(pref.getDisallowAsianText());
TheGameSpyInfo->setDisallowNonAsianText( pref.getDisallowNonAsianText());
TheGameSpyConfig = GameSpyConfigInterface::create(configBuffer);
TheLadderList = NEW LadderList;
ThePinger = PingerInterface::createNewPingerInterface();
ThePinger->startThreads();
TheRankPointValues = NEW RankPoints;
}
void TearDownGameSpy( void )
{
// save off cached stats
if (TheGameSpyInfo && TheGameSpyInfo->getLocalProfileID())
{
// /* This was done on the score screen, so there is no need to do it now.
// *
PSPlayerStats localPSStats = TheGameSpyPSMessageQueue->findPlayerStatsByID(TheGameSpyInfo->getLocalProfileID());
if (localPSStats.id != 0)
{
GameSpyMiscPreferences mPref;
mPref.setCachedStats(GameSpyPSMessageQueueInterface::formatPlayerKVPairs(localPSStats).c_str());
mPref.write();
}
// */
}
// End our threads before we kill off the singletons they reference. No crashy-crash for you!
if (TheGameSpyPSMessageQueue)
TheGameSpyPSMessageQueue->endThread();
if (TheGameSpyBuddyMessageQueue)
TheGameSpyBuddyMessageQueue->endThread();
if (TheGameSpyPeerMessageQueue)
TheGameSpyPeerMessageQueue->endThread();
if (ThePinger)
ThePinger->endThreads();
if(TheRankPointValues)
{
delete TheRankPointValues;
TheRankPointValues = NULL;
}
if (TheGameSpyPSMessageQueue)
{
delete TheGameSpyPSMessageQueue;
TheGameSpyPSMessageQueue = NULL;
}
if (TheGameSpyBuddyMessageQueue)
{
delete TheGameSpyBuddyMessageQueue;
TheGameSpyBuddyMessageQueue = NULL;
}
if (TheGameSpyPeerMessageQueue)
{
delete TheGameSpyPeerMessageQueue;
TheGameSpyPeerMessageQueue = NULL;
}
if (TheGameSpyInfo)
{
if (TheGameSpyInfo->getInternalIP())
{
// we've logged in before. mark us as logging out.
SignalUIInteraction(SHELL_SCRIPT_HOOK_GENERALS_ONLINE_LOGOUT);
}
delete TheGameSpyInfo;
TheGameSpyInfo = NULL;
}
if (ThePinger)
{
delete ThePinger;
ThePinger = NULL;
}
if (TheLadderList)
{
delete TheLadderList;
TheLadderList = NULL;
}
if (TheGameSpyConfig)
{
delete TheGameSpyConfig;
TheGameSpyConfig = NULL;
}
// make sure the notification box doesn't exist
deleteNotificationBox();
}
void GameSpyInfo::addToIgnoreList( AsciiString nick )
{
m_ignoreList.insert(nick);
}
void GameSpyInfo::removeFromIgnoreList( AsciiString nick )
{
m_ignoreList.erase(nick);
}
Bool GameSpyInfo::isIgnored( AsciiString nick )
{
return m_ignoreList.find(nick) != m_ignoreList.end();
}
IgnoreList GameSpyInfo::returnIgnoreList( void )
{
return m_ignoreList;
}
void GameSpyInfo::addToSavedIgnoreList( Int profileID, AsciiString nick)
{
m_savedIgnoreMap[profileID] = nick;
IgnorePreferences pref;
pref.setIgnore(nick, profileID, true);
pref.write();
}
void GameSpyInfo::removeFromSavedIgnoreList( Int profileID )
{
m_savedIgnoreMap.erase(profileID);
IgnorePreferences pref;
pref.setIgnore(AsciiString::TheEmptyString, profileID, false);
pref.write();
}
Bool GameSpyInfo::isSavedIgnored( Int profileID )
{
return m_savedIgnoreMap.find(profileID) != m_savedIgnoreMap.end();
}
SavedIgnoreMap GameSpyInfo::returnSavedIgnoreList( void )
{
return m_savedIgnoreMap;
}
static Int grabHexInt(const char *s)
{
char tmp[5] = "0xff";
tmp[2] = s[0];
tmp[3] = s[1];
Int b = strtol(tmp, NULL, 16);
return b;
}
Int GameSpyInfo::getPingValue( const AsciiString& otherPing )
{
if (m_pingString.getLength() != otherPing.getLength())
{
return TheGameSpyConfig->getPingTimeoutInMs();
}
if (m_pingString.getLength() % 2 != 0)
{
return TheGameSpyConfig->getPingTimeoutInMs();
}
Int best = 255+255;
const char *myStr = m_pingString.str();
const char *otherStr = otherPing.str();
while (*myStr)
{
Int myVal = grabHexInt(myStr);
Int otherVal = grabHexInt(otherStr);
Int val = myVal + otherVal;
best = (val < best) ? val : best;
myStr += 2;
otherStr += 2;
}
return best * TheGameSpyConfig->getPingTimeoutInMs() / (255+255);
}
Bool PlayerInfo::isIgnored( void )
{
return (m_profileID)?TheGameSpyInfo->isSavedIgnored(m_profileID):TheGameSpyInfo->isIgnored(m_name);
}
void GameSpyInfo::loadSavedIgnoreList( void )
{
m_savedIgnoreMap.clear();
IgnorePreferences prefs;
m_savedIgnoreMap = prefs.getIgnores();
}
void GameSpyInfo::setDisallowAsianText( Bool val )
{
m_disallowAsainText = val;
}
void GameSpyInfo::setDisallowNonAsianText( Bool val )
{
m_disallowNonAsianText = val;
}
Bool GameSpyInfo::getDisallowAsianText( void )
{
return m_disallowAsainText;
}
Bool GameSpyInfo::getDisallowNonAsianText(void )
{
return m_disallowNonAsianText;
}
void GameSpyInfo::setMaxMessagesPerUpdate( Int num )
{
m_maxMessagesPerUpdate = num;
}
Int GameSpyInfo::getMaxMessagesPerUpdate( void )
{
return m_maxMessagesPerUpdate;
}
/**This function is used to force an update of player's gamespy stats with an additional
disconnection. This is used upon starting a new game so that if user disconnects prior
to finishing game, the disconnection stays on the server. If he completes the game, we
remove this extra disconnection inside of populatePlayerInfo() on the ScoreScreen. This
seems like the only secure way to handle this issue since users can abort the process
before we can detect/log disconnections.*/
void GameSpyInfo::updateAdditionalGameSpyDisconnections(Int count)
{
if (TheRecorder->isMultiplayer() && TheGameLogic->isInInternetGame())
{
Int localID = TheGameSpyInfo->getLocalProfileID();
PSPlayerStats stats = TheGameSpyPSMessageQueue->findPlayerStatsByID(localID);
Player *player=ThePlayerList->getLocalPlayer();
Int ptIdx;
const PlayerTemplate *myTemplate = player->getPlayerTemplate();
DEBUG_LOG(("myTemplate = %X(%s)\n", myTemplate, myTemplate->getName().str()));
for (ptIdx = 0; ptIdx < ThePlayerTemplateStore->getPlayerTemplateCount(); ++ptIdx)
{
const PlayerTemplate *nthTemplate = ThePlayerTemplateStore->getNthPlayerTemplate(ptIdx);
DEBUG_LOG(("nthTemplate = %X(%s)\n", nthTemplate, nthTemplate->getName().str()));
if (nthTemplate == myTemplate)
{
break;
}
}
Bool anyAI = FALSE;
for (Int i=0; i<MAX_SLOTS; ++i)
{
const GameSlot *slot = TheGameInfo->getConstSlot(i);
if (slot->isAI())
{
anyAI = TRUE;
}
}
//Check for cases where we're not tracking stats.
if (anyAI || stats.id == 0 || myTemplate->isObserver() || player->getPlayerType() != PLAYER_HUMAN || player->isPlayerObserver())
return;
Int disCons=stats.discons[ptIdx];
disCons += count;
if (disCons < 0)
{ DEBUG_LOG(("updateAdditionalGameSpyDisconnections() - disconnection count below zero\n"));
return; //something is wrong here
}
stats.discons[ptIdx] = disCons; //add an additional disconnection to their stats.
//Add an additional disconnection to player stats.
PSRequest req;
req.requestType = PSRequest::PSREQUEST_UPDATEPLAYERSTATS;
req.email = TheGameSpyInfo->getLocalEmail().str();
req.nick = TheGameSpyInfo->getLocalBaseName().str();
req.password = TheGameSpyInfo->getLocalPassword().str();
req.player = stats;
req.addDesync = FALSE;
req.addDiscon = FALSE;
req.lastHouse = ptIdx;
TheGameSpyPSMessageQueue->addRequest(req);
TheGameSpyPSMessageQueue->trackPlayerStats(stats);
// force an update of our shtuff
PSResponse newResp;
newResp.responseType = PSResponse::PSRESPONSE_PLAYERSTATS;
newResp.player = stats;
TheGameSpyPSMessageQueue->addResponse(newResp);
// cache our stuff for easy reading next time
GameSpyMiscPreferences mPref;
mPref.setCachedStats(GameSpyPSMessageQueueInterface::formatPlayerKVPairs(stats).c_str());
mPref.write();
}
}

View File

@@ -0,0 +1,896 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: StagingRoomGameInfo.cpp //////////////////////////////////////////////////////
// Generals GameSpy GameInfo-related code
// Author: Matthew D. Campbell, July 2002
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameState.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/PlayerTemplate.h"
#include "Common/RandomValue.h"
#include "Common/ScoreKeeper.h"
#include "GameClient/GameText.h"
#include "GameClient/MapUtil.h"
#include "GameClient/Shell.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/VictoryConditions.h"
#include "GameNetwork/FileTransfer.h"
#include "GameNetwork/GameSpy/BuddyThread.h"
#include "GameNetwork/GameSpy/PeerDefs.h"
#include "GameNetwork/GameSpy/PersistentStorageThread.h"
#include "GameNetwork/GameSpy/ThreadUtils.h"
#include "GameNetwork/GameSpyOverlay.h"
#include "GameNetwork/NAT.h"
#include "GameNetwork/NetworkInterface.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// GameSpyGameSlot -------------------------------------------
GameSpyGameSlot::GameSpyGameSlot()
{
GameSlot();
m_gameSpyLogin.clear();
m_gameSpyLocale.clear();
m_profileID = 0;
m_wins = 0;
m_losses = 0;
m_rankPoints = 0;
m_favoriteSide = 0;
m_pingInt = 0;
// Added By Sadullah Nader
// Initializations missing and needed
m_profileID = 0;
m_pingStr.clear();
}
// Helper Functions ----------------------------------------
/*
** Function definitions for the MIB-II entry points.
*/
BOOL (__stdcall *SnmpExtensionInitPtr)(IN DWORD dwUpTimeReference, OUT HANDLE *phSubagentTrapEvent, OUT AsnObjectIdentifier *pFirstSupportedRegion);
BOOL (__stdcall *SnmpExtensionQueryPtr)(IN BYTE bPduType, IN OUT RFC1157VarBindList *pVarBindList, OUT AsnInteger32 *pErrorStatus, OUT AsnInteger32 *pErrorIndex);
LPVOID (__stdcall *SnmpUtilMemAllocPtr)(IN DWORD bytes);
VOID (__stdcall *SnmpUtilMemFreePtr)(IN LPVOID pMem);
typedef struct tConnInfoStruct {
unsigned int State;
unsigned long LocalIP;
unsigned short LocalPort;
unsigned long RemoteIP;
unsigned short RemotePort;
} ConnInfoStruct;
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
#endif
/***********************************************************************************************
* Get_Local_Chat_Connection_Address -- Which address are we using to talk to the chat server? *
* *
* *
* *
* INPUT: Ptr to address to return local address * *
* *
* OUTPUT: True if success *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 10/27/00 3:24PM ST : Created *
*=============================================================================================*/
Bool GetLocalChatConnectionAddress(AsciiString serverName, UnsignedShort serverPort, UnsignedInt& localIP)
{
//return false;
/*
** Local defines.
*/
enum {
CLOSED = 1,
LISTENING,
SYN_SENT,
SEN_RECEIVED,
ESTABLISHED,
FIN_WAIT,
FIN_WAIT2,
CLOSE_WAIT,
LAST_ACK,
CLOSING,
TIME_WAIT,
DELETE_TCB
};
enum {
tcpConnState = 1,
tcpConnLocalAddress,
tcpConnLocalPort,
tcpConnRemAddress,
tcpConnRemPort
};
/*
** Locals.
*/
unsigned char serverAddress[4];
unsigned char remoteAddress[4];
HANDLE trap_handle;
AsnObjectIdentifier first_supported_region;
std::vector<ConnInfoStruct> connectionVector;
int last_field;
int index;
AsnInteger error_status;
AsnInteger error_index;
int conn_entry_type_index;
int conn_entry_type;
Bool found;
/*
** Statics.
*/
static char _conn_state[][32] = {
"?",
"CLOSED",
"LISTENING",
"SYN_SENT",
"SEN_RECEIVED",
"ESTABLISHED",
"FIN_WAIT",
"FIN_WAIT2",
"CLOSE_WAIT",
"LAST_ACK",
"CLOSING",
"TIME_WAIT",
"DELETE_TCB"
};
DEBUG_LOG(("Finding local address used to talk to the chat server\n"));
DEBUG_LOG(("Current chat server name is %s\n", serverName.str()));
DEBUG_LOG(("Chat server port is %d\n", serverPort));
/*
** Get the address of the chat server.
*/
DEBUG_LOG( ("About to call gethostbyname\n"));
struct hostent *host_info = gethostbyname(serverName.str());
if (!host_info) {
DEBUG_LOG( ("gethostbyname failed! Error code %d\n", WSAGetLastError()));
return(false);
}
memcpy(serverAddress, &host_info->h_addr_list[0][0], 4);
unsigned long temp = *((unsigned long*)(&serverAddress[0]));
temp = ntohl(temp);
*((unsigned long*)(&serverAddress[0])) = temp;
DEBUG_LOG(("Host address is %d.%d.%d.%d\n", serverAddress[3], serverAddress[2], serverAddress[1], serverAddress[0]));
/*
** Load the MIB-II SNMP DLL.
*/
DEBUG_LOG(("About to load INETMIB1.DLL\n"));
HINSTANCE mib_ii_dll = LoadLibrary("inetmib1.dll");
if (mib_ii_dll == NULL) {
DEBUG_LOG(("Failed to load INETMIB1.DLL\n"));
return(false);
}
DEBUG_LOG(("About to load SNMPAPI.DLL\n"));
HINSTANCE snmpapi_dll = LoadLibrary("snmpapi.dll");
if (snmpapi_dll == NULL) {
DEBUG_LOG(("Failed to load SNMPAPI.DLL\n"));
FreeLibrary(mib_ii_dll);
return(false);
}
/*
** Get the function pointers into the .dll
*/
SnmpExtensionInitPtr = (int (__stdcall *)(unsigned long,void ** ,AsnObjectIdentifier *)) GetProcAddress(mib_ii_dll, "SnmpExtensionInit");
SnmpExtensionQueryPtr = (int (__stdcall *)(unsigned char,SnmpVarBindList *,long *,long *)) GetProcAddress(mib_ii_dll, "SnmpExtensionQuery");
SnmpUtilMemAllocPtr = (void *(__stdcall *)(unsigned long)) GetProcAddress(snmpapi_dll, "SnmpUtilMemAlloc");
SnmpUtilMemFreePtr = (void (__stdcall *)(void *)) GetProcAddress(snmpapi_dll, "SnmpUtilMemFree");
if (SnmpExtensionInitPtr == NULL || SnmpExtensionQueryPtr == NULL || SnmpUtilMemAllocPtr == NULL || SnmpUtilMemFreePtr == NULL) {
DEBUG_LOG(("Failed to get proc addresses for linked functions\n"));
FreeLibrary(snmpapi_dll);
FreeLibrary(mib_ii_dll);
return(false);
}
RFC1157VarBindList *bind_list_ptr = (RFC1157VarBindList *) SnmpUtilMemAllocPtr(sizeof(RFC1157VarBindList));
RFC1157VarBind *bind_ptr = (RFC1157VarBind *) SnmpUtilMemAllocPtr(sizeof(RFC1157VarBind));
/*
** OK, here we go. Try to initialise the .dll
*/
DEBUG_LOG(("About to init INETMIB1.DLL\n"));
int ok = SnmpExtensionInitPtr(GetCurrentTime(), &trap_handle, &first_supported_region);
if (!ok) {
/*
** Aw crap.
*/
DEBUG_LOG(("Failed to init the .dll\n"));
SnmpUtilMemFreePtr(bind_list_ptr);
SnmpUtilMemFreePtr(bind_ptr);
FreeLibrary(snmpapi_dll);
FreeLibrary(mib_ii_dll);
return(false);
}
/*
** Name of mib_ii object we want to query. See RFC 1213.
**
** iso.org.dod.internet.mgmt.mib-2.tcp.tcpConnTable.TcpConnEntry.tcpConnState
** 1 3 6 1 2 1 6 13 1 1
*/
unsigned int mib_ii_name[] = {1,3,6,1,2,1,6,13,1,1};
unsigned int *mib_ii_name_ptr = (unsigned int *) SnmpUtilMemAllocPtr(sizeof(mib_ii_name));
memcpy(mib_ii_name_ptr, mib_ii_name, sizeof(mib_ii_name));
/*
** Get the index of the conn entry data.
*/
conn_entry_type_index = ARRAY_SIZE(mib_ii_name) - 1;
/*
** Set up the bind list.
*/
bind_ptr->name.idLength = ARRAY_SIZE(mib_ii_name);
bind_ptr->name.ids = mib_ii_name;
bind_list_ptr->list = bind_ptr;
bind_list_ptr->len = 1;
/*
** We start with the tcpConnLocalAddress field.
*/
last_field = 1;
/*
** First connection.
*/
index = 0;
/*
** Suck out that tcp connection info....
*/
while (true) {
if (!SnmpExtensionQueryPtr(SNMP_PDU_GETNEXT, bind_list_ptr, &error_status, &error_index)) {
//if (!SnmpExtensionQueryPtr(ASN_RFC1157_GETNEXTREQUEST, bind_list_ptr, &error_status, &error_index)) {
DEBUG_LOG(("SnmpExtensionQuery returned false\n"));
SnmpUtilMemFreePtr(bind_list_ptr);
SnmpUtilMemFreePtr(bind_ptr);
FreeLibrary(snmpapi_dll);
FreeLibrary(mib_ii_dll);
return(false);
}
/*
** If this is something new we aren't looking for then we are done.
*/
if (bind_ptr->name.idLength < ARRAY_SIZE(mib_ii_name)) {
break;
}
/*
** Get the type of info we are looking at. See RFC1213.
**
** 1 = tcpConnState
** 2 = tcpConnLocalAddress
** 3 = tcpConnLocalPort
** 4 = tcpConnRemAddress
** 5 = tcpConnRemPort
**
** tcpConnState is one of the following...
**
** 1 closed
** 2 listen
** 3 synSent
** 4 synReceived
** 5 established
** 6 finWait1
** 7 finWait2
** 8 closeWait
** 9 lastAck
** 10 closing
** 11 timeWait
** 12 deleteTCB
*/
conn_entry_type = bind_ptr->name.ids[conn_entry_type_index];
if (last_field != conn_entry_type) {
index = 0;
last_field = conn_entry_type;
}
switch (conn_entry_type) {
/*
** 1. First field in the entry. Need to create a new connection info struct
** here to store this connection in.
*/
case tcpConnState:
{
ConnInfoStruct new_conn;
new_conn.State = bind_ptr->value.asnValue.number;
connectionVector.push_back(new_conn);
break;
}
/*
** 2. Local address field.
*/
case tcpConnLocalAddress:
DEBUG_ASSERTCRASH(index < connectionVector.size(), ("Bad connection index"));
connectionVector[index].LocalIP = *((unsigned long*)bind_ptr->value.asnValue.address.stream);
index++;
break;
/*
** 3. Local port field.
*/
case tcpConnLocalPort:
DEBUG_ASSERTCRASH(index < connectionVector.size(), ("Bad connection index"));
connectionVector[index].LocalPort = bind_ptr->value.asnValue.number;
//connectionVector[index]->LocalPort = ntohs(connectionVector[index]->LocalPort);
index++;
break;
/*
** 4. Remote address field.
*/
case tcpConnRemAddress:
DEBUG_ASSERTCRASH(index < connectionVector.size(), ("Bad connection index"));
connectionVector[index].RemoteIP = *((unsigned long*)bind_ptr->value.asnValue.address.stream);
index++;
break;
/*
** 5. Remote port field.
*/
case tcpConnRemPort:
DEBUG_ASSERTCRASH(index < connectionVector.size(), ("Bad connection index"));
connectionVector[index].RemotePort = bind_ptr->value.asnValue.number;
//connectionVector[index]->RemotePort = ntohs(connectionVector[index]->RemotePort);
index++;
break;
}
}
SnmpUtilMemFreePtr(bind_list_ptr);
SnmpUtilMemFreePtr(bind_ptr);
SnmpUtilMemFreePtr(mib_ii_name_ptr);
DEBUG_LOG(("Got %d connections in list, parsing...\n", connectionVector.size()));
/*
** Right, we got the lot. Lets see if any of them have the same address as the chat
** server we think we are talking to.
*/
found = false;
for (Int i=0; i<connectionVector.size(); ++i) {
ConnInfoStruct connection = connectionVector[i];
temp = ntohl(connection.RemoteIP);
memcpy(remoteAddress, (unsigned char*)&temp, 4);
/*
** See if this connection has the same address as our server.
*/
if (!found && memcmp(remoteAddress, serverAddress, 4) == 0) {
DEBUG_LOG(("Found connection with same remote address as server\n"));
if (serverPort == 0 || serverPort == (unsigned int)connection.RemotePort) {
DEBUG_LOG(("Connection has same port\n"));
/*
** Make sure the connection is current.
*/
if (connection.State == ESTABLISHED) {
DEBUG_LOG(("Connection is ESTABLISHED\n"));
localIP = connection.LocalIP;
found = true;
} else {
DEBUG_LOG(("Connection is not ESTABLISHED - skipping\n"));
}
} else {
DEBUG_LOG(("Connection has different port. Port is %d, looking for %d\n", connection.RemotePort, serverPort));
}
}
}
if (found) {
DEBUG_LOG(("Using address 0x%8.8X to talk to chat server\n", localIP));
}
FreeLibrary(snmpapi_dll);
FreeLibrary(mib_ii_dll);
return(found);
}
// GameSpyGameSlot ----------------------------------------
void GameSpyGameSlot::setPingString( AsciiString pingStr )
{
m_pingStr = pingStr;
m_pingInt = TheGameSpyInfo->getPingValue(pingStr);
}
// GameSpyStagingRoom ----------------------------------------
GameSpyStagingRoom::GameSpyStagingRoom()
{
cleanUpSlotPointers();
setLocalIP(0);
m_transport = NULL;
m_localName = "localhost";
m_ladderIP.clear();
m_ladderPort = 0;
}
void GameSpyStagingRoom::cleanUpSlotPointers( void )
{
for (Int i = 0; i< MAX_SLOTS; ++i)
setSlotPointer(i, &m_GameSpySlot[i]);
}
GameSpyGameSlot * GameSpyStagingRoom::getGameSpySlot( Int index )
{
GameSlot *slot = getSlot(index);
DEBUG_ASSERTCRASH(slot && (slot == &(m_GameSpySlot[index])), ("Bad game slot pointer\n"));
return (GameSpyGameSlot *)slot;
}
void GameSpyStagingRoom::init( void )
{
GameInfo::init();
}
void GameSpyStagingRoom::setPingString( AsciiString pingStr )
{
m_pingStr = pingStr;
m_pingInt = TheGameSpyInfo->getPingValue(pingStr);
}
Bool GameSpyStagingRoom::amIHost( void ) const
{
DEBUG_ASSERTCRASH(m_inGame, ("Looking for game slot while not in game"));
if (!m_inGame)
return false;
return getConstSlot(0)->isPlayer(m_localName);
}
void GameSpyStagingRoom::resetAccepted( void )
{
GameInfo::resetAccepted();
if (amIHost())
{
/*
peerStateChanged(TheGameSpyChat->getPeer());
m_hasBeenQueried = false;
DEBUG_LOG(("resetAccepted() called peerStateChange()\n"));
*/
}
}
Int GameSpyStagingRoom::getLocalSlotNum( void ) const
{
DEBUG_ASSERTCRASH(m_inGame, ("Looking for local game slot while not in game"));
if (!m_inGame)
return -1;
AsciiString localName = TheGameSpyInfo->getLocalName();
for (Int i=0; i<MAX_SLOTS; ++i)
{
const GameSlot *slot = getConstSlot(i);
if (slot == NULL) {
continue;
}
if (slot->isPlayer(localName))
return i;
}
return -1;
}
void GameSpyStagingRoom::startGame(Int gameID)
{
DEBUG_ASSERTCRASH(m_inGame, ("Starting a game while not in game"));
DEBUG_LOG(("GameSpyStagingRoom::startGame - game id = %d\n", gameID));
DEBUG_ASSERTCRASH(m_transport == NULL, ("m_transport is not NULL when it should be"));
DEBUG_ASSERTCRASH(TheNAT == NULL, ("TheNAT is not NULL when it should be"));
UnsignedInt localIP = TheGameSpyInfo->getInternalIP();
setLocalIP(localIP);
if (TheNAT != NULL) {
delete TheNAT;
TheNAT = NULL;
}
// fill in GS-specific info
Int numHumans = 0;
for (Int i=0; i<MAX_SLOTS; ++i)
{
if (m_GameSpySlot[i].isHuman())
{
++numHumans;
AsciiString gsName;
gsName.translate( m_GameSpySlot[i].getName() );
m_GameSpySlot[i].setLoginName( gsName );
if (m_isQM)
{
if (getLocalSlotNum() == i)
m_GameSpySlot[i].setProfileID(TheGameSpyInfo->getLocalProfileID()); // hehe - we know our own. the rest, they'll tell us.
}
else
{
PlayerInfoMap *pInfoMap = TheGameSpyInfo->getPlayerInfoMap();
PlayerInfoMap::iterator it = pInfoMap->find(gsName);
if (it != pInfoMap->end())
{
m_GameSpySlot[i].setProfileID(it->second.m_profileID);
m_GameSpySlot[i].setLocale(it->second.m_locale);
m_GameSpySlot[i].setSlotRankPoints(it->second.m_rankPoints);
m_GameSpySlot[i].setFavoriteSide(it->second.m_side);
}
else
{
DEBUG_CRASH(("No player info for %s", gsName.str()));
}
}
}
}
//#if defined(_DEBUG) || defined(_INTERNAL)
if (numHumans < 2)
{
launchGame();
if (TheGameSpyInfo)
TheGameSpyInfo->leaveStagingRoom();
}
else
//#endif defined(_DEBUG) || defined(_INTERNAL)
{
TheNAT = NEW NAT();
TheNAT->attachSlotList(m_slot, getLocalSlotNum(), m_localIP);
TheNAT->establishConnectionPaths();
}
}
AsciiString GameSpyStagingRoom::generateGameSpyGameResultsPacket( void )
{
Int i;
Int endFrame = TheVictoryConditions->getEndFrame();
Int localSlotNum = getLocalSlotNum();
Int winningTeam = -1;
Int numHumans = 0;
Int numPlayers = 0;
Int numAIs = 0;
Int numTeamsAtGameEnd = 0;
Int lastTeamAtGameEnd = -1;
for (i=0; i<MAX_SLOTS; ++i)
{
AsciiString playerName;
playerName.format("player%d", i);
Player *p = ThePlayerList->findPlayerWithNameKey(NAMEKEY(playerName));
if (p)
{
++numHumans;
if (TheVictoryConditions->hasAchievedVictory(p))
{
winningTeam = getSlot(i)->getTeamNumber();
}
// check if he lasted
GameSlot *slot = getSlot(i);
if (!slot->disconnected())
{
if (slot->getTeamNumber() != lastTeamAtGameEnd || numTeamsAtGameEnd == 0)
{
lastTeamAtGameEnd = slot->getTeamNumber();
++numTeamsAtGameEnd;
}
}
}
else
{
if (m_GameSpySlot[i].isAI())
++numAIs;
}
}
numPlayers = numHumans + numAIs;
AsciiString mapName;
for (i=0; i<getMap().getLength(); ++i)
{
char c = getMap().getCharAt(i);
if (c == '\\')
c = '/';
mapName.concat(c);
}
AsciiString results;
results.format("\\seed\\%d\\hostname\\%s\\mapname\\%s\\numplayers\\%d\\duration\\%d\\gamemode\\exiting\\localplayer\\%d",
getSeed(), m_GameSpySlot[0].getLoginName().str(), mapName.str(), numPlayers, endFrame, localSlotNum);
Int playerID = 0;
for (i=0; i<MAX_SLOTS; ++i)
{
AsciiString playerName;
playerName.format("player%d", i);
Player *p = ThePlayerList->findPlayerWithNameKey(NAMEKEY(playerName));
if (p)
{
GameSpyGameSlot *slot = &(m_GameSpySlot[i]);
AsciiString playerName = (slot->isHuman())?slot->getLoginName():"AIPlayer";
Int gsPlayerID = slot->getProfileID();
Bool disconnected = slot->disconnected();
AsciiString result = "loss", side = "USA";
if (disconnected)
result = "discon";
else if (TheNetwork->sawCRCMismatch())
result = "desync";
else if (TheVictoryConditions->hasAchievedVictory(p))
result = "win";
side = p->getPlayerTemplate()->getSide();
if (side == "America")
side = "USA";
AsciiString playerStr;
playerStr.format("\\player_%d\\%s\\pid_%d\\%d\\team_%d\\%d\\result_%d\\%s\\side_%d\\%s",
playerID, playerName.str(), playerID, gsPlayerID, playerID, slot->getTeamNumber(),
playerID, result.str(), playerID, side.str());
results.concat(playerStr);
++playerID;
}
}
return results;
}
AsciiString GameSpyStagingRoom::generateLadderGameResultsPacket( void )
{
Int i;
Int endFrame = TheVictoryConditions->getEndFrame();
Int localSlotNum = getLocalSlotNum();
Bool sawGameEnd = (endFrame > 0);
Int winningTeam = -1;
Int numPlayers = 0;
Int numTeamsAtGameEnd = 0;
Int lastTeamAtGameEnd = -1;
Player* p[MAX_SLOTS];
for (i=0; i<MAX_SLOTS; ++i)
{
AsciiString playerName;
playerName.format("player%d", i);
p[i] = ThePlayerList->findPlayerWithNameKey(NAMEKEY(playerName));
if (p[i])
{
++numPlayers;
if (TheVictoryConditions->hasAchievedVictory(p[i]))
{
winningTeam = getSlot(i)->getTeamNumber();
}
// check if he lasted
GameSlot *slot = getSlot(i);
if (!slot->disconnected())
{
if (slot->getTeamNumber() != lastTeamAtGameEnd || numTeamsAtGameEnd == 0)
{
lastTeamAtGameEnd = slot->getTeamNumber();
++numTeamsAtGameEnd;
}
}
}
}
AsciiString results;
results.format("seed=%d,slotNum=%d,sawDesync=%d,sawGameEnd=%d,winningTeam=%d,disconEnd=%d,duration=%d,numPlayers=%d,isQM=%d,map=%s",
getSeed(), localSlotNum, TheNetwork->sawCRCMismatch(), sawGameEnd, winningTeam, (numTeamsAtGameEnd < 2),
endFrame, numPlayers, m_isQM, TheGameState->realMapPathToPortableMapPath(getMap()).str());
AsciiString tempStr;
tempStr.format(",ladderIP=%s,ladderPort=%d", getLadderIP().str(), getLadderPort());
results.concat(tempStr);
Int playerID = 0;
for (i=0; i<MAX_SLOTS; ++i)
{
AsciiString playerName;
playerName.format("player%d", i);
if (p[i])
{
GameSpyGameSlot *slot = &(m_GameSpySlot[i]);
ScoreKeeper *keeper = p[i]->getScoreKeeper();
AsciiString playerName = slot->getLoginName();
Int gsPlayerID = slot->getProfileID();
PSPlayerStats stats = TheGameSpyPSMessageQueue->findPlayerStatsByID(gsPlayerID);
Int fps = TheNetwork->getAverageFPS();
Int unitsKilled = keeper->getTotalUnitsDestroyed();
Int unitsLost = keeper->getTotalUnitsLost();
Int unitsBuilt = keeper->getTotalUnitsBuilt();
Int buildingsKilled = keeper->getTotalBuildingsDestroyed();
Int buildingsLost = keeper->getTotalBuildingsLost();
Int buildingsBuilt = keeper->getTotalBuildingsBuilt();
Int earnings = keeper->getTotalMoneyEarned();
Int techCaptured = keeper->getTotalTechBuildingsCaptured();
Bool disconnected = slot->disconnected();
AsciiString playerStr;
playerStr.format(",player%d=%s,playerID%d=%d,locale%d=%d",
playerID, playerName.str(), playerID, gsPlayerID, playerID, stats.locale);
results.concat(playerStr);
playerStr.format(",unitsKilled%d=%d,unitsLost%d=%d,unitsBuilt%d=%d",
playerID, unitsKilled, playerID, unitsLost, playerID, unitsBuilt);
results.concat(playerStr);
playerStr.format(",buildingsKilled%d=%d,buildingsLost%d=%d,buildingsBuilt%d=%d",
playerID, buildingsKilled, playerID, buildingsLost, playerID, buildingsBuilt);
results.concat(playerStr);
playerStr.format(",fps%d=%d,cash%d=%d,capturedTech%d=%d,discon%d=%d,side%d=%s,team%d=%d",
playerID, fps, playerID, earnings, playerID, techCaptured, playerID, disconnected, playerID, p[i]->getPlayerTemplate()->getSide().str(), playerID, slot->getTeamNumber());
results.concat(playerStr);
++playerID;
}
}
// Add a trailing size value (so the server can ensure it got the entire packet)
results.concat(",size=");
int resultsLen = results.getLength()+10;
AsciiString tail;
tail.format("%10.10d", resultsLen);
results.concat(tail);
return results;
}
void GameSpyStagingRoom::launchGame( void )
{
setGameInProgress(TRUE);
for (Int i=0; i<MAX_SLOTS; ++i)
{
const GameSpyGameSlot *slot = getGameSpySlot(i);
if (slot->isHuman())
{
if (TheGameSpyInfo->didPlayerPreorder(slot->getProfileID()))
markPlayerAsPreorder(i);
}
}
// Set up the game network
AsciiString user;
AsciiString userList;
DEBUG_ASSERTCRASH(TheNetwork == NULL, ("For some reason TheNetwork isn't NULL at the start of this game. Better look into that."));
if (TheNetwork != NULL) {
delete TheNetwork;
TheNetwork = NULL;
}
// Time to initialize TheNetwork for this game.
TheNetwork = NetworkInterface::createNetwork();
TheNetwork->init();
TheNetwork->setLocalAddress(getLocalIP(), (TheNAT)?TheNAT->getSlotPort(getLocalSlotNum()):8888);
if (TheNAT)
TheNetwork->attachTransport(TheNAT->getTransport());
else
TheNetwork->initTransport();
TheNetwork->parseUserList(this);
if (TheGameLogic->isInGame()) {
TheGameLogic->clearGameData();
}
Bool filesOk = DoAnyMapTransfers(this);
// see if we really have the map. if not, back out.
TheMapCache->updateCache();
if (!filesOk || TheMapCache->findMap(getMap()) == NULL)
{
DEBUG_LOG(("After transfer, we didn't really have the map. Bailing...\n"));
if (TheNetwork != NULL) {
delete TheNetwork;
TheNetwork = NULL;
}
GSMessageBoxOk(TheGameText->fetch("GUI:Error"), TheGameText->fetch("GUI:CouldNotTransferMap"));
void PopBackToLobby( void );
PopBackToLobby();
return;
}
// shutdown the top, but do not pop it off the stack
// TheShell->hideShell();
// setup the Global Data with the Map and Seed
TheWritableGlobalData->m_pendingFile = TheGameSpyGame->getMap();
// send a message to the logic for a new game
GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_NEW_GAME );
msg->appendIntegerArgument(GAME_INTERNET);
TheWritableGlobalData->m_useFpsLimit = false;
// Set the random seed
InitGameLogicRandom( getSeed() );
DEBUG_LOG(("InitGameLogicRandom( %d )\n", getSeed()));
// mark us as "Loading" in the buddy list
BuddyRequest req;
req.buddyRequestType = BuddyRequest::BUDDYREQUEST_SETSTATUS;
req.arg.status.status = GP_PLAYING;
strcpy(req.arg.status.statusString, "Loading");
sprintf(req.arg.status.locationString, "%s", WideCharStringToMultiByte(TheGameSpyGame->getGameName().str()).c_str());
TheGameSpyBuddyMessageQueue->addRequest(req);
if (TheNAT != NULL) {
delete TheNAT;
TheNAT = NULL;
}
}
void GameSpyStagingRoom::reset(void)
{
#ifdef DEBUG_LOGGING
if (this == TheGameSpyGame)
{
WindowLayout *theLayout = TheShell->findScreenByFilename("Menus/GameSpyGameOptionsMenu.wnd");
if (theLayout)
{
DEBUG_LOG(("Resetting TheGameSpyGame on the game options menu!\n"));
}
}
#endif
GameInfo::reset();
}

View File

@@ -0,0 +1,679 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: BuddyThread.cpp //////////////////////////////////////////////////////
// GameSpy Presence & Messaging (Buddy) thread
// This thread communicates with GameSpy's buddy server
// and talks through a message queue with the rest of
// the game.
// Author: Matthew D. Campbell, June 2002
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameNetwork/GameSpy/BuddyThread.h"
#include "GameNetwork/GameSpy/PeerThread.h"
#include "GameNetwork/GameSpy/PersistentStorageThread.h"
#include "GameNetwork/GameSpy/ThreadUtils.h"
#include "Common/StackDump.h"
#include "mutex.h"
#include "thread.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------
typedef std::queue<BuddyRequest> RequestQueue;
typedef std::queue<BuddyResponse> ResponseQueue;
class BuddyThreadClass;
class GameSpyBuddyMessageQueue : public GameSpyBuddyMessageQueueInterface
{
public:
virtual ~GameSpyBuddyMessageQueue();
GameSpyBuddyMessageQueue();
virtual void startThread( void );
virtual void endThread( void );
virtual Bool isThreadRunning( void );
virtual Bool isConnected( void );
virtual Bool isConnecting( void );
virtual void addRequest( const BuddyRequest& req );
virtual Bool getRequest( BuddyRequest& req );
virtual void addResponse( const BuddyResponse& resp );
virtual Bool getResponse( BuddyResponse& resp );
virtual GPProfile getLocalProfileID( void );
BuddyThreadClass* getThread( void );
private:
MutexClass m_requestMutex;
MutexClass m_responseMutex;
RequestQueue m_requests;
ResponseQueue m_responses;
BuddyThreadClass *m_thread;
};
GameSpyBuddyMessageQueueInterface* GameSpyBuddyMessageQueueInterface::createNewMessageQueue( void )
{
return NEW GameSpyBuddyMessageQueue;
}
GameSpyBuddyMessageQueueInterface *TheGameSpyBuddyMessageQueue;
#define MESSAGE_QUEUE ((GameSpyBuddyMessageQueue *)TheGameSpyBuddyMessageQueue)
//-------------------------------------------------------------------------
class BuddyThreadClass : public ThreadClass
{
public:
BuddyThreadClass() : ThreadClass() { m_isNewAccount = m_isdeleting = m_isConnecting = m_isConnected = false; m_profileID = 0; m_lastErrorCode = 0; }
void Thread_Function();
void errorCallback( GPConnection *con, GPErrorArg *arg );
void messageCallback( GPConnection *con, GPRecvBuddyMessageArg *arg );
void connectCallback( GPConnection *con, GPConnectResponseArg *arg );
void requestCallback( GPConnection *con, GPRecvBuddyRequestArg *arg );
void statusCallback( GPConnection *con, GPRecvBuddyStatusArg *arg );
Bool isConnecting( void ) { return m_isConnecting; }
Bool isConnected( void ) { return m_isConnected; }
GPProfile getLocalProfileID( void ) { return m_profileID; }
private:
Bool m_isNewAccount;
Bool m_isConnecting;
Bool m_isConnected;
GPProfile m_profileID;
Int m_lastErrorCode;
Bool m_isdeleting;
std::string m_nick, m_email, m_pass;
};
static enum CallbackType
{
CALLBACK_CONNECT,
CALLBACK_ERROR,
CALLBACK_RECVMESSAGE,
CALLBACK_RECVREQUEST,
CALLBACK_RECVSTATUS,
CALLBACK_MAX
};
void callbackWrapper( GPConnection *con, void *arg, void *param )
{
CallbackType info = (CallbackType)(Int)param;
BuddyThreadClass *thread = MESSAGE_QUEUE->getThread() ? MESSAGE_QUEUE->getThread() : NULL /*(TheGameSpyBuddyMessageQueue)?TheGameSpyBuddyMessageQueue->getThread():NULL*/;
if (!thread)
return;
switch (info)
{
case CALLBACK_CONNECT:
thread->connectCallback( con, (GPConnectResponseArg *)arg );
break;
case CALLBACK_ERROR:
thread->errorCallback( con, (GPErrorArg *)arg );
break;
case CALLBACK_RECVMESSAGE:
thread->messageCallback( con, (GPRecvBuddyMessageArg *)arg );
break;
case CALLBACK_RECVREQUEST:
thread->requestCallback( con, (GPRecvBuddyRequestArg *)arg );
break;
case CALLBACK_RECVSTATUS:
thread->statusCallback( con, (GPRecvBuddyStatusArg *)arg );
break;
}
}
//-------------------------------------------------------------------------
GameSpyBuddyMessageQueue::GameSpyBuddyMessageQueue()
{
m_thread = NULL;
}
GameSpyBuddyMessageQueue::~GameSpyBuddyMessageQueue()
{
endThread();
}
void GameSpyBuddyMessageQueue::startThread( void )
{
if (!m_thread)
{
m_thread = NEW BuddyThreadClass;
m_thread->Execute();
}
else
{
if (!m_thread->Is_Running())
{
m_thread->Execute();
}
}
}
void GameSpyBuddyMessageQueue::endThread( void )
{
if (m_thread)
delete m_thread;
m_thread = NULL;
}
Bool GameSpyBuddyMessageQueue::isThreadRunning( void )
{
return (m_thread) ? m_thread->Is_Running() : false;
}
Bool GameSpyBuddyMessageQueue::isConnected( void )
{
return (m_thread) ? m_thread->isConnected() : false;
}
Bool GameSpyBuddyMessageQueue::isConnecting( void )
{
return (m_thread) ? m_thread->isConnecting() : false;
}
void GameSpyBuddyMessageQueue::addRequest( const BuddyRequest& req )
{
MutexClass::LockClass m(m_requestMutex);
if (m.Failed())
return;
m_requests.push(req);
}
Bool GameSpyBuddyMessageQueue::getRequest( BuddyRequest& req )
{
MutexClass::LockClass m(m_requestMutex, 0);
if (m.Failed())
return false;
if (m_requests.empty())
return false;
req = m_requests.front();
m_requests.pop();
return true;
}
void GameSpyBuddyMessageQueue::addResponse( const BuddyResponse& resp )
{
MutexClass::LockClass m(m_responseMutex);
if (m.Failed())
return;
m_responses.push(resp);
}
Bool GameSpyBuddyMessageQueue::getResponse( BuddyResponse& resp )
{
MutexClass::LockClass m(m_responseMutex, 0);
if (m.Failed())
return false;
if (m_responses.empty())
return false;
resp = m_responses.front();
m_responses.pop();
return true;
}
BuddyThreadClass* GameSpyBuddyMessageQueue::getThread( void )
{
return m_thread;
}
GPProfile GameSpyBuddyMessageQueue::getLocalProfileID( void )
{
return (m_thread) ? m_thread->getLocalProfileID() : 0;
}
//-------------------------------------------------------------------------
void BuddyThreadClass::Thread_Function()
{
try {
_set_se_translator( DumpExceptionInfo ); // Hook that allows stack trace.
GPConnection gpCon;
GPConnection *con = &gpCon;
gpInitialize( con, 0 );
m_isConnected = m_isConnecting = false;
gpSetCallback( con, GP_ERROR, callbackWrapper, (void *)CALLBACK_ERROR );
gpSetCallback( con, GP_RECV_BUDDY_MESSAGE, callbackWrapper, (void *)CALLBACK_RECVMESSAGE );
gpSetCallback( con, GP_RECV_BUDDY_REQUEST, callbackWrapper, (void *)CALLBACK_RECVREQUEST );
gpSetCallback( con, GP_RECV_BUDDY_STATUS, callbackWrapper, (void *)CALLBACK_RECVSTATUS );
GPEnum lastStatus = GP_OFFLINE;
std::string lastStatusString;
BuddyRequest incomingRequest;
while ( running )
{
// deal with requests
if (TheGameSpyBuddyMessageQueue->getRequest(incomingRequest))
{
switch (incomingRequest.buddyRequestType)
{
case BuddyRequest::BUDDYREQUEST_LOGIN:
m_isConnecting = true;
m_nick = incomingRequest.arg.login.nick;
m_email = incomingRequest.arg.login.email;
m_pass = incomingRequest.arg.login.password;
m_isConnected = (gpConnect( con, incomingRequest.arg.login.nick, incomingRequest.arg.login.email,
incomingRequest.arg.login.password, (incomingRequest.arg.login.hasFirewall)?GP_FIREWALL:GP_NO_FIREWALL,
GP_BLOCKING, callbackWrapper, (void *)CALLBACK_CONNECT ) == GP_NO_ERROR);
m_isConnecting = false;
break;
case BuddyRequest::BUDDYREQUEST_RELOGIN:
m_isConnecting = true;
m_isConnected = (gpConnect( con, m_nick.c_str(), m_email.c_str(), m_pass.c_str(), GP_FIREWALL,
GP_BLOCKING, callbackWrapper, (void *)CALLBACK_CONNECT ) == GP_NO_ERROR);
m_isConnecting = false;
break;
case BuddyRequest::BUDDYREQUEST_DELETEACCT:
m_isdeleting = true;
gpDeleteProfile( con );
break;
case BuddyRequest::BUDDYREQUEST_LOGOUT:
m_isConnecting = m_isConnected = false;
gpDisconnect( con );
break;
case BuddyRequest::BUDDYREQUEST_MESSAGE:
{
std::string s = WideCharStringToMultiByte( incomingRequest.arg.message.text );
DEBUG_LOG(("Sending a buddy message to %d [%s]\n", incomingRequest.arg.message.recipient, s.c_str()));
gpSendBuddyMessage( con, incomingRequest.arg.message.recipient, s.c_str() );
}
break;
case BuddyRequest::BUDDYREQUEST_LOGINNEW:
{
m_isConnecting = true;
m_nick = incomingRequest.arg.login.nick;
m_email = incomingRequest.arg.login.email;
m_pass = incomingRequest.arg.login.password;
m_isNewAccount = TRUE;
m_isConnected = (gpConnectNewUser( con, incomingRequest.arg.login.nick, incomingRequest.arg.login.email,
incomingRequest.arg.login.password, (incomingRequest.arg.login.hasFirewall)?GP_FIREWALL:GP_NO_FIREWALL,
GP_BLOCKING, callbackWrapper, (void *)CALLBACK_CONNECT ) == GP_NO_ERROR);
if (m_isNewAccount) // if we didn't re-login
{
gpSetInfoMask( con, GP_MASK_NONE ); // don't share info
}
m_isConnecting = false;
}
break;
case BuddyRequest::BUDDYREQUEST_ADDBUDDY:
{
std::string s = WideCharStringToMultiByte( incomingRequest.arg.addbuddy.text );
gpSendBuddyRequest( con, incomingRequest.arg.addbuddy.id, s.c_str() );
}
break;
case BuddyRequest::BUDDYREQUEST_DELBUDDY:
{
gpDeleteBuddy( con, incomingRequest.arg.profile.id );
}
break;
case BuddyRequest::BUDDYREQUEST_OKADD:
{
gpAuthBuddyRequest( con, incomingRequest.arg.profile.id );
}
break;
case BuddyRequest::BUDDYREQUEST_DENYADD:
{
gpDenyBuddyRequest( con, incomingRequest.arg.profile.id );
}
case BuddyRequest::BUDDYREQUEST_SETSTATUS:
{
//don't blast our 'Loading' status with 'Online'.
if (lastStatus == GP_PLAYING && lastStatusString == "Loading" && incomingRequest.arg.status.status == GP_ONLINE)
break;
DEBUG_LOG(("BUDDYREQUEST_SETSTATUS: status is now %d:%s/%s\n",
incomingRequest.arg.status.status, incomingRequest.arg.status.statusString, incomingRequest.arg.status.locationString));
gpSetStatus( con, incomingRequest.arg.status.status, incomingRequest.arg.status.statusString,
incomingRequest.arg.status.locationString );
lastStatus = incomingRequest.arg.status.status;
lastStatusString = incomingRequest.arg.status.statusString;
}
break;
}
}
// update the network
GPEnum isConnected = GP_CONNECTED;
GPResult res = GP_NO_ERROR;
res = gpIsConnected( con, &isConnected );
if ( isConnected == GP_CONNECTED && res == GP_NO_ERROR )
gpProcess( con );
// end our timeslice
Switch_Thread();
}
gpDestroy( con );
} catch ( ... ) {
DEBUG_CRASH(("Exception in buddy thread!"));
}
}
void BuddyThreadClass::errorCallback( GPConnection *con, GPErrorArg *arg )
{
// log the error
DEBUG_LOG(("GPErrorCallback\n"));
m_lastErrorCode = arg->errorCode;
char errorCodeString[256];
char resultString[256];
#define RESULT(x) case x: strcpy(resultString, #x); break;
switch(arg->result)
{
RESULT(GP_NO_ERROR)
RESULT(GP_MEMORY_ERROR)
RESULT(GP_PARAMETER_ERROR)
RESULT(GP_NETWORK_ERROR)
RESULT(GP_SERVER_ERROR)
default:
strcpy(resultString, "Unknown result!");
}
#undef RESULT
#define ERRORCODE(x) case x: strcpy(errorCodeString, #x); break;
switch(arg->errorCode)
{
ERRORCODE(GP_GENERAL)
ERRORCODE(GP_PARSE)
ERRORCODE(GP_NOT_LOGGED_IN)
ERRORCODE(GP_BAD_SESSKEY)
ERRORCODE(GP_DATABASE)
ERRORCODE(GP_NETWORK)
ERRORCODE(GP_FORCED_DISCONNECT)
ERRORCODE(GP_CONNECTION_CLOSED)
ERRORCODE(GP_LOGIN)
ERRORCODE(GP_LOGIN_TIMEOUT)
ERRORCODE(GP_LOGIN_BAD_NICK)
ERRORCODE(GP_LOGIN_BAD_EMAIL)
ERRORCODE(GP_LOGIN_BAD_PASSWORD)
ERRORCODE(GP_LOGIN_BAD_PROFILE)
ERRORCODE(GP_LOGIN_PROFILE_DELETED)
ERRORCODE(GP_LOGIN_CONNECTION_FAILED)
ERRORCODE(GP_LOGIN_SERVER_AUTH_FAILED)
ERRORCODE(GP_NEWUSER)
ERRORCODE(GP_NEWUSER_BAD_NICK)
ERRORCODE(GP_NEWUSER_BAD_PASSWORD)
ERRORCODE(GP_UPDATEUI)
ERRORCODE(GP_UPDATEUI_BAD_EMAIL)
ERRORCODE(GP_NEWPROFILE)
ERRORCODE(GP_NEWPROFILE_BAD_NICK)
ERRORCODE(GP_NEWPROFILE_BAD_OLD_NICK)
ERRORCODE(GP_UPDATEPRO)
ERRORCODE(GP_UPDATEPRO_BAD_NICK)
ERRORCODE(GP_ADDBUDDY)
ERRORCODE(GP_ADDBUDDY_BAD_FROM)
ERRORCODE(GP_ADDBUDDY_BAD_NEW)
ERRORCODE(GP_ADDBUDDY_ALREADY_BUDDY)
ERRORCODE(GP_AUTHADD)
ERRORCODE(GP_AUTHADD_BAD_FROM)
ERRORCODE(GP_AUTHADD_BAD_SIG)
ERRORCODE(GP_STATUS)
ERRORCODE(GP_BM)
ERRORCODE(GP_BM_NOT_BUDDY)
ERRORCODE(GP_GETPROFILE)
ERRORCODE(GP_GETPROFILE_BAD_PROFILE)
ERRORCODE(GP_DELBUDDY)
ERRORCODE(GP_DELBUDDY_NOT_BUDDY)
ERRORCODE(GP_DELPROFILE)
ERRORCODE(GP_DELPROFILE_LAST_PROFILE)
ERRORCODE(GP_SEARCH)
ERRORCODE(GP_SEARCH_CONNECTION_FAILED)
default:
strcpy(errorCodeString, "Unknown error code!");
}
#undef ERRORCODE
if(arg->fatal)
{
DEBUG_LOG(( "-----------\n"));
DEBUG_LOG(( "GP FATAL ERROR\n"));
DEBUG_LOG(( "-----------\n"));
}
else
{
DEBUG_LOG(( "-----\n"));
DEBUG_LOG(( "GP ERROR\n"));
DEBUG_LOG(( "-----\n"));
}
DEBUG_LOG(( "RESULT: %s (%d)\n", resultString, arg->result));
DEBUG_LOG(( "ERROR CODE: %s (0x%X)\n", errorCodeString, arg->errorCode));
DEBUG_LOG(( "ERROR STRING: %s\n", arg->errorString));
if (arg->fatal == GP_FATAL)
{
BuddyResponse errorResponse;
errorResponse.buddyResponseType = BuddyResponse::BUDDYRESPONSE_DISCONNECT;
errorResponse.result = arg->result;
errorResponse.arg.error.errorCode = arg->errorCode;
errorResponse.arg.error.fatal = arg->fatal;
strncpy(errorResponse.arg.error.errorString, arg->errorString, MAX_BUDDY_CHAT_LEN);
errorResponse.arg.error.errorString[MAX_BUDDY_CHAT_LEN-1] = 0;
m_isConnecting = m_isConnected = false;
TheGameSpyBuddyMessageQueue->addResponse( errorResponse );
if (m_isdeleting)
{
PeerRequest req;
req.peerRequestType = PeerRequest::PEERREQUEST_LOGOUT;
TheGameSpyPeerMessageQueue->addRequest( req );
m_isdeleting = false;
}
}
}
static void getNickForMessage( GPConnection *con, GPGetInfoResponseArg *arg, void *param )
{
BuddyResponse *resp = (BuddyResponse *)param;
strcpy(resp->arg.message.nick, arg->nick);
}
void BuddyThreadClass::messageCallback( GPConnection *con, GPRecvBuddyMessageArg *arg )
{
BuddyResponse messageResponse;
messageResponse.buddyResponseType = BuddyResponse::BUDDYRESPONSE_MESSAGE;
messageResponse.profile = arg->profile;
// get info about the person asking to be our buddy
gpGetInfo( con, arg->profile, GP_CHECK_CACHE, GP_BLOCKING, (GPCallback)getNickForMessage, &messageResponse);
std::wstring s = MultiByteToWideCharSingleLine( arg->message );
wcsncpy(messageResponse.arg.message.text, s.c_str(), MAX_BUDDY_CHAT_LEN);
messageResponse.arg.message.text[MAX_BUDDY_CHAT_LEN-1] = 0;
messageResponse.arg.message.date = arg->date;
DEBUG_LOG(("Got a buddy message from %d [%ls]\n", arg->profile, s.c_str()));
TheGameSpyBuddyMessageQueue->addResponse( messageResponse );
}
void BuddyThreadClass::connectCallback( GPConnection *con, GPConnectResponseArg *arg )
{
BuddyResponse loginResponse;
if (arg->result == GP_NO_ERROR)
{
loginResponse.buddyResponseType = BuddyResponse::BUDDYRESPONSE_LOGIN;
loginResponse.result = arg->result;
loginResponse.profile = arg->profile;
TheGameSpyBuddyMessageQueue->addResponse( loginResponse );
m_profileID = arg->profile;
if (!TheGameSpyPeerMessageQueue->isConnected() && !TheGameSpyPeerMessageQueue->isConnecting())
{
DEBUG_LOG(("Buddy connect: trying chat connect\n"));
PeerRequest req;
req.peerRequestType = PeerRequest::PEERREQUEST_LOGIN;
req.nick = m_nick;
req.password = m_pass;
req.email = m_email;
req.login.profileID = arg->profile;
TheGameSpyPeerMessageQueue->addRequest(req);
}
}
else
{
if (!TheGameSpyPeerMessageQueue->isConnected() && !TheGameSpyPeerMessageQueue->isConnecting())
{
if (m_lastErrorCode == GP_NEWUSER_BAD_NICK)
{
m_isNewAccount = FALSE;
// they just hit 'create account' instead of 'log in'. Fix them.
DEBUG_LOG(("User Error: Create Account instead of Login. Fixing them...\n"));
BuddyRequest req;
req.buddyRequestType = BuddyRequest::BUDDYREQUEST_LOGIN;
strcpy(req.arg.login.nick, m_nick.c_str());
strcpy(req.arg.login.email, m_email.c_str());
strcpy(req.arg.login.password, m_pass.c_str());
req.arg.login.hasFirewall = true;
TheGameSpyBuddyMessageQueue->addRequest( req );
return;
}
DEBUG_LOG(("Buddy connect failed (%d/%d): posting a failed chat connect\n", arg->result, m_lastErrorCode));
PeerResponse resp;
resp.peerResponseType = PeerResponse::PEERRESPONSE_DISCONNECT;
resp.discon.reason = DISCONNECT_COULDNOTCONNECT;
switch (m_lastErrorCode)
{
case GP_LOGIN_TIMEOUT:
resp.discon.reason = DISCONNECT_GP_LOGIN_TIMEOUT;
break;
case GP_LOGIN_BAD_NICK:
resp.discon.reason = DISCONNECT_GP_LOGIN_BAD_NICK;
break;
case GP_LOGIN_BAD_EMAIL:
resp.discon.reason = DISCONNECT_GP_LOGIN_BAD_EMAIL;
break;
case GP_LOGIN_BAD_PASSWORD:
resp.discon.reason = DISCONNECT_GP_LOGIN_BAD_PASSWORD;
break;
case GP_LOGIN_BAD_PROFILE:
resp.discon.reason = DISCONNECT_GP_LOGIN_BAD_PROFILE;
break;
case GP_LOGIN_PROFILE_DELETED:
resp.discon.reason = DISCONNECT_GP_LOGIN_PROFILE_DELETED;
break;
case GP_LOGIN_CONNECTION_FAILED:
resp.discon.reason = DISCONNECT_GP_LOGIN_CONNECTION_FAILED;
break;
case GP_LOGIN_SERVER_AUTH_FAILED:
resp.discon.reason = DISCONNECT_GP_LOGIN_SERVER_AUTH_FAILED;
break;
case GP_NEWUSER_BAD_NICK:
resp.discon.reason = DISCONNECT_GP_NEWUSER_BAD_NICK;
break;
case GP_NEWUSER_BAD_PASSWORD:
resp.discon.reason = DISCONNECT_GP_NEWUSER_BAD_PASSWORD;
break;
case GP_NEWPROFILE_BAD_NICK:
resp.discon.reason = DISCONNECT_GP_NEWPROFILE_BAD_NICK;
break;
case GP_NEWPROFILE_BAD_OLD_NICK:
resp.discon.reason = DISCONNECT_GP_NEWPROFILE_BAD_OLD_NICK;
break;
}
TheGameSpyPeerMessageQueue->addResponse(resp);
}
}
}
// -----------------------
static void getInfoResponseForRequest( GPConnection *con, GPGetInfoResponseArg *arg, void *param )
{
BuddyResponse *resp = (BuddyResponse *)param;
resp->profile = arg->profile;
strcpy(resp->arg.request.nick, arg->nick);
strcpy(resp->arg.request.email, arg->email);
strcpy(resp->arg.request.countrycode, arg->countrycode);
}
void BuddyThreadClass::requestCallback( GPConnection *con, GPRecvBuddyRequestArg *arg )
{
BuddyResponse response;
response.buddyResponseType = BuddyResponse::BUDDYRESPONSE_REQUEST;
response.profile = arg->profile;
// get info about the person asking to be our buddy
gpGetInfo( con, arg->profile, GP_CHECK_CACHE, GP_BLOCKING, (GPCallback)getInfoResponseForRequest, &response);
std::wstring s = MultiByteToWideCharSingleLine( arg->reason );
wcsncpy(response.arg.request.text, s.c_str(), GP_REASON_LEN);
response.arg.request.text[GP_REASON_LEN-1] = 0;
TheGameSpyBuddyMessageQueue->addResponse( response );
}
// -----------------------
static void getInfoResponseForStatus(GPConnection * connection, GPGetInfoResponseArg * arg, void * param)
{
BuddyResponse *resp = (BuddyResponse *)param;
resp->profile = arg->profile;
strcpy(resp->arg.status.nick, arg->nick);
strcpy(resp->arg.status.email, arg->email);
strcpy(resp->arg.status.countrycode, arg->countrycode);
}
void BuddyThreadClass::statusCallback( GPConnection *con, GPRecvBuddyStatusArg *arg )
{
BuddyResponse response;
// get user's name
response.buddyResponseType = BuddyResponse::BUDDYRESPONSE_STATUS;
gpGetInfo( con, arg->profile, GP_CHECK_CACHE, GP_BLOCKING, (GPCallback)getInfoResponseForStatus, &response);
// get user's status
GPBuddyStatus status;
gpGetBuddyStatus( con, arg->index, &status );
strcpy(response.arg.status.location, status.locationString);
strcpy(response.arg.status.statusString, status.statusString);
response.arg.status.status = status.status;
DEBUG_LOG(("Got buddy status for %d(%s) - status %d\n", status.profile, response.arg.status.nick, status.status));
// relay to UI
TheGameSpyBuddyMessageQueue->addResponse( response );
}
//-------------------------------------------------------------------------

View File

@@ -0,0 +1,410 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: PingThread.cpp //////////////////////////////////////////////////////
// Ping thread
// Author: Matthew D. Campbell, August 2002
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include <winsock.h> // This one has to be here. Prevents collisions with winsock2.h
#include "GameNetwork/GameSpy/GameResultsThread.h"
#include "mutex.h"
#include "thread.h"
#include "Common/StackDump.h"
#include "Common/SubsystemInterface.h"
//-------------------------------------------------------------------------
static const Int NumWorkerThreads = 1;
typedef std::queue<GameResultsRequest> RequestQueue;
typedef std::queue<GameResultsResponse> ResponseQueue;
class GameResultsThreadClass;
class GameResultsQueue : public GameResultsInterface
{
public:
virtual ~GameResultsQueue();
GameResultsQueue();
virtual void init() {}
virtual void reset() {}
virtual void update() {}
virtual void startThreads( void );
virtual void endThreads( void );
virtual Bool areThreadsRunning( void );
virtual void addRequest( const GameResultsRequest& req );
virtual Bool getRequest( GameResultsRequest& resp );
virtual void addResponse( const GameResultsResponse& resp );
virtual Bool getResponse( GameResultsResponse& resp );
virtual Bool areGameResultsBeingSent( void );
private:
MutexClass m_requestMutex;
MutexClass m_responseMutex;
RequestQueue m_requests;
ResponseQueue m_responses;
Int m_requestCount;
Int m_responseCount;
GameResultsThreadClass *m_workerThreads[NumWorkerThreads];
};
GameResultsInterface* GameResultsInterface::createNewGameResultsInterface( void )
{
return NEW GameResultsQueue;
}
GameResultsInterface *TheGameResultsQueue;
//-------------------------------------------------------------------------
class GameResultsThreadClass : public ThreadClass
{
public:
GameResultsThreadClass() : ThreadClass() {}
void Thread_Function();
private:
Int sendGameResults( UnsignedInt IP, UnsignedShort port, const std::string& results );
};
//-------------------------------------------------------------------------
GameResultsQueue::GameResultsQueue() : m_requestCount(0), m_responseCount(0)
{
for (Int i=0; i<NumWorkerThreads; ++i)
{
m_workerThreads[i] = NULL;
}
startThreads();
}
GameResultsQueue::~GameResultsQueue()
{
endThreads();
}
void GameResultsQueue::startThreads( void )
{
endThreads();
for (Int i=0; i<NumWorkerThreads; ++i)
{
m_workerThreads[i] = NEW GameResultsThreadClass;
m_workerThreads[i]->Execute();
}
}
void GameResultsQueue::endThreads( void )
{
for (Int i=0; i<NumWorkerThreads; ++i)
{
if (m_workerThreads[i])
{
delete m_workerThreads[i];
m_workerThreads[i] = NULL;
}
}
}
Bool GameResultsQueue::areThreadsRunning( void )
{
for (Int i=0; i<NumWorkerThreads; ++i)
{
if (m_workerThreads[i])
{
if (m_workerThreads[i]->Is_Running())
return true;
}
}
return false;
}
void GameResultsQueue::addRequest( const GameResultsRequest& req )
{
MutexClass::LockClass m(m_requestMutex);
++m_requestCount;
m_requests.push(req);
}
Bool GameResultsQueue::getRequest( GameResultsRequest& req )
{
MutexClass::LockClass m(m_requestMutex, 0);
if (m.Failed())
return false;
if (m_requests.empty())
return false;
req = m_requests.front();
m_requests.pop();
return true;
}
void GameResultsQueue::addResponse( const GameResultsResponse& resp )
{
{
MutexClass::LockClass m(m_responseMutex);
++m_responseCount;
m_responses.push(resp);
}
}
Bool GameResultsQueue::getResponse( GameResultsResponse& resp )
{
MutexClass::LockClass m(m_responseMutex, 0);
if (m.Failed())
return false;
if (m_responses.empty())
return false;
resp = m_responses.front();
m_responses.pop();
return true;
}
Bool GameResultsQueue::areGameResultsBeingSent( void )
{
MutexClass::LockClass m(m_requestMutex, 0);
if (m.Failed())
return true;
return m_requestCount > 0;
}
//-------------------------------------------------------------------------
// Wrap ladder results in HTTP POST
static WrapHTTP( const std::string& hostname, std::string& results )
{
const char HEADER[] =
"PUT / HTTP/1.1\r\n"
"Connection: Close\r\n"
"Host: %s\r\n"
"Content-Length: %d\r\n"
"\r\n";
char szHdr[256] = {0};
_snprintf( szHdr, 255, HEADER, hostname.c_str(), results.length() );
results = szHdr + results;
} //WrapHTTP
//-------------------------------------------------------------------------
void GameResultsThreadClass::Thread_Function()
{
try {
_set_se_translator( DumpExceptionInfo ); // Hook that allows stack trace.
GameResultsRequest req;
WSADATA wsaData;
// Fire up winsock (prob already done, but doesn't matter)
WORD wVersionRequested = MAKEWORD(1, 1);
WSAStartup( wVersionRequested, &wsaData );
while ( running )
{
// deal with requests
if (TheGameResultsQueue && TheGameResultsQueue->getRequest(req))
{
// resolve the hostname
const char *hostnameBuffer = req.hostname.c_str();
UnsignedInt IP = 0xFFFFFFFF;
if (isdigit(hostnameBuffer[0]))
{
IP = inet_addr(hostnameBuffer);
in_addr hostNode;
hostNode.s_addr = IP;
DEBUG_LOG(("sending game results to %s - IP = %s\n", hostnameBuffer, inet_ntoa(hostNode) ));
}
else
{
HOSTENT *hostStruct;
in_addr *hostNode;
hostStruct = gethostbyname(hostnameBuffer);
if (hostStruct == NULL)
{
DEBUG_LOG(("sending game results to %s - host lookup failed\n", hostnameBuffer));
// Even though this failed to resolve IP, still need to send a
// callback.
IP = 0xFFFFFFFF; // flag for IP resolve failed
}
hostNode = (in_addr *) hostStruct->h_addr;
IP = hostNode->s_addr;
DEBUG_LOG(("sending game results to %s IP = %s\n", hostnameBuffer, inet_ntoa(*hostNode) ));
}
int result = sendGameResults( IP, req.port, req.results );
GameResultsResponse resp;
resp.hostname = req.hostname;
resp.port = req.port;
resp.sentOk = (result == req.results.length());
}
// end our timeslice
Switch_Thread();
}
WSACleanup();
} catch ( ... ) {
DEBUG_CRASH(("Exception in results thread!"));
}
}
//-------------------------------------------------------------------------
#define CASE(x) case (x): return #x;
static const char *getWSAErrorString( Int error )
{
switch (error)
{
CASE(WSABASEERR)
CASE(WSAEINTR)
CASE(WSAEBADF)
CASE(WSAEACCES)
CASE(WSAEFAULT)
CASE(WSAEINVAL)
CASE(WSAEMFILE)
CASE(WSAEWOULDBLOCK)
CASE(WSAEINPROGRESS)
CASE(WSAEALREADY)
CASE(WSAENOTSOCK)
CASE(WSAEDESTADDRREQ)
CASE(WSAEMSGSIZE)
CASE(WSAEPROTOTYPE)
CASE(WSAENOPROTOOPT)
CASE(WSAEPROTONOSUPPORT)
CASE(WSAESOCKTNOSUPPORT)
CASE(WSAEOPNOTSUPP)
CASE(WSAEPFNOSUPPORT)
CASE(WSAEAFNOSUPPORT)
CASE(WSAEADDRINUSE)
CASE(WSAEADDRNOTAVAIL)
CASE(WSAENETDOWN)
CASE(WSAENETUNREACH)
CASE(WSAENETRESET)
CASE(WSAECONNABORTED)
CASE(WSAECONNRESET)
CASE(WSAENOBUFS)
CASE(WSAEISCONN)
CASE(WSAENOTCONN)
CASE(WSAESHUTDOWN)
CASE(WSAETOOMANYREFS)
CASE(WSAETIMEDOUT)
CASE(WSAECONNREFUSED)
CASE(WSAELOOP)
CASE(WSAENAMETOOLONG)
CASE(WSAEHOSTDOWN)
CASE(WSAEHOSTUNREACH)
CASE(WSAENOTEMPTY)
CASE(WSAEPROCLIM)
CASE(WSAEUSERS)
CASE(WSAEDQUOT)
CASE(WSAESTALE)
CASE(WSAEREMOTE)
CASE(WSAEDISCON)
CASE(WSASYSNOTREADY)
CASE(WSAVERNOTSUPPORTED)
CASE(WSANOTINITIALISED)
CASE(WSAHOST_NOT_FOUND)
CASE(WSATRY_AGAIN)
CASE(WSANO_RECOVERY)
CASE(WSANO_DATA)
default:
return "Not a Winsock error";
}
}
#undef CASE
//-------------------------------------------------------------------------
Int GameResultsThreadClass::sendGameResults( UnsignedInt IP, UnsignedShort port, const std::string& results )
{
int error = 0;
// create the socket
Int sock = socket( AF_INET, SOCK_STREAM, 0 );
if (sock < 0)
{
DEBUG_LOG(("GameResultsThreadClass::sendGameResults() - socket() returned %d(%s)\n", sock, getWSAErrorString(sock)));
return sock;
}
// fill in address info
struct sockaddr_in sockAddr;
memset( &sockAddr, 0, sizeof( sockAddr ) );
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.s_addr = IP;
sockAddr.sin_port = htons(port);
// Start the connection process....
if( connect( sock, (struct sockaddr *)&sockAddr, sizeof( sockAddr ) ) == -1 )
{
error = WSAGetLastError();
DEBUG_LOG(("GameResultsThreadClass::sendGameResults() - connect() returned %d(%s)\n", error, getWSAErrorString(error)));
if( ( error == WSAEWOULDBLOCK ) || ( error == WSAEINVAL ) || ( error == WSAEALREADY ) )
{
return( -1 );
}
if( error != WSAEISCONN )
{
closesocket( sock );
return( -1 );
}
}
if (send( sock, results.c_str(), results.length(), 0 ) == SOCKET_ERROR)
{
error = WSAGetLastError();
DEBUG_LOG(("GameResultsThreadClass::sendGameResults() - send() returned %d(%s)\n", error, getWSAErrorString(error)));
closesocket(sock);
return WSAGetLastError();
}
closesocket(sock);
return results.length();
}
//-------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,575 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: PingThread.cpp //////////////////////////////////////////////////////
// Ping thread
// Author: Matthew D. Campbell, August 2002
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include <winsock.h> // This one has to be here. Prevents collisions with windsock2.h
#include "GameNetwork/GameSpy/PingThread.h"
#include "mutex.h"
#include "thread.h"
#include "Common/StackDump.h"
#include "Common/SubsystemInterface.h"
//-------------------------------------------------------------------------
static const Int NumWorkerThreads = 10;
typedef std::queue<PingRequest> RequestQueue;
typedef std::queue<PingResponse> ResponseQueue;
class PingThreadClass;
class Pinger : public PingerInterface
{
public:
virtual ~Pinger();
Pinger();
virtual void startThreads( void );
virtual void endThreads( void );
virtual Bool areThreadsRunning( void );
virtual void addRequest( const PingRequest& req );
virtual Bool getRequest( PingRequest& resp );
virtual void addResponse( const PingResponse& resp );
virtual Bool getResponse( PingResponse& resp );
virtual Bool arePingsInProgress( void );
virtual Int getPing( AsciiString hostname );
virtual void clearPingMap( void );
virtual AsciiString getPingString( Int timeout );
private:
MutexClass m_requestMutex;
MutexClass m_responseMutex;
MutexClass m_pingMapMutex;
RequestQueue m_requests;
ResponseQueue m_responses;
Int m_requestCount;
Int m_responseCount;
std::map<std::string, Int> m_pingMap;
PingThreadClass *m_workerThreads[NumWorkerThreads];
};
PingerInterface* PingerInterface::createNewPingerInterface( void )
{
return NEW Pinger;
}
PingerInterface *ThePinger;
//-------------------------------------------------------------------------
class PingThreadClass : public ThreadClass
{
public:
PingThreadClass() : ThreadClass() {}
void Thread_Function();
private:
Int doPing( UnsignedInt IP, Int timeout );
};
//-------------------------------------------------------------------------
Pinger::Pinger() : m_requestCount(0), m_responseCount(0)
{
for (Int i=0; i<NumWorkerThreads; ++i)
{
m_workerThreads[i] = NULL;
}
}
Pinger::~Pinger()
{
endThreads();
}
void Pinger::startThreads( void )
{
endThreads();
for (Int i=0; i<NumWorkerThreads; ++i)
{
m_workerThreads[i] = NEW PingThreadClass;
m_workerThreads[i]->Execute();
}
}
void Pinger::endThreads( void )
{
for (Int i=0; i<NumWorkerThreads; ++i)
{
if (m_workerThreads[i])
{
delete m_workerThreads[i];
m_workerThreads[i] = NULL;
}
}
}
Bool Pinger::areThreadsRunning( void )
{
for (Int i=0; i<NumWorkerThreads; ++i)
{
if (m_workerThreads[i])
{
if (m_workerThreads[i]->Is_Running())
return true;
}
}
return false;
}
void Pinger::addRequest( const PingRequest& req )
{
MutexClass::LockClass m(m_requestMutex);
++m_requestCount;
m_requests.push(req);
}
Bool Pinger::getRequest( PingRequest& req )
{
MutexClass::LockClass m(m_requestMutex, 0);
if (m.Failed())
return false;
if (m_requests.empty())
return false;
req = m_requests.front();
m_requests.pop();
return true;
}
void Pinger::addResponse( const PingResponse& resp )
{
{
MutexClass::LockClass m(m_pingMapMutex);
m_pingMap[resp.hostname] = resp.avgPing;
}
{
MutexClass::LockClass m(m_responseMutex);
++m_responseCount;
m_responses.push(resp);
}
}
Bool Pinger::getResponse( PingResponse& resp )
{
MutexClass::LockClass m(m_responseMutex, 0);
if (m.Failed())
return false;
if (m_responses.empty())
return false;
resp = m_responses.front();
m_responses.pop();
return true;
}
Bool Pinger::arePingsInProgress( void )
{
return (m_requestCount != m_responseCount);
}
Int Pinger::getPing( AsciiString hostname )
{
MutexClass::LockClass m(m_pingMapMutex, 0);
if (m.Failed())
return false;
std::map<std::string, Int>::const_iterator it = m_pingMap.find(hostname.str());
if (it != m_pingMap.end())
return it->second;
return -1;
}
void Pinger::clearPingMap( void )
{
MutexClass::LockClass m(m_pingMapMutex);
m_pingMap.clear();
}
AsciiString Pinger::getPingString( Int timeout )
{
MutexClass::LockClass m(m_pingMapMutex);
AsciiString pingString;
AsciiString tmp;
for (std::map<std::string, Int>::const_iterator it = m_pingMap.begin(); it != m_pingMap.end(); ++it)
{
Int ping = it->second;
if (ping < 0 || ping > timeout)
ping = timeout;
ping = ping * 255 / timeout;
tmp.format("%2.2X", ping);
pingString.concat(tmp);
}
return pingString;
}
//-------------------------------------------------------------------------
void PingThreadClass::Thread_Function()
{
try {
_set_se_translator( DumpExceptionInfo ); // Hook that allows stack trace.
PingRequest req;
WSADATA wsaData;
// Fire up winsock (prob already done, but doesn't matter)
WORD wVersionRequested = MAKEWORD(1, 1);
WSAStartup( wVersionRequested, &wsaData );
while ( running )
{
// deal with requests
if (ThePinger->getRequest(req))
{
// resolve the hostname
const char *hostnameBuffer = req.hostname.c_str();
UnsignedInt IP = 0xFFFFFFFF;
if (isdigit(hostnameBuffer[0]))
{
IP = inet_addr(hostnameBuffer);
in_addr hostNode;
hostNode.s_addr = IP;
DEBUG_LOG(("pinging %s - IP = %s\n", hostnameBuffer, inet_ntoa(hostNode) ));
}
else
{
HOSTENT *hostStruct;
in_addr *hostNode;
hostStruct = gethostbyname(hostnameBuffer);
if (hostStruct == NULL)
{
DEBUG_LOG(("pinging %s - host lookup failed\n", hostnameBuffer));
// Even though this failed to resolve IP, still need to send a
// callback.
IP = 0xFFFFFFFF; // flag for IP resolve failed
}
hostNode = (in_addr *) hostStruct->h_addr;
IP = hostNode->s_addr;
DEBUG_LOG(("pinging %s IP = %s\n", hostnameBuffer, inet_ntoa(*hostNode) ));
}
// do ping
Int totalPing = 0;
Int goodReps = 0;
Int reps = req.repetitions;
while (reps-- && running && IP != 0xFFFFFFFF)
{
Int ping = doPing(IP, req.timeout);
if (ping >= 0)
{
totalPing += ping;
++goodReps;
}
// end our timeslice
Switch_Thread();
}
if (!goodReps)
totalPing = -1;
else
totalPing = totalPing / goodReps;
PingResponse resp;
resp.hostname = req.hostname;
resp.avgPing = totalPing;
resp.repetitions = goodReps;
ThePinger->addResponse(resp);
}
// end our timeslice
Switch_Thread();
}
WSACleanup();
} catch ( ... ) {
DEBUG_CRASH(("Exception in ping thread!"));
}
}
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
HANDLE WINAPI IcmpCreateFile(VOID); /* INVALID_HANDLE_VALUE on error */
BOOL WINAPI IcmpCloseHandle(HANDLE IcmpHandle); /* FALSE on error */
/* Note 2: For the most part, you can refer to RFC 791 for detials
* on how to fill in values for the IP option information structure.
*/
typedef struct ip_option_information
{
UnsignedByte Ttl; /* Time To Live (used for traceroute) */
UnsignedByte Tos; /* Type Of Service (usually 0) */
UnsignedByte Flags; /* IP header flags (usually 0) */
UnsignedByte OptionsSize; /* Size of options data (usually 0, max 40) */
UnsignedByte FAR *OptionsData; /* Options data buffer */
}
IPINFO, *PIPINFO, FAR *LPIPINFO;
/* Note 1: The Reply Buffer will have an array of ICMP_ECHO_REPLY
* structures, followed by options and the data in ICMP echo reply
* datagram received. You must have room for at least one ICMP
* echo reply structure, plus 8 bytes for an ICMP header.
*/
typedef struct icmp_echo_reply
{
UnsignedInt Address; /* source address */
////////UnsignedInt Status; /* IP status value (see below) */
UnsignedInt RTTime; /* Round Trip Time in milliseconds */
UnsignedShort DataSize; /* reply data size */
UnsignedShort Reserved; /* */
void FAR *Data; /* reply data buffer */
struct ip_option_information Options; /* reply options */
}
ICMPECHO, *PICMPECHO, FAR *LPICMPECHO;
DWORD WINAPI IcmpSendEcho(
HANDLE IcmpHandle, /* handle returned from IcmpCreateFile() */
UnsignedInt DestAddress, /* destination IP address (in network order) */
LPVOID RequestData, /* pointer to buffer to send */
WORD RequestSize, /* length of data in buffer */
LPIPINFO RequestOptns, /* see Note 2 */
LPVOID ReplyBuffer, /* see Note 1 */
DWORD ReplySize, /* length of reply (must allow at least 1 reply) */
DWORD Timeout /* time in milliseconds to wait for reply */
);
#define IP_STATUS_BASE 11000
#define IP_SUCCESS 0
#define IP_BUF_TOO_SMALL (IP_STATUS_BASE + 1)
#define IP_DEST_NET_UNREACHABLE (IP_STATUS_BASE + 2)
#define IP_DEST_HOST_UNREACHABLE (IP_STATUS_BASE + 3)
#define IP_DEST_PROT_UNREACHABLE (IP_STATUS_BASE + 4)
#define IP_DEST_PORT_UNREACHABLE (IP_STATUS_BASE + 5)
#define IP_NO_RESOURCES (IP_STATUS_BASE + 6)
#define IP_BAD_OPTION (IP_STATUS_BASE + 7)
#define IP_HW_ERROR (IP_STATUS_BASE + 8)
#define IP_PACKET_TOO_BIG (IP_STATUS_BASE + 9)
#define IP_REQ_TIMED_OUT (IP_STATUS_BASE + 10)
#define IP_BAD_REQ (IP_STATUS_BASE + 11)
#define IP_BAD_ROUTE (IP_STATUS_BASE + 12)
#define IP_TTL_EXPIRED_TRANSIT (IP_STATUS_BASE + 13)
#define IP_TTL_EXPIRED_REASSEM (IP_STATUS_BASE + 14)
#define IP_PARAM_PROBLEM (IP_STATUS_BASE + 15)
#define IP_SOURCE_QUENCH (IP_STATUS_BASE + 16)
#define IP_OPTION_TOO_BIG (IP_STATUS_BASE + 17)
#define IP_BAD_DESTINATION (IP_STATUS_BASE + 18)
#define IP_ADDR_DELETED (IP_STATUS_BASE + 19)
#define IP_SPEC_MTU_CHANGE (IP_STATUS_BASE + 20)
#define IP_MTU_CHANGE (IP_STATUS_BASE + 21)
#define IP_UNLOAD (IP_STATUS_BASE + 22)
#define IP_GENERAL_FAILURE (IP_STATUS_BASE + 50)
#define MAX_IP_STATUS IP_GENERAL_FAILURE
#define IP_PENDING (IP_STATUS_BASE + 255)
#define BUFSIZE 8192
#define DEFAULT_LEN 32
#define LOOPLIMIT 4
#define DEFAULT_TTL 64
Int PingThreadClass::doPing(UnsignedInt IP, Int timeout)
{
/*
* Initialize default settings
*/
IPINFO stIPInfo, *lpstIPInfo;
HANDLE hICMP, hICMP_DLL;
int i, j, nDataLen, nLoopLimit, nTimeOut, nTTL, nTOS;
DWORD dwReplyCount;
///////IN_ADDR stDestAddr;
BOOL fRet, fDontStop;
///BOOL fTraceRoute;
nDataLen = DEFAULT_LEN;
nLoopLimit = LOOPLIMIT;
nTimeOut = timeout;
fDontStop = FALSE;
lpstIPInfo = NULL;
nTTL = DEFAULT_TTL;
nTOS = 0;
Int pingTime = -1; // in case of error
char achReqData[BUFSIZE];
char achRepData[sizeof(ICMPECHO) + BUFSIZE];
HANDLE ( WINAPI *lpfnIcmpCreateFile )( VOID ) = NULL;
BOOL ( WINAPI *lpfnIcmpCloseHandle )( HANDLE ) = NULL;
DWORD (WINAPI *lpfnIcmpSendEcho)(HANDLE, DWORD, LPVOID, WORD, LPVOID,
LPVOID, DWORD, DWORD) = NULL;
/*
* Load the ICMP.DLL
*/
hICMP_DLL = LoadLibrary("ICMP.DLL");
if (hICMP_DLL == 0)
{
DEBUG_LOG(("LoadLibrary() failed: Unable to locate ICMP.DLL!\n"));
goto cleanup;
}
/*
* Get pointers to ICMP.DLL functions
*/
lpfnIcmpCreateFile = (void * (__stdcall *)(void))GetProcAddress( (HINSTANCE)hICMP_DLL, "IcmpCreateFile");
lpfnIcmpCloseHandle = (int (__stdcall *)(void *))GetProcAddress( (HINSTANCE)hICMP_DLL, "IcmpCloseHandle");
lpfnIcmpSendEcho = (unsigned long (__stdcall *)(void *, unsigned long, void *, unsigned short,
void *, void *, unsigned long, unsigned long))GetProcAddress( (HINSTANCE)hICMP_DLL, "IcmpSendEcho" );
if ((!lpfnIcmpCreateFile) ||
(!lpfnIcmpCloseHandle) ||
(!lpfnIcmpSendEcho))
{
DEBUG_LOG(("GetProcAddr() failed for at least one function.\n"));
goto cleanup;
}
/*
* IcmpCreateFile() - Open the ping service
*/
hICMP = (HANDLE) lpfnIcmpCreateFile();
if (hICMP == INVALID_HANDLE_VALUE)
{
DEBUG_LOG(("IcmpCreateFile() failed"));
goto cleanup;
}
/*
* Init data buffer printable ASCII
* 32 (space) through 126 (tilde)
*/
for (j = 0, i = 32; j < nDataLen; j++, i++)
{
if (i >= 126)
i = 32;
achReqData[j] = i;
}
/*
* Init IPInfo structure
*/
lpstIPInfo = &stIPInfo;
stIPInfo.Ttl = nTTL;
stIPInfo.Tos = nTOS;
stIPInfo.Flags = 0;
stIPInfo.OptionsSize = 0;
stIPInfo.OptionsData = NULL;
/*
* IcmpSendEcho() - Send the ICMP Echo Request
* and read the Reply
*/
dwReplyCount = lpfnIcmpSendEcho(
hICMP,
IP,
achReqData,
nDataLen,
lpstIPInfo,
achRepData,
sizeof(achRepData),
nTimeOut);
if (dwReplyCount != 0)
{
//////////IN_ADDR stDestAddr;
DWORD dwStatus;
pingTime = (*(UnsignedInt *) & (achRepData[8]));
// I've seen the ping time bigger than the timeout by a little
// bit. How lame.
if (pingTime > timeout)
pingTime = timeout;
dwStatus = *(DWORD *) & (achRepData[4]);
if (dwStatus != IP_SUCCESS)
{
DEBUG_LOG(("ICMPERR: %d\n", dwStatus));
}
}
else
{
DEBUG_LOG(("IcmpSendEcho() failed: %d\n", dwReplyCount));
// Ok we didn't get a packet, just say everything's OK
// and the time was -1
pingTime = -1;
goto cleanup;
}
/*
* IcmpCloseHandle - Close the ICMP handle
*/
fRet = lpfnIcmpCloseHandle(hICMP);
if (fRet == FALSE)
{
DEBUG_LOG(("Error closing ICMP handle\n"));
}
// Say what you will about goto's but it's handy for stuff like this
cleanup:
// Shut down...
if (hICMP_DLL)
FreeLibrary((HINSTANCE)hICMP_DLL);
return pingTime;
}
//-------------------------------------------------------------------------

View File

@@ -0,0 +1,81 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: ThreadUtils.cpp //////////////////////////////////////////////////////
// GameSpy thread utils
// Author: Matthew D. Campbell, July 2002
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
//-------------------------------------------------------------------------
std::wstring MultiByteToWideCharSingleLine( const char *orig )
{
Int len = strlen(orig);
WideChar *dest = NEW WideChar[len+1];
MultiByteToWideChar(CP_UTF8, 0, orig, -1, dest, len);
WideChar *c = NULL;
do
{
c = wcschr(dest, L'\n');
if (c)
{
*c = L' ';
}
}
while ( c != NULL );
do
{
c = wcschr(dest, L'\r');
if (c)
{
*c = L' ';
}
}
while ( c != NULL );
dest[len] = 0;
std::wstring ret = dest;
delete dest;
return ret;
}
std::string WideCharStringToMultiByte( const WideChar *orig )
{
std::string ret;
Int len = WideCharToMultiByte( CP_UTF8, 0, orig, wcslen(orig), NULL, 0, NULL, NULL ) + 1;
if (len > 0)
{
char *dest = NEW char[len];
WideCharToMultiByte( CP_UTF8, 0, orig, -1, dest, len, NULL, NULL );
dest[len-1] = 0;
ret = dest;
delete dest;
}
return ret;
}
//-------------------------------------------------------------------------

View File

@@ -0,0 +1,455 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: GameSpyChat.cpp //////////////////////////////////////////////////////
// GameSpy chat handlers
// Author: Matthew D. Campbell, February 2002
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameClient/GameText.h"
#include "GameClient/GadgetListBox.h"
#include "GameClient/LanguageFilter.h"
#include "GameNetwork/GameSpy.h"
#include "GameNetwork/GameSpyChat.h"
#include "Common/QuotedPrintable.h"
typedef set<AsciiString>::const_iterator AsciiSetIter;
/**
* handleSlashCommands looks for slash ccommands and handles them,
* returning true if it found one, false otherwise.
* /i,/ignore list ignored players
* /i,/ignore +name1 -name2 ignore name1, stop ignoring name2
* /m,/me: shorthand for an action
* /o,/on: find command to look up a user's location
* /f,/find: find command to look up a user's location
* /p,/page: page user(s)
* /r,/reply: reply to last page
* /raw: raw IRC command (only in debug & internal)
* /oper: become an IRC op (only in debug & internal)
* /quit: send the IRC quit command to exit WOL
*/
static Bool handleSlashCommands( UnicodeString message, Bool isAction, GameWindow *playerListbox )
{
/*
if (message.getCharAt(0) == L'/')
{
UnicodeString remainder = UnicodeString(message.str() + 1);
UnicodeString token;
switch (message.getCharAt(1))
{
case L'i':
case L'I':
remainder.nextToken(&token);
if (token.compareNoCase(L"i") == 0 || token.compareNoCase(L"ignore") == 0)
{
if (remainder.isEmpty())
{
// List the people we're ignoring
TheWOL->addText(TheGameText->fetch("WOL:BeginIgnoreList"));
set<AsciiString> *ignoreList = getIgnoreList();
if (ignoreList)
{
UnicodeString msg;
UnicodeString uName;
AsciiSetIter iter = ignoreList->begin();
while (iter != ignoreList->end())
{
uName.translate(*iter);
msg.format(TheGameText->fetch("WOL:IgnoredUser"), uName.str());
TheWOL->addText(msg);
iter++;
}
}
TheWOL->addText(TheGameText->fetch("WOL:EndIgnoreList"));
}
while ( remainder.nextToken(&token) )
{
AsciiString name;
int doIgnore = 0;
if (token.getCharAt(0) == L'+')
{
// Ignore somebody
token = UnicodeString(token.str() + 1);
name.translate(token);
doIgnore = 1;
}
else if (token.getCharAt(0) == L'-')
{
// Listen to someone again
token = UnicodeString(token.str() + 1);
name.translate(token);
doIgnore = 0;
}
else
{
// Ignore somebody
token = UnicodeString(token.str());
name.translate(token);
doIgnore = 1;
}
IChat *ichat = TheWOL->getIChat();
User user;
strncpy((char *)user.name, name.str(), 9);
user.name[9] = 0;
ichat->SetSquelch(&user, doIgnore);
if (doIgnore)
addIgnore(name);
else
removeIgnore(name);
UnicodeString msg;
UnicodeString uName;
uName.translate(name);
msg.format(TheGameText->fetch("WOL:IgnoredUser"), uName.str());
TheWOL->addText(msg);
}
return true;
}
break;
case L'r':
case L'R':
remainder.nextToken(&token);
#if defined _DEBUG || defined _INTERNAL
if (token.compareNoCase(L"raw") == 0)
{
// Send raw IRC commands (Ascii only)
AsciiString str;
str.translate(remainder);
str.concat('\n');
IChat *ichat = TheWOL->getIChat();
ichat->RequestRawMessage(str.str());
TheWOL->addText(remainder);
return true; // show it anyway
}
#endif
break;
#if defined _DEBUG || defined _INTERNAL
case L'k':
case L'K':
remainder.nextToken(&token);
if (token.compareNoCase(L"kick") == 0)
{
while ( remainder.nextToken(&token) )
{
AsciiString name;
name.translate(token);
IChat *ichat = TheWOL->getIChat();
User user;
strncpy((char *)user.name, name.str(), 9);
user.name[9] = 0;
ichat->RequestUserKick(&user);
}
return true;
}
break;
#endif
case L'o':
case L'O':
remainder.nextToken(&token);
if (token.compareNoCase(L"on") == 0 || token.compareNoCase(L"o") == 0)
{
remainder.nextToken(&token);
AsciiString userName;
userName.translate(token);
User user;
strncpy((char *)user.name, userName.str(), 10);
user.name[9] = 0;
if (user.name[0] == 0)
{
// didn't enter a name
TheWOL->addText(message);
}
else
{
// Send find command
IChat *ichat = TheWOL->getIChat();
ichat->RequestGlobalFind(&user);
}
return true; // show it anyway
}
#if defined _DEBUG || defined _INTERNAL
else if (token.compareNoCase(L"oper") == 0)
{
// Send raw IRC oper command
AsciiString str;
str.translate(message);
str.concat('\n');
IChat *ichat = TheWOL->getIChat();
ichat->RequestRawMessage(str.str());
TheWOL->addText(message);
return true; // show it anyway
}
#endif
break;
case L'p':
case L'P':
remainder.nextToken(&token);
if (token.compareNoCase(L"page") == 0 || token.compareNoCase(L"p") == 0)
{
remainder.nextToken(&token);
AsciiString userName;
userName.translate(token);
User user;
strncpy((char *)user.name, userName.str(), 10);
user.name[9] = 0;
remainder.trim();
if (user.name[0] == 0 || remainder.isEmpty())
{
// didn't enter a name or message
TheWOL->addText(message);
}
else
{
// Send page command
IChat *ichat = TheWOL->getIChat();
ichat->RequestGlobalUnicodePage(&user, remainder.str());
}
return true; // show it anyway
}
break;
case L'q':
case L'Q':
remainder.nextToken(&token);
if (token.compareNoCase(L"quit") == 0)
{
TheWOL->setState(WOLAPI_LOGIN);
TheWOL->addCommand(WOLCOMMAND_LOGOUT);
//TheWOL->setScreen(WOLAPI_MENU_WELCOME);
return true; // show it anyway
}
break;
#if defined _DEBUG || defined _INTERNAL
case L'c':
case L'C':
remainder.nextToken(&token);
if (token.compareNoCase(L"colortest") == 0)
{
addColorText(token, 0xDD, 0xE2, 0x0D, 0xff);
addColorText(token, 0xFF, 0x19, 0x19, 0xff);
addColorText(token, 0x2A, 0x74, 0xE2, 0xff);
addColorText(token, 0x3E, 0xD1, 0x2E, 0xff);
addColorText(token, 0xFF, 0xA0, 0x19, 0xff);
addColorText(token, 0x32, 0xD7, 0xE6, 0xff);
addColorText(token, 0x95, 0x28, 0xBD, 0xff);
addColorText(token, 0xFF, 0x9A, 0xEB, 0xff);
return true; // show it anyway
}
break;
#endif // _DEBUG || defined _INTERNAL
}
}
*/
return false;
}
static handleUnicodeMessage( const char *nick, UnicodeString msg, Bool isPublic, Bool isAction );
Bool GameSpySendChat( UnicodeString message, Bool isAction, GameWindow *playerListbox )
{
RoomType roomType = StagingRoom;
if (TheGameSpyChat->getCurrentGroupRoomID())
roomType = GroupRoom;
message.trim();
// Echo the user's input to the chat window
if (!message.isEmpty())
{
// Check for slash commands
if (handleSlashCommands(message, isAction, playerListbox))
{
return false; // already handled
}
if (!playerListbox)
{
// Public message
if (isAction)
{
peerMessageRoom(TheGameSpyChat->getPeer(), roomType, UnicodeStringToQuotedPrintable(message).str(), ActionMessage);
//if (roomType == StagingRoom)
//handleUnicodeMessage(TheGameSpyChat->getloginName().str(), message, true, true);
}
else
{
peerMessageRoom(TheGameSpyChat->getPeer(), roomType, UnicodeStringToQuotedPrintable(message).str(), NormalMessage);
//if (roomType == StagingRoom)
//handleUnicodeMessage(TheGameSpyChat->getloginName().str(), message, true, false);
}
return false;
}
// Get the selections (is this a private message?)
Int maxSel = GadgetListBoxGetListLength(playerListbox);
Int *selections;
GadgetListBoxGetSelected(playerListbox, (Int *)&selections);
if (selections[0] == -1)
{
// Public message
if (isAction)
{
peerMessageRoom(TheGameSpyChat->getPeer(), roomType, UnicodeStringToQuotedPrintable(message).str(), ActionMessage);
//if (roomType == StagingRoom)
//handleUnicodeMessage(TheGameSpyChat->getloginName().str(), message, true, true);
}
else
{
peerMessageRoom(TheGameSpyChat->getPeer(), roomType, UnicodeStringToQuotedPrintable(message).str(), NormalMessage);
//if (roomType == StagingRoom)
//handleUnicodeMessage(TheGameSpyChat->getloginName().str(), message, true, false);
}
return false;
}
else
{
// Private message
// Construct a list
AsciiString names = AsciiString::TheEmptyString;
AsciiString tmp = AsciiString::TheEmptyString;
AsciiString aStr; // AsciiString buf for translating Unicode entries
names.format("%s", TheGameSpyChat->getLoginName().str());
for (int i=0; i<maxSel; i++)
{
if (selections[i] != -1)
{
aStr.translate(GadgetListBoxGetText(playerListbox, selections[i], 0));
if (aStr.compareNoCase(TheGameSpyChat->getLoginName()))
{
tmp.format(",%s", aStr.str());
names.concat(tmp);
}
}
else
{
break;
}
}
if (!names.isEmpty())
{
if (isAction)
{
peerMessagePlayer(TheGameSpyChat->getPeer(), names.str(), UnicodeStringToQuotedPrintable(message).str(), ActionMessage);
}
else
{
peerMessagePlayer(TheGameSpyChat->getPeer(), names.str(), UnicodeStringToQuotedPrintable(message).str(), NormalMessage);
}
}
return true;
}
}
return false;
}
void RoomMessageCallback(PEER peer, RoomType roomType,
const char * nick, const char * message,
MessageType messageType, void * param)
{
DEBUG_LOG(("RoomMessageCallback\n"));
handleUnicodeMessage(nick, QuotedPrintableToUnicodeString(message), true, (messageType == ActionMessage));
}
void PlayerMessageCallback(PEER peer,
const char * nick, const char * message,
MessageType messageType, void * param)
{
DEBUG_LOG(("PlayerMessageCallback\n"));
handleUnicodeMessage(nick, QuotedPrintableToUnicodeString(message), false, (messageType == ActionMessage));
}
static handleUnicodeMessage( const char *nick, UnicodeString msg, Bool isPublic, Bool isAction )
{
GameSpyColors style;
Bool isOwner = false;
Int flags = 0;
if (TheGameSpyChat->getCurrentGroupRoomID())
peerGetPlayerFlags(TheGameSpyChat->getPeer(), nick, GroupRoom, &flags);
else
peerGetPlayerFlags(TheGameSpyChat->getPeer(), nick, StagingRoom, &flags);
isOwner = flags & PEER_FLAG_OP;
if (isPublic && isAction)
{
style = (isOwner)?GSCOLOR_CHAT_OWNER_EMOTE:GSCOLOR_CHAT_EMOTE;
}
else if (isPublic)
{
style = (isOwner)?GSCOLOR_CHAT_OWNER:GSCOLOR_CHAT_NORMAL;
}
else if (isAction)
{
style = (isOwner)?GSCOLOR_CHAT_PRIVATE_OWNER_EMOTE:GSCOLOR_CHAT_PRIVATE_EMOTE;
}
else
{
style = (isOwner)?GSCOLOR_CHAT_PRIVATE_OWNER:GSCOLOR_CHAT_PRIVATE;
}
UnicodeString name;
name.translate(nick);
// filters language
// if( TheGlobalData->m_languageFilterPref )
// {
TheLanguageFilter->filterLine(msg);
// }
UnicodeString fullMsg;
if (isAction)
{
fullMsg.format( L"%ls %ls", name.str(), msg.str() );
}
else
{
fullMsg.format( L"[%ls] %ls", name.str(), msg.str() );
}
GameSpyAddText(fullMsg, style);
}
void GameSpyAddText( UnicodeString message, GameSpyColors color )
{
GameWindow *textWindow = NULL;
if (!textWindow)
textWindow = listboxLobbyChat;
if (!textWindow)
textWindow = listboxGameSetupChat;
if (!textWindow)
return;
GadgetListBoxAddEntryText(textWindow, message, GameSpyColor[color], -1, -1);
}

View File

@@ -0,0 +1,162 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: GameSpyGP.cpp //////////////////////////////////////////////////////
// GameSpy GP callbacks, utils, etc
// Author: Matthew D. Campbell, February 2002
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameClient/GameText.h"
#include "GameNetwork/GameSpy.h"
#include "GameNetwork/GameSpyGP.h"
#include "GameNetwork/GameSpyOverlay.h"
GPConnection TheGPConnectionObj;
GPConnection *TheGPConnection = &TheGPConnectionObj;
GPProfile GameSpyLocalProfile = 0;
char GameSpyProfilePassword[64];
void GPRecvBuddyMessageCallback(GPConnection * pconnection, GPRecvBuddyMessageArg * arg, void * param)
{
DEBUG_LOG(("GPRecvBuddyMessageCallback: message from %d is %s\n", arg->profile, arg->message));
//gpGetInfo(pconn, arg->profile, GP_DONT_CHECK_CACHE, GP_BLOCKING, (GPCallback)Whois, NULL);
//printf("MESSAGE (%d): %s: %s\n", msgCount,whois, arg->message);
}
static void buddyTryReconnect( void )
{
TheGameSpyChat->reconnectProfile();
}
void GPErrorCallback(GPConnection * pconnection, GPErrorArg * arg, void * param)
{
DEBUG_LOG(("GPErrorCallback\n"));
AsciiString errorCodeString;
AsciiString resultString;
#define RESULT(x) case x: resultString = #x; break;
switch(arg->result)
{
RESULT(GP_NO_ERROR)
RESULT(GP_MEMORY_ERROR)
RESULT(GP_PARAMETER_ERROR)
RESULT(GP_NETWORK_ERROR)
RESULT(GP_SERVER_ERROR)
default:
resultString = "Unknown result!";
}
#undef RESULT
#define ERRORCODE(x) case x: errorCodeString = #x; break;
switch(arg->errorCode)
{
ERRORCODE(GP_GENERAL)
ERRORCODE(GP_PARSE)
ERRORCODE(GP_NOT_LOGGED_IN)
ERRORCODE(GP_BAD_SESSKEY)
ERRORCODE(GP_DATABASE)
ERRORCODE(GP_NETWORK)
ERRORCODE(GP_FORCED_DISCONNECT)
ERRORCODE(GP_CONNECTION_CLOSED)
ERRORCODE(GP_LOGIN)
ERRORCODE(GP_LOGIN_TIMEOUT)
ERRORCODE(GP_LOGIN_BAD_NICK)
ERRORCODE(GP_LOGIN_BAD_EMAIL)
ERRORCODE(GP_LOGIN_BAD_PASSWORD)
ERRORCODE(GP_LOGIN_BAD_PROFILE)
ERRORCODE(GP_LOGIN_PROFILE_DELETED)
ERRORCODE(GP_LOGIN_CONNECTION_FAILED)
ERRORCODE(GP_LOGIN_SERVER_AUTH_FAILED)
ERRORCODE(GP_NEWUSER)
ERRORCODE(GP_NEWUSER_BAD_NICK)
ERRORCODE(GP_NEWUSER_BAD_PASSWORD)
ERRORCODE(GP_UPDATEUI)
ERRORCODE(GP_UPDATEUI_BAD_EMAIL)
ERRORCODE(GP_NEWPROFILE)
ERRORCODE(GP_NEWPROFILE_BAD_NICK)
ERRORCODE(GP_NEWPROFILE_BAD_OLD_NICK)
ERRORCODE(GP_UPDATEPRO)
ERRORCODE(GP_UPDATEPRO_BAD_NICK)
ERRORCODE(GP_ADDBUDDY)
ERRORCODE(GP_ADDBUDDY_BAD_FROM)
ERRORCODE(GP_ADDBUDDY_BAD_NEW)
ERRORCODE(GP_ADDBUDDY_ALREADY_BUDDY)
ERRORCODE(GP_AUTHADD)
ERRORCODE(GP_AUTHADD_BAD_FROM)
ERRORCODE(GP_AUTHADD_BAD_SIG)
ERRORCODE(GP_STATUS)
ERRORCODE(GP_BM)
ERRORCODE(GP_BM_NOT_BUDDY)
ERRORCODE(GP_GETPROFILE)
ERRORCODE(GP_GETPROFILE_BAD_PROFILE)
ERRORCODE(GP_DELBUDDY)
ERRORCODE(GP_DELBUDDY_NOT_BUDDY)
ERRORCODE(GP_DELPROFILE)
ERRORCODE(GP_DELPROFILE_LAST_PROFILE)
ERRORCODE(GP_SEARCH)
ERRORCODE(GP_SEARCH_CONNECTION_FAILED)
default:
errorCodeString = "Unknown error code!";
}
#undef ERRORCODE
if(arg->fatal)
{
DEBUG_LOG(( "-----------\n"));
DEBUG_LOG(( "GP FATAL ERROR\n"));
DEBUG_LOG(( "-----------\n"));
// if we're still connected to the chat server, tell the user. He can always hit the buddy
// button to try reconnecting. Oh yes, also hide the buddy popup.
GameSpyCloseOverlay(GSOVERLAY_BUDDY);
if (TheGameSpyChat->isConnected())
{
GSMessageBoxYesNo(TheGameText->fetch("GUI:GPErrorTitle"), TheGameText->fetch("GUI:GPDisconnected"), buddyTryReconnect, NULL);
}
}
else
{
DEBUG_LOG(( "-----\n"));
DEBUG_LOG(( "GP ERROR\n"));
DEBUG_LOG(( "-----\n"));
}
DEBUG_LOG(( "RESULT: %s (%d)\n", resultString.str(), arg->result));
DEBUG_LOG(( "ERROR CODE: %s (0x%X)\n", errorCodeString.str(), arg->errorCode));
DEBUG_LOG(( "ERROR STRING: %s\n", arg->errorString));
}
void GPRecvBuddyStatusCallback(GPConnection * connection, GPRecvBuddyStatusArg * arg, void * param)
{
DEBUG_LOG(("GPRecvBuddyStatusCallback: info on %d is in %d\n", arg->profile, arg->index));
//GameSpyUpdateBuddyOverlay();
}
void GPRecvBuddyRequestCallback(GPConnection * connection, GPRecvBuddyRequestArg * arg, void * param)
{
DEBUG_LOG(("GPRecvBuddyRequestCallback: %d wants to be our buddy because '%s'\n", arg->profile, arg->reason));
}

View File

@@ -0,0 +1,754 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: GameSpyGameInfo.cpp //////////////////////////////////////////////////////
// GameSpy game setup state info
// Author: Matthew D. Campbell, December 2001
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameEngine.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/RandomValue.h"
#include "Common/Scorekeeper.h"
#include "GameClient/Shell.h"
#include "GameClient/GameText.h"
#include "GameNetwork/GameSpy/PeerDefs.h"
#include "GameNetwork/GameSpyGameInfo.h"
#include "GameNetwork/NetworkInterface.h"
#include "GameNetwork/NetworkUtil.h"
#include "GameNetwork/NetworkDefs.h"
#include "GameNetwork/NAT.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/VictoryConditions.h"
// Singleton ------------------------------------------
GameSpyGameInfo *TheGameSpyGame = NULL;
// Helper Functions ----------------------------------------
GameSpyGameSlot::GameSpyGameSlot()
{
GameSlot();
m_gameSpyLogin.clear();
m_gameSpyLocale.clear();
m_profileID = 0;
}
// Helper Functions ----------------------------------------
/*
** Function definitions for the MIB-II entry points.
*/
BOOL (__stdcall *SnmpExtensionInitPtr)(IN DWORD dwUpTimeReference, OUT HANDLE *phSubagentTrapEvent, OUT AsnObjectIdentifier *pFirstSupportedRegion);
BOOL (__stdcall *SnmpExtensionQueryPtr)(IN BYTE bPduType, IN OUT RFC1157VarBindList *pVarBindList, OUT AsnInteger32 *pErrorStatus, OUT AsnInteger32 *pErrorIndex);
LPVOID (__stdcall *SnmpUtilMemAllocPtr)(IN DWORD bytes);
VOID (__stdcall *SnmpUtilMemFreePtr)(IN LPVOID pMem);
typedef struct tConnInfoStruct {
unsigned int State;
unsigned long LocalIP;
unsigned short LocalPort;
unsigned long RemoteIP;
unsigned short RemotePort;
} ConnInfoStruct;
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
#endif
/***********************************************************************************************
* Get_Local_Chat_Connection_Address -- Which address are we using to talk to the chat server? *
* *
* *
* *
* INPUT: Ptr to address to return local address * *
* *
* OUTPUT: True if success *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 10/27/00 3:24PM ST : Created *
*=============================================================================================*/
Bool GetLocalChatConnectionAddress(AsciiString serverName, UnsignedShort serverPort, UnsignedInt& localIP)
{
//return false;
/*
** Local defines.
*/
enum {
CLOSED = 1,
LISTENING,
SYN_SENT,
SEN_RECEIVED,
ESTABLISHED,
FIN_WAIT,
FIN_WAIT2,
CLOSE_WAIT,
LAST_ACK,
CLOSING,
TIME_WAIT,
DELETE_TCB
};
enum {
tcpConnState = 1,
tcpConnLocalAddress,
tcpConnLocalPort,
tcpConnRemAddress,
tcpConnRemPort
};
/*
** Locals.
*/
unsigned char serverAddress[4];
unsigned char remoteAddress[4];
HANDLE trap_handle;
AsnObjectIdentifier first_supported_region;
std::vector<ConnInfoStruct> connectionVector;
int last_field;
int index;
AsnInteger error_status;
AsnInteger error_index;
int conn_entry_type_index;
int conn_entry_type;
Bool found;
/*
** Statics.
*/
static char _conn_state[][32] = {
"?",
"CLOSED",
"LISTENING",
"SYN_SENT",
"SEN_RECEIVED",
"ESTABLISHED",
"FIN_WAIT",
"FIN_WAIT2",
"CLOSE_WAIT",
"LAST_ACK",
"CLOSING",
"TIME_WAIT",
"DELETE_TCB"
};
DEBUG_LOG(("Finding local address used to talk to the chat server\n"));
DEBUG_LOG(("Current chat server name is %s\n", serverName.str()));
DEBUG_LOG(("Chat server port is %d\n", serverPort));
/*
** Get the address of the chat server.
*/
DEBUG_LOG( ("About to call gethostbyname\n"));
struct hostent *host_info = gethostbyname(serverName.str());
if (!host_info) {
DEBUG_LOG( ("gethostbyname failed! Error code %d\n", WSAGetLastError()));
return(false);
}
memcpy(serverAddress, &host_info->h_addr_list[0][0], 4);
unsigned long temp = *((unsigned long*)(&serverAddress[0]));
temp = ntohl(temp);
*((unsigned long*)(&serverAddress[0])) = temp;
DEBUG_LOG(("Host address is %d.%d.%d.%d\n", serverAddress[3], serverAddress[2], serverAddress[1], serverAddress[0]));
/*
** Load the MIB-II SNMP DLL.
*/
DEBUG_LOG(("About to load INETMIB1.DLL\n"));
HINSTANCE mib_ii_dll = LoadLibrary("inetmib1.dll");
if (mib_ii_dll == NULL) {
DEBUG_LOG(("Failed to load INETMIB1.DLL\n"));
return(false);
}
DEBUG_LOG(("About to load SNMPAPI.DLL\n"));
HINSTANCE snmpapi_dll = LoadLibrary("snmpapi.dll");
if (snmpapi_dll == NULL) {
DEBUG_LOG(("Failed to load SNMPAPI.DLL\n"));
FreeLibrary(mib_ii_dll);
return(false);
}
/*
** Get the function pointers into the .dll
*/
SnmpExtensionInitPtr = (int (__stdcall *)(unsigned long,void ** ,AsnObjectIdentifier *)) GetProcAddress(mib_ii_dll, "SnmpExtensionInit");
SnmpExtensionQueryPtr = (int (__stdcall *)(unsigned char,SnmpVarBindList *,long *,long *)) GetProcAddress(mib_ii_dll, "SnmpExtensionQuery");
SnmpUtilMemAllocPtr = (void *(__stdcall *)(unsigned long)) GetProcAddress(snmpapi_dll, "SnmpUtilMemAlloc");
SnmpUtilMemFreePtr = (void (__stdcall *)(void *)) GetProcAddress(snmpapi_dll, "SnmpUtilMemFree");
if (SnmpExtensionInitPtr == NULL || SnmpExtensionQueryPtr == NULL || SnmpUtilMemAllocPtr == NULL || SnmpUtilMemFreePtr == NULL) {
DEBUG_LOG(("Failed to get proc addresses for linked functions\n"));
FreeLibrary(snmpapi_dll);
FreeLibrary(mib_ii_dll);
return(false);
}
RFC1157VarBindList *bind_list_ptr = (RFC1157VarBindList *) SnmpUtilMemAllocPtr(sizeof(RFC1157VarBindList));
RFC1157VarBind *bind_ptr = (RFC1157VarBind *) SnmpUtilMemAllocPtr(sizeof(RFC1157VarBind));
/*
** OK, here we go. Try to initialise the .dll
*/
DEBUG_LOG(("About to init INETMIB1.DLL\n"));
int ok = SnmpExtensionInitPtr(GetCurrentTime(), &trap_handle, &first_supported_region);
if (!ok) {
/*
** Aw crap.
*/
DEBUG_LOG(("Failed to init the .dll\n"));
SnmpUtilMemFreePtr(bind_list_ptr);
SnmpUtilMemFreePtr(bind_ptr);
FreeLibrary(snmpapi_dll);
FreeLibrary(mib_ii_dll);
return(false);
}
/*
** Name of mib_ii object we want to query. See RFC 1213.
**
** iso.org.dod.internet.mgmt.mib-2.tcp.tcpConnTable.TcpConnEntry.tcpConnState
** 1 3 6 1 2 1 6 13 1 1
*/
unsigned int mib_ii_name[] = {1,3,6,1,2,1,6,13,1,1};
unsigned int *mib_ii_name_ptr = (unsigned int *) SnmpUtilMemAllocPtr(sizeof(mib_ii_name));
memcpy(mib_ii_name_ptr, mib_ii_name, sizeof(mib_ii_name));
/*
** Get the index of the conn entry data.
*/
conn_entry_type_index = ARRAY_SIZE(mib_ii_name) - 1;
/*
** Set up the bind list.
*/
bind_ptr->name.idLength = ARRAY_SIZE(mib_ii_name);
bind_ptr->name.ids = mib_ii_name;
bind_list_ptr->list = bind_ptr;
bind_list_ptr->len = 1;
/*
** We start with the tcpConnLocalAddress field.
*/
last_field = 1;
/*
** First connection.
*/
index = 0;
/*
** Suck out that tcp connection info....
*/
while (true) {
if (!SnmpExtensionQueryPtr(SNMP_PDU_GETNEXT, bind_list_ptr, &error_status, &error_index)) {
//if (!SnmpExtensionQueryPtr(ASN_RFC1157_GETNEXTREQUEST, bind_list_ptr, &error_status, &error_index)) {
DEBUG_LOG(("SnmpExtensionQuery returned false\n"));
SnmpUtilMemFreePtr(bind_list_ptr);
SnmpUtilMemFreePtr(bind_ptr);
FreeLibrary(snmpapi_dll);
FreeLibrary(mib_ii_dll);
return(false);
}
/*
** If this is something new we aren't looking for then we are done.
*/
if (bind_ptr->name.idLength < ARRAY_SIZE(mib_ii_name)) {
break;
}
/*
** Get the type of info we are looking at. See RFC1213.
**
** 1 = tcpConnState
** 2 = tcpConnLocalAddress
** 3 = tcpConnLocalPort
** 4 = tcpConnRemAddress
** 5 = tcpConnRemPort
**
** tcpConnState is one of the following...
**
** 1 closed
** 2 listen
** 3 synSent
** 4 synReceived
** 5 established
** 6 finWait1
** 7 finWait2
** 8 closeWait
** 9 lastAck
** 10 closing
** 11 timeWait
** 12 deleteTCB
*/
conn_entry_type = bind_ptr->name.ids[conn_entry_type_index];
if (last_field != conn_entry_type) {
index = 0;
last_field = conn_entry_type;
}
switch (conn_entry_type) {
/*
** 1. First field in the entry. Need to create a new connection info struct
** here to store this connection in.
*/
case tcpConnState:
{
ConnInfoStruct new_conn;
new_conn.State = bind_ptr->value.asnValue.number;
connectionVector.push_back(new_conn);
break;
}
/*
** 2. Local address field.
*/
case tcpConnLocalAddress:
DEBUG_ASSERTCRASH(index < connectionVector.size(), ("Bad connection index"));
connectionVector[index].LocalIP = *((unsigned long*)bind_ptr->value.asnValue.address.stream);
index++;
break;
/*
** 3. Local port field.
*/
case tcpConnLocalPort:
DEBUG_ASSERTCRASH(index < connectionVector.size(), ("Bad connection index"));
connectionVector[index].LocalPort = bind_ptr->value.asnValue.number;
//connectionVector[index]->LocalPort = ntohs(connectionVector[index]->LocalPort);
index++;
break;
/*
** 4. Remote address field.
*/
case tcpConnRemAddress:
DEBUG_ASSERTCRASH(index < connectionVector.size(), ("Bad connection index"));
connectionVector[index].RemoteIP = *((unsigned long*)bind_ptr->value.asnValue.address.stream);
index++;
break;
/*
** 5. Remote port field.
*/
case tcpConnRemPort:
DEBUG_ASSERTCRASH(index < connectionVector.size(), ("Bad connection index"));
connectionVector[index].RemotePort = bind_ptr->value.asnValue.number;
//connectionVector[index]->RemotePort = ntohs(connectionVector[index]->RemotePort);
index++;
break;
}
}
SnmpUtilMemFreePtr(bind_list_ptr);
SnmpUtilMemFreePtr(bind_ptr);
SnmpUtilMemFreePtr(mib_ii_name_ptr);
DEBUG_LOG(("Got %d connections in list, parsing...\n", connectionVector.size()));
/*
** Right, we got the lot. Lets see if any of them have the same address as the chat
** server we think we are talking to.
*/
found = false;
for (Int i=0; i<connectionVector.size(); ++i) {
ConnInfoStruct connection = connectionVector[i];
temp = ntohl(connection.RemoteIP);
memcpy(remoteAddress, (unsigned char*)&temp, 4);
/*
** See if this connection has the same address as our server.
*/
if (!found && memcmp(remoteAddress, serverAddress, 4) == 0) {
DEBUG_LOG(("Found connection with same remote address as server\n"));
if (serverPort == 0 || serverPort == (unsigned int)connection.RemotePort) {
DEBUG_LOG(("Connection has same port\n"));
/*
** Make sure the connection is current.
*/
if (connection.State == ESTABLISHED) {
DEBUG_LOG(("Connection is ESTABLISHED\n"));
localIP = connection.LocalIP;
found = true;
} else {
DEBUG_LOG(("Connection is not ESTABLISHED - skipping\n"));
}
} else {
DEBUG_LOG(("Connection has different port. Port is %d, looking for %d\n", connection.RemotePort, serverPort));
}
}
}
if (found) {
DEBUG_LOG(("Using address 0x%8.8X to talk to chat server\n", localIP));
}
FreeLibrary(snmpapi_dll);
FreeLibrary(mib_ii_dll);
return(found);
}
// GameSpyGameInfo ----------------------------------------
GameSpyGameInfo::GameSpyGameInfo()
{
m_isQM = FALSE;
m_hasBeenQueried = FALSE;
for (Int i = 0; i< MAX_SLOTS; ++i)
setSlotPointer(i, &m_GameSpySlot[i]);
UnsignedInt localIP;
if (GetLocalChatConnectionAddress("peerchat.gamespy.com", 6667, localIP))
{
localIP = ntohl(localIP); // The IP returned from GetLocalChatConnectionAddress is in network byte order.
setLocalIP(localIP);
}
else
{
setLocalIP(0);
}
m_server = NULL;
m_transport = NULL;
}
// Misc game-related functionality --------------------
void GameSpyStartGame( void )
{
if (TheGameSpyGame)
{
int i;
int numUsers = 0;
for (i=0; i<MAX_SLOTS; ++i)
{
GameSlot *slot = TheGameSpyGame->getSlot(i);
if (slot && slot->isOccupied())
numUsers++;
}
if (numUsers < 2)
{
if (TheGameSpyGame->amIHost())
{
UnicodeString text;
text.format(TheGameText->fetch("LAN:NeedMorePlayers"),numUsers);
TheGameSpyInfo->addText(text, GSCOLOR_DEFAULT, NULL);
}
return;
}
TheGameSpyGame->startGame(0);
}
}
void GameSpyLaunchGame( void )
{
if (TheGameSpyGame)
{
// Set up the game network
AsciiString user;
AsciiString userList;
DEBUG_ASSERTCRASH(TheNetwork == NULL, ("For some reason TheNetwork isn't NULL at the start of this game. Better look into that."));
if (TheNetwork != NULL) {
delete TheNetwork;
TheNetwork = NULL;
}
// Time to initialize TheNetwork for this game.
TheNetwork = NetworkInterface::createNetwork();
TheNetwork->init();
/*
if (!TheGameSpyGame->amIHost())
TheNetwork->setLocalAddress((207<<24) | (138<<16) | (47<<8) | 15, 8088);
else
*/
TheNetwork->setLocalAddress(TheGameSpyGame->getLocalIP(), TheNAT->getSlotPort(TheGameSpyGame->getLocalSlotNum()));
TheNetwork->attachTransport(TheNAT->getTransport());
user = TheGameSpyInfo->getLocalName();
for (Int i=0; i<MAX_SLOTS; ++i)
{
GameSlot *slot = TheGameSpyGame->getSlot(i);
if (!slot)
{
DEBUG_CRASH(("No GameSlot[%d]!", i));
delete TheNetwork;
TheNetwork = NULL;
return;
}
// UnsignedInt ip = htonl(slot->getIP());
UnsignedInt ip = slot->getIP();
AsciiString tmpUserName;
tmpUserName.translate(slot->getName());
if (ip)
{
/*
if (i == 1)
{
user.format(",%s@207.138.47.15:8088", tmpUserName.str());
}
else
*/
{
user.format(",%s@%d.%d.%d.%d:%d", tmpUserName.str(),
((ip & 0xff000000) >> 24),
((ip & 0xff0000) >> 16),
((ip & 0xff00) >> 8),
((ip & 0xff)),
TheNAT->getSlotPort(i)
);
}
userList.concat(user);
}
}
userList.trim();
TheNetwork->parseUserList(TheGameSpyGame);
// shutdown the top, but do not pop it off the stack
// TheShell->hideShell();
// setup the Global Data with the Map and Seed
TheGlobalData->m_pendingFile = TheGameSpyGame->getMap();
if (TheGameLogic->isInGame()) {
TheGameLogic->clearGameData();
}
// send a message to the logic for a new game
GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_NEW_GAME );
msg->appendIntegerArgument(GAME_INTERNET);
TheGlobalData->m_useFpsLimit = false;
// Set the random seed
InitGameLogicRandom( TheGameSpyGame->getSeed() );
DEBUG_LOG(("InitGameLogicRandom( %d )\n", TheGameSpyGame->getSeed()));
if (TheNAT != NULL) {
delete TheNAT;
TheNAT = NULL;
}
}
}
void GameSpyGameInfo::init( void )
{
GameInfo::init();
m_hasBeenQueried = false;
}
void GameSpyGameInfo::resetAccepted( void )
{
GameInfo::resetAccepted();
if (m_hasBeenQueried && amIHost())
{
// ANCIENTMUNKEE peerStateChanged(TheGameSpyChat->getPeer());
m_hasBeenQueried = false;
DEBUG_LOG(("resetAccepted() called peerStateChange()\n"));
}
}
Int GameSpyGameInfo::getLocalSlotNum( void ) const
{
DEBUG_ASSERTCRASH(m_inGame, ("Looking for local game slot while not in game"));
if (!m_inGame)
return -1;
AsciiString localName = TheGameSpyInfo->getLocalName();
for (Int i=0; i<MAX_SLOTS; ++i)
{
const GameSlot *slot = getConstSlot(i);
if (slot == NULL) {
continue;
}
if (slot->isPlayer(localName))
return i;
}
return -1;
}
void GameSpyGameInfo::gotGOACall( void )
{
DEBUG_LOG(("gotGOACall()\n"));
m_hasBeenQueried = true;
}
void GameSpyGameInfo::startGame(Int gameID)
{
DEBUG_LOG(("GameSpyGameInfo::startGame - game id = %d\n", gameID));
DEBUG_ASSERTCRASH(m_transport == NULL, ("m_transport is not NULL when it should be"));
DEBUG_ASSERTCRASH(TheNAT == NULL, ("TheNAT is not NULL when it should be"));
// fill in GS-specific info
for (Int i=0; i<MAX_SLOTS; ++i)
{
if (m_GameSpySlot[i].isHuman())
{
AsciiString gsName;
gsName.translate( m_GameSpySlot[i].getName() );
m_GameSpySlot[i].setLoginName( gsName );
PlayerInfoMap *pInfoMap = TheGameSpyInfo->getPlayerInfoMap();
PlayerInfoMap::iterator it = pInfoMap->find(gsName);
if (it != pInfoMap->end())
{
m_GameSpySlot[i].setProfileID(it->second.m_profileID);
m_GameSpySlot[i].setLocale(it->second.m_locale);
}
else
{
DEBUG_CRASH(("No player info for %s", gsName.str()));
}
}
}
if (TheNAT != NULL) {
delete TheNAT;
TheNAT = NULL;
}
TheNAT = NEW NAT();
TheNAT->attachSlotList(m_slot, getLocalSlotNum(), m_localIP);
TheNAT->establishConnectionPaths();
}
AsciiString GameSpyGameInfo::generateGameResultsPacket( void )
{
Int i;
Int endFrame = TheVictoryConditions->getEndFrame();
Int localSlotNum = getLocalSlotNum();
//GameSlot *localSlot = getSlot(localSlotNum);
Bool sawGameEnd = (endFrame > 0);// && localSlot->lastFrameInGame() <= endFrame);
Int winningTeam = -1;
Int numPlayers = 0;
Int numTeamsAtGameEnd = 0;
Int lastTeamAtGameEnd = -1;
for (i=0; i<MAX_SLOTS; ++i)
{
AsciiString playerName;
playerName.format("player%d", i);
Player *p = ThePlayerList->findPlayerWithNameKey(NAMEKEY(playerName));
if (p)
{
++numPlayers;
if (TheVictoryConditions->hasAchievedVictory(p))
{
winningTeam = getSlot(i)->getTeamNumber();
}
// check if he lasted
GameSlot *slot = getSlot(i);
if (!slot->disconnected())
{
if (slot->getTeamNumber() != lastTeamAtGameEnd || numTeamsAtGameEnd == 0)
{
lastTeamAtGameEnd = slot->getTeamNumber();
++numTeamsAtGameEnd;
}
}
}
}
AsciiString results;
results.format("seed=%d,slotNum=%d,sawDesync=%d,sawGameEnd=%d,winningTeam=%d,disconEnd=%d,duration=%d,numPlayers=%d,isQM=%d",
getSeed(), localSlotNum, TheNetwork->sawCRCMismatch(), sawGameEnd, winningTeam, (numTeamsAtGameEnd != 0),
endFrame, numPlayers, m_isQM);
Int playerID = 0;
for (i=0; i<MAX_SLOTS; ++i)
{
AsciiString playerName;
playerName.format("player%d", i);
Player *p = ThePlayerList->findPlayerWithNameKey(NAMEKEY(playerName));
if (p)
{
GameSpyGameSlot *slot = &(m_GameSpySlot[i]);
ScoreKeeper *keeper = p->getScoreKeeper();
AsciiString playerName = slot->getLoginName();
Int gsPlayerID = slot->getProfileID();
AsciiString locale = slot->getLocale();
Int fps = TheNetwork->getAverageFPS();
Int unitsKilled = keeper->getTotalUnitsDestroyed();
Int unitsLost = keeper->getTotalUnitsLost();
Int unitsBuilt = keeper->getTotalUnitsBuilt();
Int buildingsKilled = keeper->getTotalBuildingsDestroyed();
Int buildingsLost = keeper->getTotalBuildingsLost();
Int buildingsBuilt = keeper->getTotalBuildingsBuilt();
Int earnings = keeper->getTotalMoneyEarned();
Int techCaptured = keeper->getTotalTechBuildingsCaptured();
Bool disconnected = slot->disconnected();
AsciiString playerStr;
playerStr.format(",player%d=%s,playerID%d=%d,locale%d=%s",
playerID, playerName.str(), playerID, gsPlayerID, playerID, locale.str());
results.concat(playerStr);
playerStr.format(",unitsKilled%d=%d,unitsLost%d=%d,unitsBuilt%d=%d",
playerID, unitsKilled, playerID, unitsLost, playerID, unitsBuilt);
results.concat(playerStr);
playerStr.format(",buildingsKilled%d=%d,buildingsLost%d=%d,buildingsBuilt%d=%d",
playerID, buildingsKilled, playerID, buildingsLost, playerID, buildingsBuilt);
results.concat(playerStr);
playerStr.format(",fps%d=%d,cash%d=%d,capturedTech%d=%d,discon%d=%d",
playerID, fps, playerID, earnings, playerID, techCaptured, playerID, disconnected);
results.concat(playerStr);
++playerID;
}
}
// Add a trailing size value (so the server can ensure it got the entire packet)
int resultsLen = results.getLength()+10;
AsciiString tail;
tail.format("%10.10d", resultsLen);
results.concat(tail);
return results;
}

View File

@@ -0,0 +1,333 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: wolscreens.cpp //////////////////////////////////////////////////////
// Westwood Online screen setup/teardown
// Author: Matthew D. Campbell, November 2001
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/AudioEventRTS.h"
#include "GameClient/GadgetListBox.h"
#include "GameClient/GameText.h"
#include "GameClient/MessageBox.h"
#include "GameClient/ShellHooks.h"
//#include "GameNetwork/GameSpy.h"
//#include "GameNetwork/GameSpyGP.h"
#include "GameNetwork/GameSpyOverlay.h"
//#include "GameNetwork/GameSpy/PeerDefs.h"
#include "GameNetwork/GameSpy/BuddyThread.h"
void deleteNotificationBox( void );
static void raiseOverlays( void );
// Message boxes -------------------------------------
static GameWindow *messageBoxWindow = NULL;
static GameWinMsgBoxFunc okFunc = NULL;
static GameWinMsgBoxFunc cancelFunc = NULL;
static Bool reOpenPlayerInfoFlag = FALSE;
/**
* messageBoxOK is called when a message box is destroyed
* by way of an OK button, so we can clear our pointers to it.
*/
static void messageBoxOK( void )
{
DEBUG_ASSERTCRASH(messageBoxWindow, ("Message box window went away without being there in the first place!"));
messageBoxWindow = NULL;
if (okFunc)
{
okFunc();
okFunc = NULL;
}
}
/**
* messageBoxCancel is called when a message box is destroyed
* by way of a Cancel button, so we can clear our pointers to it.
*/
static void messageBoxCancel( void )
{
DEBUG_ASSERTCRASH(messageBoxWindow, ("Message box window went away without being there in the first place!"));
messageBoxWindow = NULL;
if (cancelFunc)
{
cancelFunc();
cancelFunc = NULL;
}
}
/**
* clearGSMessageBoxes removes the current message box if
* one is present. This is usually done when putting up a
* second messageBox.
*/
void ClearGSMessageBoxes( void )
{
if (messageBoxWindow)
{
TheWindowManager->winDestroy(messageBoxWindow);
messageBoxWindow = NULL;
}
if (okFunc)
{
okFunc = NULL;
}
if (cancelFunc)
{
cancelFunc = NULL;
}
}
/**
* GSMessageBoxOk puts up an OK dialog box and saves the
* pointers to it and its callbacks.
*/
void GSMessageBoxOk(UnicodeString title, UnicodeString message, GameWinMsgBoxFunc newOkFunc)
{
ClearGSMessageBoxes();
messageBoxWindow = MessageBoxOk(title, message, messageBoxOK);
okFunc = newOkFunc;
}
/**
* GSMessageBoxOkCancel puts up an OK/Cancel dialog box and saves the
* pointers to it and its callbacks.
*/
void GSMessageBoxOkCancel(UnicodeString title, UnicodeString message, GameWinMsgBoxFunc newOkFunc, GameWinMsgBoxFunc newCancelFunc)
{
ClearGSMessageBoxes();
messageBoxWindow = MessageBoxOkCancel(title, message, messageBoxOK, messageBoxCancel);
okFunc = newOkFunc;
cancelFunc = newCancelFunc;
}
/**
* GSMessageBoxYesNo puts up a Yes/No dialog box and saves the
* pointers to it and its callbacks.
*/
void GSMessageBoxYesNo(UnicodeString title, UnicodeString message, GameWinMsgBoxFunc newYesFunc, GameWinMsgBoxFunc newNoFunc)
{
ClearGSMessageBoxes();
messageBoxWindow = MessageBoxYesNo(title, message, messageBoxOK, messageBoxCancel);
okFunc = newYesFunc;
cancelFunc = newNoFunc;
}
/**
* If the screen transitions underneath the dialog box, we
* need to raise it to keep it visible.
*/
void RaiseGSMessageBox( void )
{
raiseOverlays();
if (!messageBoxWindow)
return;
messageBoxWindow->winBringToTop();
}
// Overlay screens -------------------------------------
/**
* gsOverlays holds a list of the .wnd files used in GS overlays.
* The entries *MUST* be in the same order as the GSOverlayType enum.
*/
static const char * gsOverlays[GSOVERLAY_MAX] =
{
"Menus/PopupPlayerInfo.wnd", // Player info (right-click)
"Menus/WOLMapSelectMenu.wnd", // Map select
"Menus/WOLBuddyOverlay.wnd", // Buddy list
"Menus/WOLPageOverlay.wnd", // Find/page
"Menus/PopupHostGame.wnd", // Hosting options (game name, password, etc)
"Menus/PopupJoinGame.wnd", // Joining options (password, etc)
"Menus/PopupLadderSelect.wnd",// LadderSelect
"Menus/PopupLocaleSelect.wnd",// Prompt for user's locale
"Menus/OptionsMenu.wnd", // popup options
};
static WindowLayout *overlayLayouts[GSOVERLAY_MAX] =
{
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
};
static void buddyTryReconnect( void )
{
BuddyRequest req;
req.buddyRequestType = BuddyRequest::BUDDYREQUEST_RELOGIN;
TheGameSpyBuddyMessageQueue->addRequest( req );
}
void GameSpyOpenOverlay( GSOverlayType overlay )
{
if (overlay == GSOVERLAY_BUDDY)
{
if (!TheGameSpyBuddyMessageQueue->isConnected())
{
// not connected - is it because we were disconnected?
if (TheGameSpyBuddyMessageQueue->getLocalProfileID())
{
// used to be connected
GSMessageBoxYesNo(TheGameText->fetch("GUI:GPErrorTitle"), TheGameText->fetch("GUI:GPDisconnected"), buddyTryReconnect, NULL);
}
else
{
// no profile
GSMessageBoxOk(TheGameText->fetch("GUI:GPErrorTitle"), TheGameText->fetch("GUI:GPNoProfile"), NULL);
}
return;
}
AudioEventRTS buttonClick("GUICommunicatorOpen");
if( TheAudio )
{
TheAudio->addAudioEvent( &buttonClick );
} // end if
}
if (overlayLayouts[overlay])
{
overlayLayouts[overlay]->hide( FALSE );
overlayLayouts[overlay]->bringForward();
}
else
{
overlayLayouts[overlay] = TheWindowManager->winCreateLayout( AsciiString( gsOverlays[overlay] ) );
overlayLayouts[overlay]->runInit();
overlayLayouts[overlay]->hide( FALSE );
overlayLayouts[overlay]->bringForward();
}
}
void GameSpyCloseOverlay( GSOverlayType overlay )
{
switch(overlay)
{
case GSOVERLAY_PLAYERINFO:
DEBUG_LOG(("Closing overlay GSOVERLAY_PLAYERINFO\n"));
break;
case GSOVERLAY_MAPSELECT:
DEBUG_LOG(("Closing overlay GSOVERLAY_MAPSELECT\n"));
break;
case GSOVERLAY_BUDDY:
DEBUG_LOG(("Closing overlay GSOVERLAY_BUDDY\n"));
break;
case GSOVERLAY_PAGE:
DEBUG_LOG(("Closing overlay GSOVERLAY_PAGE\n"));
break;
case GSOVERLAY_GAMEOPTIONS:
DEBUG_LOG(("Closing overlay GSOVERLAY_GAMEOPTIONS\n"));
break;
case GSOVERLAY_GAMEPASSWORD:
DEBUG_LOG(("Closing overlay GSOVERLAY_GAMEPASSWORD\n"));
break;
case GSOVERLAY_LADDERSELECT:
DEBUG_LOG(("Closing overlay GSOVERLAY_LADDERSELECT\n"));
break;
case GSOVERLAY_OPTIONS:
DEBUG_LOG(("Closing overlay GSOVERLAY_OPTIONS\n"));
if( overlayLayouts[overlay] )
{
SignalUIInteraction(SHELL_SCRIPT_HOOK_OPTIONS_CLOSED);
}
break;
}
if( overlayLayouts[overlay] )
{
overlayLayouts[overlay]->runShutdown();
overlayLayouts[overlay]->destroyWindows();
overlayLayouts[overlay]->deleteInstance();
overlayLayouts[overlay] = NULL;
}
}
Bool GameSpyIsOverlayOpen( GSOverlayType overlay )
{
return (overlayLayouts[overlay] != NULL);
}
void GameSpyToggleOverlay( GSOverlayType overlay )
{
if (GameSpyIsOverlayOpen(overlay))
GameSpyCloseOverlay(overlay);
else
GameSpyOpenOverlay(overlay);
}
void raiseOverlays( void )
{
for (int i=0; i<GSOVERLAY_MAX; ++i)
{
if (overlayLayouts[(GSOverlayType)i])
{
overlayLayouts[(GSOverlayType)i]->bringForward();
}
}
}
void GameSpyCloseAllOverlays( void )
{
for (int i=0; i<GSOVERLAY_MAX; ++i)
{
GameSpyCloseOverlay((GSOverlayType)i);
}
// if we're shutting down the rest, chances are we don't want this popping up.
deleteNotificationBox();
}
void GameSpyUpdateOverlays( void )
{
for (int i=0; i<GSOVERLAY_MAX; ++i)
{
if (overlayLayouts[(GSOverlayType)i])
{
overlayLayouts[(GSOverlayType)i]->runUpdate();
}
}
}
void ReOpenPlayerInfo( void )
{
reOpenPlayerInfoFlag = TRUE;
}
void CheckReOpenPlayerInfo(void )
{
if(!reOpenPlayerInfoFlag)
return;
GameSpyOpenOverlay(GSOVERLAY_PLAYERINFO);
reOpenPlayerInfoFlag = FALSE;
}

View File

@@ -0,0 +1,392 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: GameSpyPersistentStorage.cpp //////////////////////////////////////////////////////
// GameSpy Persistent Storage callbacks, utils, etc
// Author: Matthew D. Campbell, March 2002
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameSpy/gstats/gpersist.h"
#include "GameClient/Shell.h"
#include "GameClient/MessageBox.h"
#include "GameNetwork/GameSpy.h"
#include "GameNetwork/GameSpyGP.h"
#include "GameNetwork/GameSpyPersistentStorage.h"
#include "GameNetwork/GameSpyThread.h"
static Bool isProfileAuthorized = false;
static Bool gameSpyInitPersistentStorageConnection( void );
static void getPersistentDataCallback(int localid, int profileid, persisttype_t type, int index, int success, char *data, int len, void *instance);
static void setPersistentDataCallback(int localid, int profileid, persisttype_t type, int index, int success, void *instance);
class GameSpyPlayerInfo : public GameSpyPlayerInfoInterface
{
public:
GameSpyPlayerInfo() { m_locale.clear(); m_wins = m_losses = m_operationCount = 0; m_shouldDisconnect = false; }
virtual ~GameSpyPlayerInfo() { reset(); }
virtual void init( void ) { m_locale.clear(); m_wins = m_losses = m_operationCount = 0; queueDisconnect(); };
virtual void reset( void ) { m_locale.clear(); m_wins = m_losses = m_operationCount = 0; queueDisconnect(); };
virtual void update( void );
virtual AsciiString getLocale( void ) { return m_locale; }
virtual Int getWins( void ) { return m_wins; }
virtual Int getLosses( void ) { return m_losses; }
virtual void setLocale( AsciiString locale, Bool setOnServer );
virtual void setWins( Int wins, Bool setOnServer );
virtual void setLosses( Int losses, Bool setOnServer );
virtual void readFromServer( void );
virtual void threadReadFromServer( void );
virtual void threadSetLocale( AsciiString val );
virtual void threadSetWins ( AsciiString val );
virtual void threadSetLosses( AsciiString val );
void queueDisconnect( void ) { MutexClass::LockClass m(TheGameSpyMutex); if (IsStatsConnected()) m_shouldDisconnect = true; else m_shouldDisconnect = false; }
private:
void setValue( AsciiString key, AsciiString val, Bool setOnServer );
AsciiString m_locale;
Int m_wins;
Int m_losses;
Int m_operationCount;
Bool m_shouldDisconnect;
};
void GameSpyPlayerInfo::update( void )
{
if (IsStatsConnected())
{
if (m_shouldDisconnect)
{
DEBUG_LOG(("Persistent Storage close\n"));
CloseStatsConnection();
}
else
{
PersistThink();
}
}
}
void GameSpyPlayerInfo::readFromServer( void )
{
TheGameSpyThread->queueReadPersistentStatsFromServer();
}
void GameSpyPlayerInfo::threadReadFromServer( void )
{
MutexClass::LockClass m(TheGameSpyMutex);
if (gameSpyInitPersistentStorageConnection())
{
// get persistent info
m_operationCount++;
DEBUG_LOG(("GameSpyPlayerInfo::readFromServer() operation count = %d\n", m_operationCount));
GetPersistDataValues(0, TheGameSpyChat->getProfileID(), pd_public_rw, 0, "\\locale\\wins\\losses", getPersistentDataCallback, &m_operationCount);
}
else
{
//TheGameSpyThread->setNextShellScreen("Menus/WOLWelcomeMenu.wnd");
//TheShell->pop();
//TheShell->push("Menus/WOLWelcomeMenu.wnd");
}
}
void GameSpyPlayerInfo::setLocale( AsciiString locale, Bool setOnServer )
{
m_locale = locale;
if (!TheGameSpyChat->getProfileID() || !setOnServer)
return;
setValue("locale", m_locale, setOnServer);
}
void GameSpyPlayerInfo::setWins( Int wins, Bool setOnServer )
{
m_wins = wins;
if (!TheGameSpyChat->getProfileID() || !setOnServer)
return;
AsciiString winStr;
winStr.format("%d", wins);
setValue("wins", winStr, setOnServer);
}
void GameSpyPlayerInfo::setLosses( Int losses, Bool setOnServer )
{
m_losses = losses;
if (!TheGameSpyChat->getProfileID() || !setOnServer)
return;
AsciiString lossesStr;
lossesStr.format("%d", losses);
setValue("losses", lossesStr, setOnServer);
}
void GameSpyPlayerInfo::setValue( AsciiString key, AsciiString val, Bool setOnServer )
{
if (!setOnServer)
return;
if (key == "locale")
TheGameSpyThread->queueUpdateLocale(val);
else if (key == "wins")
TheGameSpyThread->queueUpdateWins(val);
else if (key == "losses")
TheGameSpyThread->queueUpdateLosses(val);
}
void GameSpyPlayerInfo::threadSetLocale( AsciiString val )
{
MutexClass::LockClass m(TheGameSpyMutex);
if (!gameSpyInitPersistentStorageConnection())
return;
// set locale info
AsciiString key = "locale";
AsciiString str;
str.format("\\%s\\%s", key.str(), val.str());
char *writable = strdup(str.str());
m_operationCount++;
DEBUG_LOG(("GameSpyPlayerInfo::set%s() operation count = %d\n", key.str(), m_operationCount));
SetPersistDataValues(0, TheGameSpyChat->getProfileID(), pd_public_rw, 0, writable, setPersistentDataCallback, &m_operationCount);
free(writable);
}
void GameSpyPlayerInfo::threadSetWins( AsciiString val )
{
MutexClass::LockClass m(TheGameSpyMutex);
if (!gameSpyInitPersistentStorageConnection())
return;
// set win info
AsciiString key = "wins";
AsciiString str;
str.format("\\%s\\%s", key.str(), val.str());
char *writable = strdup(str.str());
m_operationCount++;
DEBUG_LOG(("GameSpyPlayerInfo::set%s() operation count = %d\n", key.str(), m_operationCount));
SetPersistDataValues(0, TheGameSpyChat->getProfileID(), pd_public_rw, 0, writable, setPersistentDataCallback, &m_operationCount);
free(writable);
}
void GameSpyPlayerInfo::threadSetLosses( AsciiString val )
{
MutexClass::LockClass m(TheGameSpyMutex);
if (!gameSpyInitPersistentStorageConnection())
return;
// set loss info
AsciiString key = "losses";
AsciiString str;
str.format("\\%s\\%s", key.str(), val.str());
char *writable = strdup(str.str());
m_operationCount++;
DEBUG_LOG(("GameSpyPlayerInfo::set%s() operation count = %d\n", key.str(), m_operationCount));
SetPersistDataValues(0, TheGameSpyChat->getProfileID(), pd_public_rw, 0, writable, setPersistentDataCallback, &m_operationCount);
free(writable);
}
GameSpyPlayerInfoInterface *TheGameSpyPlayerInfo = NULL;
GameSpyPlayerInfoInterface *createGameSpyPlayerInfo( void )
{
return NEW GameSpyPlayerInfo;
}
static void persAuthCallback(int localid, int profileid, int authenticated, char *errmsg, void *instance)
{
DEBUG_LOG(("Auth callback: localid: %d profileid: %d auth: %d err: %s\n",localid, profileid, authenticated, errmsg));
isProfileAuthorized = (authenticated != 0);
}
static void getPersistentDataCallback(int localid, int profileid, persisttype_t type, int index, int success, char *data, int len, void *instance)
{
DEBUG_LOG(("Data get callback: localid: %d profileid: %d success: %d len: %d data: %s\n",localid, profileid, success, len, data));
if (!TheGameSpyPlayerInfo)
{
//TheGameSpyThread->setNextShellScreen("Menus/WOLWelcomeMenu.wnd");
//TheShell->pop();
//TheShell->push("Menus/WOLWelcomeMenu.wnd");
return;
}
AsciiString str = data;
AsciiString key, val;
while (!str.isEmpty())
{
str.nextToken(&key, "\\");
str.nextToken(&val, "\\");
if (!key.isEmpty() && !val.isEmpty())
{
if (!key.compareNoCase("locale"))
{
TheGameSpyPlayerInfo->setLocale(val, false);
}
else if (!key.compareNoCase("wins"))
{
TheGameSpyPlayerInfo->setWins(atoi(val.str()), false);
}
else if (!key.compareNoCase("losses"))
{
TheGameSpyPlayerInfo->setLosses(atoi(val.str()), false);
}
}
}
// decrement count of active operations
Int *opCount = (Int *)instance;
(*opCount) --;
DEBUG_LOG(("getPersistentDataCallback() operation count = %d\n", (*opCount)));
if (!*opCount)
{
DEBUG_LOG(("getPersistentDataCallback() queue disconnect\n"));
((GameSpyPlayerInfo *)TheGameSpyPlayerInfo)->queueDisconnect();
}
const char *keys[3] = { "locale", "wins", "losses" };
char valueStrings[3][20];
char *values[3] = { valueStrings[0], valueStrings[1], valueStrings[2] };
_snprintf(values[0], 20, "%s", TheGameSpyPlayerInfo->getLocale().str());
_snprintf(values[1], 20, "%d", TheGameSpyPlayerInfo->getWins());
_snprintf(values[2], 20, "%d", TheGameSpyPlayerInfo->getLosses());
peerSetGlobalKeys(TheGameSpyChat->getPeer(), 3, (const char **)keys, (const char **)values);
peerSetGlobalWatchKeys(TheGameSpyChat->getPeer(), GroupRoom, 3, keys, PEERTrue);
peerSetGlobalWatchKeys(TheGameSpyChat->getPeer(), StagingRoom, 3, keys, PEERTrue);
// choose next screen
if (TheGameSpyPlayerInfo->getLocale().isEmpty())
{
TheGameSpyThread->setShowLocaleSelect(true);
}
}
static void setPersistentDataCallback(int localid, int profileid, persisttype_t type, int index, int success, void *instance)
{
DEBUG_LOG(("Data save callback: localid: %d profileid: %d success: %d\n", localid, profileid, success));
Int *opCount = (Int *)instance;
(*opCount) --;
DEBUG_LOG(("setPersistentDataCallback() operation count = %d\n", (*opCount)));
if (!*opCount)
{
DEBUG_LOG(("setPersistentDataCallback() queue disconnect\n"));
((GameSpyPlayerInfo *)TheGameSpyPlayerInfo)->queueDisconnect();
}
}
static Bool gameSpyInitPersistentStorageConnection( void )
{
if (IsStatsConnected())
return true;
isProfileAuthorized = false;
Int result;
/*********
First step, set our game authentication info
We could do:
strcpy(gcd_gamename,"gmtest");
strcpy(gcd_secret_key,"HA6zkS");
...but this is more secure:
**********/
gcd_gamename[0]='g';gcd_gamename[1]='m';gcd_gamename[2]='t';gcd_gamename[3]='e';
gcd_gamename[4]='s';gcd_gamename[5]='t';gcd_gamename[6]='\0';
gcd_secret_key[0]='H';gcd_secret_key[1]='A';gcd_secret_key[2]='6';gcd_secret_key[3]='z';
gcd_secret_key[4]='k';gcd_secret_key[5]='S';gcd_secret_key[6]='\0';
/*********
Next, open the stats connection. This may block for
a 1-2 seconds, so it should be done before the actual game starts.
**********/
result = InitStatsConnection(0);
if (result != GE_NOERROR)
{
DEBUG_LOG(("InitStatsConnection returned %d\n",result));
return isProfileAuthorized;
}
if (TheGameSpyChat->getProfileID())
{
char validate[33];
/***********
We'll go ahead and start the authentication, using a Presence & Messaging SDK
profileid / password. To generate the new validation token, we'll need to pass
in the password for the profile we are authenticating.
Again, if this is done in a client/server setting, with the Persistent Storage
access being done on the server, and the P&M SDK is used on the client, the
server will need to send the challenge (GetChallenge(NULL)) to the client, the
client will create the validation token using GenerateAuth, and send it
back to the server for use in PreAuthenticatePlayerPM
***********/
char *munkeeHack = strdup(TheGameSpyChat->getPassword().str()); // GenerateAuth takes a char*, not a const char* :P
GenerateAuth(GetChallenge(NULL), munkeeHack, validate);
free (munkeeHack);
/************
After we get the validation token, we pass it and the profileid of the user
we are authenticating into PreAuthenticatePlayerPM.
We pass the same authentication callback as for the first user, but a different
localid this time.
************/
PreAuthenticatePlayerPM(0, TheGameSpyChat->getProfileID(), validate, persAuthCallback, NULL);
}
else
{
return isProfileAuthorized;
}
UnsignedInt timeoutTime = timeGetTime() + 5000;
while (!isProfileAuthorized && timeGetTime() < timeoutTime && IsStatsConnected())
{
PersistThink();
msleep(10);
}
DEBUG_LOG(("Persistent Storage connect: %d\n", isProfileAuthorized));
return isProfileAuthorized;
}

View File

@@ -0,0 +1,185 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameNetwork/IPEnumeration.h"
IPEnumeration::IPEnumeration( void )
{
m_IPlist = NULL;
m_isWinsockInitialized = false;
}
IPEnumeration::~IPEnumeration( void )
{
if (m_isWinsockInitialized)
{
WSACleanup();
m_isWinsockInitialized = false;
}
EnumeratedIP *ip = m_IPlist;
while (ip)
{
ip = ip->getNext();
m_IPlist->deleteInstance();
m_IPlist = ip;
}
}
EnumeratedIP * IPEnumeration::getAddresses( void )
{
if (m_IPlist)
return m_IPlist;
if (!m_isWinsockInitialized)
{
WORD verReq = MAKEWORD(2, 2);
WSADATA wsadata;
int err = WSAStartup(verReq, &wsadata);
if (err != 0) {
return NULL;
}
if ((LOBYTE(wsadata.wVersion) != 2) || (HIBYTE(wsadata.wVersion) !=2)) {
WSACleanup();
return NULL;
}
m_isWinsockInitialized = true;
}
// get the local machine's host name
char hostname[256];
if (gethostname(hostname, sizeof(hostname)))
{
DEBUG_LOG(("Failed call to gethostname; WSAGetLastError returned %d\n", WSAGetLastError()));
return NULL;
}
DEBUG_LOG(("Hostname is '%s'\n", hostname));
// get host information from the host name
HOSTENT* hostEnt = gethostbyname(hostname);
if (hostEnt == NULL)
{
DEBUG_LOG(("Failed call to gethostnyname; WSAGetLastError returned %d\n", WSAGetLastError()));
return NULL;
}
// sanity-check the length of the IP adress
if (hostEnt->h_length != 4)
{
DEBUG_LOG(("gethostbyname returns oddly-sized IP addresses!\n"));
return NULL;
}
// construct a list of addresses
int numAddresses = 0;
char *entry;
while ( (entry = hostEnt->h_addr_list[numAddresses++]) != 0 )
{
EnumeratedIP *newIP = newInstance(EnumeratedIP);
AsciiString str;
str.format("%d.%d.%d.%d", (unsigned char)entry[0], (unsigned char)entry[1], (unsigned char)entry[2], (unsigned char)entry[3]);
UnsignedInt testIP = *((UnsignedInt *)entry);
UnsignedInt ip = ntohl(testIP);
/*
ip = *entry++;
ip <<= 8;
ip += *entry++;
ip <<= 8;
ip += *entry++;
ip <<= 8;
ip += *entry++;
*/
newIP->setIPstring(str);
newIP->setIP(ip);
DEBUG_LOG(("IP: 0x%8.8X / 0x%8.8X (%s)\n", testIP, ip, str.str()));
// Add the IP to the list in ascending order
if (!m_IPlist)
{
m_IPlist = newIP;
newIP->setNext(NULL);
}
else
{
if (newIP->getIP() < m_IPlist->getIP())
{
newIP->setNext(m_IPlist);
m_IPlist = newIP;
}
else
{
EnumeratedIP *p = m_IPlist;
while (p->getNext() && p->getNext()->getIP() < newIP->getIP())
{
p = p->getNext();
}
newIP->setNext(p->getNext());
p->setNext(newIP);
}
}
}
return m_IPlist;
}
AsciiString IPEnumeration::getMachineName( void )
{
if (!m_isWinsockInitialized)
{
WORD verReq = MAKEWORD(2, 2);
WSADATA wsadata;
int err = WSAStartup(verReq, &wsadata);
if (err != 0) {
return NULL;
}
if ((LOBYTE(wsadata.wVersion) != 2) || (HIBYTE(wsadata.wVersion) !=2)) {
WSACleanup();
return NULL;
}
m_isWinsockInitialized = true;
}
// get the local machine's host name
char hostname[256];
if (gethostname(hostname, sizeof(hostname)))
{
DEBUG_LOG(("Failed call to gethostname; WSAGetLastError returned %d\n", WSAGetLastError()));
return NULL;
}
return AsciiString(hostname);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,742 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
// FILE: LANAPICallbacks.cpp
// Author: Chris Huybregts, October 2001
// Description: LAN API Callbacks
///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "strtok_r.h"
#include "Common/GameEngine.h"
#include "Common/GlobalData.h"
#include "Common/MessageStream.h"
#include "Common/MultiplayerSettings.h"
#include "Common/PlayerTemplate.h"
#include "Common/QuotedPrintable.h"
#include "Common/RandomValue.h"
#include "Common/UserPreferences.h"
#include "GameClient/GameText.h"
#include "GameClient/LanguageFilter.h"
#include "GameClient/MapUtil.h"
#include "GameClient/MessageBox.h"
#include "GameLogic/GameLogic.h"
#include "GameNetwork/FileTransfer.h"
#include "GameNetwork/LANAPICallbacks.h"
#include "GameNetwork/NetworkUtil.h"
LANAPI *TheLAN = NULL;
extern Bool LANbuttonPushed;
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//Colors used for the chat dialogs
const Color playerColor = GameMakeColor(255,255,255,255);
const Color gameColor = GameMakeColor(255,255,255,255);
const Color gameInProgressColor = GameMakeColor(128,128,128,255);
const Color chatNormalColor = GameMakeColor(50,215,230,255);
const Color chatActionColor = GameMakeColor(255,0,255,255);
const Color chatLocalNormalColor = GameMakeColor(255,128,0,255);
const Color chatLocalActionColor = GameMakeColor(128,255,255,255);
const Color chatSystemColor = GameMakeColor(255,255,255,255);
const Color acceptTrueColor = GameMakeColor(0,255,0,255);
const Color acceptFalseColor = GameMakeColor(255,0,0,255);
UnicodeString LANAPIInterface::getErrorStringFromReturnType( ReturnType ret )
{
switch (ret)
{
case RET_OK:
return TheGameText->fetch("LAN:OK");
case RET_TIMEOUT:
return TheGameText->fetch("LAN:ErrorTimeout");
case RET_GAME_FULL:
return TheGameText->fetch("LAN:ErrorGameFull");
case RET_DUPLICATE_NAME:
return TheGameText->fetch("LAN:ErrorDuplicateName");
case RET_CRC_MISMATCH:
return TheGameText->fetch("LAN:ErrorCRCMismatch");
case RET_GAME_STARTED:
return TheGameText->fetch("LAN:ErrorGameStarted");
case RET_GAME_EXISTS:
return TheGameText->fetch("LAN:ErrorGameExists");
case RET_GAME_GONE:
return TheGameText->fetch("LAN:ErrorGameGone");
case RET_BUSY:
return TheGameText->fetch("LAN:ErrorBusy");
case RET_SERIAL_DUPE:
return TheGameText->fetch("WOL:ChatErrorSerialDup");
default:
return TheGameText->fetch("LAN:ErrorUnknown");
}
}
// On functions are (generally) the result of network traffic
void LANAPI::OnAccept( UnsignedInt playerIP, Bool status )
{
if( AmIHost() )
{
for (Int i = 0; i < MAX_SLOTS; i++)
{
if (m_currentGame->getIP(i) == playerIP)
{
if(status)
m_currentGame->getLANSlot(i)->setAccept();
else
m_currentGame->getLANSlot(i)->unAccept();
break;
}// if
}// for
if (i != MAX_SLOTS )
{
RequestGameOptions( GenerateGameOptionsString(), false );
lanUpdateSlotList();
}
}//if
else
{
//i'm not the host but if the accept came from the host...
if( m_currentGame->getIP(0) == playerIP )
{
UnicodeString text;
text = TheGameText->fetch("GUI:HostWantsToStart");
OnChat(UnicodeString(L"SYSTEM"), m_localIP, text, LANCHAT_SYSTEM);
}
}
}// void LANAPI::OnAccept( UnicodeString player, Bool status )
void LANAPI::OnHasMap( UnsignedInt playerIP, Bool status )
{
if( AmIHost() )
{
for (Int i = 0; i < MAX_SLOTS; i++)
{
if (m_currentGame->getIP(i) == playerIP)
{
m_currentGame->getLANSlot(i)->setMapAvailability( status );
break;
}// if
}// for
if (i != MAX_SLOTS )
{
UnicodeString mapDisplayName;
const MapMetaData *mapData = TheMapCache->findMap( m_currentGame->getMap() );
Bool willTransfer = TRUE;
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)
{
UnicodeString text;
if (willTransfer)
text.format(TheGameText->fetch("GUI:PlayerNoMapWillTransfer"), m_currentGame->getLANSlot(i)->getName().str(), mapDisplayName.str());
else
text.format(TheGameText->fetch("GUI:PlayerNoMap"), m_currentGame->getLANSlot(i)->getName().str(), mapDisplayName.str());
OnChat(UnicodeString(L"SYSTEM"), m_localIP, text, LANCHAT_SYSTEM);
}
lanUpdateSlotList();
}
}//if
}// void LANAPI::OnHasMap( UnicodeString player, Bool status )
void LANAPI::OnGameStartTimer( Int seconds )
{
UnicodeString text;
if (seconds == 1)
text.format(TheGameText->fetch("LAN:GameStartTimerSingular"), seconds);
else
text.format(TheGameText->fetch("LAN:GameStartTimerPlural"), seconds);
OnChat(UnicodeString(L"SYSTEM"), m_localIP, text, LANCHAT_SYSTEM);
}
void LANAPI::OnGameStart( void )
{
//DEBUG_LOG(("Map is '%s', preview is '%s'\n", m_currentGame->getMap().str(), GetPreviewFromMap(m_currentGame->getMap()).str()));
//DEBUG_LOG(("Map is '%s', INI is '%s'\n", m_currentGame->getMap().str(), GetINIFromMap(m_currentGame->getMap()).str()));
#if !defined(_PLAYTEST)
if (m_currentGame)
{
LANPreferences pref;
AsciiString option;
option.format("%d", m_currentGame->getLANSlot( m_currentGame->getLocalSlotNum() )->getPlayerTemplate());
pref["PlayerTemplate"] = option;
option.format("%d", m_currentGame->getLANSlot( m_currentGame->getLocalSlotNum() )->getColor());
pref["Color"] = option;
if (m_currentGame->amIHost())
pref["Map"] = AsciiStringToQuotedPrintable(m_currentGame->getMap());
pref.write();
m_isInLANMenu = FALSE;
//m_currentGame->startGame(0);
// Set up the game network
DEBUG_ASSERTCRASH(TheNetwork == NULL, ("For some reason TheNetwork isn't NULL at the start of this game. Better look into that."));
if (TheNetwork != NULL) {
delete TheNetwork;
TheNetwork = NULL;
}
// Time to initialize TheNetwork for this game.
TheNetwork = NetworkInterface::createNetwork();
TheNetwork->init();
TheNetwork->setLocalAddress(m_localIP, 8088);
TheNetwork->initTransport();
TheNetwork->parseUserList(m_currentGame);
if (TheGameLogic->isInGame())
TheGameLogic->clearGameData();
Bool filesOk = DoAnyMapTransfers(m_currentGame);
// see if we really have the map. if not, back out.
TheMapCache->updateCache();
if (!filesOk || TheMapCache->findMap(m_currentGame->getMap()) == NULL)
{
DEBUG_LOG(("After transfer, we didn't really have the map. Bailing...\n"));
OnPlayerLeave(m_name);
removeGame(m_currentGame);
m_currentGame = NULL;
m_inLobby = TRUE;
if (TheNetwork != NULL) {
delete TheNetwork;
TheNetwork = NULL;
}
OnChat(UnicodeString::TheEmptyString, 0, TheGameText->fetch("GUI:CouldNotTransferMap"), LANCHAT_SYSTEM);
return;
}
m_currentGame->startGame(0);
// shutdown the top, but do not pop it off the stack
//TheShell->hideShell();
// setup the Global Data with the Map and Seed
TheWritableGlobalData->m_pendingFile = m_currentGame->getMap();
// send a message to the logic for a new game
GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_NEW_GAME );
msg->appendIntegerArgument(GAME_LAN);
TheWritableGlobalData->m_useFpsLimit = false;
// Set the random seed
InitGameLogicRandom( m_currentGame->getSeed() );
DEBUG_LOG(("InitGameLogicRandom( %d )\n", m_currentGame->getSeed()));
}
#endif
}
void LANAPI::OnGameOptions( UnsignedInt playerIP, Int playerSlot, AsciiString options )
{
if (!m_currentGame)
return;
if (m_currentGame->getIP(playerSlot) != playerIP)
return; // He's not in our game?!?
if (m_currentGame->isGameInProgress())
return; // we don't want to process any game options while in game.
if (playerSlot == 0 && !m_currentGame->amIHost())
{
m_currentGame->setLastHeard(timeGetTime());
AsciiString oldOptions = GameInfoToAsciiString(m_currentGame); // save these off for if we get booted
if(ParseGameOptionsString(m_currentGame,options))
{
lanUpdateSlotList();
updateGameOptions();
}
Bool booted = true;
for(Int player = 1; player< MAX_SLOTS; player++)
{
if(m_currentGame->getIP(player) == m_localIP)
{
booted = false;
break;
}
}
if(booted)
{
// restore the options with us in so we can save prefs
ParseGameOptionsString(m_currentGame, oldOptions);
OnPlayerLeave(m_name);
}
}
else
{
// Check for user/host updates
{
AsciiString key;
AsciiString munkee = options;
munkee.nextToken(&key, "=");
//DEBUG_LOG(("GameOpt request: key=%s, val=%s from player %d\n", key.str(), munkee.str(), playerSlot));
LANGameSlot *slot = m_currentGame->getLANSlot(playerSlot);
if (!slot)
return;
if (key == "User")
{
slot->setLogin(munkee.str()+1);
return;
}
else if (key == "Host")
{
slot->setHost(munkee.str()+1);
return;
}
}
// Parse player requests (side, color, etc)
if( AmIHost() && m_localIP != playerIP)
{
if (options.compare("HELLO") == 0)
{
m_currentGame->setPlayerLastHeard(playerSlot, timeGetTime());
}
else
{
m_currentGame->setPlayerLastHeard(playerSlot, timeGetTime());
Bool change = false;
Bool shouldUnaccept = false;
AsciiString key;
options.nextToken(&key, "=");
Int val = atoi(options.str()+1);
DEBUG_LOG(("GameOpt request: key=%s, val=%s from player %d\n", key.str(), options.str(), playerSlot));
LANGameSlot *slot = m_currentGame->getLANSlot(playerSlot);
if (!slot)
return;
if (key == "Color")
{
if (val >= -1 && val < TheMultiplayerSettings->getNumColors() && val != slot->getColor() && slot->getPlayerTemplate() != PLAYERTEMPLATE_OBSERVER)
{
Bool colorAvailable = TRUE;
if(val != -1 )
{
for(Int i=0; i <MAX_SLOTS; i++)
{
LANGameSlot *checkSlot = m_currentGame->getLANSlot(i);
if(val == checkSlot->getColor() && slot != checkSlot)
{
colorAvailable = FALSE;
break;
}
}
}
if(colorAvailable)
slot->setColor(val);
change = true;
}
else
{
DEBUG_LOG(("Rejecting invalid color %d\n", val));
}
}
else if (key == "PlayerTemplate")
{
if (val >= PLAYERTEMPLATE_MIN && val < ThePlayerTemplateStore->getPlayerTemplateCount() && val != slot->getPlayerTemplate())
{
slot->setPlayerTemplate(val);
if (val == PLAYERTEMPLATE_OBSERVER)
{
slot->setColor(-1);
slot->setStartPos(-1);
slot->setTeamNumber(-1);
}
change = true;
shouldUnaccept = true;
}
else
{
DEBUG_LOG(("Rejecting invalid PlayerTemplate %d\n", val));
}
}
else if (key == "StartPos" && slot->getPlayerTemplate() != PLAYERTEMPLATE_OBSERVER)
{
if (val >= -1 && val < MAX_SLOTS && val != slot->getStartPos())
{
Bool startPosAvailable = TRUE;
if(val != -1)
for(Int i=0; i <MAX_SLOTS; i++)
{
LANGameSlot *checkSlot = m_currentGame->getLANSlot(i);
if(val == checkSlot->getStartPos() && slot != checkSlot)
{
startPosAvailable = FALSE;
break;
}
}
if(startPosAvailable)
slot->setStartPos(val);
change = true;
shouldUnaccept = true;
}
else
{
DEBUG_LOG(("Rejecting invalid startPos %d\n", val));
}
}
else if (key == "Team")
{
if (val >= -1 && val < MAX_SLOTS/2 && val != slot->getTeamNumber() && slot->getPlayerTemplate() != PLAYERTEMPLATE_OBSERVER)
{
slot->setTeamNumber(val);
change = true;
shouldUnaccept = true;
}
else
{
DEBUG_LOG(("Rejecting invalid team %d\n", val));
}
}
else if (key == "NAT")
{
if ((val >= FirewallHelperClass::FIREWALL_TYPE_SIMPLE) &&
(val <= FirewallHelperClass::FIREWALL_TYPE_DESTINATION_PORT_DELTA))
{
slot->setNATBehavior((FirewallHelperClass::FirewallBehaviorType)val);
DEBUG_LOG(("NAT behavior set to %d for player %d\n", val, playerSlot));
change = true;
}
else
{
DEBUG_LOG(("Rejecting invalid NAT behavior %d\n", (Int)val));
}
}
if (change)
{
if (shouldUnaccept)
m_currentGame->resetAccepted();
RequestGameOptions(GenerateGameOptionsString(), true);
lanUpdateSlotList();
DEBUG_LOG(("Slot value is color=%d, PlayerTemplate=%d, startPos=%d, team=%d\n",
slot->getColor(), slot->getPlayerTemplate(), slot->getStartPos(), slot->getTeamNumber()));
DEBUG_LOG(("Slot list updated to %s\n", GenerateGameOptionsString().str()));
}
}
}
}
}
/*
void LANAPI::OnSlotList( ReturnType ret, LANGameInfo *theGame )
{
if (!theGame || theGame != m_currentGame)
return;
Bool foundMe = false;
for (int player = 0; player < MAX_SLOTS; ++player)
{
if (m_currentGame->getIP(player) == m_localIP)
{
foundMe = true;
break;
}
}
if (!foundMe)
{
// I've been kicked - back to the lobby for me!
// We're counting on the fact that OnPlayerLeave winds up calling reset on TheLAN.
OnPlayerLeave(m_name);
return;
}
lanUpdateSlotList();
}
*/
void LANAPI::OnPlayerJoin( Int slot, UnicodeString playerName )
{
if (m_currentGame && m_currentGame->getIP(0) == m_localIP)
{
// Someone New Joined.. lets reset the accepts
m_currentGame->resetAccepted();
// Send out the game options
RequestGameOptions(GenerateGameOptionsString(), true);
}
lanUpdateSlotList();
}
void LANAPI::OnGameJoin( ReturnType ret, LANGameInfo *theGame )
{
if (ret == RET_OK)
{
LANbuttonPushed = true;
TheShell->push( AsciiString("Menus/LanGameOptionsMenu.wnd") );
//lanUpdateSlotList();
LANPreferences pref;
AsciiString options;
options.format("PlayerTemplate=%d", pref.getPreferredFaction());
RequestGameOptions(options, true);
options.format("Color=%d", pref.getPreferredColor());
RequestGameOptions(options, true);
options.format("User=%s", m_userName.str());
RequestGameOptions( options, true );
options.format("Host=%s", m_hostName.str());
RequestGameOptions( options, true );
options.format("NAT=%d", FirewallHelperClass::FIREWALL_TYPE_SIMPLE); // BGC: This is a LAN game, so there is no firewall.
RequestGameOptions( options, true );
}
else if (ret != RET_BUSY)
{
/// @todo: re-enable lobby controls? Error msgs?
UnicodeString title, body;
title = TheGameText->fetch("LAN:JoinFailed");
body = getErrorStringFromReturnType(ret);
MessageBoxOk(title, body, NULL);
}
}
void LANAPI::OnHostLeave( void )
{
DEBUG_ASSERTCRASH(!m_inLobby && m_currentGame, ("Game info is gone!"));
if (m_inLobby || !m_currentGame)
return;
LANbuttonPushed = true;
DEBUG_LOG(("Host left - popping to lobby\n"));
TheShell->pop();
}
void LANAPI::OnPlayerLeave( UnicodeString player )
{
DEBUG_ASSERTCRASH(!m_inLobby && m_currentGame, ("Game info is gone!"));
if (m_inLobby || !m_currentGame || m_currentGame->isGameInProgress())
return;
if (m_name.compare(player) == 0)
{
// We're leaving. Save options and Pop the shell up a screen.
//DEBUG_ASSERTCRASH(false, ("Slot is %d\n", m_currentGame->getLocalSlotNum()));
if (m_currentGame && m_currentGame->isInGame() && m_currentGame->getLocalSlotNum() >= 0)
{
LANPreferences pref;
AsciiString option;
option.format("%d", m_currentGame->getLANSlot( m_currentGame->getLocalSlotNum() )->getPlayerTemplate());
pref["PlayerTemplate"] = option;
option.format("%d", m_currentGame->getLANSlot( m_currentGame->getLocalSlotNum() )->getColor());
pref["Color"] = option;
if (m_currentGame->amIHost())
pref["Map"] = AsciiStringToQuotedPrintable(m_currentGame->getMap());
pref.write();
}
LANbuttonPushed = true;
DEBUG_LOG(("OnPlayerLeave says we're leaving! pop away!\n"));
TheShell->pop();
}
else
{
if (m_currentGame && m_currentGame->getIP(0) == m_localIP)
{
// Force a new slotlist send
m_lastResendTime = 0;
lanUpdateSlotList();
RequestGameOptions( GenerateGameOptionsString(), true );
}
}
}
void LANAPI::OnGameList( LANGameInfo *gameList )
{
if (m_inLobby)
{
LANDisplayGameList(listboxGames, gameList);
}
}//void LANAPI::OnGameList( LANGameInfo *gameList )
void LANAPI::OnGameCreate( ReturnType ret )
{
if (ret == RET_OK)
{
LANbuttonPushed = true;
TheShell->push( AsciiString("Menus/LanGameOptionsMenu.wnd") );
RequestLobbyLeave( false );
//RequestGameAnnounce( ); // can't do this here, since we don't have a map set
}
else
{
if(m_inLobby)
{
switch( ret )
{
case RET_GAME_EXISTS:
GadgetListBoxAddEntryText(listboxChatWindow, TheGameText->fetch("LAN:ErrorGameExists"), chatSystemColor, -1, -1);
break;
case RET_BUSY:
GadgetListBoxAddEntryText(listboxChatWindow, TheGameText->fetch("LAN:ErrorBusy"), chatSystemColor, -1, -1);
break;
default:
GadgetListBoxAddEntryText(listboxChatWindow, TheGameText->fetch("LAN:ErrorUnknown"), chatSystemColor, -1, -1);
}
}
}
}//void OnGameCreate( ReturnType ret )
void LANAPI::OnPlayerList( LANPlayer *playerList )
{
if (m_inLobby)
{
UnsignedInt selectedIP = 0;
Int selectedIndex = -1;
Int indexToSelect = -1;
GadgetListBoxGetSelected(listboxPlayers, &selectedIndex);
if (selectedIndex != -1 )
selectedIP = (UnsignedInt) GadgetListBoxGetItemData(listboxPlayers, selectedIndex, 0);
GadgetListBoxReset(listboxPlayers);
LANPlayer *player = m_lobbyPlayers;
while (player)
{
Int addedIndex = GadgetListBoxAddEntryText(listboxPlayers, player->getName(), playerColor, -1, -1);
GadgetListBoxSetItemData(listboxPlayers, (void *)player->getIP(),addedIndex, 0 );
if (selectedIP == player->getIP())
indexToSelect = addedIndex;
player = player->getNext();
}
if (indexToSelect >= 0)
GadgetListBoxSetSelected(listboxPlayers, indexToSelect);
}
}
void LANAPI::OnNameChange( UnsignedInt IP, UnicodeString newName )
{
OnPlayerList(m_lobbyPlayers);
}
void LANAPI::OnInActive(UnsignedInt IP) {
}
void LANAPI::OnChat( UnicodeString player, UnsignedInt ip, UnicodeString message, ChatType format )
{
GameWindow *chatWindow = NULL;
if (m_inLobby)
{
chatWindow = listboxChatWindow;
}
else if( m_currentGame && m_currentGame->isGameInProgress() && TheShell->isShellActive())
{
chatWindow = listboxChatWindowScoreScreen;
}
else if( m_currentGame && !m_currentGame->isGameInProgress())
{
chatWindow = listboxChatWindowLanGame;
}
if (chatWindow == NULL)
return;
Int index = -1;
UnicodeString unicodeChat;
switch (format)
{
case LANAPIInterface::LANCHAT_SYSTEM:
unicodeChat = L"";
unicodeChat.concat(message);
unicodeChat.concat(L"");
index =GadgetListBoxAddEntryText(chatWindow, unicodeChat, chatSystemColor, -1, -1);
break;
case LANAPIInterface::LANCHAT_EMOTE:
unicodeChat = player;
unicodeChat.concat(L' ');
unicodeChat.concat(message);
if (ip == m_localIP)
index =GadgetListBoxAddEntryText(chatWindow, unicodeChat, chatLocalActionColor, -1, -1);
else
index =GadgetListBoxAddEntryText(chatWindow, unicodeChat, chatActionColor, -1, -1);
break;
case LANAPIInterface::LANCHAT_NORMAL:
default:
{
// Do the language filtering.
TheLanguageFilter->filterLine(message);
Color chatColor = GameMakeColor(255, 255, 255, 255);
if (m_currentGame)
{
Int slotNum = m_currentGame->getSlotNum(player);
// it'll be -1 if its invalid.
if (slotNum >= 0) {
GameSlot *gs = m_currentGame->getSlot(slotNum);
if (gs) {
Int colorIndex = gs->getColor();
MultiplayerColorDefinition *def = TheMultiplayerSettings->getColor(colorIndex);
if (def)
chatColor = def->getColor();
}
}
}
unicodeChat = L"[";
unicodeChat.concat(player);
unicodeChat.concat(L"] ");
unicodeChat.concat(message);
if (ip == m_localIP)
index =GadgetListBoxAddEntryText(chatWindow, unicodeChat, chatColor, -1, -1);
else
index =GadgetListBoxAddEntryText(chatWindow, unicodeChat, chatColor, -1, -1);
break;
}
}
GadgetListBoxSetItemData(chatWindow, (void *)-1, index);
}

View File

@@ -0,0 +1,680 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
// FILE: LANAPIHandlers.cpp
// Author: Matthew D. Campbell, October 2001
// Description: LAN callback handlers
///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/CRC.h"
#include "Common/GameState.h"
#include "Common/Registry.h"
#include "Common/GlobalData.h"
#include "Common/QuotedPrintable.h"
#include "Common/UserPreferences.h"
#include "GameNetwork/LANAPI.h"
#include "GameNetwork/LANAPICallbacks.h"
#include "GameClient/MapUtil.h"
void LANAPI::handleRequestLocations( LANMessage *msg, UnsignedInt senderIP )
{
if (m_inLobby)
{
LANMessage reply;
fillInLANMessage( &reply );
reply.LANMessageType = LANMessage::MSG_LOBBY_ANNOUNCE;
sendMessage(&reply);
m_lastResendTime = timeGetTime();
}
else
{
// In game - are we a game host?
if (m_currentGame)
{
if (m_currentGame->getIP(0) == m_localIP)
{
LANMessage reply;
fillInLANMessage( &reply );
reply.LANMessageType = LANMessage::MSG_GAME_ANNOUNCE;
AsciiString gameOpts = GenerateGameOptionsString();
strncpy(reply.GameInfo.options,gameOpts.str(),m_lanMaxOptionsLength);
wcsncpy(reply.GameInfo.gameName, m_currentGame->getName().str(), g_lanGameNameLength);
reply.GameInfo.gameName[g_lanGameNameLength] = 0;
reply.GameInfo.inProgress = m_currentGame->isGameInProgress();
sendMessage(&reply);
}
else
{
// We're a joiner
}
}
}
// Add the player to the lobby player list
LANPlayer *player = LookupPlayer(senderIP);
if (!player)
{
player = NEW LANPlayer;
player->setIP(senderIP);
}
else
{
removePlayer(player);
}
player->setName(UnicodeString(msg->name));
player->setHost(msg->hostName);
player->setLogin(msg->userName);
player->setLastHeard(timeGetTime());
addPlayer(player);
OnNameChange(player->getIP(), player->getName());
}
void LANAPI::handleGameAnnounce( LANMessage *msg, UnsignedInt senderIP )
{
if (senderIP == m_localIP)
{
return; // Don't try to update own info
}
else if (m_currentGame && m_currentGame->isGameInProgress())
{
return; // Don't care about games if we're playing
}
else if (senderIP == m_directConnectRemoteIP)
{
if (m_currentGame == NULL)
{
LANGameInfo *game = LookupGame(UnicodeString(msg->GameInfo.gameName));
if (!game)
{
game = NEW LANGameInfo;
game->setName(UnicodeString(msg->GameInfo.gameName));
addGame(game);
}
Bool success = ParseGameOptionsString(game,AsciiString(msg->GameInfo.options));
game->setGameInProgress(msg->GameInfo.inProgress);
game->setIsDirectConnect(msg->GameInfo.isDirectConnect);
game->setLastHeard(timeGetTime());
if (!success)
{
// remove from list
removeGame(game);
delete game;
game = NULL;
return;
}
RequestGameJoin(game, m_directConnectRemoteIP);
}
}
else
{
LANGameInfo *game = LookupGame(UnicodeString(msg->GameInfo.gameName));
if (!game)
{
game = NEW LANGameInfo;
game->setName(UnicodeString(msg->GameInfo.gameName));
addGame(game);
}
Bool success = ParseGameOptionsString(game,AsciiString(msg->GameInfo.options));
game->setGameInProgress(msg->GameInfo.inProgress);
game->setIsDirectConnect(msg->GameInfo.isDirectConnect);
game->setLastHeard(timeGetTime());
if (!success)
{
// remove from list
removeGame(game);
delete game;
game = NULL;
}
OnGameList( m_games );
// if (game == m_currentGame && !m_inLobby)
// OnSlotList(RET_OK, game);
}
}
void LANAPI::handleLobbyAnnounce( LANMessage *msg, UnsignedInt senderIP )
{
LANPlayer *player = LookupPlayer(senderIP);
if (!player)
{
player = NEW LANPlayer;
player->setIP(senderIP);
}
else
{
removePlayer(player);
}
player->setName(UnicodeString(msg->name));
player->setHost(msg->hostName);
player->setLogin(msg->userName);
player->setLastHeard(timeGetTime());
addPlayer(player);
OnNameChange(player->getIP(), player->getName());
}
void LANAPI::handleRequestGameInfo( LANMessage *msg, UnsignedInt senderIP )
{
// In game - are we a game host?
if (m_currentGame)
{
if (m_currentGame->getIP(0) == m_localIP || (m_currentGame->isGameInProgress() && TheNetwork && TheNetwork->isPacketRouter())) // if we're in game we should reply if we're the packet router
{
LANMessage reply;
fillInLANMessage( &reply );
reply.LANMessageType = LANMessage::MSG_GAME_ANNOUNCE;
AsciiString gameOpts = GameInfoToAsciiString(m_currentGame);
strncpy(reply.GameInfo.options,gameOpts.str(),m_lanMaxOptionsLength);
wcsncpy(reply.GameInfo.gameName, m_currentGame->getName().str(), g_lanGameNameLength);
reply.GameInfo.gameName[g_lanGameNameLength] = 0;
reply.GameInfo.inProgress = m_currentGame->isGameInProgress();
reply.GameInfo.isDirectConnect = m_currentGame->getIsDirectConnect();
sendMessage(&reply, senderIP);
}
}
}
void LANAPI::handleRequestJoin( LANMessage *msg, UnsignedInt senderIP )
{
UnsignedInt responseIP = senderIP; // need this cause the player may or may not be
// in the player list at the sendMessage.
if (msg->GameToJoin.gameIP != m_localIP)
{
return; // Not us. Ignore it.
}
LANMessage reply;
fillInLANMessage( &reply );
if (!m_inLobby && m_currentGame && m_currentGame->getIP(0) == m_localIP)
{
if (m_currentGame->isGameInProgress())
{
reply.LANMessageType = LANMessage::MSG_JOIN_DENY;
reply.GameNotJoined.reason = LANAPIInterface::RET_GAME_STARTED;
reply.GameNotJoined.gameIP = m_localIP;
reply.GameNotJoined.playerIP = senderIP;
DEBUG_LOG(("LANAPI::handleRequestJoin - join denied because game already started.\n"));
}
else
{
int player;
Bool canJoin = true;
// see if the CRCs match
#if defined(_DEBUG) || defined(_INTERNAL)
if (TheGlobalData->m_netMinPlayers > 0) {
#endif
if (msg->GameToJoin.iniCRC != TheGlobalData->m_iniCRC ||
msg->GameToJoin.exeCRC != TheGlobalData->m_exeCRC)
{
DEBUG_LOG(("LANAPI::handleRequestJoin - join denied because of CRC mismatch. CRCs are them/us INI:%X/%X exe:%X/%X\n",
msg->GameToJoin.iniCRC, TheGlobalData->m_iniCRC,
msg->GameToJoin.exeCRC, TheGlobalData->m_exeCRC));
reply.LANMessageType = LANMessage::MSG_JOIN_DENY;
reply.GameNotJoined.reason = LANAPIInterface::RET_CRC_MISMATCH;
reply.GameNotJoined.gameIP = m_localIP;
reply.GameNotJoined.playerIP = senderIP;
canJoin = false;
}
#if defined(_DEBUG) || defined(_INTERNAL)
}
#endif
// check for a duplicate serial
AsciiString s;
for (player = 0; canJoin && player<MAX_SLOTS; ++player)
{
LANGameSlot *slot = m_currentGame->getLANSlot(player);
s.clear();
if (player == 0)
{
GetStringFromRegistry("\\ergc", "", s);
}
else if (slot->isHuman())
{
s = slot->getSerial();
if (s.isEmpty())
s = "<Munkee>";
}
if (s.isNotEmpty())
{
DEBUG_LOG(("Checking serial '%s' in slot %d\n", s.str(), player));
if (!strncmp(s.str(), msg->GameToJoin.serial, g_maxSerialLength))
{
// serials match! kick the punk!
reply.LANMessageType = LANMessage::MSG_JOIN_DENY;
reply.GameNotJoined.reason = LANAPIInterface::RET_SERIAL_DUPE;
reply.GameNotJoined.gameIP = m_localIP;
reply.GameNotJoined.playerIP = senderIP;
canJoin = false;
DEBUG_LOG(("LANAPI::handleRequestJoin - join denied because of duplicate serial # (%s).\n", s.str()));
break;
}
}
}
// We're the host, so see if he has a duplicate name
for (player = 0; canJoin && player<MAX_SLOTS; ++player)
{
LANGameSlot *slot = m_currentGame->getLANSlot(player);
if (slot->isHuman() && slot->getName().compare(msg->name) == 0)
{
// just deny duplicates
reply.LANMessageType = LANMessage::MSG_JOIN_DENY;
reply.GameNotJoined.reason = LANAPIInterface::RET_DUPLICATE_NAME;
reply.GameNotJoined.gameIP = m_localIP;
reply.GameNotJoined.playerIP = senderIP;
canJoin = false;
DEBUG_LOG(("LANAPI::handleRequestJoin - join denied because of duplicate names.\n"));
break;
}
}
// See if there's room
// First get the number of players currently in the room.
Int numPlayers = 0;
for (player = 0; player < MAX_SLOTS; ++player)
{
if (m_currentGame->getLANSlot(player)->isOccupied()
&& !(m_currentGame->getLANSlot(player)->getPlayerTemplate() == PLAYERTEMPLATE_OBSERVER))
{
++numPlayers;
}
}
// now get the number of starting spots on the map.
Int numStartingSpots = MAX_SLOTS;
const MapMetaData *md = TheMapCache->findMap(m_currentGame->getMap());
if (md != NULL)
{
numStartingSpots = md->m_numPlayers;
}
if (numPlayers < numStartingSpots) {
for (player = 0; canJoin && player<MAX_SLOTS; ++player)
{
if (m_currentGame->getLANSlot(player)->isOpen())
{
// OK, add him in.
reply.LANMessageType = LANMessage::MSG_JOIN_ACCEPT;
wcsncpy(reply.GameJoined.gameName, m_currentGame->getName().str(), g_lanGameNameLength);
reply.GameJoined.gameName[g_lanGameNameLength] = 0;
reply.GameJoined.slotPosition = player;
reply.GameJoined.gameIP = m_localIP;
reply.GameJoined.playerIP = senderIP;
LANGameSlot newSlot;
newSlot.setState(SLOT_PLAYER, UnicodeString(msg->name));
newSlot.setIP(senderIP);
newSlot.setPort(NETWORK_BASE_PORT_NUMBER);
newSlot.setLastHeard(timeGetTime());
newSlot.setSerial(msg->GameToJoin.serial);
m_currentGame->setSlot(player,newSlot);
DEBUG_LOG(("LANAPI::handleRequestJoin - added player %ls at ip 0x%08x to the game\n", msg->name, senderIP));
OnPlayerJoin(player, UnicodeString(msg->name));
responseIP = 0;
break;
}
}
}
if (canJoin && player == MAX_SLOTS)
{
reply.LANMessageType = LANMessage::MSG_JOIN_DENY;
wcsncpy(reply.GameNotJoined.gameName, m_currentGame->getName().str(), g_lanGameNameLength);
reply.GameNotJoined.gameName[g_lanGameNameLength] = 0;
reply.GameNotJoined.reason = LANAPIInterface::RET_GAME_FULL;
reply.GameNotJoined.gameIP = m_localIP;
reply.GameNotJoined.playerIP = senderIP;
DEBUG_LOG(("LANAPI::handleRequestJoin - join denied because game is full.\n"));
}
}
}
else
{
reply.LANMessageType = LANMessage::MSG_JOIN_DENY;
reply.GameNotJoined.reason = LANAPIInterface::RET_GAME_GONE;
reply.GameNotJoined.gameIP = m_localIP;
reply.GameNotJoined.playerIP = senderIP;
}
sendMessage(&reply, responseIP);
RequestGameOptions(GenerateGameOptionsString(), true);
}
void LANAPI::handleJoinAccept( LANMessage *msg, UnsignedInt senderIP )
{
if (msg->GameJoined.playerIP == m_localIP) // Is it for us?
{
if (m_pendingAction == ACT_JOIN) // Are we trying to join?
{
m_currentGame = LookupGame(UnicodeString(msg->GameJoined.gameName));
if (!m_currentGame)
{
DEBUG_ASSERTCRASH(false, ("Could not find game to join!"));
OnGameJoin(RET_UNKNOWN, NULL);
}
else
{
m_inLobby = false;
AsciiString options = GameInfoToAsciiString(m_currentGame);
m_currentGame->enterGame();
ParseAsciiStringToGameInfo(m_currentGame, options);
Int pos = msg->GameJoined.slotPosition;
LANGameSlot slot;
slot.setState(SLOT_PLAYER, m_name);
slot.setIP(m_localIP);
slot.setPort(NETWORK_BASE_PORT_NUMBER);
slot.setLastHeard(0);
slot.setLogin(m_userName);
slot.setHost(m_hostName);
m_currentGame->setSlot(pos, slot);
m_currentGame->getLANSlot(0)->setHost(msg->hostName);
m_currentGame->getLANSlot(0)->setLogin(msg->userName);
LANPreferences prefs;
AsciiString entry;
entry.format("%d.%d.%d.%d:%s", senderIP >> 24, (senderIP & 0xff0000) >> 16, (senderIP & 0xff00) >> 8, senderIP & 0xff, UnicodeStringToQuotedPrintable(m_currentGame->getSlot(0)->getName()).str());
prefs["RemoteIP0"] = entry;
prefs.write();
OnGameJoin(RET_OK, m_currentGame);
//DEBUG_ASSERTCRASH(false, ("setting host to %ls@%ls\n", m_currentGame->getLANSlot(0)->getUser()->getLogin().str(),
// m_currentGame->getLANSlot(0)->getUser()->getHost().str()));
}
m_pendingAction = ACT_NONE;
m_expiration = 0;
}
}
}
void LANAPI::handleJoinDeny( LANMessage *msg, UnsignedInt senderIP )
{
if (msg->GameJoined.playerIP == m_localIP) // Is it for us?
{
if (m_pendingAction == ACT_JOIN) // Are we trying to join?
{
OnGameJoin(msg->GameNotJoined.reason, LookupGame(UnicodeString(msg->GameNotJoined.gameName)));
m_pendingAction = ACT_NONE;
m_expiration = 0;
}
}
}
void LANAPI::handleRequestGameLeave( LANMessage *msg, UnsignedInt senderIP )
{
if (!m_inLobby && m_currentGame && !m_currentGame->isGameInProgress())
{
int player;
for (player = 0; player < MAX_SLOTS; ++player)
{
if (m_currentGame->getIP(player) == senderIP)
{
if (player == 0)
{
OnHostLeave();
removeGame(m_currentGame);
delete m_currentGame;
m_currentGame = NULL;
/// @todo re-add myself to lobby? Or just keep me there all the time? If we send a LOBBY_ANNOUNCE things'll work out...
LANPlayer *lanPlayer = LookupPlayer(m_localIP);
if (!lanPlayer)
{
lanPlayer = NEW LANPlayer;
lanPlayer->setIP(m_localIP);
}
else
{
removePlayer(lanPlayer);
}
lanPlayer->setName(UnicodeString(m_name));
lanPlayer->setHost(m_hostName);
lanPlayer->setLogin(m_userName);
lanPlayer->setLastHeard(timeGetTime());
addPlayer(lanPlayer);
}
else
{
if (AmIHost())
{
// remove the deadbeat
LANGameSlot slot;
slot.setState(SLOT_OPEN);
m_currentGame->setSlot( player, slot );
}
OnPlayerLeave(UnicodeString(msg->name));
m_currentGame->getLANSlot(player)->setState(SLOT_OPEN);
m_currentGame->resetAccepted();
RequestGameOptions(GenerateGameOptionsString(), false, senderIP);
//m_currentGame->endGame();
}
break;
}
DEBUG_ASSERTCRASH(player < MAX_SLOTS, ("Didn't find player!"));
}
}
else if (m_inLobby)
{
// Look for dissappearing games
LANGameInfo *game = m_games;
while (game)
{
if (game->getName().compare(msg->GameToLeave.gameName) == 0)
{
removeGame(game);
delete game;
OnGameList(m_games);
break;
}
game = game->getNext();
}
}
}
void LANAPI::handleRequestLobbyLeave( LANMessage *msg, UnsignedInt senderIP )
{
if (m_inLobby)
{
LANPlayer *player = m_lobbyPlayers;
while (player)
{
if (player->getIP() == senderIP)
{
removePlayer(player);
OnPlayerList(m_lobbyPlayers);
break;
}
player = player->getNext();
}
}
}
void LANAPI::handleSetAccept( LANMessage *msg, UnsignedInt senderIP )
{
if (!m_inLobby && m_currentGame && !m_currentGame->isGameInProgress())
{
int player;
for (player = 0; player < MAX_SLOTS; ++player)
{
if (m_currentGame->getIP(player) == senderIP)
{
OnAccept(senderIP, msg->Accept.isAccepted);
break;
}
}
}
}
void LANAPI::handleHasMap( LANMessage *msg, UnsignedInt senderIP )
{
if (!m_inLobby && m_currentGame)
{
CRC mapNameCRC;
// mapNameCRC.computeCRC(m_currentGame->getMap().str(), m_currentGame->getMap().getLength());
AsciiString portableMapName = TheGameState->realMapPathToPortableMapPath(m_currentGame->getMap());
mapNameCRC.computeCRC(portableMapName.str(), portableMapName.getLength());
if (mapNameCRC.get() != msg->MapStatus.mapCRC)
{
return;
}
int player;
for (player = 0; player < MAX_SLOTS; ++player)
{
if (m_currentGame->getIP(player) == senderIP)
{
OnHasMap(senderIP, msg->MapStatus.hasMap);
break;
}
}
}
}
void LANAPI::handleChat( LANMessage *msg, UnsignedInt senderIP )
{
if (m_inLobby)
{
LANPlayer *player;
if((player=LookupPlayer(senderIP)) != 0)
{
OnChat(UnicodeString(player->getName()), player->getIP(), UnicodeString(msg->Chat.message), msg->Chat.chatType);
player->setLastHeard(timeGetTime());
}
}
else
{
if (LookupGame(UnicodeString(msg->Chat.gameName)) != m_currentGame)
{
DEBUG_LOG(("Game '%ls' is not my game\n", msg->Chat.gameName));
if (m_currentGame)
{
DEBUG_LOG(("Current game is '%ls'\n", m_currentGame->getName().str()));
}
return;
}
int player;
for (player = 0; player < MAX_SLOTS; ++player)
{
if (m_currentGame && m_currentGame->getIP(player) == senderIP)
{
OnChat(UnicodeString(msg->name), m_currentGame->getIP(player), UnicodeString(msg->Chat.message), msg->Chat.chatType);
break;
}
}
}
}
void LANAPI::handleGameStart( LANMessage *msg, UnsignedInt senderIP )
{
if (!m_inLobby && m_currentGame && m_currentGame->getIP(0) == senderIP && !m_currentGame->isGameInProgress())
{
OnGameStart();
}
}
void LANAPI::handleGameStartTimer( LANMessage *msg, UnsignedInt senderIP )
{
if (!m_inLobby && m_currentGame && m_currentGame->getIP(0) == senderIP && !m_currentGame->isGameInProgress())
{
OnGameStartTimer(msg->StartTimer.seconds);
}
}
void LANAPI::handleGameOptions( LANMessage *msg, UnsignedInt senderIP )
{
if (!m_inLobby && m_currentGame && !m_currentGame->isGameInProgress())
{
int player;
for (player = 0; player < MAX_SLOTS; ++player)
{
if (m_currentGame->getIP(player) == senderIP)
{
OnGameOptions(senderIP, player, AsciiString(msg->GameOptions.options));
break;
}
}
}
}
void LANAPI::handleInActive(LANMessage *msg, UnsignedInt senderIP) {
if (m_inLobby || (m_currentGame == NULL) || (m_currentGame->isGameInProgress())) {
return;
}
// check to see if we are the host of this game.
if (m_currentGame->amIHost() == FALSE) {
return;
}
UnicodeString playerName;
playerName = msg->name;
Int slotNum = m_currentGame->getSlotNum(playerName);
if (slotNum < 0)
return;
GameSlot *slot = m_currentGame->getSlot(slotNum);
if (slot == NULL) {
return;
}
if (senderIP != slot->getIP()) {
return;
}
// don't want to unaccept the host, that's silly. They can't hit start alt-tabbed anyways.
if (senderIP == TheLAN->GetLocalIP()) {
return;
}
// only unaccept if the timer hasn't started yet.
if (m_gameStartTime != 0) {
return;
}
slot->unAccept();
AsciiString options = GenerateGameOptionsString();
RequestGameOptions(options, FALSE);
lanUpdateSlotList();
}

View File

@@ -0,0 +1,320 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: LANGameInfo.cpp //////////////////////////////////////////////////////
// LAN game setup state info
// Author: Matthew D. Campbell, December 2001
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameClient/GameInfoWindow.h"
#include "GameClient/GameText.h"
#include "GameClient/GadgetListBox.h"
#include "GameNetwork/LANGameInfo.h"
#include "GameNetwork/LANAPICallbacks.h"
#include "Common/MultiplayerSettings.h"
#include "strtok_r.h"
/*
#include "GameNetwork/LAN.h"
#include "GameNetwork/LANGame.h"
#include "GameNetwork/LANPing.h"
#include "GameNetwork/LANusers.h"
#include "GameNetwork/LANmenus.h"
*/
// Singleton ------------------------------------------
LANGameInfo *TheLANGameInfo = NULL;
// LANGameSlot ----------------------------------------
LANGameSlot::LANGameSlot()
{
m_lastHeard = 0;
}
LANPlayer * LANGameSlot::getUser( void )
{
if (isHuman())
{
m_user.setIP(getIP());
m_user.setLastHeard(getLastHeard());
m_user.setName(getName());
m_user.setNext(NULL);
return &m_user;
}
return NULL;
}
// Various tests
Bool LANGameSlot::isUser( LANPlayer *user )
{
return (user && m_state == SLOT_PLAYER && user->getIP() == getIP());
}
Bool LANGameSlot::isUser( UnicodeString userName )
{
return (m_state == SLOT_PLAYER && !userName.compareNoCase(getName()));
}
Bool LANGameSlot::isLocalPlayer( void ) const
{
return isHuman() && TheLAN && TheLAN->GetLocalIP() == getIP();
}
// LANGameInfo ----------------------------------------
LANGameInfo::LANGameInfo()
{
//Added By Sadullah Nader
//Initializtions missing and needed
m_lastHeard = 0;
m_next = NULL;
//
for (Int i = 0; i< MAX_SLOTS; ++i)
setSlotPointer(i, &m_LANSlot[i]);
setLocalIP(TheLAN->GetLocalIP());
}
void LANGameInfo::setSlot( Int slotNum, LANGameSlot slotInfo )
{
DEBUG_ASSERTCRASH( slotNum >= 0 && slotNum < MAX_SLOTS, ("LANGameInfo::setSlot - Invalid slot number"));
if (slotNum < 0 || slotNum >= MAX_SLOTS)
return;
m_LANSlot[slotNum] = slotInfo;
if (slotNum == 0)
{
m_LANSlot[slotNum].setAccept();
m_LANSlot[slotNum].setMapAvailability(true);
}
}
LANGameSlot* LANGameInfo::getLANSlot( Int slotNum )
{
DEBUG_ASSERTCRASH( slotNum >= 0 && slotNum < MAX_SLOTS, ("LANGameInfo::getLANSlot - Invalid slot number"));
if (slotNum < 0 || slotNum >= MAX_SLOTS)
return NULL;
return &m_LANSlot[slotNum];
}
const LANGameSlot* LANGameInfo::getConstLANSlot( Int slotNum ) const
{
DEBUG_ASSERTCRASH( slotNum >= 0 && slotNum < MAX_SLOTS, ("LANGameInfo::getConstLANSlot - Invalid slot number"));
if (slotNum < 0 || slotNum >= MAX_SLOTS)
return NULL;
return &m_LANSlot[slotNum];
}
Int LANGameInfo::getLocalSlotNum( void ) const
{
DEBUG_ASSERTCRASH(m_inGame, ("Looking for local game slot while not in game"));
if (!m_inGame)
return -1;
for (Int i=0; i<MAX_SLOTS; ++i)
{
const LANGameSlot *slot = getConstLANSlot(i);
if (slot->isLocalPlayer())
return i;
}
return -1;
}
Int LANGameInfo::getSlotNum( UnicodeString userName )
{
DEBUG_ASSERTCRASH(m_inGame, ("Looking for game slot while not in game"));
if (!m_inGame)
return -1;
for (Int i=0; i<MAX_SLOTS; ++i)
{
LANGameSlot *slot = getLANSlot(i);
if (slot->isUser( userName ))
return i;
}
return -1;
}
Bool LANGameInfo::amIHost( void )
{
DEBUG_ASSERTCRASH(m_inGame, ("Looking for game slot while not in game"));
if (!m_inGame)
return false;
return getLANSlot(0)->isLocalPlayer();
}
void LANGameInfo::setMap( AsciiString mapName )
{
GameInfo::setMap(mapName);
}
void LANGameInfo::setSeed( Int seed )
{
GameInfo::setSeed(seed);
}
void LANGameInfo::resetAccepted( void )
{
if (TheLAN)
{
TheLAN->ResetGameStartTimer();
if (TheLAN->GetMyGame() == this && TheLAN->AmIHost())
LANEnableStartButton(true);
}
for(int i = 0; i< MAX_SLOTS; i++)
{
m_LANSlot[i].unAccept();
}
}
// Misc game-related functionality --------------------
void LANDisplayGameList( GameWindow *gameListbox, LANGameInfo *gameList )
{
LANGameInfo *selectedPtr = NULL;
Int selectedIndex = -1;
Int indexToSelect = -1;
if (gameListbox)
{
GadgetListBoxGetSelected(gameListbox, &selectedIndex);
if (selectedIndex != -1 )
{
selectedPtr = (LANGameInfo *)GadgetListBoxGetItemData(gameListbox, selectedIndex, 0);
}
GadgetListBoxReset(gameListbox);
while (gameList)
{
UnicodeString txtGName;
txtGName = L"";
if( gameList->isGameInProgress() )
{
txtGName.concat(L"[");
}
txtGName.concat(gameList->getPlayerName(0));
if( gameList->isGameInProgress() )
{
txtGName.concat(L"]");
}
Int addedIndex = GadgetListBoxAddEntryText(gameListbox, txtGName, (gameList->isGameInProgress())?gameInProgressColor:gameColor, -1, -1);
GadgetListBoxSetItemData(gameListbox, (void *)gameList, addedIndex, 0 );
if (selectedPtr == gameList)
indexToSelect = addedIndex;
gameList = gameList->getNext();
}
if (indexToSelect >= 0)
GadgetListBoxSetSelected(gameListbox, indexToSelect);
else
HideGameInfoWindow(TRUE);
}
}
AsciiString GenerateGameOptionsString( void )
{
if(!TheLAN->GetMyGame() || !TheLAN->GetMyGame()->amIHost())
return AsciiString.TheEmptyString;
return GameInfoToAsciiString(TheLAN->GetMyGame());
}
Bool ParseGameOptionsString(LANGameInfo *game, AsciiString options)
{
if (!TheLAN || !game)
return false;
Int oldLocalSlotNum = (game->isInGame()) ? game->getLocalSlotNum() : -1;
Bool wasInGame = oldLocalSlotNum >= 0;
// Int hadMap = wasInGame && game->getSlot(oldLocalSlotNum)->hasMap();
AsciiString oldMap = game->getMap();
UnsignedInt oldMapCRC, newMapCRC;
oldMapCRC = game->getMapCRC();
std::map<UnicodeString, UnicodeString> oldLogins, oldMachines;
std::map<UnicodeString, UnicodeString>::iterator mapIt;
Int i;
for (i=0; i<MAX_SLOTS; ++i)
{
LANGameSlot *slot = game->getLANSlot(i);
if (slot && slot->isHuman())
{
//DEBUG_LOG(("Saving off %ls@%ls for %ls\n", slot->getUser()->getLogin().str(), slot->getUser()->getHost().str(), slot->getName().str()));
oldLogins[slot->getName()] = slot->getUser()->getLogin();
oldMachines[slot->getName()] = slot->getUser()->getHost();
}
}
if (ParseAsciiStringToGameInfo(game, options))
{
Int newLocalSlotNum = (game->isInGame()) ? game->getLocalSlotNum() : -1;
Bool isInGame = newLocalSlotNum >= 0;
if (!TheLAN->AmIHost() && isInGame)
{
// Int hasMap = game->getSlot(newLocalSlotNum)->hasMap();
newMapCRC = game->getMapCRC();
//DEBUG_LOG(("wasInGame:%d isInGame:%d hadMap:%d hasMap:%d oldMap:%s newMap:%s\n", wasInGame, isInGame, hadMap, hasMap, oldMap.str(), game->getMap().str()));
if ( (oldMapCRC ^ newMapCRC)/*(hasMap ^ hadMap)*/ || (!wasInGame && isInGame) )
{
// it changed. send it
TheLAN->RequestHasMap();
lanUpdateSlotList();
updateGameOptions();
}
}
// clean up LAN users, etc.
UnsignedInt now = timeGetTime();
for (i=0; i<MAX_SLOTS; ++i)
{
LANGameSlot *slot = game->getLANSlot(i);
if (slot->isHuman())
{
slot->setLastHeard(now);
mapIt = oldLogins.find(slot->getName());
if (mapIt != oldLogins.end())
slot->setLogin(mapIt->second);
mapIt = oldMachines.find(slot->getName());
if (mapIt != oldMachines.end())
slot->setHost(mapIt->second);
//DEBUG_LOG(("Restored %ls@%ls for %ls\n", slot->getUser()->getLogin().str(), slot->getUser()->getHost().str(), slot->getName().str()));
}
}
return true;
}
return false;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,455 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameNetwork/NetCommandList.h"
#include "GameNetwork/NetworkUtil.h"
/**
* Constructor.
*/
NetCommandList::NetCommandList() {
m_first = NULL;
m_last = NULL;
m_lastMessageInserted = NULL;
}
/**
* Destructor.
*/
NetCommandList::~NetCommandList() {
reset();
}
/**
* Append the given list of commands to this list.
*/
void NetCommandList::appendList(NetCommandList *list) {
if (list == NULL) {
return;
}
// Need to do it this way because of the reference counting that needs to happen in appendMessage.
NetCommandRef *msg = list->getFirstMessage();
NetCommandRef *next = NULL;
while (msg != NULL) {
next = msg->getNext();
NetCommandRef *temp = addMessage(msg->getCommand());
if (temp != NULL) {
temp->setRelay(msg->getRelay());
}
msg = next;
}
}
/**
* Return the first message in this list.
*/
NetCommandRef *NetCommandList::getFirstMessage() {
return m_first;
}
/**
* Remove the given message from this list.
*/
void NetCommandList::removeMessage(NetCommandRef *msg) {
if (m_lastMessageInserted == msg) {
m_lastMessageInserted = msg->getNext();
}
if (msg->getPrev() != NULL) {
msg->getPrev()->setNext(msg->getNext());
}
if (msg->getNext() != NULL) {
msg->getNext()->setPrev(msg->getPrev());
}
if (msg == m_first) {
m_first = msg->getNext();
}
if (msg == m_last) {
m_last = msg->getPrev();
}
msg->setNext(NULL);
msg->setPrev(NULL);
}
/**
* Initialize the list.
*/
void NetCommandList::init() {
reset();
}
/**
* Reset the contents of this list.
*/
void NetCommandList::reset() {
NetCommandRef *temp = m_first;
while (m_first != NULL) {
temp = m_first->getNext();
m_first->setNext(NULL);
m_first->setPrev(NULL);
m_first->deleteInstance();
m_first = temp;
}
m_last = NULL;
m_lastMessageInserted = NULL;
}
/**
* Insert sorts msg. Assumes that all the previous message inserts were done using this function.
* The message is sorted in based first on command type, then player id, and then command id.
*/
NetCommandRef * NetCommandList::addMessage(NetCommandMsg *cmdMsg) {
if (cmdMsg == NULL) {
DEBUG_ASSERTCRASH(cmdMsg != NULL, ("NetCommandList::addMessage - command message was NULL"));
return NULL;
}
// UnsignedInt id = cmdMsg->getID();
NetCommandRef *msg = NEW_NETCOMMANDREF(cmdMsg);
if (m_first == NULL) {
// this is the first node, so we don't have to worry about ordering it.
m_first = msg;
m_last = msg;
m_lastMessageInserted = msg;
return msg;
}
if (m_lastMessageInserted != NULL) {
// Messages that are inserted in order should just be put in one right after the other.
// So saving the placement of the last message inserted can give us a huge boost in
// efficiency.
NetCommandRef *theNext = m_lastMessageInserted->getNext();
if ((m_lastMessageInserted->getCommand()->getNetCommandType() == msg->getCommand()->getNetCommandType()) &&
(m_lastMessageInserted->getCommand()->getPlayerID() == msg->getCommand()->getPlayerID()) &&
(m_lastMessageInserted->getCommand()->getID() < msg->getCommand()->getID()) &&
((theNext == NULL) || ((theNext->getCommand()->getNetCommandType() > msg->getCommand()->getNetCommandType()) ||
(theNext->getCommand()->getPlayerID() > msg->getCommand()->getPlayerID()) ||
(theNext->getCommand()->getID() > msg->getCommand()->getID())))) {
// Make sure this command isn't already in the list.
if (isEqualCommandMsg(m_lastMessageInserted->getCommand(), msg->getCommand())) {
// This command is already in the list, don't duplicate it.
msg->deleteInstance();
msg = NULL;
return NULL;
}
if (theNext == NULL) {
// this means that m_lastMessageInserted == m_last, so m_last should point to the msg that is being inserted.
msg->setNext(m_lastMessageInserted->getNext());
msg->setPrev(m_lastMessageInserted);
m_lastMessageInserted->setNext(msg);
m_lastMessageInserted = msg;
m_last = msg;
} else {
msg->setNext(m_lastMessageInserted->getNext());
msg->setPrev(m_lastMessageInserted);
m_lastMessageInserted->setNext(msg);
msg->getNext()->setPrev(msg);
m_lastMessageInserted = msg;
}
return msg;
}
}
if (msg->getCommand()->getNetCommandType() > m_last->getCommand()->getNetCommandType()) {
// easy optimization for a command that goes at the end of the list
// since they are likely to be added in order.
// Make sure this command isn't already in the list.
if (isEqualCommandMsg(m_last->getCommand(), msg->getCommand())) {
// This command is already in the list, don't duplicate it.
msg->deleteInstance();
msg = NULL;
return NULL;
}
msg->setPrev(m_last);
msg->setNext(NULL);
m_last->setNext(msg);
m_last = msg;
m_lastMessageInserted = msg;
return msg;
}
if (msg->getCommand()->getNetCommandType() < m_first->getCommand()->getNetCommandType()) {
// Make sure this command isn't already in the list.
if (isEqualCommandMsg(m_first->getCommand(), msg->getCommand())) {
// This command is already in the list, don't duplicate it.
msg->deleteInstance();
msg = NULL;
return NULL;
}
// The command goes at the head of the list.
msg->setNext(m_first);
msg->setPrev(NULL);
m_first->setPrev(msg);
m_first = msg;
m_lastMessageInserted = msg;
return msg;
}
// Find the start of the command type we're looking for.
NetCommandRef *tempmsg = m_first;
while ((tempmsg != NULL) && (msg->getCommand()->getNetCommandType() > tempmsg->getCommand()->getNetCommandType())) {
tempmsg = tempmsg->getNext();
}
if (tempmsg == NULL) {
// Make sure this command isn't already in the list.
if (isEqualCommandMsg(m_last->getCommand(), msg->getCommand())) {
// This command is already in the list, don't duplicate it.
msg->deleteInstance();
msg = NULL;
return NULL;
}
// message goes at the end of the list.
msg->setPrev(m_last);
msg->setNext(NULL);
m_last->setNext(msg);
m_last = msg;
m_lastMessageInserted = msg;
return msg;
}
// Now find the player position. munkee.
while ((tempmsg != NULL) && (msg->getCommand()->getNetCommandType() == tempmsg->getCommand()->getNetCommandType()) && (msg->getCommand()->getPlayerID() > tempmsg->getCommand()->getPlayerID())) {
tempmsg = tempmsg->getNext();
}
if (tempmsg == NULL) {
// Make sure this command isn't already in the list.
if (isEqualCommandMsg(m_last->getCommand(), msg->getCommand())) {
// This command is already in the list, don't duplicate it.
msg->deleteInstance();
msg = NULL;
return NULL;
}
// message goes at the end of the list.
msg->setPrev(m_last);
msg->setNext(NULL);
m_last->setNext(msg);
m_last = msg;
m_lastMessageInserted = msg;
return msg;
}
// Find the position within the player's section based on the command ID.
// If the command type doesn't require a command ID, sort by whatever it should be sorted by.
while ((tempmsg != NULL) && (msg->getCommand()->getNetCommandType() == tempmsg->getCommand()->getNetCommandType()) && (msg->getCommand()->getPlayerID() == tempmsg->getCommand()->getPlayerID()) && (msg->getCommand()->getSortNumber() > tempmsg->getCommand()->getSortNumber())) {
tempmsg = tempmsg->getNext();
}
if (tempmsg == NULL) {
// Make sure this command isn't already in the list.
if (isEqualCommandMsg(m_last->getCommand(), msg->getCommand())) {
// This command is already in the list, don't duplicate it.
msg->deleteInstance();
msg = NULL;
return NULL;
}
// This message goes at the end of the list.
msg->setPrev(m_last);
msg->setNext(NULL);
m_last->setNext(msg);
m_last = msg;
m_lastMessageInserted = msg;
return msg;
}
if (tempmsg == m_first) {
// Make sure this command isn't already in the list.
if (isEqualCommandMsg(m_first->getCommand(), msg->getCommand())) {
// This command is already in the list, don't duplicate it.
msg->deleteInstance();
return NULL;
}
// This message goes at the head of the list.
msg->setNext(m_first);
msg->setPrev(NULL);
m_first->setPrev(msg);
m_first = msg;
m_lastMessageInserted = msg;
return msg;
}
// Make sure this command isn't already in the list.
if (isEqualCommandMsg(tempmsg->getCommand(), msg->getCommand())) {
// This command is already in the list, don't duplicate it.
msg->deleteInstance();
msg = NULL;
return NULL;
}
// Insert message before tempmsg.
msg->setNext(tempmsg);
msg->setPrev(tempmsg->getPrev());
msg->getPrev()->setNext(msg);
tempmsg->setPrev(msg);
m_lastMessageInserted = msg;
return msg;
}
Int NetCommandList::length() {
Int retval = 0;
NetCommandRef *temp = m_first;
while (temp != NULL) {
++retval;
temp = temp->getNext();
}
return retval;
}
/**
* This is really inefficient, but we can probably get away with it because
* there shouldn't be too many messages for any given frame.
*/
NetCommandRef * NetCommandList::findMessage(NetCommandMsg *msg) {
NetCommandRef *retval = m_first;
while ((retval != NULL) && (isEqualCommandMsg(retval->getCommand(), msg) == FALSE)) {
retval = retval->getNext();
}
return retval;
}
NetCommandRef * NetCommandList::findMessage(UnsignedShort commandID, UnsignedByte playerID) {
NetCommandRef *retval = m_first;
while (retval != NULL) {
if (DoesCommandRequireACommandID(retval->getCommand()->getNetCommandType())) {
if ((retval->getCommand()->getID() == commandID) && (retval->getCommand()->getPlayerID() == playerID)) {
return retval;
}
}
retval = retval->getNext();
}
return retval;
}
Bool NetCommandList::isEqualCommandMsg(NetCommandMsg *msg1, NetCommandMsg *msg2) {
if (DoesCommandRequireACommandID(msg1->getNetCommandType()) != DoesCommandRequireACommandID(msg2->getNetCommandType())) {
return FALSE;
}
// At this point we know that the commands both do or do not require a command id.
// Do or do not, there is no try.
if (DoesCommandRequireACommandID(msg1->getNetCommandType())) {
// Are the commands from the same player?
if (msg1->getPlayerID() != msg2->getPlayerID()) {
return FALSE;
}
// Do they have the same command ID?
if (msg1->getID() != msg2->getID()) {
return FALSE;
}
return TRUE;
}
// If we've gotten this far, we know that the commands do not require a command id.
// So now our equality checking becomes type-specific.
// Are they the same type?
if (msg1->getNetCommandType() != msg2->getNetCommandType()) {
return FALSE;
}
// Are they from the same player?
if (msg1->getPlayerID() != msg2->getPlayerID()) {
return FALSE;
}
// They are the same type and from the same player.
// Time for the type specific stuff.
if (msg1->getNetCommandType() == NETCOMMANDTYPE_ACKSTAGE1) {
NetAckStage1CommandMsg *ack1 = (NetAckStage1CommandMsg *)msg1;
NetAckStage1CommandMsg *ack2 = (NetAckStage1CommandMsg *)msg2;
if (ack1->getOriginalPlayerID() != ack2->getOriginalPlayerID()) {
return FALSE;
}
if (ack1->getCommandID() != ack2->getCommandID()) {
return FALSE;
}
return TRUE;
}
// They are the same type and from the same player.
// Time for the type specific stuff.
if (msg1->getNetCommandType() == NETCOMMANDTYPE_ACKSTAGE2) {
NetAckStage2CommandMsg *ack1 = (NetAckStage2CommandMsg *)msg1;
NetAckStage2CommandMsg *ack2 = (NetAckStage2CommandMsg *)msg2;
if (ack1->getOriginalPlayerID() != ack2->getOriginalPlayerID()) {
return FALSE;
}
if (ack1->getCommandID() != ack2->getCommandID()) {
return FALSE;
}
return TRUE;
}
// They are the same type and from the same player.
// Time for the type specific stuff.
if (msg1->getNetCommandType() == NETCOMMANDTYPE_ACKBOTH) {
NetAckBothCommandMsg *ack1 = (NetAckBothCommandMsg *)msg1;
NetAckBothCommandMsg *ack2 = (NetAckBothCommandMsg *)msg2;
if (ack1->getOriginalPlayerID() != ack2->getOriginalPlayerID()) {
return FALSE;
}
if (ack1->getCommandID() != ack2->getCommandID()) {
return FALSE;
}
return TRUE;
}
return FALSE;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,71 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameNetwork/NetCommandRef.h"
#ifdef DEBUG_NETCOMMANDREF
static UnsignedInt refNum = 0;
#endif
/**
* Constructor. Attach to the given network command.
*/
#ifdef DEBUG_NETCOMMANDREF
NetCommandRef::NetCommandRef(NetCommandMsg *msg, char *filename, int line)
#else
NetCommandRef::NetCommandRef(NetCommandMsg *msg)
#endif
{
m_msg = msg;
m_next = NULL;
m_prev = NULL;
m_msg->attach();
m_timeLastSent = -1;
#ifdef DEBUG_NETCOMMANDREF
m_id = ++refNum;
DEBUG_LOG(("NetCommandRef %d allocated in file %s line %d\n", m_id, filename, line));
#endif
}
/**
* Destructor. Detach from the network command.
*/
NetCommandRef::~NetCommandRef()
{
if (m_msg != NULL)
{
m_msg->detach();
}
DEBUG_ASSERTCRASH(m_next == NULL, ("NetCommandRef::~NetCommandRef - m_next != NULL"));
DEBUG_ASSERTCRASH(m_prev == NULL, ("NetCommandRef::~NetCommandRef - m_prev != NULL"));
#ifdef DEBUG_NETCOMMANDREF
DEBUG_LOG(("NetCommandRef %d deleted\n", m_id));
#endif
}

View File

@@ -0,0 +1,233 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
////// NetCommandWrapperList.cpp ////////////////////////////////
// Bryan Cleveland
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameNetwork/NetCommandWrapperList.h"
#include "GameNetwork/NetPacket.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
////// NetCommandWrapperListNode ///////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
NetCommandWrapperListNode::NetCommandWrapperListNode(NetWrapperCommandMsg *msg)
{
//Added By Sadullah Nader
//Initializations inserted
m_next = NULL;
//
m_numChunks = msg->getNumChunks();
m_chunksPresent = NEW Bool[m_numChunks]; // pool[]ify
m_numChunksPresent = 0;
for (Int i = 0; i < m_numChunks; ++i) {
m_chunksPresent[i] = FALSE;
}
m_dataLength = msg->getTotalDataLength();
m_data = NEW UnsignedByte[m_dataLength]; // pool[]ify
m_commandID = msg->getWrappedCommandID();
}
NetCommandWrapperListNode::~NetCommandWrapperListNode() {
if (m_chunksPresent != NULL) {
delete[] m_chunksPresent;
m_chunksPresent = NULL;
}
if (m_data != NULL) {
delete[] m_data;
m_data = NULL;
}
}
Bool NetCommandWrapperListNode::isComplete() {
return m_numChunksPresent == m_numChunks;
}
Int NetCommandWrapperListNode::getPercentComplete(void) {
if (isComplete())
return 100;
else
return min(99, REAL_TO_INT( ((Real)m_numChunksPresent)/((Real)m_numChunks)*100.0f ));
}
UnsignedShort NetCommandWrapperListNode::getCommandID() {
return m_commandID;
}
UnsignedInt NetCommandWrapperListNode::getRawDataLength() {
return m_dataLength;
}
void NetCommandWrapperListNode::copyChunkData(NetWrapperCommandMsg *msg) {
if (msg == NULL) {
DEBUG_CRASH(("Trying to copy data from a non-existent wrapper command message"));
return;
}
DEBUG_ASSERTCRASH(msg->getChunkNumber() < m_numChunks, ("MunkeeChunk %d of %d\n",
msg->getChunkNumber(), m_numChunks));
if (msg->getChunkNumber() >= m_numChunks)
return;
DEBUG_LOG(("NetCommandWrapperListNode::copyChunkData() - copying chunk %d\n",
msg->getChunkNumber()));
if (m_chunksPresent[msg->getChunkNumber()] == TRUE) {
// we already received this chunk, no need to recopy it.
return;
}
m_chunksPresent[msg->getChunkNumber()] = TRUE;
UnsignedInt offset = msg->getDataOffset();
memcpy(m_data + offset, msg->getData(), msg->getDataLength());
++m_numChunksPresent;
}
UnsignedByte * NetCommandWrapperListNode::getRawData() {
return m_data;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
////// NetCommandWrapperList ///////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
NetCommandWrapperList::NetCommandWrapperList() {
m_list = NULL;
}
NetCommandWrapperList::~NetCommandWrapperList() {
NetCommandWrapperListNode *temp;
while (m_list != NULL) {
temp = m_list->m_next;
m_list->deleteInstance();
m_list = temp;
}
}
void NetCommandWrapperList::init() {
m_list = NULL;
}
void NetCommandWrapperList::reset() {
NetCommandWrapperListNode *temp;
while (m_list != NULL) {
temp = m_list->m_next;
m_list->deleteInstance();
m_list = temp;
}
}
Int NetCommandWrapperList::getPercentComplete(UnsignedShort wrappedCommandID)
{
NetCommandWrapperListNode *temp = m_list;
while ((temp != NULL) && (temp->getCommandID() != wrappedCommandID)) {
temp = temp->m_next;
}
if (!temp)
return 0;
return temp->getPercentComplete();
}
void NetCommandWrapperList::processWrapper(NetCommandRef *ref) {
NetCommandWrapperListNode *temp = m_list;
NetWrapperCommandMsg *msg = (NetWrapperCommandMsg *)(ref->getCommand());
while ((temp != NULL) && (temp->getCommandID() != msg->getWrappedCommandID())) {
temp = temp->m_next;
}
if (temp == NULL) {
temp = newInstance(NetCommandWrapperListNode)(msg);
temp->m_next = m_list;
m_list = temp;
}
temp->copyChunkData(msg);
}
NetCommandList * NetCommandWrapperList::getReadyCommands()
{
NetCommandList *retlist = newInstance(NetCommandList);
retlist->init();
NetCommandWrapperListNode *temp = m_list;
NetCommandWrapperListNode *next = NULL;
while (temp != NULL) {
next = temp->m_next;
if (temp->isComplete()) {
NetCommandRef *msg = NetPacket::ConstructNetCommandMsgFromRawData(temp->getRawData(), temp->getRawDataLength());
NetCommandRef *ret = retlist->addMessage(msg->getCommand());
ret->setRelay(msg->getRelay());
msg->deleteInstance();
msg = NULL;
removeFromList(temp);
temp = NULL;
}
temp = next;
}
return retlist;
}
void NetCommandWrapperList::removeFromList(NetCommandWrapperListNode *node) {
if (node == NULL) {
return;
}
NetCommandWrapperListNode *temp = m_list;
NetCommandWrapperListNode *prev = NULL;
while ((temp != NULL) && (temp->getCommandID() != node->getCommandID())) {
prev = temp;
temp = temp->m_next;
}
if (temp == NULL) {
return;
}
if (prev == NULL) {
m_list = temp->m_next;
temp->deleteInstance();
temp = NULL;
} else {
prev->m_next = temp->m_next;
temp->deleteInstance();
temp = NULL;
}
}

View File

@@ -0,0 +1,227 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
// NetMessageStream.cpp
// Holds misc functions to encapsulate GameMessages into Command Packets to send
// over the network.
// Author: Matthew D. Campbell, July 2001
/*
#include "stdlib.h" // VC++ wants this here, or gives compile error...
#include "Common/GameType.h"
#include "Common/MessageStream.h"
#include "Common/GameEngine.h"
#include "GameLogic/GameLogic.h"
#include "GameNetwork/NetworkInterface.h"
#include "GameNetwork/NetworkDefs.h"
// The per-player pointers for the list of commands
static CommandMsg *CommandHead[MAX_SLOTS] = { /// @todo: remove static initialization
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
static CommandMsg *CommandTail[MAX_SLOTS] = {
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
/**
* AddToNetCommandList adds a CommandMsg to a list of commands.
*
static Bool AddToNetCommandList(GameMessage *msg, UnsignedInt timestamp, CommandMsg *& CommandHead, CommandMsg *& CommandTail)
{
CommandMsg *cmdMsg = NEW CommandMsg(timestamp, msg);
if (!cmdMsg)
{
DEBUG_LOG(("Alloc failed!\n"));
return false;
}
if (CommandTail == NULL)
{
CommandHead = cmdMsg;
CommandTail = cmdMsg;
}
else
{
cmdMsg->SetPrevCommandMsg(CommandTail);
CommandTail->SetNextCommandMsg(cmdMsg);
CommandTail = cmdMsg;
}
return true;
}
/**
* AddToRemoteNetCommandList is used by TheNetwork to queue up commands recieved from other players.
*
Bool AddToNetCommandList(Int playerNum, GameMessage *msg, UnsignedInt timestamp)
{
if (playerNum < 0 || playerNum >= MAX_SLOTS)
return false;
DEBUG_LOG(("Adding msg to NetCommandList %d\n", playerNum));
return AddToNetCommandList(msg, timestamp, CommandHead[playerNum], CommandTail[playerNum]);
}
/**
* GetCommandMsg returns a GameMessage (deleting its CommandMsg wrapper) that is valid
* for the current frame, or NULL.
*
static GameMessage * GetCommandMsg(UnsignedInt timestamp, CommandMsg *& CommandHead, CommandMsg *& CommandTail)
{
if (!CommandHead)
return NULL;
if (CommandHead->GetTimestamp() < timestamp)
{
DEBUG_LOG(("Time is %d, yet message timestamp is %d!\n", timestamp, CommandHead->GetTimestamp()));
return NULL;
}
if (CommandHead->GetTimestamp() != timestamp)
return NULL;
CommandMsg *theMsg = CommandHead;
if (CommandHead->GetNextCommandMsg())
{
CommandHead->GetNextCommandMsg()->SetPrevCommandMsg(NULL);
CommandHead = CommandHead->GetNextCommandMsg();
}
else
{
CommandHead = CommandTail = NULL;
}
GameMessage *msg = theMsg->GetGameMessage();
delete theMsg;
return msg;
}
/**
* GetCommandMsg returns a message from the command list.
*
GameMessage * GetCommandMsg(UnsignedInt timestamp, Int playerNum)
{
if (playerNum < 0 || playerNum >= MAX_SLOTS)
return NULL;
//DEBUG_LOG(("Adding msg to NetCommandList %d\n", playerNum));
return GetCommandMsg(timestamp, CommandHead[playerNum], CommandTail[playerNum]);
}
//====================================================================================
// The commandBuf & commandPacket hold the commands we're building up for the frame.
static unsigned char commandBuf[sizeof(CommandPacket)+1];
static CommandPacket *commandPacket = (CommandPacket *)(commandBuf+1);
/**
* ClearCommandPacket clears the command packet at the start of the frame.
*
void ClearCommandPacket(UnsignedInt frame)
{
commandPacket->m_frame = frame;
commandPacket->m_numCommands = 0;
}
/**
* AddCommandToPacket creates a packet containing all move/attack/etc commands
* for the current frame.
*
Bool AddCommandToPacket(const GameMessage *msg)
{
int messageSize = sizeofMessageHeader + sizeofMessageArg * msg->getArgumentCount();
// If we have too much, send what we have
if (bytesUsed && (bytesUsed + sizeof(CommandPacketHeader) + messageSize >= MAX_MESSAGE_LEN))
{
commandBuf[0] = MSGTYPE_PARTIALCOMMAND;
if (!TheNetwork->queueSend(BROADCAST_CON, commandBuf, bytesUsed + sizeof(CommandPacketHeader) + 1, MSG_NEEDACK | MSG_SEQUENCED))
{
//DEBUG_ASSERTCRASH(false, ("Too many commands in one frame! Some will be dropped."));
DEBUG_LOG(("Too many commands in one frame! Some will be dropped."));
return false;
}
commandBuf[0] = MSGTYPE_COMMANDCOUNT;
commandPacket->header.m_numCommands = 0;
bytesUsed = 0;
}
if (bytesUsed + sizeof(CommandPacketHeader) + messageSize >= MAX_MESSAGE_LEN)
{
//DEBUG_ASSERTCRASH(false, ("Too many commands in one frame! Some will be dropped."));
DEBUG_LOG(("Too many commands in one frame! Some will be dropped."));
return false;
}
// We have room, so add the message
commandPacket->header.m_numCommands++;
commandPacket->m_commands[bytesUsed++] = (unsigned char)msg->getType();
commandPacket->m_commands[bytesUsed++] = msg->getArgumentCount();
for (int i=0; i<msg->getArgumentCount(); ++i)
{
memcpy((unsigned char *)(commandPacket->m_commands + bytesUsed), (unsigned char *)msg->getArgument(i), sizeofMessageArg);
bytesUsed += sizeofMessageArg;
}
//DEBUG_ASSERTCRASH(bytesUsed + sizeof(CommandPacketHeader) < MAX_MESSAGE_LEN, ("Memory overwrite constructing command packet!"));
//DEBUG_LOG(("Memory overwrite constructing command packet!"));
return true;
}
/**
* TheNetwork calls GetCommandPacket to get commands to send.
*
CommandPacket *GetCommandPacket(void)
{
commandBuf[0] = MSGTYPE_COMMANDCOUNT;
return commandPacket;
}
//====================================================================================
*/

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,272 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameNetwork/NetworkUtil.h"
Int MAX_FRAMES_AHEAD = 128;
Int MIN_RUNAHEAD = 10;
Int FRAME_DATA_LENGTH = (MAX_FRAMES_AHEAD+1)*2;
Int FRAMES_TO_KEEP = (MAX_FRAMES_AHEAD/2) + 1;
#ifdef DEBUG_LOGGING
void dumpBufferToLog(const void *vBuf, Int len, const char *fname, Int line)
{
DEBUG_LOG(("======= dumpBufferToLog() %d bytes =======\n", len));
DEBUG_LOG(("Source: %s:%d\n", fname, line));
const char *buf = (const char *)vBuf;
Int numLines = len / 8;
if ((len % 8) != 0)
{
++numLines;
}
for (Int dumpindex = 0; dumpindex < numLines; ++dumpindex)
{
Int offset = dumpindex*8;
DEBUG_LOG(("\t%5.5d\t", offset));
Int dumpindex2;
Int numBytesThisLine = min(8, len - offset);
for (dumpindex2 = 0; dumpindex2 < numBytesThisLine; ++dumpindex2)
{
Int c = (buf[offset + dumpindex2] & 0xff);
DEBUG_LOG(("%02X ", c));
}
for (; dumpindex2 < 8; ++dumpindex2)
{
DEBUG_LOG((" "));
}
DEBUG_LOG((" | "));
for (dumpindex2 = 0; dumpindex2 < numBytesThisLine; ++dumpindex2)
{
char c = buf[offset + dumpindex2];
DEBUG_LOG(("%c", (isprint(c)?c:'.')));
}
DEBUG_LOG(("\n"));
}
DEBUG_LOG(("End of packet dump\n"));
}
#endif // DEBUG_LOGGING
/**
* ResolveIP turns a string ("games2.westwood.com", or "192.168.0.1") into
* a 32-bit unsigned integer.
*/
UnsignedInt ResolveIP(AsciiString host)
{
struct hostent *hostStruct;
struct in_addr *hostNode;
if (host.getLength() == 0)
{
DEBUG_LOG(("ResolveIP(): Can't resolve NULL\n"));
return 0;
}
// String such as "127.0.0.1"
if (isdigit(host.getCharAt(0)))
{
return ( ntohl(inet_addr(host.str())) );
}
// String such as "localhost"
hostStruct = gethostbyname(host.str());
if (hostStruct == NULL)
{
DEBUG_LOG(("ResolveIP(): Can't resolve %s\n", host.str()));
return 0;
}
hostNode = (struct in_addr *) hostStruct->h_addr;
return ( ntohl(hostNode->s_addr) );
}
/**
* Returns the next network command ID.
*/
UnsignedShort GenerateNextCommandID() {
static UnsignedShort commandID = 64000;
++commandID;
return commandID;
}
/**
* Returns true if this type of command requires a unique command ID.
*/
Bool DoesCommandRequireACommandID(NetCommandType type) {
if ((type == NETCOMMANDTYPE_GAMECOMMAND) ||
(type == NETCOMMANDTYPE_FRAMEINFO) ||
(type == NETCOMMANDTYPE_PLAYERLEAVE) ||
(type == NETCOMMANDTYPE_DESTROYPLAYER) ||
(type == NETCOMMANDTYPE_RUNAHEADMETRICS) ||
(type == NETCOMMANDTYPE_RUNAHEAD) ||
(type == NETCOMMANDTYPE_CHAT) ||
(type == NETCOMMANDTYPE_DISCONNECTVOTE) ||
(type == NETCOMMANDTYPE_LOADCOMPLETE) ||
(type == NETCOMMANDTYPE_TIMEOUTSTART) ||
(type == NETCOMMANDTYPE_WRAPPER) ||
(type == NETCOMMANDTYPE_FILE) ||
(type == NETCOMMANDTYPE_FILEANNOUNCE) ||
(type == NETCOMMANDTYPE_FILEPROGRESS) ||
(type == NETCOMMANDTYPE_DISCONNECTPLAYER) ||
(type == NETCOMMANDTYPE_DISCONNECTFRAME) ||
(type == NETCOMMANDTYPE_DISCONNECTSCREENOFF) ||
(type == NETCOMMANDTYPE_FRAMERESENDREQUEST))
{
return TRUE;
}
return FALSE;
}
/**
* Returns true if this type of network command requires an ack.
*/
Bool CommandRequiresAck(NetCommandMsg *msg) {
if ((msg->getNetCommandType() == NETCOMMANDTYPE_GAMECOMMAND) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_FRAMEINFO) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_PLAYERLEAVE) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_DESTROYPLAYER) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_RUNAHEADMETRICS) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_RUNAHEAD) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_CHAT) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTVOTE) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTPLAYER) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_LOADCOMPLETE) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_TIMEOUTSTART) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_WRAPPER) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_FILE) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_FILEANNOUNCE) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_FILEPROGRESS) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTPLAYER) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTFRAME) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTSCREENOFF) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_FRAMERESENDREQUEST))
{
return TRUE;
}
return FALSE;
}
Bool IsCommandSynchronized(NetCommandType type) {
if ((type == NETCOMMANDTYPE_GAMECOMMAND) ||
(type == NETCOMMANDTYPE_FRAMEINFO) ||
(type == NETCOMMANDTYPE_PLAYERLEAVE) ||
(type == NETCOMMANDTYPE_DESTROYPLAYER) ||
(type == NETCOMMANDTYPE_RUNAHEAD))
{
return TRUE;
}
return FALSE;
}
/**
* Returns true if this type of network command requires the ack to be sent directly to the player
* rather than going through the packet router. This should really only be used by commands
* used on the disconnect screen.
*/
Bool CommandRequiresDirectSend(NetCommandMsg *msg) {
if ((msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTVOTE) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTPLAYER) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_LOADCOMPLETE) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_TIMEOUTSTART) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_FILE) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_FILEANNOUNCE) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_FILEPROGRESS) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTFRAME) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTSCREENOFF) ||
(msg->getNetCommandType() == NETCOMMANDTYPE_FRAMERESENDREQUEST)) {
return TRUE;
}
return FALSE;
}
AsciiString GetAsciiNetCommandType(NetCommandType type) {
AsciiString s;
if (type == NETCOMMANDTYPE_FRAMEINFO) {
s.set("NETCOMMANDTYPE_FRAMEINFO");
} else if (type == NETCOMMANDTYPE_GAMECOMMAND) {
s.set("NETCOMMANDTYPE_GAMECOMMAND");
} else if (type == NETCOMMANDTYPE_PLAYERLEAVE) {
s.set("NETCOMMANDTYPE_PLAYERLEAVE");
} else if (type == NETCOMMANDTYPE_RUNAHEADMETRICS) {
s.set("NETCOMMANDTYPE_RUNAHEADMETRICS");
} else if (type == NETCOMMANDTYPE_RUNAHEAD) {
s.set("NETCOMMANDTYPE_RUNAHEAD");
} else if (type == NETCOMMANDTYPE_DESTROYPLAYER) {
s.set("NETCOMMANDTYPE_DESTROYPLAYER");
} else if (type == NETCOMMANDTYPE_ACKBOTH) {
s.set("NETCOMMANDTYPE_ACKBOTH");
} else if (type == NETCOMMANDTYPE_ACKSTAGE1) {
s.set("NETCOMMANDTYPE_ACKSTAGE1");
} else if (type == NETCOMMANDTYPE_ACKSTAGE2) {
s.set("NETCOMMANDTYPE_ACKSTAGE2");
} else if (type == NETCOMMANDTYPE_FRAMEINFO) {
s.set("NETCOMMANDTYPE_FRAMEINFO");
} else if (type == NETCOMMANDTYPE_KEEPALIVE) {
s.set("NETCOMMANDTYPE_KEEPALIVE");
} else if (type == NETCOMMANDTYPE_DISCONNECTCHAT) {
s.set("NETCOMMANDTYPE_DISCONNECTCHAT");
} else if (type == NETCOMMANDTYPE_CHAT) {
s.set("NETCOMMANDTYPE_CHAT");
} else if (type == NETCOMMANDTYPE_MANGLERQUERY) {
s.set("NETCOMMANDTYPE_MANGLERQUERY");
} else if (type == NETCOMMANDTYPE_MANGLERRESPONSE) {
s.set("NETCOMMANDTYPE_MANGLERRESPONSE");
} else if (type == NETCOMMANDTYPE_DISCONNECTKEEPALIVE) {
s.set("NETCOMMANDTYPE_DISCONNECTKEEPALIVE");
} else if (type == NETCOMMANDTYPE_DISCONNECTPLAYER) {
s.set("NETCOMMANDTYPE_DISCONNECTPLAYER");
} else if (type == NETCOMMANDTYPE_PACKETROUTERQUERY) {
s.set("NETCOMMANDTYPE_PACKETROUTERQUERY");
} else if (type == NETCOMMANDTYPE_PACKETROUTERACK) {
s.set("NETCOMMANDTYPE_PACKETROUTERACK");
} else if (type == NETCOMMANDTYPE_DISCONNECTVOTE) {
s.set("NETCOMMANDTYPE_DISCONNECTVOTE");
} else if (type == NETCOMMANDTYPE_PROGRESS) {
s.set("NETCOMMANDTYPE_PROGRESS");
} else if (type == NETCOMMANDTYPE_LOADCOMPLETE) {
s.set("NETCOMMANDTYPE_LOADCOMPLETE");
} else if (type == NETCOMMANDTYPE_TIMEOUTSTART) {
s.set("NETCOMMANDTYPE_TIMEOUTSTART");
} else if (type == NETCOMMANDTYPE_WRAPPER) {
s.set("NETCOMMANDTYPE_WRAPPER");
} else if (type == NETCOMMANDTYPE_FILE) {
s.set("NETCOMMANDTYPE_FILE");
} else if (type == NETCOMMANDTYPE_FILEANNOUNCE) {
s.set("NETCOMMANDTYPE_FILEANNOUNCE");
} else if (type == NETCOMMANDTYPE_FILEPROGRESS) {
s.set("NETCOMMANDTYPE_FILEPROGRESS");
} else if (type == NETCOMMANDTYPE_DISCONNECTFRAME) {
s.set("NETCOMMANDTYPE_DISCONNECTFRAME");
} else if (type == NETCOMMANDTYPE_DISCONNECTSCREENOFF) {
s.set("NETCOMMANDTYPE_DISCONNECTSCREENOFF");
} else if (type == NETCOMMANDTYPE_FRAMERESENDREQUEST) {
s.set("NETCOMMANDTYPE_FRAMERESENDREQUEST");
} else {
s.set("UNKNOWN");
}
return s;
}

View File

@@ -0,0 +1,508 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/CRC.h"
#include "GameNetwork/Transport.h"
#include "GameNetwork/NetworkInterface.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//--------------------------------------------------------------------------
// Packet-level encryption is an XOR operation, for speed reasons. To get
// the max throughput, we only XOR whole 4-byte words, so the last bytes
// can be non-XOR'd.
// This assumes the buf is a multiple of 4 bytes. Extra is not encrypted.
static inline void encryptBuf( unsigned char *buf, Int len )
{
UnsignedInt mask = 0x0000Fade;
UnsignedInt *uintPtr = (UnsignedInt *) (buf);
for (int i=0 ; i<len/4 ; i++) {
*uintPtr = (*uintPtr) ^ mask;
*uintPtr = htonl(*uintPtr);
uintPtr++;
mask += 0x00000321; // just for fun
}
}
// This assumes the buf is a multiple of 4 bytes. Extra is not encrypted.
static inline void decryptBuf( unsigned char *buf, Int len )
{
UnsignedInt mask = 0x0000Fade;
UnsignedInt *uintPtr = (UnsignedInt *) (buf);
for (int i=0 ; i<len/4 ; i++) {
*uintPtr = htonl(*uintPtr);
*uintPtr = (*uintPtr) ^ mask;
uintPtr++;
mask += 0x00000321; // just for fun
}
}
//--------------------------------------------------------------------------
Transport::Transport(void)
{
m_winsockInit = false;
m_udpsock = NULL;
}
Transport::~Transport(void)
{
reset();
}
Bool Transport::init( AsciiString ip, UnsignedShort port )
{
return init(ResolveIP(ip), port);
}
Bool Transport::init( UnsignedInt ip, UnsignedShort port )
{
// ----- Initialize Winsock -----
if (!m_winsockInit)
{
WORD verReq = MAKEWORD(2, 2);
WSADATA wsadata;
int err = WSAStartup(verReq, &wsadata);
if (err != 0) {
return false;
}
if ((LOBYTE(wsadata.wVersion) != 2) || (HIBYTE(wsadata.wVersion) !=2)) {
WSACleanup();
return false;
}
m_winsockInit = true;
}
// ------- Bind our port --------
if (m_udpsock)
delete m_udpsock;
m_udpsock = NEW UDP();
if (!m_udpsock)
return false;
int retval = -1;
time_t now = timeGetTime();
while ((retval != 0) && ((timeGetTime() - now) < 1000)) {
retval = m_udpsock->Bind(ip, port);
}
if (retval != 0) {
DEBUG_CRASH(("Could not bind to 0x%8.8X:%d\n", ip, port));
DEBUG_LOG(("Transport::init - Failure to bind socket with error code %x\n", retval));
delete m_udpsock;
m_udpsock = NULL;
return false;
}
// ------- Clear buffers --------
for (int i=0; i<MAX_MESSAGES; ++i)
{
m_outBuffer[i].length = 0;
m_inBuffer[i].length = 0;
#if defined(_DEBUG) || defined(_INTERNAL)
m_delayedInBuffer[i].message.length = 0;
#endif
}
for (i=0; i<MAX_TRANSPORT_STATISTICS_SECONDS; ++i)
{
m_incomingBytes[i] = 0;
m_outgoingBytes[i] = 0;
m_unknownBytes[i] = 0;
m_incomingPackets[i] = 0;
m_outgoingPackets[i] = 0;
m_unknownPackets[i] = 0;
}
m_statisticsSlot = 0;
m_lastSecond = timeGetTime();
m_port = port;
#if defined(_DEBUG) || defined(_INTERNAL)
if (TheGlobalData->m_latencyAverage > 0 || TheGlobalData->m_latencyNoise)
m_useLatency = true;
if (TheGlobalData->m_packetLoss)
m_usePacketLoss = true;
#endif
return true;
}
void Transport::reset( void )
{
if (m_udpsock)
{
delete m_udpsock;
m_udpsock = NULL;
}
if (m_winsockInit)
{
WSACleanup();
m_winsockInit = false;
}
}
Bool Transport::update( void )
{
Bool retval = TRUE;
if (doRecv() == FALSE && m_udpsock && m_udpsock->GetStatus() == UDP::ADDRNOTAVAIL)
{
retval = FALSE;
}
DEBUG_ASSERTLOG(retval, ("WSA error is %s\n", GetWSAErrorString(WSAGetLastError()).str()));
if (doSend() == FALSE && m_udpsock && m_udpsock->GetStatus() == UDP::ADDRNOTAVAIL)
{
retval = FALSE;
}
DEBUG_ASSERTLOG(retval, ("WSA error is %s\n", GetWSAErrorString(WSAGetLastError()).str()));
return retval;
}
Bool Transport::doSend() {
if (!m_udpsock)
{
DEBUG_LOG(("Transport::doSend() - m_udpSock is NULL!\n"));
return FALSE;
}
Bool retval = TRUE;
// Statistics gathering
UnsignedInt now = timeGetTime();
if (m_lastSecond + 1000 < now)
{
m_lastSecond = now;
m_statisticsSlot = (m_statisticsSlot + 1) % MAX_TRANSPORT_STATISTICS_SECONDS;
m_outgoingPackets[m_statisticsSlot] = 0;
m_outgoingBytes[m_statisticsSlot] = 0;
m_incomingPackets[m_statisticsSlot] = 0;
m_incomingBytes[m_statisticsSlot] = 0;
m_unknownPackets[m_statisticsSlot] = 0;
m_unknownBytes[m_statisticsSlot] = 0;
}
// Send all messages
int i;
for (i=0; i<MAX_MESSAGES; ++i)
{
if (m_outBuffer[i].length != 0)
{
int bytesSent = 0;
// Send this message
if ((bytesSent = m_udpsock->Write((unsigned char *)(&m_outBuffer[i]), m_outBuffer[i].length + sizeof(TransportMessageHeader), m_outBuffer[i].addr, m_outBuffer[i].port)) > 0)
{
//DEBUG_LOG(("Sending %d bytes to %d:%d\n", m_outBuffer[i].length + sizeof(TransportMessageHeader), m_outBuffer[i].addr, m_outBuffer[i].port));
m_outgoingPackets[m_statisticsSlot]++;
m_outgoingBytes[m_statisticsSlot] += m_outBuffer[i].length + sizeof(TransportMessageHeader);
m_outBuffer[i].length = 0; // Remove from queue
// DEBUG_LOG(("Transport::doSend - sent %d butes to %d.%d.%d.%d:%d\n", bytesSent,
// (m_outBuffer[i].addr >> 24) & 0xff,
// (m_outBuffer[i].addr >> 16) & 0xff,
// (m_outBuffer[i].addr >> 8) & 0xff,
// m_outBuffer[i].addr & 0xff,
// m_outBuffer[i].port));
}
else
{
//DEBUG_LOG(("Could not write to socket!!! Not discarding message!\n"));
retval = FALSE;
//DEBUG_LOG(("Transport::doSend returning FALSE\n"));
}
}
} // for (i=0; i<MAX_MESSAGES; ++i)
#if defined(_DEBUG) || defined(_INTERNAL)
// Latency simulation - deliver anything we're holding on to that is ready
if (m_useLatency)
{
for (i=0; i<MAX_MESSAGES; ++i)
{
if (m_delayedInBuffer[i].message.length != 0 && m_delayedInBuffer[i].deliveryTime <= now)
{
for (int j=0; j<MAX_MESSAGES; ++j)
{
if (m_inBuffer[j].length == 0)
{
// Empty slot; use it
memcpy(&m_inBuffer[j], &m_delayedInBuffer[i].message, sizeof(TransportMessage));
m_delayedInBuffer[i].message.length = 0;
break;
}
}
}
}
}
#endif
return retval;
}
Bool Transport::doRecv()
{
if (!m_udpsock)
{
DEBUG_LOG(("Transport::doRecv() - m_udpSock is NULL!\n"));
return FALSE;
}
Bool retval = TRUE;
// Read in anything on our socket
sockaddr_in from;
#if defined(_DEBUG) || defined(_INTERNAL)
UnsignedInt now = timeGetTime();
#endif
TransportMessage incomingMessage;
unsigned char *buf = (unsigned char *)&incomingMessage;
int len = MAX_MESSAGE_LEN;
// DEBUG_LOG(("Transport::doRecv - checking\n"));
while ( (len=m_udpsock->Read(buf, MAX_MESSAGE_LEN, &from)) > 0 )
{
#if defined(_DEBUG) || defined(_INTERNAL)
// Packet loss simulation
if (m_usePacketLoss)
{
if ( TheGlobalData->m_packetLoss >= GameClientRandomValue(0, 100) )
{
continue;
}
}
#endif
// DEBUG_LOG(("Transport::doRecv - Got something! len = %d\n", len));
// Decrypt the packet
// DEBUG_LOG(("buffer = "));
// for (Int munkee = 0; munkee < len; ++munkee) {
// DEBUG_LOG(("%02x", *(buf + munkee)));
// }
// DEBUG_LOG(("\n"));
decryptBuf(buf, len);
incomingMessage.length = len - sizeof(TransportMessageHeader);
if (len <= sizeof(TransportMessageHeader) || !isGeneralsPacket( &incomingMessage ))
{
m_unknownPackets[m_statisticsSlot]++;
m_unknownBytes[m_statisticsSlot] += len;
continue;
}
// Something there; stick it somewhere
// DEBUG_LOG(("Saw %d bytes from %d:%d\n", len, ntohl(from.sin_addr.S_un.S_addr), ntohs(from.sin_port)));
m_incomingPackets[m_statisticsSlot]++;
m_incomingBytes[m_statisticsSlot] += len;
for (int i=0; i<MAX_MESSAGES; ++i)
{
#if defined(_DEBUG) || defined(_INTERNAL)
// Latency simulation
if (m_useLatency)
{
if (m_delayedInBuffer[i].message.length == 0)
{
// Empty slot; use it
m_delayedInBuffer[i].deliveryTime =
now + TheGlobalData->m_latencyAverage +
(Int)(TheGlobalData->m_latencyAmplitude * sin(now * TheGlobalData->m_latencyPeriod)) +
GameClientRandomValue(-TheGlobalData->m_latencyNoise, TheGlobalData->m_latencyNoise);
m_delayedInBuffer[i].message.length = incomingMessage.length;
m_delayedInBuffer[i].message.addr = ntohl(from.sin_addr.S_un.S_addr);
m_delayedInBuffer[i].message.port = ntohs(from.sin_port);
memcpy(&m_delayedInBuffer[i].message, buf, len);
break;
}
}
else
{
#endif
if (m_inBuffer[i].length == 0)
{
// Empty slot; use it
m_inBuffer[i].length = incomingMessage.length;
m_inBuffer[i].addr = ntohl(from.sin_addr.S_un.S_addr);
m_inBuffer[i].port = ntohs(from.sin_port);
memcpy(&m_inBuffer[i], buf, len);
break;
}
#if defined(_DEBUG) || defined(_INTERNAL)
}
#endif
}
//DEBUG_ASSERTCRASH(i<MAX_MESSAGES, ("Message lost!"));
}
if (len == -1) {
// there was a socket error trying to perform a read.
//DEBUG_LOG(("Transport::doRecv returning FALSE\n"));
retval = FALSE;
}
return retval;
}
Bool Transport::queueSend(UnsignedInt addr, UnsignedShort port, const UnsignedByte *buf, Int len /*,
NetMessageFlags flags, Int id */)
{
int i;
if (len < 1 || len > MAX_PACKET_SIZE)
{
return false;
}
for (i=0; i<MAX_MESSAGES; ++i)
{
if (m_outBuffer[i].length == 0)
{
// Insert data here
m_outBuffer[i].length = len;
memcpy(m_outBuffer[i].data, buf, len);
m_outBuffer[i].addr = addr;
m_outBuffer[i].port = port;
// m_outBuffer[i].header.flags = flags;
// m_outBuffer[i].header.id = id;
m_outBuffer[i].header.magic = GENERALS_MAGIC_NUMBER;
CRC crc;
crc.computeCRC( (unsigned char *)(&(m_outBuffer[i].header.magic)), m_outBuffer[i].length + sizeof(TransportMessageHeader) - sizeof(UnsignedInt) );
// DEBUG_LOG(("About to assign the CRC for the packet\n"));
m_outBuffer[i].header.crc = crc.get();
// Encrypt packet
// DEBUG_LOG(("buffer: "));
encryptBuf((unsigned char *)&m_outBuffer[i], len + sizeof(TransportMessageHeader));
// DEBUG_LOG(("\n"));
return true;
}
}
return false;
}
Bool Transport::isGeneralsPacket( TransportMessage *msg )
{
if (!msg)
return false;
if (msg->length < 0 || msg->length > MAX_MESSAGE_LEN)
return false;
CRC crc;
// crc.computeCRC( (unsigned char *)msg->data, msg->length );
crc.computeCRC( (unsigned char *)(&(msg->header.magic)), msg->length + sizeof(TransportMessageHeader) - sizeof(UnsignedInt) );
if (crc.get() != msg->header.crc)
return false;
if (msg->header.magic != GENERALS_MAGIC_NUMBER)
return false;
return true;
}
// Statistics ---------------------------------------------------
Real Transport::getIncomingBytesPerSecond( void )
{
Real val = 0.0;
for (int i=0; i<MAX_TRANSPORT_STATISTICS_SECONDS; ++i)
{
if (i != m_statisticsSlot)
val += m_incomingBytes[i];
}
return val / (MAX_TRANSPORT_STATISTICS_SECONDS-1);
}
Real Transport::getIncomingPacketsPerSecond( void )
{
Real val = 0.0;
for (int i=0; i<MAX_TRANSPORT_STATISTICS_SECONDS; ++i)
{
if (i != m_statisticsSlot)
val += m_incomingPackets[i];
}
return val / (MAX_TRANSPORT_STATISTICS_SECONDS-1);
}
Real Transport::getOutgoingBytesPerSecond( void )
{
Real val = 0.0;
for (int i=0; i<MAX_TRANSPORT_STATISTICS_SECONDS; ++i)
{
if (i != m_statisticsSlot)
val += m_outgoingBytes[i];
}
return val / (MAX_TRANSPORT_STATISTICS_SECONDS-1);
}
Real Transport::getOutgoingPacketsPerSecond( void )
{
Real val = 0.0;
for (int i=0; i<MAX_TRANSPORT_STATISTICS_SECONDS; ++i)
{
if (i != m_statisticsSlot)
val += m_outgoingPackets[i];
}
return val / (MAX_TRANSPORT_STATISTICS_SECONDS-1);
}
Real Transport::getUnknownBytesPerSecond( void )
{
Real val = 0.0;
for (int i=0; i<MAX_TRANSPORT_STATISTICS_SECONDS; ++i)
{
if (i != m_statisticsSlot)
val += m_unknownBytes[i];
}
return val / (MAX_TRANSPORT_STATISTICS_SECONDS-1);
}
Real Transport::getUnknownPacketsPerSecond( void )
{
Real val = 0.0;
for (int i=0; i<MAX_TRANSPORT_STATISTICS_SECONDS; ++i)
{
if (i != m_statisticsSlot)
val += m_unknownPackets[i];
}
return val / (MAX_TRANSPORT_STATISTICS_SECONDS-1);
}

View File

@@ -0,0 +1,74 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// User class copy and comparisons
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameNetwork/User.h"
/**
* Constructor. Sets up the member variables with the appropriate values.
*/
User::User(UnicodeString name, UnsignedInt addr, UnsignedInt port) {
m_name.set(name);
m_ipaddr = addr;
m_port = port;
}
/**
* The assignment operator.
*/
User & User::operator= (const User *other)
{
m_name = other->m_name;
m_ipaddr = other->m_ipaddr;
m_port = other->m_port;
return *this;
}
/**
* The equality operator.
*/
Bool User::operator== (const User *other)
{
return (m_name.compare(other->m_name) == 0);
}
/**
* The inequality operator.
*/
Bool User::operator!= (const User *other)
{
return (m_name.compare(other->m_name) != 0);
}
/**
* Set the name of this user.
*/
void User::setName(UnicodeString name) {
m_name.set(name);
}

View File

@@ -0,0 +1,315 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
/******************************************************************************
*
* NAME
* $Archive: $
*
* DESCRIPTION
*
* PROGRAMMER
* Bryan Cleveland
* $Author: $
*
* VERSION INFO
* $Revision: $
* $Modtime: $
*
******************************************************************************/
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
//#include "WinMain.h"
#include "GameNetwork/WOLBrowser/WebBrowser.h"
#include "GameClient/GameWindow.h"
#include "GameClient/Display.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
/**
* OLEInitializer class - Init and shutdown OLE & COM as a global
* object. Scary, nasty stuff, COM. /me shivers.
*/
class OLEInitializer
{
public:
OLEInitializer()
{
// Initialize this instance
OleInitialize(NULL);
}
~OLEInitializer()
{
OleUninitialize();
}
};
OLEInitializer g_OLEInitializer;
CComModule _Module;
CComObject<WebBrowser> * TheWebBrowser = NULL;
/******************************************************************************
*
* NAME
* WebBrowser::WebBrowser
*
* DESCRIPTION
* Default constructor
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
WebBrowser::WebBrowser() :
mRefCount(1)
{
DEBUG_LOG(("Instantiating embedded WebBrowser\n"));
m_urlList = NULL;
}
/******************************************************************************
*
* NAME
* WebBrowser::~WebBrowser
*
* DESCRIPTION
* Destructor
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
WebBrowser::~WebBrowser()
{
DEBUG_LOG(("Destructing embedded WebBrowser\n"));
if (this == TheWebBrowser) {
DEBUG_LOG(("WebBrowser::~WebBrowser - setting TheWebBrowser to NULL\n"));
TheWebBrowser = NULL;
}
WebBrowserURL *url = m_urlList;
while (url != NULL) {
WebBrowserURL *temp = url;
url = url->m_next;
temp->deleteInstance();
temp = NULL;
}
}
//-------------------------------------------------------------------------------------------------
/** The INI data fields for Webpage URL's */
//-------------------------------------------------------------------------------------------------
const FieldParse WebBrowserURL::m_URLFieldParseTable[] =
{
{ "URL", INI::parseAsciiString, NULL, offsetof( WebBrowserURL, m_url ) },
{ NULL, NULL, NULL, 0 },
};
WebBrowserURL::WebBrowserURL()
{
m_next = NULL;
m_tag.clear();
m_url.clear();
}
WebBrowserURL::~WebBrowserURL()
{
}
/******************************************************************************
*
* NAME
* WebBrowser::init
*
* DESCRIPTION
* Perform post creation initialization.
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
void WebBrowser::init()
{
m_urlList = NULL;
INI ini;
ini.load( AsciiString( "Data\\INI\\Webpages.ini" ), INI_LOAD_OVERWRITE, NULL );
}
/******************************************************************************
*
* NAME
* WebBrowser::reset
*
* DESCRIPTION
* Perform post creation initialization.
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
void WebBrowser::reset()
{
}
void WebBrowser::update( void )
{
}
WebBrowserURL * WebBrowser::findURL(AsciiString tag)
{
WebBrowserURL *retval = m_urlList;
while ((retval != NULL) && tag.compareNoCase(retval->m_tag.str()))
{
retval = retval->m_next;
}
return retval;
}
WebBrowserURL * WebBrowser::makeNewURL(AsciiString tag)
{
WebBrowserURL *newURL = newInstance(WebBrowserURL);
newURL->m_tag = tag;
newURL->m_next = m_urlList;
m_urlList = newURL;
return newURL;
}
/******************************************************************************
*
* NAME
* IUnknown::QueryInterface
*
* DESCRIPTION
*
* INPUTS
* IID - Interface ID
*
* RESULT
*
******************************************************************************/
STDMETHODIMP WebBrowser::QueryInterface(REFIID iid, void** ppv)
{
*ppv = NULL;
if ((iid == IID_IUnknown) || (iid == IID_IBrowserDispatch))
{
*ppv = static_cast<IBrowserDispatch*>(this);
}
else
{
return E_NOINTERFACE;
}
static_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
/******************************************************************************
*
* NAME
* IUnknown::AddRef
*
* DESCRIPTION
*
* INPUTS
* NONE
*
* RESULT
*
******************************************************************************/
ULONG STDMETHODCALLTYPE WebBrowser::AddRef(void)
{
return ++mRefCount;
}
/******************************************************************************
*
* NAME
* IUnknown::Release
*
* DESCRIPTION
*
* INPUTS
* NONE
*
* RESULT
*
******************************************************************************/
ULONG STDMETHODCALLTYPE WebBrowser::Release(void)
{
DEBUG_ASSERTCRASH(mRefCount > 0, ("Negative reference count"));
--mRefCount;
if (mRefCount == 0)
{
DEBUG_LOG(("WebBrowser::Release - all references released, deleting the object.\n"));
if (this == TheWebBrowser) {
TheWebBrowser = NULL;
}
delete this;
return 0;
}
return mRefCount;
}
STDMETHODIMP WebBrowser::TestMethod(Int num1)
{
DEBUG_LOG(("WebBrowser::TestMethod - num1 = %d\n", num1));
return S_OK;
}

View File

@@ -0,0 +1,513 @@
/*
** Command & Conquer Generals(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/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: Udp.cpp //////////////////////////////////////////////////////////////
// Implementation of UDP socket wrapper class (taken from wnet lib)
// Author: Matthew D. Campbell, July 2001
///////////////////////////////////////////////////////////////////////////////
// SYSTEM INCLUDES ////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
// USER INCLUDES //////////////////////////////////////////////////////////////
#include "Common/GameEngine.h"
//#include "GameNetwork/NetworkInterface.h"
#include "GameNetwork/udp.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------
#if defined(_DEBUG) || defined(_INTERNAL)
#define CASE(x) case (x): return #x;
AsciiString GetWSAErrorString( Int error )
{
switch (error)
{
CASE(WSABASEERR)
CASE(WSAEINTR)
CASE(WSAEBADF)
CASE(WSAEACCES)
CASE(WSAEFAULT)
CASE(WSAEINVAL)
CASE(WSAEMFILE)
CASE(WSAEWOULDBLOCK)
CASE(WSAEINPROGRESS)
CASE(WSAEALREADY)
CASE(WSAENOTSOCK)
CASE(WSAEDESTADDRREQ)
CASE(WSAEMSGSIZE)
CASE(WSAEPROTOTYPE)
CASE(WSAENOPROTOOPT)
CASE(WSAEPROTONOSUPPORT)
CASE(WSAESOCKTNOSUPPORT)
CASE(WSAEOPNOTSUPP)
CASE(WSAEPFNOSUPPORT)
CASE(WSAEAFNOSUPPORT)
CASE(WSAEADDRINUSE)
CASE(WSAEADDRNOTAVAIL)
CASE(WSAENETDOWN)
CASE(WSAENETUNREACH)
CASE(WSAENETRESET)
CASE(WSAECONNABORTED)
CASE(WSAECONNRESET)
CASE(WSAENOBUFS)
CASE(WSAEISCONN)
CASE(WSAENOTCONN)
CASE(WSAESHUTDOWN)
CASE(WSAETOOMANYREFS)
CASE(WSAETIMEDOUT)
CASE(WSAECONNREFUSED)
CASE(WSAELOOP)
CASE(WSAENAMETOOLONG)
CASE(WSAEHOSTDOWN)
CASE(WSAEHOSTUNREACH)
CASE(WSAENOTEMPTY)
CASE(WSAEPROCLIM)
CASE(WSAEUSERS)
CASE(WSAEDQUOT)
CASE(WSAESTALE)
CASE(WSAEREMOTE)
CASE(WSAEDISCON)
CASE(WSASYSNOTREADY)
CASE(WSAVERNOTSUPPORTED)
CASE(WSANOTINITIALISED)
CASE(WSAHOST_NOT_FOUND)
CASE(WSATRY_AGAIN)
CASE(WSANO_RECOVERY)
CASE(WSANO_DATA)
default:
{
AsciiString ret;
ret.format("Not a Winsock error (%d)", error);
return ret;
}
}
return AsciiString::TheEmptyString; // will not be hit, ever.
}
#undef CASE
#endif // defined(_DEBUG) || defined(_INTERNAL)
//-------------------------------------------------------------------------
UDP::UDP()
{
fd=0;
}
UDP::~UDP()
{
if (fd)
closesocket(fd);
}
Int UDP::Bind(const char *Host,UnsignedShort port)
{
char hostName[100];
struct hostent *hostStruct;
struct in_addr *hostNode;
if (isdigit(Host[0]))
return ( Bind( ntohl(inet_addr(Host)), port) );
strcpy(hostName, Host);
hostStruct = gethostbyname(Host);
if (hostStruct == NULL)
return (0);
hostNode = (struct in_addr *) hostStruct->h_addr;
return ( Bind(ntohl(hostNode->s_addr),port) );
}
// You must call bind, implicit binding is for sissies
// Well... you can get implicit binding if you pass 0 for either arg
Int UDP::Bind(UnsignedInt IP,UnsignedShort Port)
{
int retval;
int status;
IP=htonl(IP);
Port=htons(Port);
addr.sin_family=AF_INET;
addr.sin_port=Port;
addr.sin_addr.s_addr=IP;
fd=socket(AF_INET,SOCK_DGRAM,DEFAULT_PROTOCOL);
#ifdef _WINDOWS
if (fd==SOCKET_ERROR)
fd=-1;
#endif
if (fd==-1)
return(UNKNOWN);
retval=bind(fd,(struct sockaddr *)&addr,sizeof(addr));
#ifdef _WINDOWS
if (retval==SOCKET_ERROR)
{
retval=-1;
m_lastError = WSAGetLastError();
}
#endif
if (retval==-1)
{
status=GetStatus();
//CERR("Bind failure (" << status << ") IP " << IP << " PORT " << Port )
return(status);
}
int namelen=sizeof(addr);
getsockname(fd, (struct sockaddr *)&addr, &namelen);
myIP=ntohl(addr.sin_addr.s_addr);
myPort=ntohs(addr.sin_port);
retval=SetBlocking(FALSE);
if (retval==-1)
fprintf(stderr,"Couldn't set nonblocking mode!\n");
return(OK);
}
Int UDP::getLocalAddr(UnsignedInt &ip, UnsignedShort &port)
{
ip=myIP;
port=myPort;
return(OK);
}
// private function
Int UDP::SetBlocking(Int block)
{
#ifdef _WINDOWS
unsigned long flag=1;
if (block)
flag=0;
int retval;
retval=ioctlsocket(fd,FIONBIO,&flag);
if (retval==SOCKET_ERROR)
return(UNKNOWN);
else
return(OK);
#else // UNIX
int flags = fcntl(fd, F_GETFL, 0);
if (block==FALSE) // set nonblocking
flags |= O_NONBLOCK;
else // set blocking
flags &= ~(O_NONBLOCK);
if (fcntl(fd, F_SETFL, flags) < 0)
{
return(UNKNOWN);
}
return(OK);
#endif
}
Int UDP::Write(const unsigned char *msg,UnsignedInt len,UnsignedInt IP,UnsignedShort port)
{
Int retval;
struct sockaddr_in to;
// This happens frequently
if ((IP==0)||(port==0)) return(ADDRNOTAVAIL);
#ifdef _UNIX
errno=0;
#endif
to.sin_port=htons(port);
to.sin_addr.s_addr=htonl(IP);
to.sin_family=AF_INET;
ClearStatus();
retval=sendto(fd,(const char *)msg,len,0,(struct sockaddr *)&to,sizeof(to));
#ifdef _WINDOWS
if (retval==SOCKET_ERROR)
{
retval=-1;
m_lastError = WSAGetLastError();
#ifdef DEBUG_LOGGING
static Int errCount = 0;
#endif
DEBUG_ASSERTLOG(errCount++ > 100, ("UDP::Write() - WSA error is %s\n", GetWSAErrorString(WSAGetLastError()).str()));
}
#endif
return(retval);
}
Int UDP::Read(unsigned char *msg,UnsignedInt len,sockaddr_in *from)
{
Int retval;
int alen=sizeof(sockaddr_in);
if (from!=NULL)
{
retval=recvfrom(fd,(char *)msg,len,0,(struct sockaddr *)from,&alen);
#ifdef _WINDOWS
if (retval == SOCKET_ERROR)
{
if (WSAGetLastError() != WSAEWOULDBLOCK)
{
// failing because of a blocking error isn't really such a bad thing.
m_lastError = WSAGetLastError();
#ifdef DEBUG_LOGGING
static Int errCount = 0;
#endif
DEBUG_ASSERTLOG(errCount++ > 100, ("UDP::Read() - WSA error is %s\n", GetWSAErrorString(WSAGetLastError()).str()));
retval = -1;
} else {
retval = 0;
}
}
#endif
}
else
{
retval=recvfrom(fd,(char *)msg,len,0,NULL,NULL);
#ifdef _WINDOWS
if (retval==SOCKET_ERROR)
{
if (WSAGetLastError() != WSAEWOULDBLOCK)
{
// failing because of a blocking error isn't really such a bad thing.
m_lastError = WSAGetLastError();
#ifdef DEBUG_LOGGING
static Int errCount = 0;
#endif
DEBUG_ASSERTLOG(errCount++ > 100, ("UDP::Read() - WSA error is %s\n", GetWSAErrorString(WSAGetLastError()).str()));
retval = -1;
} else {
retval = 0;
}
}
#endif
}
return(retval);
}
void UDP::ClearStatus(void)
{
#ifndef _WINDOWS
errno=0;
#endif
m_lastError = 0;
}
UDP::sockStat UDP::GetStatus(void)
{
Int status = m_lastError;
#ifdef _WINDOWS
//int status=WSAGetLastError();
if (status==0) return(OK);
else if (status==WSAEINTR) return(INTR);
else if (status==WSAEINPROGRESS) return(INPROGRESS);
else if (status==WSAECONNREFUSED) return(CONNREFUSED);
else if (status==WSAEINVAL) return(INVAL);
else if (status==WSAEISCONN) return(ISCONN);
else if (status==WSAENOTSOCK) return(NOTSOCK);
else if (status==WSAETIMEDOUT) return(TIMEDOUT);
else if (status==WSAEALREADY) return(ALREADY);
else if (status==WSAEWOULDBLOCK) return(WOULDBLOCK);
else if (status==WSAEBADF) return(BADF);
else return((UDP::sockStat)status);
#else
//int status=errno;
if (status==0) return(OK);
else if (status==EINTR) return(INTR);
else if (status==EINPROGRESS) return(INPROGRESS);
else if (status==ECONNREFUSED) return(CONNREFUSED);
else if (status==EINVAL) return(INVAL);
else if (status==EISCONN) return(ISCONN);
else if (status==ENOTSOCK) return(NOTSOCK);
else if (status==ETIMEDOUT) return(TIMEDOUT);
else if (status==EALREADY) return(ALREADY);
else if (status==EAGAIN) return(AGAIN);
else if (status==EWOULDBLOCK) return(WOULDBLOCK);
else if (status==EBADF) return(BADF);
else return(UNKNOWN);
#endif
}
/*
//
// Wait for net activity on this socket
//
int UDP::Wait(Int sec,Int usec,fd_set &returnSet)
{
fd_set inputSet;
FD_ZERO(&inputSet);
FD_SET(fd,&inputSet);
return(Wait(sec,usec,inputSet,returnSet));
}
*/
/*
//
// Wait for net activity on a list of sockets
//
int UDP::Wait(Int sec,Int usec,fd_set &givenSet,fd_set &returnSet)
{
Wtime timeout,timenow,timethen;
fd_set backupSet;
int retval=0,done,givenMax;
Bool noTimeout=FALSE;
timeval tv;
returnSet=givenSet;
backupSet=returnSet;
if ((sec==-1)&&(usec==-1))
noTimeout=TRUE;
timeout.SetSec(sec);
timeout.SetUsec(usec);
timethen+=timeout;
givenMax=fd;
for (UnsignedInt i=0; i<(sizeof(fd_set)*8); i++) // i=maxFD+1
{
if (FD_ISSET(i,&givenSet))
givenMax=i;
}
///DBGMSG("WAIT fd="<<fd<<" givenMax="<<givenMax);
done=0;
while( ! done)
{
if (noTimeout)
retval=select(givenMax+1,&returnSet,0,0,NULL);
else
{
timeout.GetTimevalMT(tv);
retval=select(givenMax+1,&returnSet,0,0,&tv);
}
if (retval>=0)
done=1;
else if ((retval==-1)&&(errno==EINTR)) // in case of signal
{
if (noTimeout==FALSE)
{
timenow.Update();
timeout=timethen-timenow;
}
if ((noTimeout==FALSE)&&(timenow.GetSec()==0)&&(timenow.GetUsec()==0))
done=1;
else
returnSet=backupSet;
}
else // maybe out of memory?
{
done=1;
}
}
///DBGMSG("Wait retval: "<<retval);
return(retval);
}
*/
// Set the kernel buffer sizes for incoming, and outgoing packets
//
// Linux seems to have a buffer max of 32767 bytes for this,
// (which is the default). If you try and set the size to
// greater than the default it just sets it to 32767.
Int UDP::SetInputBuffer(UnsignedInt bytes)
{
int retval,arg=bytes;
retval=setsockopt(fd,SOL_SOCKET,SO_RCVBUF,
(char *)&arg,sizeof(int));
if (retval==0)
return(TRUE);
else
return(FALSE);
}
// Same note goes for the output buffer
Int UDP::SetOutputBuffer(UnsignedInt bytes)
{
int retval,arg=bytes;
retval=setsockopt(fd,SOL_SOCKET,SO_SNDBUF,
(char *)&arg,sizeof(int));
if (retval==0)
return(TRUE);
else
return(FALSE);
}
// Get the system buffer sizes
int UDP::GetInputBuffer(void)
{
int retval,arg=0,len=sizeof(int);
retval=getsockopt(fd,SOL_SOCKET,SO_RCVBUF,
(char *)&arg,&len);
return(arg);
}
int UDP::GetOutputBuffer(void)
{
int retval,arg=0,len=sizeof(int);
retval=getsockopt(fd,SOL_SOCKET,SO_SNDBUF,
(char *)&arg,&len);
return(arg);
}
Int UDP::AllowBroadcasts(Bool status)
{
int retval;
BOOL val = status;
retval = setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char *)&val, sizeof(BOOL));
if (retval == 0)
return TRUE;
else
return FALSE;
}