]> icculus.org git repositories - icculus/iodoom3.git/blob - neo/framework/async/AsyncNetwork.cpp
hello world
[icculus/iodoom3.git] / neo / framework / async / AsyncNetwork.cpp
1 /*
2 ===========================================================================
3
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. 
6
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).  
8
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
23
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25
26 ===========================================================================
27 */
28
29 #include "../../idlib/precompiled.h"
30 #pragma hdrstop
31
32 #include "AsyncNetwork.h"
33
34 idAsyncServer           idAsyncNetwork::server;
35 idAsyncClient           idAsyncNetwork::client;
36
37 idCVar                          idAsyncNetwork::verbose( "net_verbose", "0", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "1 = verbose output, 2 = even more verbose output", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> );
38 idCVar                          idAsyncNetwork::allowCheats( "net_allowCheats", "0", CVAR_SYSTEM | CVAR_BOOL | CVAR_NETWORKSYNC, "Allow cheats in network game" );
39 #ifdef ID_DEDICATED
40 // dedicated executable can only have a value of 1 for net_serverDedicated
41 idCVar                          idAsyncNetwork::serverDedicated( "net_serverDedicated", "1", CVAR_SERVERINFO | CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT | CVAR_ROM, "" );
42 #else
43 idCVar                          idAsyncNetwork::serverDedicated( "net_serverDedicated", "0", CVAR_SERVERINFO | CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "1 = text console dedicated server, 2 = graphical dedicated server", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> );
44 #endif
45 idCVar                          idAsyncNetwork::serverSnapshotDelay( "net_serverSnapshotDelay", "50", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "delay between snapshots in milliseconds" );
46 idCVar                          idAsyncNetwork::serverMaxClientRate( "net_serverMaxClientRate", "16000", CVAR_SYSTEM | CVAR_INTEGER | CVAR_ARCHIVE | CVAR_NOCHEAT, "maximum rate to a client in bytes/sec" );
47 idCVar                          idAsyncNetwork::clientMaxRate( "net_clientMaxRate", "16000", CVAR_SYSTEM | CVAR_INTEGER | CVAR_ARCHIVE | CVAR_NOCHEAT, "maximum rate requested by client from server in bytes/sec" );
48 idCVar                          idAsyncNetwork::serverMaxUsercmdRelay( "net_serverMaxUsercmdRelay", "5", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "maximum number of usercmds from other clients the server relays to a client", 1, MAX_USERCMD_RELAY, idCmdSystem::ArgCompletion_Integer<1,MAX_USERCMD_RELAY> );
49 idCVar                          idAsyncNetwork::serverZombieTimeout( "net_serverZombieTimeout", "5", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "disconnected client timeout in seconds" );
50 idCVar                          idAsyncNetwork::serverClientTimeout( "net_serverClientTimeout", "40", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "client time out in seconds" );
51 idCVar                          idAsyncNetwork::clientServerTimeout( "net_clientServerTimeout", "40", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "server time out in seconds" );
52 idCVar                          idAsyncNetwork::serverDrawClient( "net_serverDrawClient", "-1", CVAR_SYSTEM | CVAR_INTEGER, "number of client for which to draw view on server" );
53 idCVar                          idAsyncNetwork::serverRemoteConsolePassword( "net_serverRemoteConsolePassword", "", CVAR_SYSTEM | CVAR_NOCHEAT, "remote console password" );
54 idCVar                          idAsyncNetwork::clientPrediction( "net_clientPrediction", "16", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "additional client side prediction in milliseconds" );
55 idCVar                          idAsyncNetwork::clientMaxPrediction( "net_clientMaxPrediction", "1000", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "maximum number of milliseconds a client can predict ahead of server." );
56 idCVar                          idAsyncNetwork::clientUsercmdBackup( "net_clientUsercmdBackup", "5", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "number of usercmds to resend" );
57 idCVar                          idAsyncNetwork::clientRemoteConsoleAddress( "net_clientRemoteConsoleAddress", "localhost", CVAR_SYSTEM | CVAR_NOCHEAT, "remote console address" );
58 idCVar                          idAsyncNetwork::clientRemoteConsolePassword( "net_clientRemoteConsolePassword", "", CVAR_SYSTEM | CVAR_NOCHEAT, "remote console password" );
59 idCVar                          idAsyncNetwork::master0( "net_master0", IDNET_HOST ":" IDNET_MASTER_PORT, CVAR_SYSTEM | CVAR_ROM, "idnet master server address" );
60 idCVar                          idAsyncNetwork::master1( "net_master1", "", CVAR_SYSTEM | CVAR_ARCHIVE, "1st master server address" );
61 idCVar                          idAsyncNetwork::master2( "net_master2", "", CVAR_SYSTEM | CVAR_ARCHIVE, "2nd master server address" );
62 idCVar                          idAsyncNetwork::master3( "net_master3", "", CVAR_SYSTEM | CVAR_ARCHIVE, "3rd master server address" );
63 idCVar                          idAsyncNetwork::master4( "net_master4", "", CVAR_SYSTEM | CVAR_ARCHIVE, "4th master server address" );
64 idCVar                          idAsyncNetwork::LANServer( "net_LANServer", "0", CVAR_SYSTEM | CVAR_BOOL | CVAR_NOCHEAT, "config LAN games only - affects clients and servers" );
65 idCVar                          idAsyncNetwork::serverReloadEngine( "net_serverReloadEngine", "0", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "perform a full reload on next map restart (including flushing referenced pak files) - decreased if > 0" );
66 idCVar                          idAsyncNetwork::serverAllowServerMod( "net_serverAllowServerMod", "0", CVAR_SYSTEM | CVAR_BOOL | CVAR_NOCHEAT, "allow server-side mods" );
67 idCVar                          idAsyncNetwork::idleServer( "si_idleServer", "0", CVAR_SYSTEM | CVAR_BOOL | CVAR_INIT | CVAR_SERVERINFO, "game clients are idle" );
68 idCVar                          idAsyncNetwork::clientDownload( "net_clientDownload", "1", CVAR_SYSTEM | CVAR_INTEGER | CVAR_ARCHIVE, "client pk4 downloads policy: 0 - never, 1 - ask, 2 - always (will still prompt for binary code)" );
69
70 int                                     idAsyncNetwork::realTime;
71 master_t                        idAsyncNetwork::masters[ MAX_MASTER_SERVERS ];
72
73 /*
74 ==================
75 idAsyncNetwork::idAsyncNetwork
76 ==================
77 */
78 idAsyncNetwork::idAsyncNetwork( void ) {
79 }
80
81 /*
82 ==================
83 idAsyncNetwork::Init
84 ==================
85 */
86 void idAsyncNetwork::Init( void ) {
87
88         realTime = 0;
89
90         memset( masters, 0, sizeof( masters ) );
91         masters[0].var = &master0;
92         masters[1].var = &master1;
93         masters[2].var = &master2;
94         masters[3].var = &master3;
95         masters[4].var = &master4;
96
97 #ifndef ID_DEMO_BUILD
98         cmdSystem->AddCommand( "spawnServer", SpawnServer_f, CMD_FL_SYSTEM, "spawns a server", idCmdSystem::ArgCompletion_MapName );
99         cmdSystem->AddCommand( "nextMap", NextMap_f, CMD_FL_SYSTEM, "loads the next map on the server" );
100         cmdSystem->AddCommand( "connect", Connect_f, CMD_FL_SYSTEM, "connects to a server" );
101         cmdSystem->AddCommand( "reconnect", Reconnect_f, CMD_FL_SYSTEM, "reconnect to the last server we tried to connect to" );
102         cmdSystem->AddCommand( "serverInfo", GetServerInfo_f, CMD_FL_SYSTEM, "shows server info" );
103         cmdSystem->AddCommand( "LANScan", GetLANServers_f, CMD_FL_SYSTEM, "scans LAN for servers" );
104         cmdSystem->AddCommand( "listServers", ListServers_f, CMD_FL_SYSTEM, "lists scanned servers" );
105         cmdSystem->AddCommand( "rcon", RemoteConsole_f, CMD_FL_SYSTEM, "sends remote console command to server" );
106         cmdSystem->AddCommand( "heartbeat", Heartbeat_f, CMD_FL_SYSTEM, "send a heartbeat to the the master servers" );
107         cmdSystem->AddCommand( "kick", Kick_f, CMD_FL_SYSTEM, "kick a client by connection number" );
108         cmdSystem->AddCommand( "checkNewVersion", CheckNewVersion_f, CMD_FL_SYSTEM, "check if a new version of the game is available" );
109         cmdSystem->AddCommand( "updateUI", UpdateUI_f, CMD_FL_SYSTEM, "internal - cause a sync down of game-modified userinfo" );
110 #endif
111 }
112
113 /*
114 ==================
115 idAsyncNetwork::GetMasterAddress
116 ==================
117 */
118 netadr_t idAsyncNetwork::GetMasterAddress( void ) {
119         netadr_t ret;
120         GetMasterAddress( 0, ret );
121         return masters[ 0 ].address;
122 }
123
124 /*
125 ==================
126 idAsyncNetwork::GetMasterAddress
127 ==================
128 */
129 bool idAsyncNetwork::GetMasterAddress( int index, netadr_t &adr ) {
130         if ( !masters[ index ].var ) {
131                 return false;
132         }       
133         if ( masters[ index ].var->GetString()[0] == '\0' ) {
134                 return false;
135         }
136         if ( !masters[ index ].resolved || masters[ index ].var->IsModified() ) {
137                 masters[ index ].var->ClearModified();
138                 if ( !Sys_StringToNetAdr( masters[ index ].var->GetString(), &masters[ index ].address, true ) ) {
139                         common->Printf( "Failed to resolve master%d: %s\n", index, masters[ index ].var->GetString() );
140                         memset( &masters[ index ].address, 0, sizeof( netadr_t ) );
141                         masters[ index ].resolved = true;
142                         return false;
143                 }
144                 if ( masters[ index ].address.port == 0 ) {
145                         masters[ index ].address.port = atoi( IDNET_MASTER_PORT );
146                 }
147                 masters[ index ].resolved = true;
148         }
149         adr = masters[ index ].address;
150         return true;
151 }
152
153 /*
154 ==================
155 idAsyncNetwork::Shutdown
156 ==================
157 */
158 void idAsyncNetwork::Shutdown( void ) {
159         client.serverList.Shutdown();
160         client.DisconnectFromServer();
161         client.ClearServers();
162         client.ClosePort();
163         server.Kill();
164         server.ClosePort();
165 }
166
167 /*
168 ==================
169 idAsyncNetwork::RunFrame
170 ==================
171 */
172 void idAsyncNetwork::RunFrame( void ) {
173         if ( console->Active() ) {
174                 Sys_GrabMouseCursor( false );
175                 usercmdGen->InhibitUsercmd( INHIBIT_ASYNC, true );
176         } else {
177                 Sys_GrabMouseCursor( true );
178                 usercmdGen->InhibitUsercmd( INHIBIT_ASYNC, false );
179         }
180         client.RunFrame();
181         server.RunFrame();
182 }
183
184 /*
185 ==================
186 idAsyncNetwork::WriteUserCmdDelta
187 ==================
188 */
189 void idAsyncNetwork::WriteUserCmdDelta( idBitMsg &msg, const usercmd_t &cmd, const usercmd_t *base ) {
190         if ( base ) {
191                 msg.WriteDeltaLongCounter( base->gameTime, cmd.gameTime );
192                 msg.WriteDeltaByte( base->buttons, cmd.buttons );
193                 msg.WriteDeltaShort( base->mx, cmd.mx );
194                 msg.WriteDeltaShort( base->my, cmd.my );
195                 msg.WriteDeltaChar( base->forwardmove, cmd.forwardmove );
196                 msg.WriteDeltaChar( base->rightmove, cmd.rightmove );
197                 msg.WriteDeltaChar( base->upmove, cmd.upmove );
198                 msg.WriteDeltaShort( base->angles[0], cmd.angles[0] );
199                 msg.WriteDeltaShort( base->angles[1], cmd.angles[1] );
200                 msg.WriteDeltaShort( base->angles[2], cmd.angles[2] );
201                 return;
202         }
203
204         msg.WriteLong( cmd.gameTime );
205         msg.WriteByte( cmd.buttons );
206     msg.WriteShort( cmd.mx );
207         msg.WriteShort( cmd.my );
208         msg.WriteChar( cmd.forwardmove );
209         msg.WriteChar( cmd.rightmove );
210         msg.WriteChar( cmd.upmove );
211         msg.WriteShort( cmd.angles[0] );
212         msg.WriteShort( cmd.angles[1] );
213         msg.WriteShort( cmd.angles[2] );
214 }
215
216 /*
217 ==================
218 idAsyncNetwork::ReadUserCmdDelta
219 ==================
220 */
221 void idAsyncNetwork::ReadUserCmdDelta( const idBitMsg &msg, usercmd_t &cmd, const usercmd_t *base ) {
222         memset( &cmd, 0, sizeof( cmd ) );
223
224         if ( base ) {
225                 cmd.gameTime = msg.ReadDeltaLongCounter( base->gameTime );
226                 cmd.buttons = msg.ReadDeltaByte( base->buttons );
227                 cmd.mx = msg.ReadDeltaShort( base->mx );
228                 cmd.my = msg.ReadDeltaShort( base->my );
229                 cmd.forwardmove = msg.ReadDeltaChar( base->forwardmove );
230                 cmd.rightmove = msg.ReadDeltaChar( base->rightmove );
231                 cmd.upmove = msg.ReadDeltaChar( base->upmove );
232                 cmd.angles[0] = msg.ReadDeltaShort( base->angles[0] );
233                 cmd.angles[1] = msg.ReadDeltaShort( base->angles[1] );
234                 cmd.angles[2] = msg.ReadDeltaShort( base->angles[2] );
235                 return;
236         }
237
238         cmd.gameTime = msg.ReadLong();
239     cmd.buttons = msg.ReadByte();
240     cmd.mx = msg.ReadShort();
241         cmd.my = msg.ReadShort();
242         cmd.forwardmove = msg.ReadChar();
243         cmd.rightmove = msg.ReadChar();
244         cmd.upmove = msg.ReadChar();
245         cmd.angles[0] = msg.ReadShort();
246         cmd.angles[1] = msg.ReadShort();
247         cmd.angles[2] = msg.ReadShort();
248 }
249
250 /*
251 ==================
252 idAsyncNetwork::DuplicateUsercmd
253 ==================
254 */
255 bool idAsyncNetwork::DuplicateUsercmd( const usercmd_t &previousUserCmd, usercmd_t &currentUserCmd, int frame, int time ) {
256
257         if ( currentUserCmd.gameTime <= previousUserCmd.gameTime ) {
258
259                 currentUserCmd = previousUserCmd;
260                 currentUserCmd.gameFrame = frame;
261                 currentUserCmd.gameTime = time;
262                 currentUserCmd.duplicateCount++;
263
264                 if ( currentUserCmd.duplicateCount > MAX_USERCMD_DUPLICATION ) {
265                         currentUserCmd.buttons &= ~BUTTON_ATTACK;
266                         if ( abs( currentUserCmd.forwardmove ) > 2 ) currentUserCmd.forwardmove >>= 1;
267                         if ( abs( currentUserCmd.rightmove ) > 2 ) currentUserCmd.rightmove >>= 1;
268                         if ( abs( currentUserCmd.upmove ) > 2 ) currentUserCmd.upmove >>= 1;
269                 }
270
271                 return true;
272         }
273         return false;
274 }
275
276 /*
277 ==================
278 idAsyncNetwork::UsercmdInputChanged
279 ==================
280 */
281 bool idAsyncNetwork::UsercmdInputChanged( const usercmd_t &previousUserCmd, const usercmd_t &currentUserCmd ) {
282         return  previousUserCmd.buttons != currentUserCmd.buttons ||
283                         previousUserCmd.forwardmove != currentUserCmd.forwardmove ||
284                         previousUserCmd.rightmove != currentUserCmd.rightmove ||
285                         previousUserCmd.upmove != currentUserCmd.upmove ||
286                         previousUserCmd.angles[0] != currentUserCmd.angles[0] ||
287                         previousUserCmd.angles[1] != currentUserCmd.angles[1] ||
288                         previousUserCmd.angles[2] != currentUserCmd.angles[2];
289 }
290
291 /*
292 ==================
293 idAsyncNetwork::SpawnServer_f
294 ==================
295 */
296 void idAsyncNetwork::SpawnServer_f( const idCmdArgs &args ) {
297
298         if(args.Argc() > 1) {
299                 cvarSystem->SetCVarString("si_map", args.Argv(1));
300         }
301
302         // don't let a server spawn with singleplayer game type - it will crash
303         if ( idStr::Icmp( cvarSystem->GetCVarString( "si_gameType" ), "singleplayer" ) == 0 ) {
304                 cvarSystem->SetCVarString( "si_gameType", "deathmatch" );
305         }
306         com_asyncInput.SetBool( false );
307         // make sure the current system state is compatible with net_serverDedicated
308         switch ( cvarSystem->GetCVarInteger( "net_serverDedicated" ) ) {
309                 case 0:
310                 case 2:
311                         if ( !renderSystem->IsOpenGLRunning() ) {
312                                 common->Warning( "OpenGL is not running, net_serverDedicated == %d", cvarSystem->GetCVarInteger( "net_serverDedicated" ) );
313                         }
314                         break;
315                 case 1:
316                         if ( renderSystem->IsOpenGLRunning() ) {
317                                 Sys_ShowConsole( 1, false );
318                                 renderSystem->ShutdownOpenGL();
319                         }
320                         soundSystem->SetMute( true );
321                         soundSystem->ShutdownHW();
322                         break;
323         }
324         // use serverMapRestart if we already have a running server
325         if ( server.IsActive() ) {
326                 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "serverMapRestart" );
327         } else {
328                 server.Spawn();
329         }
330 }
331
332 /*
333 ==================
334 idAsyncNetwork::NextMap_f
335 ==================
336 */
337 void idAsyncNetwork::NextMap_f( const idCmdArgs &args ) {
338         server.ExecuteMapChange();
339 }
340
341 /*
342 ==================
343 idAsyncNetwork::Connect_f
344 ==================
345 */
346 void idAsyncNetwork::Connect_f( const idCmdArgs &args ) {
347         if ( server.IsActive() ) {
348                 common->Printf( "already running a server\n" );
349                 return;
350         }
351         if ( args.Argc() != 2 ) {
352                 common->Printf( "USAGE: connect <serverName>\n" );
353                 return;
354         }
355         com_asyncInput.SetBool( false );
356         client.ConnectToServer( args.Argv( 1 ) );
357 }
358
359 /*
360 ==================
361 idAsyncNetwork::Reconnect_f
362 ==================
363 */
364 void idAsyncNetwork::Reconnect_f( const idCmdArgs &args ) {
365         client.Reconnect();
366 }
367
368 /*
369 ==================
370 idAsyncNetwork::GetServerInfo_f
371 ==================
372 */
373 void idAsyncNetwork::GetServerInfo_f( const idCmdArgs &args ) {
374         client.GetServerInfo( args.Argv( 1 ) );
375 }
376
377 /*
378 ==================
379 idAsyncNetwork::GetLANServers_f
380 ==================
381 */
382 void idAsyncNetwork::GetLANServers_f( const idCmdArgs &args ) {
383         client.GetLANServers();
384 }
385
386 /*
387 ==================
388 idAsyncNetwork::ListServers_f
389 ==================
390 */
391 void idAsyncNetwork::ListServers_f( const idCmdArgs &args ) {
392         client.ListServers();
393 }
394
395 /*
396 ==================
397 idAsyncNetwork::RemoteConsole_f
398 ==================
399 */
400 void idAsyncNetwork::RemoteConsole_f( const idCmdArgs &args ) {
401         client.RemoteConsole( args.Args() );
402 }
403
404 /*
405 ==================
406 idAsyncNetwork::Heartbeat_f
407 ==================
408 */
409 void idAsyncNetwork::Heartbeat_f( const idCmdArgs &args ) {
410         if ( !server.IsActive() ) {
411                 common->Printf( "server is not running\n" );
412                 return;
413         }
414         server.MasterHeartbeat( true );
415 }
416
417 /*
418 ==================
419 idAsyncNetwork::Kick_f
420 ==================
421 */
422 void idAsyncNetwork::Kick_f( const idCmdArgs &args ) {
423         idStr clientId;
424         int iclient;
425
426         if ( !server.IsActive() ) {
427                 common->Printf( "server is not running\n" );
428                 return;
429         }
430
431         clientId = args.Argv( 1 );
432         if ( !clientId.IsNumeric() ) {
433                 common->Printf( "usage: kick <client number>\n" );
434                 return;
435         }
436         iclient = atoi( clientId );
437         
438         if ( server.GetLocalClientNum() == iclient ) {
439                 common->Printf( "can't kick the host\n" );
440                 return;
441         }
442
443         server.DropClient( iclient, "#str_07134" );
444 }
445
446 /*
447 ==================
448 idAsyncNetwork::GetNETServers
449 ==================
450 */
451 void idAsyncNetwork::GetNETServers( ) {
452         client.GetNETServers();
453 }
454
455 /*
456 ==================
457 idAsyncNetwork::CheckNewVersion_f
458 ==================
459 */
460 void idAsyncNetwork::CheckNewVersion_f( const idCmdArgs &args ) {
461         client.SendVersionCheck(); 
462 }
463
464 /*
465 ==================
466 idAsyncNetwork::ExecuteSessionCommand
467 ==================
468 */
469 void idAsyncNetwork::ExecuteSessionCommand( const char *sessCmd ) {
470         if ( sessCmd[ 0 ] ) {
471                 if ( !idStr::Icmp( sessCmd, "game_startmenu" ) ) {
472                         session->SetGUI( game->StartMenu(), NULL );
473                 }
474         }
475 }
476
477 /*
478 =================
479 idAsyncNetwork::UpdateUI_f
480 =================
481 */
482 void idAsyncNetwork::UpdateUI_f( const idCmdArgs &args ) {
483         if ( args.Argc() != 2 ) {
484                 common->Warning( "idAsyncNetwork::UpdateUI_f: wrong arguments\n" );
485                 return;
486         }
487         if ( !server.IsActive() ) {
488                 common->Warning( "idAsyncNetwork::UpdateUI_f: server is not active\n" );
489                 return;
490         }
491         int clientNum = atoi( args.Args( 1 ) );
492         server.UpdateUI( clientNum );
493 }
494
495 /*
496 ===============
497 idAsyncNetwork::BuildInvalidKeyMsg
498 ===============
499 */
500 void idAsyncNetwork::BuildInvalidKeyMsg( idStr &msg, bool valid[ 2 ] ) {
501         if ( !valid[ 0 ] ) {
502                 msg += common->GetLanguageDict()->GetString( "#str_07194" );
503         }
504         if ( fileSystem->HasD3XP() && !valid[ 1 ] ) {
505                 if ( msg.Length() ) {
506                         msg += "\n";
507                 }
508                 msg += common->GetLanguageDict()->GetString( "#str_07195" );
509         }
510         msg += "\n";
511         msg += common->GetLanguageDict()->GetString( "#str_04304" );    
512 }
513