You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
799 lines
21 KiB
799 lines
21 KiB
/*==LICENSE==* |
|
|
|
CyanWorlds.com Engine - MMOG client, server and tools |
|
Copyright (C) 2011 Cyan Worlds, 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/>. |
|
|
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com |
|
or by snail mail at: |
|
Cyan Worlds, Inc. |
|
14617 N Newport Hwy |
|
Mead, WA 99021 |
|
|
|
*==LICENSE==*/ |
|
/***************************************************************************** |
|
* |
|
* $/Plasma20/Sources/Plasma/PubUtilLib/plNetGameLib/Private/plNglGame.cpp |
|
* |
|
***/ |
|
|
|
#include "../Pch.h" |
|
#pragma hdrstop |
|
|
|
// This file excluded from pre-compiled header because it is auto-generated by the build server. |
|
#include "pnNetBase/pnNbGameKey.hpp" |
|
|
|
namespace Ngl { namespace Game { |
|
/***************************************************************************** |
|
* |
|
* Private |
|
* |
|
***/ |
|
|
|
struct CliGmConn : AtomicRef { |
|
LINK(CliGmConn) link; |
|
|
|
CCritSect critsect; |
|
AsyncSocket sock; |
|
AsyncCancelId cancelId; |
|
NetCli * cli; |
|
NetAddress addr; |
|
unsigned seq; |
|
bool abandoned; |
|
|
|
// ping |
|
AsyncTimer * pingTimer; |
|
unsigned pingSendTimeMs; |
|
unsigned lastHeardTimeMs; |
|
|
|
CliGmConn (); |
|
~CliGmConn (); |
|
|
|
void Send (const unsigned_ptr fields[], unsigned count); |
|
}; |
|
|
|
|
|
//============================================================================ |
|
// JoinAgeRequestTrans |
|
//============================================================================ |
|
struct JoinAgeRequestTrans : NetGameTrans { |
|
FNetCliGameJoinAgeRequestCallback m_callback; |
|
void * m_param; |
|
// sent |
|
unsigned m_ageMcpId; |
|
Uuid m_accountUuid; |
|
unsigned m_playerInt; |
|
|
|
JoinAgeRequestTrans ( |
|
unsigned ageMcpId, |
|
const Uuid & accountUuid, |
|
unsigned playerInt, |
|
FNetCliGameJoinAgeRequestCallback callback, |
|
void * param |
|
); |
|
|
|
bool Send (); |
|
void Post (); |
|
bool Recv ( |
|
const byte msg[], |
|
unsigned bytes |
|
); |
|
}; |
|
|
|
//============================================================================ |
|
// RcvdPropagatedBufferTrans |
|
//============================================================================ |
|
struct RcvdPropagatedBufferTrans : NetNotifyTrans { |
|
|
|
unsigned bufferType; |
|
unsigned bufferBytes; |
|
byte * bufferData; |
|
|
|
RcvdPropagatedBufferTrans () : NetNotifyTrans(kGmRcvdPropagatedBufferTrans) {} |
|
~RcvdPropagatedBufferTrans (); |
|
void Post (); |
|
}; |
|
|
|
//============================================================================ |
|
// RcvdGameMgrMsgTrans |
|
//============================================================================ |
|
struct RcvdGameMgrMsgTrans : NetNotifyTrans { |
|
|
|
unsigned bufferBytes; |
|
byte * bufferData; |
|
|
|
RcvdGameMgrMsgTrans () : NetNotifyTrans(kGmRcvdGameMgrMsgTrans) {} |
|
~RcvdGameMgrMsgTrans (); |
|
void Post (); |
|
}; |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* Private data |
|
* |
|
***/ |
|
|
|
enum { |
|
kPerfConnCount, |
|
kPingDisabled, |
|
kNumPerf |
|
}; |
|
|
|
static bool s_running; |
|
static CCritSect s_critsect; |
|
static LISTDECL(CliGmConn, link) s_conns; |
|
static CliGmConn * s_active; |
|
static FNetCliGameRecvBufferHandler s_bufHandler; |
|
static FNetCliGameRecvGameMgrMsgHandler s_gameMgrMsgHandler; |
|
static long s_perf[kNumPerf]; |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* Internal functions |
|
* |
|
***/ |
|
|
|
//=========================================================================== |
|
static unsigned GetNonZeroTimeMs () { |
|
if (unsigned ms = TimeGetMs()) |
|
return ms; |
|
return 1; |
|
} |
|
|
|
//============================================================================ |
|
static CliGmConn * GetConnIncRef_CS (const char tag[]) { |
|
if (CliGmConn * conn = s_active) |
|
if (conn->cli) { |
|
conn->IncRef(tag); |
|
return conn; |
|
} |
|
return nil; |
|
} |
|
|
|
//============================================================================ |
|
static CliGmConn * GetConnIncRef (const char tag[]) { |
|
CliGmConn * conn; |
|
s_critsect.Enter(); |
|
{ |
|
conn = GetConnIncRef_CS(tag); |
|
} |
|
s_critsect.Leave(); |
|
return conn; |
|
} |
|
|
|
//============================================================================ |
|
static void UnlinkAndAbandonConn_CS (CliGmConn * conn) { |
|
s_conns.Unlink(conn); |
|
conn->abandoned = true; |
|
if (conn->cancelId) { |
|
AsyncSocketConnectCancel(nil, conn->cancelId); |
|
conn->cancelId = 0; |
|
} |
|
else if (conn->sock) { |
|
AsyncSocketDisconnect(conn->sock, true); |
|
} |
|
else { |
|
conn->DecRef("Lifetime"); |
|
} |
|
} |
|
|
|
//============================================================================ |
|
static bool ConnEncrypt (ENetError error, void * param) { |
|
CliGmConn * conn = (CliGmConn *) param; |
|
|
|
if (IS_NET_SUCCESS(error)) { |
|
s_critsect.Enter(); |
|
{ |
|
SWAP(s_active, conn); |
|
} |
|
s_critsect.Leave(); |
|
} |
|
|
|
return IS_NET_SUCCESS(error); |
|
} |
|
|
|
//============================================================================ |
|
static void NotifyConnSocketConnect (CliGmConn * conn) { |
|
|
|
conn->TransferRef("Connecting", "Connected"); |
|
conn->cli = NetCliConnectAccept( |
|
conn->sock, |
|
kNetProtocolCli2Game, |
|
true, |
|
ConnEncrypt, |
|
0, |
|
nil, |
|
conn |
|
); |
|
} |
|
|
|
//============================================================================ |
|
static void NotifyConnSocketConnectFailed (CliGmConn * conn) { |
|
bool notify; |
|
s_critsect.Enter(); |
|
{ |
|
conn->cancelId = 0; |
|
s_conns.Unlink(conn); |
|
|
|
notify |
|
= s_running |
|
&& !conn->abandoned |
|
&& (!s_active || conn == s_active); |
|
|
|
if (conn == s_active) |
|
s_active = nil; |
|
} |
|
s_critsect.Leave(); |
|
|
|
NetTransCancelByConnId(conn->seq, kNetErrTimeout); |
|
conn->DecRef("Connecting"); |
|
conn->DecRef("Lifetime"); |
|
|
|
if (notify) |
|
ReportNetError(kNetProtocolCli2Game, kNetErrConnectFailed); |
|
} |
|
|
|
//============================================================================ |
|
static void NotifyConnSocketDisconnect (CliGmConn * conn) { |
|
|
|
bool notify; |
|
s_critsect.Enter(); |
|
{ |
|
s_conns.Unlink(conn); |
|
|
|
notify |
|
= s_running |
|
&& !conn->abandoned |
|
&& (!s_active || conn == s_active); |
|
|
|
if (conn == s_active) |
|
s_active = nil; |
|
} |
|
s_critsect.Leave(); |
|
|
|
// Cancel all transactions in process on this connection. |
|
NetTransCancelByConnId(conn->seq, kNetErrTimeout); |
|
conn->DecRef("Connected"); |
|
conn->DecRef("Lifetime"); |
|
|
|
if (notify) |
|
ReportNetError(kNetProtocolCli2Game, kNetErrDisconnected); |
|
} |
|
|
|
//============================================================================ |
|
static bool NotifyConnSocketRead (CliGmConn * conn, AsyncNotifySocketRead * read) { |
|
// TODO: Only dispatch messages from the active game server |
|
conn->lastHeardTimeMs = GetNonZeroTimeMs(); |
|
bool result = NetCliDispatch(conn->cli, read->buffer, read->bytes, nil); |
|
read->bytesProcessed += read->bytes; |
|
return result; |
|
} |
|
|
|
//============================================================================ |
|
static bool SocketNotifyCallback ( |
|
AsyncSocket sock, |
|
EAsyncNotifySocket code, |
|
AsyncNotifySocket * notify, |
|
void ** userState |
|
) { |
|
bool result = true; |
|
CliGmConn * conn; |
|
|
|
switch (code) { |
|
case kNotifySocketConnectSuccess: |
|
conn = (CliGmConn *) notify->param; |
|
*userState = conn; |
|
conn->TransferRef("Connecting", "Connected"); |
|
bool abandoned; |
|
s_critsect.Enter(); |
|
{ |
|
conn->sock = sock; |
|
conn->cancelId = 0; |
|
abandoned = conn->abandoned; |
|
} |
|
s_critsect.Leave(); |
|
if (abandoned) |
|
AsyncSocketDisconnect(sock, true); |
|
else |
|
NotifyConnSocketConnect(conn); |
|
break; |
|
|
|
case kNotifySocketConnectFailed: |
|
conn = (CliGmConn *) notify->param; |
|
NotifyConnSocketConnectFailed(conn); |
|
break; |
|
|
|
case kNotifySocketDisconnect: |
|
conn = (CliGmConn *) *userState; |
|
NotifyConnSocketDisconnect(conn); |
|
break; |
|
|
|
case kNotifySocketRead: |
|
conn = (CliGmConn *) *userState; |
|
result = NotifyConnSocketRead(conn, (AsyncNotifySocketRead *) notify); |
|
break; |
|
} |
|
|
|
return result; |
|
} |
|
|
|
//============================================================================ |
|
static void Connect ( |
|
const NetAddress & addr |
|
) { |
|
CliGmConn * conn = NEWZERO(CliGmConn); |
|
conn->addr = addr; |
|
conn->seq = ConnNextSequence(); |
|
conn->lastHeardTimeMs = GetNonZeroTimeMs(); |
|
|
|
conn->IncRef("Lifetime"); |
|
conn->IncRef("Connecting"); |
|
|
|
s_critsect.Enter(); |
|
{ |
|
while (CliGmConn * conn = s_conns.Head()) |
|
UnlinkAndAbandonConn_CS(conn); |
|
s_conns.Link(conn); |
|
} |
|
s_critsect.Leave(); |
|
|
|
Cli2Game_Connect connect; |
|
connect.hdr.connType = kConnTypeCliToGame; |
|
connect.hdr.hdrBytes = sizeof(connect.hdr); |
|
connect.hdr.buildId = BuildId(); |
|
connect.hdr.buildType = BuildType(); |
|
connect.hdr.branchId = BranchId(); |
|
connect.hdr.productId = ProductId(); |
|
connect.data.dataBytes = sizeof(connect.data); |
|
|
|
AsyncSocketConnect( |
|
&conn->cancelId, |
|
addr, |
|
SocketNotifyCallback, |
|
conn, |
|
&connect, |
|
sizeof(connect), |
|
0, |
|
0 |
|
); |
|
} |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* CliGmConn |
|
* |
|
***/ |
|
|
|
//============================================================================ |
|
CliGmConn::CliGmConn () { |
|
AtomicAdd(&s_perf[kPerfConnCount], 1); |
|
} |
|
|
|
//============================================================================ |
|
CliGmConn::~CliGmConn () { |
|
if (cli) |
|
NetCliDelete(cli, true); |
|
AtomicAdd(&s_perf[kPerfConnCount], -1); |
|
} |
|
|
|
//============================================================================ |
|
void CliGmConn::Send (const unsigned_ptr fields[], unsigned count) { |
|
critsect.Enter(); |
|
{ |
|
NetCliSend(cli, fields, count); |
|
NetCliFlush(cli); |
|
} |
|
critsect.Leave(); |
|
} |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* Cli2Game protocol |
|
* |
|
***/ |
|
|
|
//============================================================================ |
|
static bool Recv_PingReply ( |
|
const byte msg[], |
|
unsigned bytes, |
|
void * param |
|
) { |
|
return true; |
|
} |
|
|
|
//============================================================================ |
|
static bool Recv_JoinAgeReply ( |
|
const byte msg[], |
|
unsigned bytes, |
|
void * param |
|
) { |
|
const Game2Cli_JoinAgeReply & reply = *(const Game2Cli_JoinAgeReply *)msg; |
|
if (sizeof(reply) != bytes) |
|
return false; |
|
|
|
NetTransRecv(reply.transId, msg, bytes); |
|
|
|
return true; |
|
} |
|
|
|
//============================================================================ |
|
static bool Recv_PropagateBuffer ( |
|
const byte msg[], |
|
unsigned bytes, |
|
void * param |
|
) { |
|
const Game2Cli_PropagateBuffer & reply = *(const Game2Cli_PropagateBuffer *)msg; |
|
|
|
RcvdPropagatedBufferTrans * trans = NEW(RcvdPropagatedBufferTrans); |
|
trans->bufferType = reply.type; |
|
trans->bufferBytes = reply.bytes; |
|
trans->bufferData = (byte *)ALLOC(reply.bytes); |
|
MemCopy(trans->bufferData, reply.buffer, reply.bytes); |
|
NetTransSend(trans); |
|
|
|
return true; |
|
} |
|
|
|
//============================================================================ |
|
static bool Recv_GameMgrMsg ( |
|
const byte msg[], |
|
unsigned bytes, |
|
void * param |
|
) { |
|
const Game2Cli_GameMgrMsg & reply = *(const Game2Cli_GameMgrMsg *)msg; |
|
|
|
RcvdGameMgrMsgTrans * trans = NEW(RcvdGameMgrMsgTrans); |
|
trans->bufferBytes = reply.bytes; |
|
trans->bufferData = (byte *)ALLOC(reply.bytes); |
|
MemCopy(trans->bufferData, reply.buffer, reply.bytes); |
|
NetTransSend(trans); |
|
|
|
return true; |
|
} |
|
|
|
|
|
//============================================================================ |
|
// Send/Recv protocol handler init |
|
//============================================================================ |
|
#define MSG(s) kNetMsg_Cli2Game_##s |
|
static NetMsgInitSend s_send[] = { |
|
{ MSG(PingRequest), }, |
|
{ MSG(JoinAgeRequest), }, |
|
{ MSG(PropagateBuffer), }, |
|
{ MSG(GameMgrMsg), }, |
|
}; |
|
#undef MSG |
|
|
|
#define MSG(s) kNetMsg_Game2Cli_##s, Recv_##s |
|
static NetMsgInitRecv s_recv[] = { |
|
{ MSG(PingReply) }, |
|
{ MSG(JoinAgeReply), }, |
|
{ MSG(PropagateBuffer), }, |
|
{ MSG(GameMgrMsg), }, |
|
}; |
|
#undef MSG |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* JoinAgeRequestTrans |
|
* |
|
***/ |
|
|
|
//============================================================================ |
|
JoinAgeRequestTrans::JoinAgeRequestTrans ( |
|
unsigned ageMcpId, |
|
const Uuid & accountUuid, |
|
unsigned playerInt, |
|
FNetCliGameJoinAgeRequestCallback callback, |
|
void * param |
|
) : NetGameTrans(kJoinAgeRequestTrans) |
|
, m_ageMcpId(ageMcpId) |
|
, m_accountUuid(accountUuid) |
|
, m_playerInt(playerInt) |
|
, m_callback(callback) |
|
, m_param(param) |
|
{ |
|
} |
|
|
|
//============================================================================ |
|
bool JoinAgeRequestTrans::Send () { |
|
if (!AcquireConn()) |
|
return false; |
|
|
|
const unsigned_ptr msg[] = { |
|
kCli2Game_JoinAgeRequest, |
|
m_transId, |
|
m_ageMcpId, |
|
(unsigned_ptr) &m_accountUuid, |
|
m_playerInt, |
|
}; |
|
|
|
m_conn->Send(msg, arrsize(msg)); |
|
|
|
return true; |
|
} |
|
|
|
//============================================================================ |
|
void JoinAgeRequestTrans::Post () { |
|
m_callback( |
|
m_result, |
|
m_param |
|
); |
|
} |
|
|
|
//============================================================================ |
|
bool JoinAgeRequestTrans::Recv ( |
|
const byte msg[], |
|
unsigned bytes |
|
) { |
|
const Game2Cli_JoinAgeReply & reply = *(const Game2Cli_JoinAgeReply *) msg; |
|
m_result = reply.result; |
|
m_state = kTransStateComplete; |
|
return true; |
|
} |
|
|
|
/***************************************************************************** |
|
* |
|
* RcvdPropagatedBufferTrans |
|
* |
|
***/ |
|
|
|
//============================================================================ |
|
RcvdPropagatedBufferTrans::~RcvdPropagatedBufferTrans () { |
|
FREE(bufferData); |
|
} |
|
|
|
//============================================================================ |
|
void RcvdPropagatedBufferTrans::Post () { |
|
if (s_bufHandler) |
|
s_bufHandler(bufferType, bufferBytes, bufferData); |
|
} |
|
|
|
/***************************************************************************** |
|
* |
|
* RcvdGameMgrMsgTrans |
|
* |
|
***/ |
|
|
|
//============================================================================ |
|
RcvdGameMgrMsgTrans::~RcvdGameMgrMsgTrans () { |
|
FREE(bufferData); |
|
} |
|
|
|
//============================================================================ |
|
void RcvdGameMgrMsgTrans::Post () { |
|
if (s_gameMgrMsgHandler) |
|
s_gameMgrMsgHandler((GameMsgHeader *)bufferData); |
|
} |
|
|
|
|
|
} using namespace Game; |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* NetGameTrans |
|
* |
|
***/ |
|
|
|
//============================================================================ |
|
NetGameTrans::NetGameTrans (ETransType transType) |
|
: NetTrans(kNetProtocolCli2Game, transType) |
|
, m_conn(nil) |
|
{ |
|
} |
|
|
|
//============================================================================ |
|
NetGameTrans::~NetGameTrans () { |
|
ReleaseConn(); |
|
} |
|
|
|
//============================================================================ |
|
bool NetGameTrans::AcquireConn () { |
|
if (!m_conn) |
|
m_conn = GetConnIncRef("AcquireConn"); |
|
return m_conn != nil; |
|
} |
|
|
|
//============================================================================ |
|
void NetGameTrans::ReleaseConn () { |
|
if (m_conn) { |
|
m_conn->DecRef("AcquireConn"); |
|
m_conn = nil; |
|
} |
|
} |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* Protected functions |
|
* |
|
***/ |
|
|
|
//============================================================================ |
|
void GameInitialize () { |
|
s_running = true; |
|
NetMsgProtocolRegister( |
|
kNetProtocolCli2Game, |
|
false, |
|
s_send, arrsize(s_send), |
|
s_recv, arrsize(s_recv), |
|
kDhGValue, |
|
BigNum(sizeof(kDhXData), kDhXData), |
|
BigNum(sizeof(kDhNData), kDhNData) |
|
); |
|
} |
|
|
|
//============================================================================ |
|
void GameDestroy (bool wait) { |
|
s_running = false; |
|
s_bufHandler = nil; |
|
s_gameMgrMsgHandler = nil; |
|
|
|
NetTransCancelByProtocol( |
|
kNetProtocolCli2Game, |
|
kNetErrRemoteShutdown |
|
); |
|
NetMsgProtocolDestroy( |
|
kNetProtocolCli2Game, |
|
false |
|
); |
|
|
|
s_critsect.Enter(); |
|
{ |
|
while (CliGmConn * conn = s_conns.Head()) |
|
UnlinkAndAbandonConn_CS(conn); |
|
s_active = nil; |
|
} |
|
s_critsect.Leave(); |
|
|
|
if (!wait) |
|
return; |
|
|
|
while (s_perf[kPerfConnCount]) { |
|
NetTransUpdate(); |
|
AsyncSleep(10); |
|
} |
|
} |
|
|
|
//============================================================================ |
|
bool GameQueryConnected () { |
|
bool result; |
|
s_critsect.Enter(); |
|
{ |
|
if (nil != (result = s_active)) |
|
result &= (nil != s_active->cli); |
|
} |
|
s_critsect.Leave(); |
|
return result; |
|
} |
|
|
|
//============================================================================ |
|
unsigned GameGetConnId () { |
|
unsigned connId; |
|
s_critsect.Enter(); |
|
connId = (s_active) ? s_active->seq : 0; |
|
s_critsect.Leave(); |
|
return connId; |
|
} |
|
|
|
//============================================================================ |
|
void GamePingEnable (bool enable) { |
|
s_perf[kPingDisabled] = !enable; |
|
} |
|
|
|
} using namespace Ngl; |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* Exported functions |
|
* |
|
***/ |
|
|
|
//============================================================================ |
|
void NetCliGameStartConnect ( |
|
const NetAddressNode & node |
|
) { |
|
NetAddress addr; |
|
NetAddressFromNode(node, kNetDefaultClientPort, &addr); |
|
Connect(addr); |
|
} |
|
|
|
//============================================================================ |
|
void NetCliGameDisconnect () { |
|
|
|
s_critsect.Enter(); |
|
{ |
|
while (CliGmConn * conn = s_conns.Head()) |
|
UnlinkAndAbandonConn_CS(conn); |
|
s_active = nil; |
|
} |
|
s_critsect.Leave(); |
|
} |
|
|
|
//============================================================================ |
|
void NetCliGameJoinAgeRequest ( |
|
unsigned ageMcpId, |
|
const Uuid & accountUuid, |
|
unsigned playerInt, |
|
FNetCliGameJoinAgeRequestCallback callback, |
|
void * param |
|
) { |
|
JoinAgeRequestTrans * trans = NEWZERO(JoinAgeRequestTrans)( |
|
ageMcpId, |
|
accountUuid, |
|
playerInt, |
|
callback, |
|
param |
|
); |
|
NetTransSend(trans); |
|
} |
|
|
|
//============================================================================ |
|
void NetCliGameSetRecvBufferHandler ( |
|
FNetCliGameRecvBufferHandler handler |
|
) { |
|
s_bufHandler = handler; |
|
} |
|
|
|
//============================================================================ |
|
void NetCliGamePropagateBuffer ( |
|
unsigned type, |
|
unsigned bytes, |
|
const byte buffer[] |
|
) { |
|
CliGmConn * conn = GetConnIncRef("PropBuffer"); |
|
if (!conn) |
|
return; |
|
|
|
const unsigned_ptr msg[] = { |
|
kCli2Game_PropagateBuffer, |
|
type, |
|
bytes, |
|
(unsigned_ptr) buffer, |
|
}; |
|
|
|
conn->Send(msg, arrsize(msg)); |
|
|
|
conn->DecRef("PropBuffer"); |
|
} |
|
|
|
//============================================================================ |
|
void NetCliGameSetRecvGameMgrMsgHandler (FNetCliGameRecvGameMgrMsgHandler handler) { |
|
s_gameMgrMsgHandler = handler; |
|
} |
|
|
|
//============================================================================ |
|
void NetCliGameSendGameMgrMsg (GameMsgHeader * msgHdr) { |
|
CliGmConn * conn = GetConnIncRef("GameMgrMsg"); |
|
if (!conn) |
|
return; |
|
|
|
const unsigned_ptr msg[] = { |
|
kCli2Game_GameMgrMsg, |
|
msgHdr->messageBytes, |
|
(unsigned_ptr) msgHdr, |
|
}; |
|
|
|
conn->Send(msg, arrsize(msg)); |
|
|
|
conn->DecRef("GameMgrMsg"); |
|
}
|
|
|