• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

polserver / polserver / 13163655424

05 Feb 2025 06:02PM UTC coverage: 58.476% (+0.4%) from 58.057%
13163655424

push

github

web-flow
Expansion helper classes for central handling of features (#756)

* use uoexpansion enums for A9, B9 flags
added TOL to uoexpansion
currently unsure where this will end

simplified account initialisation, added errorcheck

* Expansion classes for server and account
fixed flags

* use of AccountExpansion,ServerExpansion
extended tests
splitted unittest file
added missing test for Min/MaxAttackRange

* more micro performance for world saving
use all 4 cores in CI builds
fix compiler flag warning on windows

* save test for weight_multiplier

* enable gothic,rustictiles when using HSA expansion

* removed ancient unused files

* addressed comments
renamed ServerExpansion to ServerFeatures
added method for expansion name

* got rid of client UOExpansionFlag which is the same as the accout
expansion

* updated core-changes and docs

* updated generated ssopt

* test for tooltips
changed method names
removed/clarified comments

* npc hitchance was never saved
added missing npc save tests

* hitchance bug exists also for items...
added missing test for movemode

465 of 525 new or added lines in 30 files covered. (88.57%)

16 existing lines in 7 files now uncovered.

41768 of 71427 relevant lines covered (58.48%)

388521.09 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

47.35
/pol-core/pol/network/client.cpp
1
/** @file
2
 *
3
 * @par History
4
 * - 2005/01/24 Shinigami: added getspyonclient2 to support packet 0xd9 (Spy on Client 2)
5
 * - 2005/08/29 Shinigami: character.spyonclient2 renamed to character.clientinfo
6
 *                         getspyonclient2 renamed to getclientinfo
7
 * - 2007/07/09 Shinigami: added isUOKR [bool] - UO:KR client used?
8
 * - 2009/07/20 MuadDib:   Added statement to bypass cryptseed at login. Handled by changing default
9
 * client recv_state using ssopt flag.
10
 * - 2009/07/23 MuadDib:   updates for new Enum::Packet Out ID
11
 * - 2009/08/25 Shinigami: STLport-5.2.1 fix: init order changed of aosresist
12
 *                         STLport-5.2.1 fix: params in call of Log2()
13
 * - 2009/09/06 Turley:    Added u8 ClientType + FlagEnum
14
 *                         Removed is*
15
 * - 2010/01/22 Turley:    Speedhack Prevention System
16
 */
17

18

19
#include "client.h"
20

21
#include <errno.h>
22
#include <stdlib.h>
23
#include <time.h>
24

25
#include "../../bscript/berror.h"
26
#include "../../bscript/bstruct.h"
27
#include "../../bscript/impstr.h"
28
#include "../../clib/clib.h"
29
#include "../../clib/logfacility.h"
30
#include "../../clib/stlutil.h"
31
#include "../../clib/strutil.h"  //CNXBUG
32
#include "../../clib/wallclock.h"
33
#include "../accounts/account.h"
34
#include "../crypt/cryptbase.h"
35
#include "../crypt/cryptengine.h"
36
#include "../globals/network.h"
37
#include "../globals/settings.h"
38
#include "../globals/state.h"
39
#include "../globals/uvars.h"
40
#include "../mobile/charactr.h"
41
#include "../polsig.h"
42
#include "../realms/WorldChangeReasons.h"
43
#include "../ufunc.h"  // only in here temporarily, until logout-on-disconnect stuff is removed
44
#include "../uoclient.h"
45
#include "../uoscrobj.h"
46
#include "../uworld.h"
47
#include "cgdata.h"
48
#include "cliface.h"
49
#include "packethelper.h"
50
#include "packets.h"
51
#include "pktdef.h"
52
#include "pktin.h"
53
#include "xbuffer.h"
54

55

56
#define PRE_ENCRYPT
57

58
#ifndef PRE_ENCRYPT
59
#include "sockio.h"
60
#endif
61

62
#ifdef _MSC_VER
63
#pragma warning( \
64
    disable : 4351 )  // new behavior: elements of array '...' will be default initialized
65
#endif
66

67
namespace Pol
68
{
69
namespace Core
70
{
71
void cancel_trade( Mobile::Character* chr1 );
72
}
73
namespace Network
74
{
75
unsigned int Client::instance_counter_;
76

77
ThreadedClient::ThreadedClient( Crypt::TCryptInfo& encryption, Client& myClient,
4✔
78
                                sockaddr& client_addr,
79
                                std::vector<boost::asio::ip::network_v4>& allowed_proxies )
4✔
80
    : myClient( myClient ),
4✔
81
      thread_pid( static_cast<size_t>( -1 ) ),
4✔
82
      csocket( INVALID_SOCKET ),
4✔
83
      preDisconnect( false ),
4✔
84
      disconnect( false ),
4✔
85
      cryptengine( create_crypt_engine( encryption ) ),
4✔
86
      encrypt_server_stream( false ),
4✔
87
      allowed_proxies( allowed_proxies ),
4✔
88
      last_activity_at( 0 ),
4✔
89
      last_packet_at( 0 ),
4✔
90
      recv_state( RECV_STATE_CRYPTSEED_WAIT ),
4✔
91
      bufcheck1_AA( 0xAA ),
4✔
92
      buffer(),  // zero-initializes the buffer
10,244✔
93
      bufcheck2_55( 0x55 ),
4✔
94
      bytes_received( 0 ),
4✔
95
      message_length( 0 ),
4✔
96
      last_msgtype( 255 ),
4✔
97
      msgtype_filter( Core::networkManager.login_filter.get() ),
4✔
98
      checkpoint( -1 ),  // CNXBUG
4✔
99
      _fpLog_lock(),
4✔
100
      fpLog( "" ),
4✔
101
      disable_inactivity_timeout( false ),
4✔
102
      first_xmit_buffer( nullptr ),
4✔
103
      last_xmit_buffer( nullptr ),
4✔
104
      n_queued( 0 ),
4✔
105
      queued_bytes_counter( 0 )
8✔
106
{
107
  memset( &counters, 0, sizeof counters );
4✔
108
  memcpy( &ipaddr, &client_addr, sizeof ipaddr );
4✔
109
  memset( &ipaddr_proxy, 0, sizeof( ipaddr_proxy ) );
4✔
110

111
  if ( ipaddr.sa_family == AF_INET )
4✔
112
  {
113
    // accept proxy protocol only from allowed ips
114
    auto ipaddrv4 = reinterpret_cast<sockaddr_in*>( &ipaddr );
4✔
115
    auto my_address =
116
#ifdef _WIN32
117
        boost::asio::ip::address_v4( htonl( ipaddrv4->sin_addr.S_un.S_addr ) );
118
#else
119
        boost::asio::ip::address_v4( htonl( ipaddrv4->sin_addr.s_addr ) );
4✔
120
#endif
121
    auto my_network = boost::asio::ip::network_v4( my_address, 32 );
4✔
122
    for ( const auto& allowed_proxy : allowed_proxies )
4✔
123
    {
124
      if ( my_network == allowed_proxy || my_network.is_subnet_of( allowed_proxy ) )
×
125
      {
126
        recv_state = RECV_STATE_PROXYPROTOCOLHEADER_WAIT;
×
127
        break;
×
128
      }
129
    }
130
  }
131
};
4✔
132

133
Client::Client( ClientInterface& aInterface, Crypt::TCryptInfo& encryption, sockaddr& ipaddr,
4✔
134
                std::vector<boost::asio::ip::network_v4>& allowed_proxies )
4✔
135
    : ThreadedClient( encryption, *this, ipaddr, allowed_proxies ),
136
      acct( nullptr ),
4✔
137
      chr( nullptr ),
4✔
138
      Interface( aInterface ),
4✔
139
      ready( false ),
4✔
140
      listen_port( 0 ),
4✔
141
      aosresist( false ),
4✔
142
      pause_count( 0 ),
4✔
143
      gd( new ClientGameData ),
4✔
144
      instance_( ++instance_counter_ ),
4✔
145
      UOExpansionFlagClient( 0 ),
4✔
146
      ClientType( 0 ),
4✔
147
      next_movement( 0 ),
4✔
148
      movementsequence( 0 ),
4✔
149
      paused_( false )
8✔
150
{
151
  weakptr.set( this );  // store weakptr for usage in scripts (see EClientRefObjImp)
4✔
152

153
  // For bypassing cryptseed packet
154
  if ( Core::settingsManager.ssopt.use_edit_server )
4✔
155
  {
156
    recv_state = RECV_STATE_MSGTYPE_WAIT;
×
157
  }
158

159
  Interface.register_client( this );
4✔
160

161
  memset( &clientinfo_, 0, sizeof( clientinfo_ ) );
4✔
162
  memset( &versiondetail_, 0, sizeof( versiondetail_ ) );
4✔
163
}
4✔
164

165
Client::~Client()
8✔
166
{
167
  PreDelete();
4✔
168
  delete cryptengine;
4✔
169
}
8✔
170

171
void Client::init_crypto( void* nseed, int type )
2✔
172
{
173
  session()->cryptengine->Init( nseed, type );
2✔
174
}
2✔
175

176
void Client::unregister()
4✔
177
{
178
  auto findClient =
179
      std::find( Core::networkManager.clients.begin(), Core::networkManager.clients.end(), this );
4✔
180
  Core::networkManager.clients.erase( findClient );  // TODO: Make networkManager more OO
4✔
181
  Interface.deregister_client( this );
4✔
182
}
4✔
183

184
void Client::PreDelete()
4✔
185
{
186
  closeConnection();
4✔
187

188
  if ( chr != nullptr && chr->client == this )
4✔
189
  {
190
    if ( chr->logged_in() )
2✔
191
    {
192
      ClrCharacterWorldPosition( chr, Realms::WorldChangeReason::PlayerExit );
2✔
193
      send_remove_character_to_nearby( chr );
2✔
194
      chr->logged_in( false );
2✔
195

196
      chr->set_opponent( nullptr );
2✔
197
      chr->removal_cleanup();
2✔
198
      if ( chr->get_opponent() != nullptr )
2✔
199
      {
200
        chr->set_opponent( nullptr, true );
×
201
      }
202
    }
203
    else
204
    {
205
      ERROR_PRINTLN( "Uhh...  active character not logged in!??" );
×
206
    }
207
  }
208

209
  // detach the account and character from this client, if they
210
  // are still associated with it.
211

212
  acct = nullptr;
4✔
213

214
  if ( chr )
4✔
215
  {
216
    if ( chr->client == this )
2✔
217
      chr->client = nullptr;
2✔
218
    chr = nullptr;
2✔
219
  }
220

221
  // stop packet-logging
222
  stop_log();
4✔
223

224
  delete gd;
4✔
225
  gd = nullptr;
4✔
226

227
  while ( first_xmit_buffer != nullptr )
4✔
228
  {
229
    Core::XmitBuffer* xbuffer = first_xmit_buffer;
×
230
    first_xmit_buffer = first_xmit_buffer->next;
×
231
    free( xbuffer );
×
232
    --n_queued;
×
233
  }
234
  last_xmit_buffer = nullptr;
4✔
235

236
  // while (!movementqueue.empty())
237
  //  movementqueue.pop();
238
}
4✔
239

240
// ClientInfo - delivers a lot of usefull infomation about client PC
241
Bscript::BStruct* Client::getclientinfo() const
×
242
{
243
  using namespace Bscript;
244
  std::unique_ptr<BStruct> ret( new BStruct );
×
245

246
  ret->addMember( "unknown1", new BLong( clientinfo_.unknown1 ) );  // Unknown - allways 0x02
×
247
  ret->addMember( "instance", new BLong( clientinfo_.instance ) );  // Unique Instance ID of UO
×
248
  ret->addMember( "os_major", new BLong( clientinfo_.os_major ) );  // OS Major
×
249
  ret->addMember( "os_minor", new BLong( clientinfo_.os_minor ) );  // OS Minor
×
250
  ret->addMember( "os_revision", new BLong( clientinfo_.os_revision ) );  // OS Revision
×
251
  ret->addMember( "cpu_manufacturer",
×
252
                  new BLong( clientinfo_.cpu_manufacturer ) );          // CPU Manufacturer
×
253
  ret->addMember( "cpu_family", new BLong( clientinfo_.cpu_family ) );  // CPU Family
×
254
  ret->addMember( "cpu_model", new BLong( clientinfo_.cpu_model ) );    // CPU Model
×
255
  ret->addMember( "cpu_clockspeed",
×
256
                  new BLong( clientinfo_.cpu_clockspeed ) );                // CPU Clock Speed [Mhz]
×
257
  ret->addMember( "cpu_quantity", new BLong( clientinfo_.cpu_quantity ) );  // CPU Quantity
×
258
  ret->addMember( "memory", new BLong( clientinfo_.memory ) );              // Memory [MB]
×
259
  ret->addMember( "screen_width", new BLong( clientinfo_.screen_width ) );  // Screen Width
×
260
  ret->addMember( "screen_height", new BLong( clientinfo_.screen_height ) );  // Screen Height
×
261
  ret->addMember( "screen_depth", new BLong( clientinfo_.screen_depth ) );    // Screen Depth [Bit]
×
262
  ret->addMember( "directx_major", new BLong( clientinfo_.directx_major ) );  // DirectX Major
×
263
  ret->addMember( "directx_minor", new BLong( clientinfo_.directx_minor ) );  // DirectX Minor
×
264

265
  unsigned maxlen_vd =
×
266
      sizeof( clientinfo_.video_description ) / sizeof( clientinfo_.video_description[0] );
267
  std::string vd = Bscript::String::fromUTF16( &clientinfo_.video_description[0], maxlen_vd, true );
×
268
  ret->addMember( "video_description",
×
269
                  new Bscript::String( vd ) );  // Video Card Description
×
270

271

272
  ret->addMember( "video_vendor", new BLong( clientinfo_.video_vendor ) );  // Video Card Vendor ID
×
273
  ret->addMember( "video_device", new BLong( clientinfo_.video_device ) );  // Video Card Device ID
×
274
  ret->addMember( "video_memory",
×
275
                  new BLong( clientinfo_.video_memory ) );  // Video Card Memory [MB]
×
276
  ret->addMember( "distribution", new BLong( clientinfo_.distribution ) );        // Distribution
×
277
  ret->addMember( "clients_running", new BLong( clientinfo_.clients_running ) );  // Clients Running
×
278
  ret->addMember( "clients_installed",
×
279
                  new BLong( clientinfo_.clients_installed ) );  // Clients Installed
×
280
  ret->addMember( "partial_installed",
×
281
                  new BLong( clientinfo_.partial_installed ) );  // Partial Insstalled
×
282

283
  unsigned maxlen_lc = sizeof( clientinfo_.langcode ) / sizeof( clientinfo_.langcode[0] );
×
284
  std::string lc = Bscript::String::fromUTF16( &clientinfo_.langcode[0], maxlen_lc, true );
×
285
  ret->addMember( "langcode",
×
286
                  new Bscript::String( lc ) );  // Language Code
×
287

288
  std::unique_ptr<ObjArray> arr_u2( new ObjArray );
×
289
  for ( unsigned i = 0; i < sizeof( clientinfo_.unknown2 ); ++i )
×
290
    arr_u2->addElement( new BLong( clientinfo_.unknown2[i] ) );
×
291
  ret->addMember( "unknown2", arr_u2.release() );  // Unknown
×
292

293
  return ret.release();
×
294
}
×
295

296
void Client::itemizeclientversion( const std::string& ver, VersionDetailStruct& detail )
2✔
297
{
298
  try
299
  {
300
    size_t dot1 = ver.find_first_of( '.', 0 );
2✔
301
    size_t dot2 = ver.find_first_of( '.', dot1 + 1 );
2✔
302
    size_t dot3 = ver.find_first_of( '.', dot2 + 1 );
2✔
303
    if ( dot3 == std::string::npos )  // since 5.0.7 patch is digit
2✔
304
    {
305
      dot3 = dot2 + 1;
×
306
      while ( ( dot3 < ver.length() ) && ( isdigit( ver[dot3] ) ) )
×
307
      {
308
        dot3++;
×
309
      }
310
    }
311

312
    detail.major = atoi( ver.substr( 0, dot1 ).c_str() );
2✔
313
    detail.minor = atoi( ver.substr( dot1 + 1, dot2 - dot1 - 1 ).c_str() );
2✔
314
    detail.rev = atoi( ver.substr( dot2 + 1, dot3 - dot2 - 1 ).c_str() );
2✔
315
    detail.patch = 0;
2✔
316
    if ( dot3 < ver.length() )
2✔
317
    {
318
      if ( ( detail.major <= 5 ) && ( detail.minor <= 0 ) && ( detail.rev <= 6 ) )
2✔
319
      {
320
        if ( ver[dot3] != ' ' )
×
321
          detail.patch = ( ver[dot3] - 'a' ) + 1;  // char to int
×
322
      }
323
      else
324
        detail.patch = atoi( ver.substr( dot3 + 1, ver.length() - dot3 - 1 ).c_str() );
2✔
325
    }
326
  }
327
  catch ( ... )
×
328
  {
329
    detail.major = 0;
×
330
    detail.minor = 0;
×
331
    detail.rev = 0;
×
332
    detail.patch = 0;
×
333
    POLLOGLN( "Malformed clientversion string: {}", ver );
×
334
  }
×
335
}
2✔
336

337
bool Client::compareVersion( const std::string& ver )
×
338
{
339
  VersionDetailStruct ver2;
340
  itemizeclientversion( ver, ver2 );
×
341
  return Client::compareVersion( ver2 );
×
342
}
343

344
bool Client::compareVersion( const VersionDetailStruct& ver2 )
16✔
345
{
346
  VersionDetailStruct ver1 = getversiondetail();
16✔
347

348
  if ( ver1.major > ver2.major )
16✔
349
    return true;
×
350
  else if ( ver1.major < ver2.major )
16✔
351
    return false;
×
352
  else if ( ver1.minor > ver2.minor )
16✔
353
    return true;
×
354
  else if ( ver1.minor < ver2.minor )
16✔
355
    return false;
×
356
  else if ( ver1.rev > ver2.rev )
16✔
357
    return true;
×
358
  else if ( ver1.rev < ver2.rev )
16✔
359
    return false;
12✔
360
  else if ( ver1.patch > ver2.patch )
4✔
361
    return true;
4✔
362
  else if ( ver1.patch < ver2.patch )
×
363
    return false;
×
364
  else
365
    return true;
×
366
}
367

368
void Client::setClientType( ClientTypeFlag type )
4✔
369
{
370
  ClientType = 0x0;
4✔
371
  // with fall through !
372
  switch ( type )
4✔
373
  {
374
  case CLIENTTYPE_70331:
×
375
    ClientType |= CLIENTTYPE_70331;
×
376
  // fall through
377
  case CLIENTTYPE_70300:
×
378
    ClientType |= CLIENTTYPE_70300;
×
379
  // fall through
380
  case CLIENTTYPE_70130:
×
381
    ClientType |= CLIENTTYPE_70130;
×
382
  // fall through
383
  case CLIENTTYPE_7090:
4✔
384
    ClientType |= CLIENTTYPE_7090;
4✔
385
  // fall through
386
  case CLIENTTYPE_UOSA:
4✔
387
    ClientType |= CLIENTTYPE_UOSA;
4✔
388
  // fall through
389
  case CLIENTTYPE_7000:
4✔
390
    ClientType |= CLIENTTYPE_7000;
4✔
391
  // fall through
392
  case CLIENTTYPE_UOKR:
4✔
393
    ClientType |= CLIENTTYPE_UOKR;
4✔
394
  // fall through
395
  case CLIENTTYPE_60142:
4✔
396
    ClientType |= CLIENTTYPE_60142;
4✔
397
  // fall through
398
  case CLIENTTYPE_6017:
4✔
399
    ClientType |= CLIENTTYPE_6017;
4✔
400
  // fall through
401
  case CLIENTTYPE_5020:
4✔
402
    ClientType |= CLIENTTYPE_5020;
4✔
403
  // fall through
404
  case CLIENTTYPE_5000:
4✔
405
    ClientType |= CLIENTTYPE_5000;
4✔
406
  // fall through
407
  case CLIENTTYPE_4070:
4✔
408
    ClientType |= CLIENTTYPE_4070;
4✔
409
  // fall through
410
  case CLIENTTYPE_4000:
4✔
411
    ClientType |= CLIENTTYPE_4000;
4✔
412
  // fall through
413
  default:
4✔
414
    break;
4✔
415
  }
416
}
4✔
417

418
bool Client::IsUOKRClient()
×
419
{
420
  return ( ( ClientType & CLIENTTYPE_UOKR ) && ( !( ClientType & CLIENTTYPE_7000 ) ) );
×
421
}
422

423
std::string Client::status() const
×
424
{
425
  std::string st;
×
426
  if ( acct != nullptr )
×
427
    st += "AC:" + std::string( acct->name() ) + " ";
×
428
  if ( chr != nullptr )
×
429
    st += "CH:" + chr->name() + " ";
×
430
  if ( have_queued_data() )
×
431
    st += "TXBUF ";
×
432
  if ( disconnect )
×
433
    st += "DISC ";
×
434
  if ( paused_ )
×
435
    st += "PAUSE ";
×
436
  if ( ready )
×
437
    st += "RDY ";
×
438
  st += ipaddrAsString() + " ";
×
439
  st += "CHK: " + Clib::tostring( checkpoint ) + " ";
×
440
  st += "PID: " + Clib::tostring( thread_pid ) + " ";
×
441
  st += "LAST: " + Clib::hexint( last_msgtype );
×
442
  return st;
×
443
}
×
444

445
void ThreadedClient::queue_data( const void* data, unsigned short datalen )
×
446
{
447
  THREAD_CHECKPOINT( active_client, 300 );
×
448
  Core::XmitBuffer* xbuffer = (Core::XmitBuffer*)malloc( sizeof( Core::XmitBuffer ) - 1 + datalen );
×
449
  THREAD_CHECKPOINT( active_client, 301 );
×
450
  if ( xbuffer )
×
451
  {
452
    THREAD_CHECKPOINT( active_client, 302 );
×
453
    xbuffer->next = nullptr;
×
454
    xbuffer->nsent = 0;
×
455
    xbuffer->lenleft = datalen;
×
456
    memcpy( xbuffer->data, data, datalen );
×
457
    THREAD_CHECKPOINT( active_client, 303 );
×
458
    if ( first_xmit_buffer == nullptr || last_xmit_buffer == nullptr )
×
459
    {  // in this case, last_xmit_buffer is also nullptr, so can't set its ->next.
460
      THREAD_CHECKPOINT( active_client, 304 );
×
461
      first_xmit_buffer = xbuffer;
×
462
    }
463
    else
464
    {
465
      THREAD_CHECKPOINT( active_client, 305 );
×
466
      last_xmit_buffer->next = xbuffer;
×
467
    }
468
    THREAD_CHECKPOINT( active_client, 306 );
×
469
    last_xmit_buffer = xbuffer;
×
470
    ++n_queued;
×
471
    queued_bytes_counter += datalen;
×
472
  }
473
  else
474
  {
475
    THREAD_CHECKPOINT( active_client, 307 );
×
476
    POLLOGLN( "Client#{}: Unable to allocate {} bytes for queued data.  Disconnecting.",
×
477
              myClient.instance_, ( sizeof( Core::XmitBuffer ) - 1 + datalen ) );
×
478
    disconnect = true;
×
479
  }
480
  THREAD_CHECKPOINT( active_client, 309 );
×
481
}
×
482

483
void ThreadedClient::xmit( const void* data, unsigned short datalen )
18,906✔
484
{
485
  if ( csocket == INVALID_SOCKET )
18,906✔
486
    return;
×
487
  if ( encrypt_server_stream )
18,906✔
488
  {
489
    if ( cryptengine == nullptr )
18,902✔
490
      return;
×
491
    this->cryptengine->Encrypt( (void*)data, (void*)data, datalen );
18,902✔
492
  }
493
  THREAD_CHECKPOINT( active_client, 200 );
18,906✔
494
  if ( last_xmit_buffer )  // this client already backlogged, schedule for later
18,906✔
495
  {
496
    THREAD_CHECKPOINT( active_client, 201 );
×
497
    queue_data( data, datalen );
×
498
    THREAD_CHECKPOINT( active_client, 202 );
×
499
    return;
×
500
  }
501
  THREAD_CHECKPOINT( active_client, 203 );
18,906✔
502

503
  /* client not backlogged - try to send. */
504
  const unsigned char* cdata = (const unsigned char*)data;
18,906✔
505
  int nsent;
506

507
  if ( -1 == ( nsent = send( csocket, (const char*)cdata, datalen, 0 ) ) )
18,906✔
508
  {
509
    THREAD_CHECKPOINT( active_client, 204 );
×
510
    int sckerr = socket_errno;
×
511

512
    if ( sckerr == SOCKET_ERRNO( EWOULDBLOCK ) )
×
513
    {
514
      THREAD_CHECKPOINT( active_client, 205 );
×
515
      POLLOG_ERRORLN( "Client#{}: Switching to queued data mode (1, {} bytes)", myClient.instance_,
×
516
                      datalen );
517
      THREAD_CHECKPOINT( active_client, 206 );
×
518
      queue_data( data, datalen );
×
519
      THREAD_CHECKPOINT( active_client, 207 );
×
520
      return;
×
521
    }
522
    else
523
    {
524
      THREAD_CHECKPOINT( active_client, 208 );
×
525
      if ( !disconnect )
×
526
        POLLOG_ERRORLN( "Client#{}: Disconnecting client due to send() error (1): {}",
×
527
                        myClient.instance_, sckerr );
×
528
      disconnect = true;
×
529
      THREAD_CHECKPOINT( active_client, 209 );
×
530
      return;
×
531
    }
532
  }
533
  else  // no error
534
  {
535
    THREAD_CHECKPOINT( active_client, 210 );
18,906✔
536
    datalen -= static_cast<unsigned short>( nsent );
18,906✔
537
    counters.bytes_transmitted += nsent;
18,906✔
538
    Core::networkManager.polstats.bytes_sent += nsent;
18,906✔
539
    if ( datalen )  // anything left? if so, queue for later.
18,906✔
540
    {
541
      THREAD_CHECKPOINT( active_client, 211 );
×
542
      POLLOG_ERRORLN( "Client#{}: Switching to queued data mode (2)", myClient.instance_ );
×
543
      THREAD_CHECKPOINT( active_client, 212 );
×
544
      queue_data( cdata + nsent, datalen );
×
545
      THREAD_CHECKPOINT( active_client, 213 );
×
546
    }
547
  }
548
  THREAD_CHECKPOINT( active_client, 214 );
18,906✔
549
}
550

551
void ThreadedClient::send_queued_data()
×
552
{
553
  std::lock_guard<std::mutex> lock( _socketMutex );
×
554
  Core::XmitBuffer* xbuffer;
555
  // hand off data to the sockets layer until it won't take any more.
556
  // note if a buffer is sent in full, we try to send the next one, ad infinitum
557
  while ( nullptr != ( xbuffer = first_xmit_buffer ) )
×
558
  {
559
    int nsent;
560
    nsent = send( csocket, (char*)&xbuffer->data[xbuffer->nsent], xbuffer->lenleft, 0 );
×
561
    if ( nsent == -1 )
×
562
    {
563
#ifdef _WIN32
564
      int sckerr = WSAGetLastError();
565
#else
566
      int sckerr = errno;
×
567
#endif
568
      if ( sckerr == SOCKET_ERRNO( EWOULDBLOCK ) )
×
569
      {
570
        // do nothing.  it'll be re-queued later, when it won't block.
571
        return;
×
572
      }
573
      else
574
      {
575
        if ( !disconnect )
×
576
          POLLOGLN( "Client#{}: Disconnecting client due to send() error (2): {}",
×
577
                    myClient.instance_, sckerr );
×
578
        disconnect = true;
×
579
        return;
×
580
      }
581
    }
582
    else
583
    {
584
      xbuffer->nsent += static_cast<unsigned short>( nsent );
×
585
      xbuffer->lenleft -= static_cast<unsigned short>( nsent );
×
586
      counters.bytes_transmitted += nsent;
×
587
      Core::networkManager.polstats.bytes_sent += nsent;
×
588
      if ( xbuffer->lenleft == 0 )
×
589
      {
590
        first_xmit_buffer = first_xmit_buffer->next;
×
591
        if ( first_xmit_buffer == nullptr )
×
592
        {
593
          last_xmit_buffer = nullptr;
×
594
          POLLOGLN( "Client#{}: Leaving queued mode ({} bytes xmitted)", myClient.instance_,
×
595
                    queued_bytes_counter );
×
596
          queued_bytes_counter = 0;
×
597
        }
598
        free( xbuffer );
×
599
        --n_queued;
×
600
      }
601
    }
602
  }
603
}
×
604

605
// 33 01 "encrypted": 4F FA
606
static const unsigned char pause_pre_encrypted[2] = { 0x4F, 0xFA };
607
// 33 00 "encrypted": 4C D0
608
static const unsigned char restart_pre_encrypted[2] = { 0x4C, 0xD0 };
609

610
void Client::send_pause()
37✔
611
{
612
  if ( Core::networkManager.uoclient_protocol.EnableFlowControlPackets && !paused_ )
37✔
613
  {
614
#ifndef PRE_ENCRYPT
615
    PKTOUT_33 msg;
616
    msg.msgtype = PKTOUT_33_ID;
617
    msg.flow = MSGOPT_33_FLOW_PAUSE;
618
    transmit( this, &msg, sizeof msg );
619
#else
620
    xmit( pause_pre_encrypted, sizeof pause_pre_encrypted );
×
621
#endif
622
    paused_ = true;
×
623
  }
624
}
37✔
625

626
void Client::pause()
18,937✔
627
{
628
  if ( !pause_count )
18,937✔
629
  {
630
    send_pause();
37✔
631
    pause_count = 1;
37✔
632
  }
633
}
18,937✔
634

635
void Client::send_restart()
35✔
636
{
637
  if ( paused_ )
35✔
638
  {
639
#ifndef PRE_ENCRYPT
640
    PKTOUT_33 msg;
641
    msg.msgtype = PKTOUT_33_ID;
642
    msg.flow = MSGOPT_33_FLOW_RESTART;
643
    transmit( this, &msg, sizeof msg );
644
#else
645
    xmit( restart_pre_encrypted, sizeof restart_pre_encrypted );
×
646
#endif
647
    paused_ = false;
×
648
  }
649
}
35✔
650

651
void Client::restart()
35✔
652
{
653
  send_restart();
35✔
654
  pause_count = 0;
35✔
655
}
35✔
656

657
// Note: this doesnt test single packets it only summs the delay and tests
658
// here only the "start"-value is set the additional delay is set in PKT_02 handler
659
bool Client::SpeedHackPrevention( bool add )
×
660
{
661
  if ( ( !movementqueue.empty() ) && ( add ) )
×
662
  {
663
    if ( movementqueue.size() > 100 )
×
664
    {
665
      POLLOG_ERRORLN( "Client#{}: More then 100 Movepackets in queue.  Disconnecting.", instance_ );
×
666
      disconnect = true;
×
667
      return false;
×
668
    }
669
    PacketThrottler throttlestruct;
670
    memcpy( &throttlestruct.pktbuffer, &buffer, PKTIN_02_SIZE );
×
671
    movementqueue.push( throttlestruct );
×
672
    return false;
×
673
  }
674
  if ( chr != nullptr && chr->can_speedhack() )
×
675
    return true;
×
676
  if ( ( next_movement == 0 ) ||
×
677
       ( Clib::wallclock() > next_movement ) )  // never moved or in the past
×
678
  {
679
    next_movement = Clib::wallclock();
×
680
    return true;
×
681
  }
682
  // now we dont alter next_movement so we can sum the delay till diff is greater then error margin
683
  Clib::wallclock_diff_t diff = Clib::wallclock_diff_ms( Clib::wallclock(), next_movement );
×
684
  if ( diff > PKTIN_02_ASYNCHRONOUS )  // delay sum greater then our error margin?
×
685
  {
686
    if ( add )  // delay packet
×
687
    {
688
      if ( movementqueue.size() > 100 )
×
689
      {
690
        POLLOG_ERRORLN( "Client#{}: More then 100 Movepackets in queue.  Disconnecting.",
×
691
                        instance_ );
×
692
        disconnect = true;
×
693
        return false;
×
694
      }
695
      PacketThrottler throttlestruct;
696
      memcpy( &throttlestruct.pktbuffer, &buffer, sizeof( throttlestruct.pktbuffer ) );
×
697
      movementqueue.push( throttlestruct );
×
698
    }
699
    return false;
×
700
  }
701
  return true;
×
702
}
703

704
Bscript::BObjectImp* Client::make_ref()
11✔
705
{
706
  return new Module::EClientRefObjImp( weakptr );
11✔
707
}
708

709
weak_ptr<Client> Client::getWeakPtr() const
18,910✔
710
{
711
  return weakptr;
18,910✔
712
}
713
void Client::set_update_range_by_client( u8 range )
2✔
714
{
715
  // only allow a change if allowed and not modified by script
716
  if ( Core::settingsManager.ssopt.allow_visual_range_modification )
2✔
717
  {
718
    // limit range to defined min/max
719
    range = std::clamp( range, Core::settingsManager.ssopt.min_visual_range,
×
720
                        Core::settingsManager.ssopt.max_visual_range );
721
    if ( !gd->script_defined_update_range )
×
722
      set_update_range( range );
×
723
    gd->original_client_update_range = range;
×
724
  }
725
  else
726
  {
727
    PktHelper::PacketOut<Network::PktOut_C8> outMsg;
2✔
728
    outMsg->Write<u8>( update_range() );
2✔
729
    outMsg.Send( this );
2✔
730
    gd->original_client_update_range = Core::settingsManager.ssopt.default_visual_range;
2✔
731
  }
2✔
732
}
2✔
733

734
void Client::set_update_range_by_script( u8 range )
2✔
735
{
736
  if ( range == 0 )
2✔
737
  {
738
    range = gd->original_client_update_range ? gd->original_client_update_range
1✔
739
                                             : Core::settingsManager.ssopt.default_visual_range;
740
    gd->script_defined_update_range = false;
1✔
741
  }
742
  else
743
  {
744
    gd->script_defined_update_range = true;
1✔
745
  }
746
  // no limit check here, script is allowed to use different values
747
  set_update_range( range );
2✔
748
}
2✔
749

750
void Client::set_update_range( u8 range )
2✔
751
{
752
  auto old_range = update_range();
2✔
753
  // delete/send all objects, but not if its the first pkt
754
  if ( old_range != range && gd->original_client_update_range != 0 )
2✔
755
    chr->update_objects_on_range_change( range );
2✔
756

757
  gd->update_range = range;
2✔
758

759
  PktHelper::PacketOut<PktOut_C8> outMsg;
2✔
760
  outMsg->Write<u8>( range );
2✔
761
  outMsg.Send( this );
2✔
762

763
  if ( old_range != range )
2✔
764
  {
765
    // update global updaterange (maximum multi radius/client view range)
766
    Core::gamestate.update_range_from_client( range );
2✔
767
  }
768
}
2✔
769

770
u8 Client::update_range() const
55,622✔
771
{
772
  return gd->update_range;
55,622✔
773
}
774

775
bool Client::acctSupports( Plib::ExpansionVersion v ) const
6,686✔
776
{
777
  return acct->expansion().hasExpansion( v );
6,686✔
778
}
779

UNCOV
780
size_t Client::estimatedSize() const
×
781
{
782
  Clib::SpinLockGuard guard( _fpLog_lock );
×
783
  size_t size = ThreadedClient::estimatedSize();
×
784
  size += sizeof( void* ) * 3                              /*acct, chr, Interface*/
×
785
          + sizeof( bool )                                 /* ready */
786
          + sizeof( unsigned short )                       /* listenpoint */
787
          + sizeof( bool )                                 /* aosresist */
788
          + sizeof( std::atomic<int> )                     /* pause_count */
789
          + sizeof( void* )                                /* gd */
790
          + sizeof( unsigned int )                         /* instance_ */
791
          + sizeof( u32 )                                  /* UOExpansionFlagClient */
792
          + sizeof( u16 )                                  /* ClientType */
793
          + sizeof( Clib::wallclock_t )                    /* next_movement */
794
          + sizeof( u8 )                                   /* movementsequence */
795
          + version_.capacity() + sizeof( Core::PKTIN_D9 ) /* clientinfo_*/
×
796
          + sizeof( bool )                                 /* paused_ */
797
          + sizeof( VersionDetailStruct )                  /* versiondetail_ */
798
          + sizeof( weak_ptr_owner<Client> )               /*weakptr*/
×
799
      ;
800
  size += Clib::memsize( movementqueue );
×
801
  if ( gd != nullptr )
×
802
    size += gd->estimatedSize();
×
803
  return size;
×
804
}
×
805

806
// Threaded client stuff
807

808
size_t ThreadedClient::estimatedSize() const
×
809
{
810
  size_t size = sizeof( ThreadedClient ) + Clib::memsize( allowed_proxies ) + fpLog.capacity();
×
811
  Core::XmitBuffer* buffer_size = first_xmit_buffer;
×
812
  while ( buffer_size != nullptr )
×
813
  {
814
    size += sizeof( buffer_size ) + buffer_size->lenleft;
×
815
    buffer_size = buffer_size->next;
×
816
  }
817
  return size;
×
818
}
819
void ThreadedClient::closeConnection()
8✔
820
{
821
  std::lock_guard<std::mutex> lock( _socketMutex );
8✔
822
  if ( csocket != INVALID_SOCKET )
8✔
823
  {
824
#ifdef _WIN32
825
    shutdown( csocket, 2 );  // 2 is both sides, defined in winsock2.h ...
826
    closesocket( csocket );
827
#else
828
    shutdown( csocket, SHUT_RDWR );
4✔
829
    close( csocket );
4✔
830
#endif
831
  }
832
  csocket = INVALID_SOCKET;
8✔
833
}
8✔
834

835

836
}  // namespace Network
837
}  // namespace Pol
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc