uox3/source/network.cpp
Xoduz a48d9ee299 UOX3 0.99.6-RC6
Removed arbitrary and hidden restrictions on refreshing list of online players shown with 'wholist command
Fixed an issue where player ghosts that get teleported would not see themselves get updated to new location
Fixed a server crash from clients disconnecting while running server in debug mode
Improved how container updates are sent to players; now only sends to clients that have actually opened the container, and who are still within range
Added more information about UO data files being loaded during server startup
Enhanced the GM 'add menu to allow clicking directly on images of items/npcs to add them, and adjusted size of menus to accommodate this
Fixed an issue with fleeing NPCs that could cause them to attempt to flee across time and space, regardless of distance to target/attacker in combat
Added NPC AI that runs away from players if they get too close (even outside combat). Applied to hind, rabbits, squirrels, ferrets and various birds by default:
	AI_ANIMAL_SCARED (aitype 12)
Fixed issues with NPCs fleeing forever, or getting stuck in flee/don't flee loop, by introducing max fleeing distance (50 tiles) and cooldown (30 sec) on fleeing
Updated onNameRequest JS Event to include third parameter with origin of name request, so script responses can be tailored appropriately:
	onNameRequest( myObj, requestedBy, requestSource )
		Potential values for requestSource:
			0 - Speech/System Messages
			1 - Guild Menus
			2 - Stat Window (self)
			3 - Stat Window (other)
			4 - Tooltip
			5 - Paperdoll Journal
			6 - Paperdoll
			7 - Single Click / All-Names
			8 - System
			9 - Secure Trade Window
Added Adaptive Performance System (APS) that dynamically adjust how often NPC AI/movement is checked based on overall shard performance. If performance drops below defined threshold, UOX3 gradually slows down checks for NPC AI/movement to prioritize player movement/speech/command responsiveness. If performance climbs back up above threshold, slowdowns are gradually removed. The following UOX.INI options have been added under [system] category to support this system:
	APSPERFTHRESHOLD=50 	// Performance threshold (measured in simulation cycles/sec) below which the APS kicks in
	APSINTERVAL=100 		// How often (in milliseconds) the APS checks performance and makes adjustments (if needed) to balance out shard performance
	APSDELAYSTEP=50 		// How much the delay timer is modified by (in milliseconds) each time APS makes adjustments
	APSDELAYMAXCAP=2000 	// Max amount of of delay APS can introduce for NPC AI/movement handling when attempting to restore shard performance
Added new JS Methods for Region objects:
	.GetOrePrefs( oreType )	// Get ore preference data for ore type found in town region. Returned as an array containing the following data:
		orePrefData -> [
			oreName,	// name of ore
			color,		// color of ore
			minskill,	// min skill to mine ore
			ingotName,	// name of ingot created from ore
			makemenu,	// makemenu entry for crafting something from ingot
			oreChance,	// default global chance of finding this ore type
			scriptID 	// script attached to mined ore
		],
		orePrefChance 	// Chance of finding this ore type in given town region
	.GetOreChance() 	// Get base chance of finding any ore in town region
Moved Mining skill plus gravedigging feature from hard code to scripts (js/skill/mining.js) to make it easier to maintain and/or customize, and removed hard coded variants
Gravedigging now relies on the ore resource system behind the scenes to restrict how often graves in a given area can be dug up
Fixed some incorrect references to .worldNumber Character property in misc scripts (should be .worldnumber)
Made some "hard-scripted" system messages in misc scripts use dictionary system instead
Added spawn region for banker NPCs in Serpent's Hold (dfndata/spawn/felucca/spawn_town_serpents_hold.dfn, dfndata/spawn/trammel/spawn_town_serpents_hold.dfn)
Fixed region definition of Ocllo to actually cover the entire town (dfndata/regions/regions.dfn)
Added numerous additional locations accessible with 'goplace # command, for key locations in Ilshenar, Malas, Tokuno Islands and Ter Mur (dfndata/location/location.dfn)
Revised travel-menu portion of GM menu (shortcut: 'travel) to include more travel options based on new locations (dfndata/items/travelmenu.dfn and travelmenu.bulk.dfn)
Fixed an issue where NPCs could attempt to attack targets in other worlds/instances
Added new JS Event that triggers for characters who are about to deal damage in combat. Complimentary to onDamage, which triggers for chars receiving damage:
	onDamageDeal( dmgDealer, dmgReceiver, damageValue, damageType )
Added new JS Event that can trigger in global script upon creation of new player chars. Note that this will trigger in place of onCreateDFN event for player characters:
	onCreatePlayer( pChar )
Added new JS Event that triggers for characters selecting a target with a spell. Complimentary to onSpellTarget, which triggers for targets selected with a spell:
	onSpellTargetSelect( caster, target, spellNum )
Updated onCombatStart and onCombatEnd JS Events to also trigger for the other party in combat
Updated onPickup JS Event to include a third parameter - the potential container item was picked up from. Event now also triggers event in scripts attached to said containers:
	onPickup( iPickedUp, pChar, iCont )
Added tracking of total playtime per individual character, and per account across all characters, and exposed these properties to JS engine:
	.totalPlayTime		// Account property, total playtime across all chars
	.playTime 			// Character property, total playtime on given character only
Added new player-accessible command ('playtime) to spit out the playtime of current character/account as a whole (js/commands/playtime.js)
Implemented first version of Young Player System:
	Replaced the UNUSED9 account flag with YOUNG, to be used by Young/New Player System
	Added new JS Account property that gets/sets whether player account is considered Young:
		.isYoung
	Added new Char timers:
		TIMER_YOUNGHEAL		// Restricts how often Young players are healed by NPC healers
		TIMER_YOUNGMESSAGE	// Restricts how often Young players are warned about dangerous looking monsters in overworld
	Updated GM commands 'get and 'set to get/set .isYoung property of player's account (js/commands/targeting/get.js and set.js)
	Added new UOX.INI setting to enable/disable Young Player System (enabled by default):
		YOUNGPLAYERSYSTEM=1
	If Young Player System is enabled, all newly created player accounts are automatically marked with Young flag
	Added new script to handle various restrictions and functions related to Young characters (js/player/young_player.js):
		Young characters will have [Young] displayed over their head
		Young characters will have their Young status checked and verified on every login + every stat/skill gain, to revoke the Young status if any of the following is true:
			Account has a total playtime of more than 40 hours
			Any character has more than 350 total (base) skill points
			Any character has more than 70 skill points in a single skill
			Any character has more than 150 total stat points
			Any character has more than 80 stat points in a single stat
		Young characters can renounce their Young status manually by saying the words "I renounce my young player status"
		Young characters get two additional items upon creation:
			a new player ticket (can be combined with any other players new player ticket for both players to get a reward)
			a sextant (shows Young players directions to nearest moongate/bank regardless of where they are, as long as outdoors)
		Young characters cannot target other player characters with hostile spells or skills
		Young characters cannot be the target of other player characters' hostile spells or skills
		Young characters can only cast beneficial spells upon themselves or other Young characters
		Young characters can only be the target of beneficial spells from other Young players
		Young players (and their pets/followers) cannot attack or harm any other players (or their pets/followers), nor themselves be attacked or harmed by any other players (or their pets/followers), whether from combat, spells, skills or items
		Young players don't lose any of their items on death, and are teleported to the nearest healer upon death with all their items intact
		Young players receive a warning upon entering dungeons that monsters there will be hostile
	Updated global script (js/server/global.js) to:
		Check/Verify Young status of players upon login
		Attach young player script on login/creation for players on Young accounts
		Give specific items to Young players upon creation
	Added new script (js/item/corpse.js) that is assigned to all freshly created corpses. This script handles restrictions related to Young players interactions with corpses, and the interactions of other players with corpses of Young players
	Updated JS Method .Carve() to return true/false depending on whether carving a corpse was successful
	Updated Healer AI in code to heal nearby injured Young players
	Updated Evil AI and Evil Caster AI in code to avoid selecting Young players as targets in combat outside of dungeons
	Added a new section to UOX.INI - [young player starting locations] - that can be used to define starting locations for Young players. If only one such location is provided, all players start there. Otherwise, it matches the same starting location setup as the regular one - with one entry per town in Britannia.
	Restricted transferring and/or friending of pets (code) and hirelings (hirelings.js) between Young and non-Young players
	Restricted Young players from recalling or gating to Felucca facet
	Young players can instantly logout from anywhere, at any time
2023-07-01 20:02:40 +08:00

1610 lines
50 KiB
C++
Raw Blame History

#include "uox3.h"
#include "network.h"
#include "movement.h"
#include "wholist.h"
#include "skills.h"
#include "cMagic.h"
#include "CJSMapping.h"
#include "cScript.h"
#include "cEffects.h"
#include "cThreadQueue.h"
#include "CPacketSend.h"
#include "scriptc.h"
#include "ssection.h"
#include "cServerDefinitions.h"
#include "Dictionary.h"
#include "mapstuff.h"
#include <cctype>
#include "ObjectFactory.h"
#include "StringUtility.hpp"
#include "osunique.hpp"
#if PLATFORM != WINDOWS
#include <sys/ioctl.h>
#endif
#include <iomanip>
#include <memory>
using namespace std::string_literals;
//o------------------------------------------------------------------------------------------------o
//| bytebuffer exceptions
//o------------------------------------------------------------------------------------------------o
//o------------------------------------------------------------------------------------------------o
ByteBufferBounds_st::ByteBufferBounds_st( int offset, int amount, int size ): std::out_of_range( "" ), offset( offset ), amount( amount ), buffersize( size )
{
_msg = "Offset : "s + std::to_string( offset ) + " Amount: "s + std::to_string( amount ) + " exceeds buffer size of: "s + std::to_string( size );
}
//=========================================================
auto ByteBufferBounds_st::what() const noexcept -> const char*
{
return _msg.c_str();
}
//o------------------------------------------------------------------------------------------------o
//| ByteBuffer_t
//o------------------------------------------------------------------------------------------------o
//| strutil functions
//o------------------------------------------------------------------------------------------------o
//o------------------------------------------------------------------------------------------------o
//| Function - ByteBuffer_t::ntos
//o------------------------------------------------------------------------------------------------o
//| Purpose - Convert a bool to a string
//| the true_value/false_value are returned based on the bool
//o------------------------------------------------------------------------------------------------o
auto ByteBuffer_t::ntos( bool value, const std::string &true_value, const std::string &false_value ) -> std::string
{
return ( value ? true_value : false_value );
}
//o------------------------------------------------------------------------------------------------o
//| Function - ByteBuffer_t::DumpByteBuffer()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Formatted dump of a byte buffer
//o------------------------------------------------------------------------------------------------o
auto ByteBuffer_t::DumpByteBuffer( std::ostream &output, const std::uint8_t *buffer, std::size_t length, radix_t radix, std::size_t entries_line ) -> void
{
// number of characters for entry
auto entry_size = 3; // decimal and octal
switch( static_cast<int>( radix ))
{
case static_cast<int>( radix_t::hex ):
entry_size = 2;
break;
case static_cast<int>( radix_t::bin ):
entry_size = 8;
break;
default:
break;
}
auto num_rows = ( length / entries_line ) + ((( length % entries_line ) == 0 ) ? 0 : 1 );
// what is the largest number for the address ?
auto max_address_chars = static_cast<int>(( ntos( num_rows * entries_line )).size() );
// first write out the header
output << std::setw( max_address_chars + 2 ) << "" << std::setw( 1 );
for( std::size_t i=0; i < entries_line; ++i )
{
output << ntos( i, radix_t::dec, false, entry_size, ' ' ) << " ";
}
output << "\n";
// now we write out the values for each line
std::string text( entries_line, ' ' );
for( std::size_t i = 0; i < length; ++i )
{
auto row = i / entries_line;
if((( i % static_cast<size_t>( entries_line ) == 0 ) && ( i >= static_cast<size_t>( entries_line ))) || ( i == 0 ))
{
// This is a new line!
output << ntos( row * entries_line, radix_t::dec, false, max_address_chars, ' ' ) << ": ";
text = std::string( entries_line, ' ' );
}
output << ntos( buffer[i], radix, false, entry_size ) << " ";
// If it is an alpha, we want to write it
if( std::isalpha( static_cast<int>( buffer[i] )) != 0 )
{
// we want to write this to the string
text[( i % entries_line )] = buffer[i];
}
else
{
text[( i % entries_line )] = '.';
}
if( i % entries_line == entries_line - 1 )
{
output << " " << text << "\n";
}
}
// what if we had a partial last line, we need to figure that out
auto last_line_entry = length%entries_line;
if( last_line_entry != 0 )
{
// we need to put the number of leading spaces
output << std::setw( static_cast<int>(( entries_line - last_line_entry ) *
( entry_size + 1 ))) << "" << std::setw( 1 ) << " " << text << "\n";
}
}
//o------------------------------------------------------------------------------------------------o
//| end strutil functions
//o------------------------------------------------------------------------------------------------o
//==================================================================================================
auto ByteBuffer_t::Exceeds( int offset, int bytelength ) const -> bool
{
auto index = offset + bytelength;
return static_cast<int>( _bytedata.size() ) < index;
}
//==================================================================================================
auto ByteBuffer_t::Exceeds( int offset, int bytelength, bool expand ) -> bool
{
auto rValue = Exceeds(offset, bytelength );
if( expand && rValue )
{
this->size( offset + bytelength );
rValue = false;
}
return rValue;
}
//==================================================================================================
ByteBuffer_t::ByteBuffer_t( int size, int reserve ):_index( 0 )
{
if(( reserve > 0 ) && ( size <= 0 ))
{
_bytedata.reserve( reserve );
}
if( size > 0 )
{
_bytedata.resize( size );
}
}
//==================================================================================================
auto ByteBuffer_t::size() const -> size_t
{
return _bytedata.size();
}
//==================================================================================================
auto ByteBuffer_t::size( int value, std::uint8_t fill ) -> void
{
_bytedata.resize( value, fill );
}
//==================================================================================================
auto ByteBuffer_t::index() const -> int
{
return _index;
}
//==================================================================================================
auto ByteBuffer_t::index( int value ) -> void
{
_index = value;
}
//==================================================================================================
auto ByteBuffer_t::raw() const -> const std::uint8_t*
{
return _bytedata.data();
}
//==================================================================================================
auto ByteBuffer_t::raw() -> std::uint8_t*
{
return _bytedata.data();
}
//==================================================================================================
auto ByteBuffer_t::operator[]( int index ) const -> const std::uint8_t &
{
return _bytedata[index];
}
//==================================================================================================
auto ByteBuffer_t::operator[]( int index ) -> std::uint8_t &
{
return _bytedata[index];
}
//==================================================================================================
auto ByteBuffer_t::Fill( std::uint8_t value, int offset, int length ) -> void
{
if( offset < 0 )
{
offset = _index;
}
if( length < 0 )
{
length = static_cast<int>( _bytedata.size() ) - offset;
}
if( length > 0 )
{
std::fill( _bytedata.begin() + offset, _bytedata.begin() + offset + length, value );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - ByteBuffer_t::log()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Formatted dump of a byte buffer
//o------------------------------------------------------------------------------------------------o
auto ByteBuffer_t::LogByteBuffer( std::ostream &output, radix_t radix, int entries_line ) const -> void
{
DumpByteBuffer( output, _bytedata.data(), _bytedata.size(), radix, entries_line );
}
CNetworkStuff *Network = nullptr;
#define _DEBUG_PACKET 0
fd_set conn;
fd_set all;
fd_set errsock;
void KillTrades( CChar *i );
void DoorMacro( CSocket *s );
void SysBroadcast( const std::string& txt );
//o------------------------------------------------------------------------------------------------o
//| Function - CNetworkStuff::ClearBuffers()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Sends ALL buffered data
//o------------------------------------------------------------------------------------------------o
void CNetworkStuff::ClearBuffers( void )
{
std::for_each( connClients.begin(), connClients.end(), []( CSocket *sock )
{
sock->FlushBuffer();
});
std::for_each( loggedInClients.begin(), loggedInClients.end(), []( CSocket *sock )
{
sock->FlushBuffer();
});
}
//o------------------------------------------------------------------------------------------------o
//| Function - CNetworkStuff::SetLastOn()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Sets the laston character member value to the current date/time
//o------------------------------------------------------------------------------------------------o
void CNetworkStuff::SetLastOn( CSocket *s )
{
assert( s != nullptr );
time_t ltime;
time( &ltime );
char buffer[100];
char *t = mctime( buffer, 100, &ltime );
// some ctime()s like to stick \r\n on the end, we don't want that
size_t mLen = strlen( t );
for( size_t end = mLen; end-- > 0 && isspace( t[end] ) && end < mLen; )
{
t[end] = '\0';
}
if( s->CurrcharObj() != nullptr )
{
s->CurrcharObj()->SetLastOn( t );
s->CurrcharObj()->SetLastOnSecs( static_cast<UI32>( ltime ));
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CNetworkStuff::Disconnect()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Forces player to disconnect/instalog
//o------------------------------------------------------------------------------------------------o
void CNetworkStuff::Disconnect( UOXSOCKET s )
{
SetLastOn( connClients[s] );
CChar *currChar = connClients[s]->CurrcharObj();
Console << "Client " << static_cast<UI32>( s ) << " disconnected. [Total:" << static_cast<SI32>( cwmWorldState->GetPlayersOnline() - 1 ) << "]" << myendl;
if( connClients[s]->AcctNo() != AB_INVALID_ID )
{
CAccountBlock_st &actbAccount = connClients[s]->GetAccount();
if( actbAccount.wAccountIndex != AB_INVALID_ID )
{
actbAccount.wFlags.set( AB_FLAGS_ONLINE, false );
}
}
//Instalog
if( currChar != nullptr )
{
if( !currChar->IsFree() && connClients[s] != nullptr )
{
// October 6, 2002 - Support for the onLogout event.
bool returnState = false;
std::vector<UI16> scriptTriggers = currChar->GetScriptTriggers();
for( auto i : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( i );
if( toExecute != nullptr )
{
if( toExecute->OnLogout( connClients[s], currChar ))
{
returnState = true;
}
}
}
if( !returnState )
{
// No individual scripts handling OnLogout were found - let's check global script!
cScript *toExecute = JSMapping->GetScript( static_cast<UI16>( 0 ));
if( toExecute != nullptr )
{
toExecute->OnLogout( connClients[s], currChar);
}
}
LogOut( connClients[s] );
}
}
try
{
connClients[s]->AcctNo( AB_INVALID_ID );
connClients[s]->IdleTimeout( -1 );
connClients[s]->FlushBuffer();
connClients[s]->ClearTimers();
connClients[s]->CloseSocket();
delete connClients[s];
connClients.erase( connClients.begin() + s );
}
catch( ... )
{
Console << "| CATCH: Invalid connClients[] encountered. Ignoring." << myendl;
}
cwmWorldState->DecPlayersOnline();
WhoList->FlagUpdate();
OffList->FlagUpdate();
}
//o------------------------------------------------------------------------------------------------o
//| Function - CNetworkStuff::LogOut()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Process client logout, and process instalog settings
//|
//| Changes - Date Unknown - Added multi checked to instalog processing
//o------------------------------------------------------------------------------------------------o
void CNetworkStuff::LogOut( CSocket *s )
{
// This would probably be a good place to do the disconnect
CChar *p = s->CurrcharObj();
bool valid = false;
SI16 x = p->GetX(), y = p->GetY();
UI08 world = p->WorldNumber();
UI08 instanceId = p->GetInstanceId();
KillTrades( p );
if( p->GetCommandLevel() >= CL_CNS || p->GetAccount().wAccountIndex == 0
|| ( cwmWorldState->ServerData()->YoungPlayerSystem() && p->GetAccount().wFlags.test( AB_FLAGS_YOUNG ) && cwmWorldState->ServerData()->ExpansionCoreShardEra() >= ER_UOR ))
{
valid = true;
}
else
{
auto logoutLocs = cwmWorldState->logoutLocs;
for( size_t a = 0; a < logoutLocs.size(); ++a )
{
if( logoutLocs[a].x1 <= x && logoutLocs[a].y1 <= y && logoutLocs[a].x2 >= x && logoutLocs[a].y2 >= y && logoutLocs[a].worldNum == world && logoutLocs[a].instanceId == instanceId )
{
valid = true;
break;
}
}
}
if( !valid )
{
CMultiObj* multi = nullptr;
if( !ValidateObject( p->GetMultiObj() ))
{
multi = FindMulti( p );
}
else
{
multi = static_cast<CMultiObj *>( p->GetMultiObj() );
}
if( ValidateObject( multi ))
{
// Check house script for onMultiLogout handling. If it returns true, instant logout is allowed
std::vector<UI16> scriptTriggers = multi->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript* toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
if( toExecute->OnMultiLogout( multi, p ) == 1 )
{
valid = true;
break;
}
}
}
}
}
//If player is holding an item on the cursor, and it was picked up off the ground, drop it back to the ground
if( s->GetCursorItem() )
{
if( s->PickupSpot() == PL_GROUND )
{
CItem *i = s->GetCursorItem();
i->SetCont( nullptr );
i->SetLocation( p->GetX(), p->GetY(), p->GetZ() );
}
}
// Iterate through list of containers opened by player
auto contsOpened = s->GetContsOpenedList();
for( const auto &iCont : contsOpened->collection() )
{
if( ValidateObject( iCont ))
{
// Remove player from container's own list of players who have previously opened it
iCont->GetContOpenedByList()->Remove( s );
}
}
s->GetContsOpenedList()->Clear(); // Then clear the socket's list of opened containers
CAccountBlock_st& actbAccount = s->GetAccount();
if( valid && !p->IsCriminal() && !p->HasStolen() && !p->IsAtWar() && !p->IsAggressor( false ))
{
if( actbAccount.wAccountIndex != AB_INVALID_ID )
{
actbAccount.dwInGame = INVALIDSERIAL;
}
p->SetTimer( tPC_LOGOUT, 0 );
s->ClearTimers();
}
else
{
if( actbAccount.wAccountIndex != AB_INVALID_ID )
{
actbAccount.dwInGame = p->GetSerial();
}
p->SetTimer( tPC_LOGOUT, BuildTimeValue( cwmWorldState->ServerData()->SystemTimer( tSERVER_LOGINTIMEOUT )));
}
s->LoginComplete( false );
actbAccount.wFlags.set( AB_FLAGS_ONLINE, false );
p->SetSocket( nullptr );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CNetworkStuff::sockInit()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Initializes and binds sockets during startup
//o------------------------------------------------------------------------------------------------o
void CNetworkStuff::sockInit( void )
{
SI32 bcode;
cwmWorldState->SetKeepRun( true );
cwmWorldState->SetError( false );
#if PLATFORM != WINDOWS
SI32 on = 1;
#endif
a_socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
if( a_socket < 0 )
{
Console.Error( " Unable to create socket");
#if PLATFORM == WINDOWS
Console.Error( oldstrutil::format( " Code %i", WSAGetLastError() ));
#else
Console << myendl;
#endif
cwmWorldState->SetKeepRun( false );
cwmWorldState->SetError( true );
Shutdown( FATAL_UOX3_ALLOC_NETWORK );
return;
}
#if PLATFORM != WINDOWS
[[maybe_unused]] int result = setsockopt( a_socket, SOL_SOCKET, SO_REUSEADDR, &on, sizeof( on ));
#endif
UI32 len_connection_addr = sizeof( struct sockaddr_in );
sockaddr_in connection;
memset(( char * )&connection, 0, len_connection_addr );
connection.sin_family = AF_INET;
connection.sin_addr.s_addr = htonl( INADDR_ANY );
connection.sin_port = htons( cwmWorldState->ServerData()->ServerPort() );
bcode = bind( a_socket, ( struct sockaddr * )&connection, len_connection_addr );
if( bcode < 0 )
{
Console.Error( oldstrutil::format( " Unable to bind socket 1 - Error code: %i", bcode ));
cwmWorldState->SetKeepRun( false );
cwmWorldState->SetError( true );
Shutdown( FATAL_UOX3_ALLOC_NETWORK );
return;
}
unsigned long mode = 1;
// set the socket to nonblocking
ioctlsocket( a_socket, FIONBIO, &mode );
listen( a_socket, 42 );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CNetworkStuff::SockClose()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Closes all sockets for shutdown
//o------------------------------------------------------------------------------------------------o
void CNetworkStuff::SockClose( void )
{
closesocket( a_socket );
std::for_each( connClients.begin(), connClients.end(), []( CSocket *sock )
{
sock->CloseSocket();
});
}
#if PLATFORM != WINDOWS
#ifdef FD_SETSIZE
#if FD_SETSIZE < 256
#undef FD_SETSIZE
#define FD_SETSIZE 256
#endif
#else
#define FD_SETSIZE 256
#endif
#endif
//o------------------------------------------------------------------------------------------------o
//| Function - CNetworkStuff::CheckConn()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Checks for connection requests
//o------------------------------------------------------------------------------------------------o
void CNetworkStuff::CheckConn( void )
{
FD_ZERO( &conn );
FD_SET( a_socket, &conn );
SI32 nfds = a_socket + 1;
SI32 s = select( nfds, &conn, nullptr, nullptr, &cwmWorldState->uoxTimeout );
if( s > 0 )
{
SI32 len = sizeof( struct sockaddr_in );
SI32 newClient;
#if PLATFORM == WINDOWS
newClient = accept( a_socket, ( struct sockaddr * )&client_addr, &len );
#else
newClient = accept( a_socket, ( struct sockaddr * )&client_addr, ( socklen_t * )&len );
if( newClient >= FD_SETSIZE )
{
Console.Error( "accept() returning unselectable fd!" );
closesocket( static_cast<UOXSOCKET>( newClient ));
return;
}
#endif
CSocket *toMake = new CSocket( newClient );
// set the ip address of the client;
toMake->ipaddress = Ip4Addr_st( client_addr.sin_addr.s_addr );
if( newClient < 0 )
{
#if PLATFORM == WINDOWS
SI32 errorCode = WSAGetLastError();
if( errorCode == WSAEWOULDBLOCK )
#else
SI32 errorCode = errno;
if( errorCode == EWOULDBLOCK )
#endif
{
delete toMake;
return;
}
Console.Error( "Error at client connection!" );
cwmWorldState->SetKeepRun( true );
cwmWorldState->SetError( true );
delete toMake;
return;
}
// April 5, 2000
// Due to an attack on the shard, and the true inability to determine who did what
// I am implementing a ShitList, any IP on this list will be immediatly dropped.
// This is a light weight firewall attempt to stop some people that are attacking
// a shard that they can easily create new accounts automatically on.
// ListFormat: x.x.x.x --> * means any for that domain class
// _ _ _
UI08 part[4];
part[0] = static_cast<UI08>( client_addr.sin_addr.s_addr & 0xFF );
part[1] = static_cast<UI08>(( client_addr.sin_addr.s_addr & 0xFF00 ) >> 8 );
part[2] = static_cast<UI08>(( client_addr.sin_addr.s_addr & 0xFF0000 ) >> 16 );
part[3] = static_cast<UI08>(( client_addr.sin_addr.s_addr & 0xFF000000 ) >> 24 );
if( IsFirewallBlocked( part ))
{
messageLoop << oldstrutil::format( "FIREWALL: Blocking address %i.%i.%i.%i --> Blocked!", part[0], part[1], part[2], part[3] );
closesocket( static_cast<UOXSOCKET>( newClient ));
delete toMake;
return;
}
//Firewall-messages are really only needed when firewall blocks, not when it lets someone through. Leads to information overload in console. Commenting out.
messageLoop << oldstrutil::format( "Client %zu [%i.%i.%i.%i] connected [Total:%lu]", cwmWorldState->GetPlayersOnline(), part[0], part[1], part[2], part[3], cwmWorldState->GetPlayersOnline() + 1 );
loggedInClients.push_back( toMake );
toMake->ClientIP( client_addr.sin_addr.s_addr );
return;
}
if( s < 0 )
{
Console.Error( " Select (Conn) failed!" );
cwmWorldState->SetKeepRun( false );
cwmWorldState->SetError( true );
return;
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CNetworkStuff::IsFirewallBlocked()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Checks if IP of connecting client is blocked by firewall
//o------------------------------------------------------------------------------------------------o
bool CNetworkStuff::IsFirewallBlocked( UI08 part[4] )
{
bool match[4];
for( size_t i = 0; i < slEntries.size(); ++i )
{
for( UI08 k = 0; k < 4; ++k )
{
if( slEntries[i].b[k] == -1 )
{
match[k] = true;
}
else
{
match[k] = ( slEntries[i].b[k] == part[k] );
}
}
if( match[0] && match[1] && match[2] && match[3] )
return true;
}
return false;
}
CNetworkStuff::~CNetworkStuff()
{
size_t i = 0;
size_t s = 0;
for( i = 0; i < loggedInClients.size(); ++i )
{
loggedInClients[i]->FlushBuffer();
loggedInClients[i]->CloseSocket();
delete loggedInClients[i];
}
for( i = 0; i < connClients.size(); ++i )
{
connClients[i]->FlushBuffer();
connClients[i]->CloseSocket();
s = std::max( s, connClients[i]->CliSocket() + 1 );
delete connClients[i];
}
loggedInClients.resize( 0 );
connClients.resize( 0 );
closesocket( static_cast<UOXSOCKET>( s ));
#if PLATFORM == WINDOWS
WSACleanup();
#endif
}
//o------------------------------------------------------------------------------------------------o
//| Function - CNetworkStuff::CheckMessage()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Check for messages from the clients
//o------------------------------------------------------------------------------------------------o
void CNetworkStuff::CheckMessage( void )
{
FD_ZERO( &all );
FD_ZERO( &errsock );
SI32 nfds = 0;
for( auto &tSock : connClients )
{
auto clientSock = static_cast<UOXSOCKET>( tSock->CliSocket() );
FD_SET( clientSock, &all );
FD_SET( clientSock, &errsock );
if( static_cast<int>( clientSock ) + 1 > nfds )
{
nfds = clientSock + 1;
}
}
SI32 s = select( nfds, &all, nullptr, &errsock, &cwmWorldState->uoxTimeout );
if( s > 0 )
{
size_t oldnow = cwmWorldState->GetPlayersOnline();
for( size_t i = 0; i < cwmWorldState->GetPlayersOnline(); ++i )
{
if( FD_ISSET( connClients[i]->CliSocket(), &errsock ))
{
Disconnect( static_cast<UOXSOCKET>( i ));
}
if(( FD_ISSET( connClients[i]->CliSocket(), &all )) && ( oldnow == cwmWorldState->GetPlayersOnline() ))
{
try
{
GetMsg( static_cast<UOXSOCKET>( i ));
}
catch( socket_error& blah )
{
#if PLATFORM != WINDOWS
Console << "Client disconnected" << myendl;
#else
if( blah.ErrorNumber() == WSAECONNRESET )
{
Console << "Client disconnected" << myendl;
}
else if( blah.ErrorNumber() != -1 )
{
Console << "Socket error: " << static_cast<SI32>( blah.ErrorNumber() ) << myendl;
}
#endif
Disconnect( static_cast<UOXSOCKET>( i )); // abnormal error
}
}
}
}
else if( s == -1 )
{
}
}
CNetworkStuff::CNetworkStuff() : peakConnectionCount( 0 ) // Initialize sockets
{
}
auto CNetworkStuff::Startup() -> void
{
FD_ZERO( &conn );
sockInit();
LoadFirewallEntries();
}
CSocket *CNetworkStuff::GetSockPtr( UOXSOCKET s )
{
if( static_cast<unsigned>(s) >= connClients.size() )
return nullptr;
return connClients[s];
}
CPInputBuffer *WhichPacket( UI08 packetId, CSocket *s );
CPInputBuffer *WhichLoginPacket( UI08 packetId, CSocket *s );
//o------------------------------------------------------------------------------------------------o
//| Function - CNetworkStuff::GetMsg()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Receive message from client
//o------------------------------------------------------------------------------------------------o
void CNetworkStuff::GetMsg( UOXSOCKET s )
{
if( static_cast<unsigned>(s) >= connClients.size() )
return;
CSocket *mSock = connClients[s];
if( mSock == nullptr )
return;
if( mSock->NewClient() )
{
SI32 count = mSock->Receive( 4 );
if( mSock->Buffer()[0] == 0x21 && count < 4 ) // UOMon
{
SI32 ho, mi, se, total;
total = ( cwmWorldState->GetUICurrentTime() - cwmWorldState->GetStartTime() ) / 1000;
ho = total / 3600;
total -= ho * 3600;
mi = total / 60;
total -= mi * 60;
se = total;
auto uoxmonitor = oldstrutil::format( "UOX LoginServer\r\nUOX3 Server: up for %ih %im %is\r\n", ho, mi, se );
mSock->Send( uoxmonitor.c_str(), static_cast<SI32>( uoxmonitor.size() ));
mSock->NewClient( false );
}
else
{
mSock->NewClient( false );
if( mSock->GetDWord( 0 ) == 0x12345678 )
{
Disconnect( s );
}
mSock->FirstPacket( true );
}
}
else
{
mSock->InLength( 0 );
CChar *ourChar = mSock->CurrcharObj();
UI08 *buffer = mSock->Buffer();
if( mSock->Receive( 1, false ) > 0 )
{
SI32 book;
UI08 packetId = buffer[0];
if( mSock->FirstPacket() && packetId != 0x80 && packetId != 0x91 )
{
// April 5, 2004 - There is a great chance that alot of the times this will be UOG2 connecting to get information from the server
if( cwmWorldState->ServerData()->ServerUOGEnabled() )
{
Console << "UOG Stats request completed. Disconnecting client. [" << static_cast<SI32>( mSock->ClientIP4() ) << "." << static_cast<SI32>( mSock->ClientIP3() ) << "." << static_cast<SI32>( mSock->ClientIP2() ) << "." << static_cast<SI32>( mSock->ClientIP1() ) << "]" << myendl;
}
else
{
Console << "Encrypted client attempting to cut in, disconnecting them: IP " << static_cast<SI32>( mSock->ClientIP1() ) << "." << static_cast<SI32>( mSock->ClientIP2() ) << "." << static_cast<SI32>( mSock->ClientIP3() ) << "." << static_cast<SI32>( mSock->ClientIP4() ) << myendl;
}
Disconnect( s );
return;
}
#if _DEBUG_PACKET
Console.Print( oldstrutil::format( "Packet ID: 0x%x\n", packetId ));
#endif
if( packetId != 0x73 && packetId != 0xA4 && packetId != 0x80 && packetId != 0x91 )
{
if( !mSock->ForceOffline() ) //Don't refresh idle-timer if character is being forced offline
{
mSock->IdleTimeout( cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_LOGINTIMEOUT ));
}
}
bool doSwitch = true;
if( cwmWorldState->ServerData()->ServerOverloadPackets() )
{
std::map<UI16, UI16>::const_iterator pFind = packetOverloads.find( packetId );
if( pFind != packetOverloads.end() )
{
cScript *pScript = JSMapping->GetScript( pFind->second );
if( pScript != nullptr )
{
doSwitch = !pScript->OnPacketReceive( mSock, packetId );
}
}
}
if( doSwitch )
{
CPInputBuffer *test = WhichPacket( packetId, mSock );
if( test != nullptr )
{
mSock->ReceiveLogging( test );
if( test->Handle() )
{
doSwitch = false;
}
delete test;
}
}
if( doSwitch )
{
size_t j = 0;
switch( packetId )
{
case 0x01: // Main Menu on the character select screen
Disconnect( s );
break;
case 0x72:// Combat Mode
{
mSock->Receive( 5 );
std::vector<UI16> scriptTriggers = ourChar->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
if( toExecute->OnWarModeToggle( ourChar ) == 0 )
{
return;
}
}
}
// No individual scripts handling OnWarModeToggle returned true - let's check global script!
cScript *toExecute = JSMapping->GetScript( static_cast<UI16>( 0 ));
if( toExecute != nullptr )
{
if( toExecute->OnWarModeToggle( ourChar ) == 0 )
{
return;
}
}
ourChar->SetWar( buffer[1] != 0 );
ourChar->SetPassive( buffer[1] == 0 ); // Set player as passive if they tab out of combat
if( ourChar->GetTimer( tCHAR_TIMEOUT ) <= cwmWorldState->GetUICurrentTime() )
{
ourChar->SetTimer( tCHAR_TIMEOUT, BuildTimeValue( 1 ));
}
else
{
ourChar->SetTimer( tCHAR_TIMEOUT, ourChar->GetTimer( tCHAR_TIMEOUT ) + 1000 );
}
mSock->Send( buffer, 5 );
#if defined( _MSC_VER )
#pragma note( "Flush location" )
#endif
mSock->FlushBuffer();
Movement->CombatWalk( ourChar );
Effects->DoSocketMusic( mSock );
ourChar->BreakConcentration( mSock );
break;
}
case 0x12:// Ext. Command
mSock->Receive( 3 );
mSock->Receive( mSock->GetWord( 1 ));
ourChar->BreakConcentration( mSock );
if( buffer[3] == 0xC7 ) // Action
{
if( ourChar->IsOnHorse() || ourChar->IsFlying() )
return; // can't bow or salute on horse or while flying
if( std::string( reinterpret_cast<char*>( &buffer[4] )) == "bow" )
{
if( ourChar->GetBodyType() == BT_GARGOYLE
|| ( cwmWorldState->ServerData()->ForceNewAnimationPacket()
&& ( ourChar->GetSocket() == nullptr || ourChar->GetSocket()->ClientType() >= CV_SA2D )))
{
// If gargoyle, human or elf, and new animation packet is enabled
Effects->PlayNewCharacterAnimation( ourChar, N_ACT_EMOTE, S_ACT_EMOTE_BOW );
}
else if(( ourChar->GetBodyType() == BT_HUMAN || ourChar->GetBodyType() == BT_ELF )
&& !cwmWorldState->ServerData()->ForceNewAnimationPacket() )
{
// If human or elf, and new animation packet is disabled
Effects->PlayCharacterAnimation( ourChar, ACT_EMOTE_BOW, 0, 5 ); // 0x20
}
else
{
// If polymorphed to other creatures
Effects->PlayCharacterAnimation( ourChar, 0x12, 0, 5 ); // Monster fidget 1
}
}
if( std::string( reinterpret_cast<char*>( &buffer[4] )) == "salute" )
{
if( ourChar->GetBodyType() == BT_GARGOYLE
|| ( cwmWorldState->ServerData()->ForceNewAnimationPacket()
&& ( ourChar->GetSocket() == nullptr || ourChar->GetSocket()->ClientType() >= CV_SA2D )))
{
// If gargoyle, human or elf, and new animation packet is enabled
Effects->PlayNewCharacterAnimation( ourChar, N_ACT_EMOTE, S_ACT_EMOTE_SALUTE );
}
else if(( ourChar->GetBodyType() == BT_HUMAN || ourChar->GetBodyType() == BT_ELF )
&& !cwmWorldState->ServerData()->ForceNewAnimationPacket() )
{
// If human or elf, and new animation packet is disabled
Effects->PlayCharacterAnimation( ourChar, ACT_EMOTE_SALUTE, 0, 5 ); // 0x21
}
else
{
// If polymorphed to other creatures
Effects->PlayCharacterAnimation( ourChar, 0x11, 0, 5 ); // Monster fidget 2
}
}
break;
}
else if( buffer[3] == 0x58 )
{
DoorMacro( mSock );
break;
}
else if( buffer[3] == 0x24 ) // Skill
{
j = 4;
while( buffer[j] != ' ' )
{
++j;
}
buffer[j] = 0;
Skills->SkillUse( mSock, static_cast<UI08>( std::stoul( std::string(( char* )&buffer[4] ))));
break;
}
else if( buffer[3] == 0x27 || buffer[3] == 0x56 ) // Spell
{
// This'll find our spellbook for us
CItem *sBook = FindItemOfType( ourChar, IT_SPELLBOOK );
CItem *p = ourChar->GetPackItem();
if( ValidateObject( sBook ))
{
bool validLoc = false;
if( sBook->GetCont() == ourChar )
{
validLoc = true;
}
else if( ValidateObject( p ) && sBook->GetCont() == p )
{
validLoc = true;
}
if( validLoc )
{
book = buffer[4] - 0x30;
if( buffer[5] > 0x20 )
{
book = ( book * 10 ) + ( buffer[5] - 0x30 );
}
if( Magic->CheckBook((( book - 1 ) / 8 ) + 1, ( book - 1 ) % 8, sBook ))
{
if( ourChar->IsFrozen() )
{
if( ourChar->IsCasting() )
{
mSock->SysMessage( 762 ); // You are already casting a spell.
}
else
{
mSock->SysMessage( 763 ); // You cannot cast spells while frozen.
}
}
else
{
mSock->CurrentSpellType( 0 );
Magic->SelectSpell( mSock, book );
}
}
else
{
mSock->SysMessage( 764 ); // You do not have that spell.
}
}
else
{
mSock->SysMessage( 765 ); // To cast spells, your spellbook must be in your hands or in the first layer of your pack.
}
}
}
else
{
if( buffer[2] == 0x05 && buffer[3] == 0x43 ) // Open spell book
{
Magic->SpellBook( mSock );
}
break;
}
break;
// client sends them out if the title and/or author gets changed on a writeable book
// its NOT send (anymore?) when a book is closed as many packet docus state.
// 7-dec 1999
case 0x93: // Old Book Packet, no longer sent by the client
mSock->Receive( 99 );
break;
case 0x69:// Client text change
mSock->Receive( 3 );// What a STUPID message... It would be useful if
mSock->Receive( mSock->GetWord( 1 )); // it included the color changed to, but it doesn't!
break;
case 0xB5: // Open Chat Window
mSock->Receive( 64 );
break;
case 0xB6: // T2A Popuphelp request
mSock->Receive( 9 );
break;
case 0xB8: // T2A Profile request
mSock->Receive( 3 );
mSock->Receive( mSock->GetWord( 1 )); // this is how it's really done
if( buffer[3] == 1 ) // Set profile, not feasible yet
{
mSock->SysMessage( 768 ); // You cannot set your profile currently.
}
else
{
SERIAL targSerial = mSock->GetDWord( 4 );
CChar *player = CalcCharObjFromSer( targSerial );
if( !ValidateObject( player ))
{
mSock->SysMessage( 769 ); // This player no longer exists.
}
else
{
// Still doesn't work
// Request Char Profile (Variable # of bytes)
// BYTE cmd
// BYTE[2] blockSize
// BYTE mode (CLIENT ONLY! Does not exist in server message.)
// BYTE[4] id
// If request, ends here.
// If Update request
// BYTE[2] cmdType (0x0001 <20> Update)
// BYTE[2] msglen (# of unicode characters)
// BYTE[msglen][2] new profile, in unicode, not null terminated.
// Else If from server
// BYTE[?] title (not unicode, null terminated.)
// BYTE[?][2] static profile ( unicode string can't be edited )
// BYTE[2] (0x0000) (static profile terminator)
// BYTE[?][2] profile (in unicode, can be edited)
// BYTE[2] (0x0000) (profile terminator)
UI08 tempBuffer[1024];
std::string ourMessage;
memset( tempBuffer, 0, sizeof( tempBuffer[0] ) * 1024 ); // zero it out
tempBuffer[0] = 0xB8;
tempBuffer[3] = buffer[4];
tempBuffer[4] = buffer[5];
tempBuffer[5] = buffer[6];
tempBuffer[6] = buffer[7];
auto playerName = player->GetNameRequest( mSock->CurrcharObj(), NRS_JOURNAL );
size_t plNameLen = playerName.size();
strncopy(( char * )&tempBuffer[7], 30, playerName.c_str(), 30 );
size_t mj = 8 + plNameLen;
auto playerTitle = player->GetTitle();
size_t plTitleLen = playerTitle.size() + 1;
for( size_t k = 0; k < plTitleLen; ++k )
{
tempBuffer[mj++] = 0;
tempBuffer[mj++] = playerTitle.data()[k];
}
mj += 2;
ourMessage = "Test of Char Profile";
for( j = 0; j < ourMessage.size(); ++j )
{
tempBuffer[mj++] = 0;
tempBuffer[mj++] = ourMessage[j];
}
mj += 2;
tempBuffer[2] = static_cast<UI08>( mj );
mSock->Send( tempBuffer, static_cast<SI32>( mj ));
#if defined( _MSC_VER )
#pragma note( "Flush location" )
#endif
mSock->FlushBuffer();
}
}
break;
case 0xBB:
// Ultima Messenger (9 bytes)
// BYTE cmd
// BYTE[4] id1
// BYTE[4] id2
// Note: This is both a client and server message.
mSock->Receive( 9 );
break;
case 0x3A: // skills management packet
mSock->Receive( 6 ); // it's always 6 so Westy tells me... let's not be intelligent =)
UI16 skillNum;
skillNum = mSock->GetWord( 3 );
ourChar->SetSkillLock( static_cast<SkillLock>( buffer[5] ), static_cast<UI08>( skillNum ));
break;
case 0x56:
Console << "'Plot Course' button on a map clicked." << myendl;
break;
case 0xD0:
// Configuration file
mSock->Receive( 3 );
mSock->Receive( mSock->GetWord( 1 ));
break;
case 0xD9: // Spy On Client
break;
case 0xFB: // Update View Public House Contents
mSock->Receive( 2 );
break;
case 0xF0: // ClassicUO Map Tracker, handled by CPIKrriosClientSpecial in CPacketReceive.cpp
break;
default:
FD_ZERO( &all );
FD_SET( mSock->CliSocket(), &all );
SI32 nfds;
nfds = static_cast<int>( mSock->CliSocket() ) + 1;
if( select( nfds, &all, nullptr, nullptr, &cwmWorldState->uoxTimeout ) > 0 )
{
mSock->Receive( MAXBUFFER );
}
Console << oldstrutil::format( "Unknown message from client: 0x%02X - Ignored", packetId ) << myendl;
break;
}
}
}
else // Sortta error type thing, they disconnected already
{
Disconnect( s );
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CNetworkStuff::CheckLoginMessage()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Check for messages from the clients
//o------------------------------------------------------------------------------------------------o
void CNetworkStuff::CheckLoginMessage( void )
{
fd_set all;
fd_set errsock;
size_t i;
cwmWorldState->uoxTimeout.tv_sec = 0;
cwmWorldState->uoxTimeout.tv_usec = 1;
FD_ZERO( &all );
FD_ZERO( &errsock );
SI32 nfds = 0;
for( i = 0; i < loggedInClients.size(); ++i )
{
size_t clientSock = loggedInClients[i]->CliSocket();
FD_SET( clientSock, &all );
FD_SET( clientSock, &errsock );
if( static_cast<int>( clientSock ) + 1 > nfds )
{
nfds = static_cast<int>( clientSock ) + 1;
}
}
SI32 s = select( static_cast<UOXSOCKET>( nfds ), &all, nullptr, &errsock, &cwmWorldState->uoxTimeout );
if( s > 0 )
{
size_t oldnow = loggedInClients.size();
for( i = 0; i < loggedInClients.size(); ++i )
{
if( FD_ISSET( loggedInClients[i]->CliSocket(), &errsock ))
{
LoginDisconnect( static_cast<UOXSOCKET>( i ));
continue;
}
if(( FD_ISSET( loggedInClients[i]->CliSocket(), &all )) && ( oldnow == loggedInClients.size() ))
{
try
{
GetLoginMsg( static_cast<UOXSOCKET>( i ));
}
catch( socket_error& blah )
{
#if PLATFORM != WINDOWS
messageLoop << "Client disconnected";
#else
if( blah.ErrorNumber() == WSAECONNRESET )
{
messageLoop << "Client disconnected";
}
else if( blah.ErrorNumber() != -1 )
{
messageLoop << oldstrutil::format( "Socket error: %i", blah.ErrorNumber() );
}
#endif
LoginDisconnect( static_cast<UOXSOCKET>( i )); // abnormal error
}
}
}
}
else if( s == -1 )
{
#if PLATFORM == WINDOWS
SI32 errorCode = WSAGetLastError();
if( errorCode != 10022 )
{
Console << static_cast<SI32>( errorCode ) << myendl;
}
#endif
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CNetworkStuff::LoginDisconnect()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Force disconnection of player //Instalog
//o------------------------------------------------------------------------------------------------o
void CNetworkStuff::LoginDisconnect( UOXSOCKET s )
{
messageLoop << oldstrutil::format( "LoginClient %u disconnected.", s );
loggedInClients[s]->FlushBuffer();
loggedInClients[s]->CloseSocket();
// 9/17/01 : fix for clients disconnecting during login not being able to reconnect.
if( loggedInClients[s]->AcctNo() != AB_INVALID_ID )
{
CAccountBlock_st& actbAccount = loggedInClients[s]->GetAccount();
if( actbAccount.wAccountIndex != AB_INVALID_ID )
{
actbAccount.wFlags.set( AB_FLAGS_ONLINE, false );
}
}
delete loggedInClients[s];
loggedInClients.erase( loggedInClients.begin() + s );
}
void CNetworkStuff::LoginDisconnect( CSocket *s ) // Force disconnection of player //Instalog
{
UOXSOCKET i = FindLoginPtr( s );
LoginDisconnect( i );
}
void CNetworkStuff::Disconnect( CSocket *s ) // Force disconnection of player //Instalog
{
UOXSOCKET i = FindNetworkPtr( s );
Disconnect( i );
}
UOXSOCKET CNetworkStuff::FindLoginPtr( CSocket *s )
{
for( UOXSOCKET i = 0; static_cast<unsigned>(i) < loggedInClients.size(); ++i )
{
if( loggedInClients[i] == s )
return i;
}
return 0xFFFFFFFF;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CNetworkStuff::Transfer()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Transfers from the logged in queue to the in world queue
//|
//| Notes - Takes something out of the logging in queue and places it in the in world queue
//| REQUIRES THREAD SAFETY
//o------------------------------------------------------------------------------------------------o
void CNetworkStuff::Transfer( CSocket *mSock )
{
UOXSOCKET s = FindLoginPtr( mSock );
if( static_cast<unsigned>(s) >= loggedInClients.size() )
return;
//std::scoped_lock lock(internallock);
connClients.push_back( loggedInClients[s] );
loggedInClients.erase( loggedInClients.begin() + s );
cwmWorldState->SetPlayersOnline( connClients.size() );
if( cwmWorldState->GetPlayersOnline() > peakConnectionCount )
{
peakConnectionCount = cwmWorldState->GetPlayersOnline();
}
return;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CNetworkStuff::GetLoginMsg()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Gets login message from socket
//o------------------------------------------------------------------------------------------------o
void CNetworkStuff::GetLoginMsg( UOXSOCKET s )
{
CSocket *mSock = loggedInClients[s];
if( mSock == nullptr )
return;
if( mSock->NewClient() )
{
[[maybe_unused]] SI32 count;
count = mSock->Receive( 4 );
auto packetId = mSock->Buffer()[0];
if( cwmWorldState->ServerData()->ServerOverloadPackets() )
{
// Allow overloading initial connection packets
std::map<UI16, UI16>::const_iterator pFind = packetOverloads.find( packetId );
if( pFind != packetOverloads.end() )
{
cScript *pScript = JSMapping->GetScript( pFind->second );
if( pScript != nullptr )
{
pScript->OnPacketReceive( mSock, packetId );
}
}
}
if( packetId == 0xEF || packetId == 0xC0 || packetId == 0xFF )
{
WhichLoginPacket( packetId, mSock );
//mSock->Receive( 21, false );
}
mSock->NewClient( false );
if( mSock->GetDWord( 0 ) == 0x12345678 )
{
LoginDisconnect( s );
}
mSock->FirstPacket( true );
}
else
{
mSock->InLength( 0 );
UI08 *buffer = mSock->Buffer();
if( mSock->Receive( 1, false ) > 0 )
{
UI08 packetId = buffer[0];
if( mSock->FirstPacket() && packetId != 0x80 && packetId != 0x91 && packetId != 0xE4 && packetId != 0xF1 )
{
// April 5, 2004 - Hmmm there are two of these ?
if( cwmWorldState->ServerData()->ServerUOGEnabled() )
{
messageLoop << oldstrutil::format( "UOG Stats Sent or Encrypted client detected. [%i.%i.%i.%i]", mSock->ClientIP4(), mSock->ClientIP3(), mSock->ClientIP2(), mSock->ClientIP1() );
}
else
{
messageLoop << oldstrutil::format( "Encrypted client detected. [%i.%i.%i.%i]", mSock->ClientIP4(), mSock->ClientIP3(), mSock->ClientIP2(), mSock->ClientIP1() );
}
LoginDisconnect( s );
return;
}
else if( mSock->FirstPacket() && packetId == 0 )
{
messageLoop << oldstrutil::format( "Buffer is empty, no packets to read. Disconnecting client. [%i.%i.%i.%i]", mSock->ClientIP4(), mSock->ClientIP3(), mSock->ClientIP2(), mSock->ClientIP1() );
LoginDisconnect( s );
return;
}
bool doSwitch = true;
if( cwmWorldState->ServerData()->ServerOverloadPackets() )
{
std::map<UI16, UI16>::const_iterator pFind = packetOverloads.find( packetId );
if( pFind != packetOverloads.end() )
{
cScript *pScript = JSMapping->GetScript( pFind->second );
if( pScript != nullptr )
{
doSwitch = !pScript->OnPacketReceive( mSock, packetId );
}
}
}
if( doSwitch )
{
CPInputBuffer *test = nullptr;
try
{
test = WhichLoginPacket( packetId, mSock );
}
catch( socket_error& )
{
Console.Warning( oldstrutil::format( "Bad packet request thrown! [packet ID: 0x%x]", packetId ));
mSock->FlushIncoming();
return;
}
if( test != nullptr )
{
#ifdef _DEBUG_PACKET
Console.Log( oldstrutil::format( "Logging packet ID: 0x%X", packetId ), "loginthread.txt" );
#endif
mSock->ReceiveLogging( test );
if( test->Handle() )
{
doSwitch = false;
}
delete test;
}
}
if( doSwitch )
{
switch( packetId )
{
case 0x01:
//Disconnect Notification (5 bytes)
// BYTE cmd
// BYTE[4] pattern (0xFFFFFFFF)
//Note: Client message
//Note: Sent when the user chooses to return to the main menu from the character select menu.
//Note: Since the character select menu no longer has a main menu button, this message is no longer sent.
//Note: It<49>s send again in current 3.0.x clients. When player does "paper-doll logout"
LoginDisconnect( s );
break;
case 0xBB:
// Ultima Messenger (9 bytes)
// BYTE cmd
// BYTE[4] id1
// BYTE[4] id2
//Note: This is both a client and server message.
mSock->Receive( 9 );
break;
case 0xBF:
mSock->Receive( 3 );
mSock->Receive( mSock->GetWord( 1 ));
break;
default:
SI32 nfds;
fd_set all;
FD_ZERO( &all );
FD_SET( mSock->CliSocket(), &all );
nfds = static_cast<UOXSOCKET>( mSock->CliSocket() ) + 1;
if( select( nfds, &all, nullptr, nullptr, &cwmWorldState->uoxTimeout ) > 0 )
{
mSock->Receive( MAXBUFFER );
}
messageLoop << oldstrutil::format( "Unknown message from client: 0x%02X - Ignored", packetId );
break;
}
}
}
else // Sortta error type thing, they disconnected already
{
LoginDisconnect( s );
}
}
}
UOXSOCKET CNetworkStuff::FindNetworkPtr( CSocket *toFind )
{
for( UOXSOCKET i = 0; static_cast<unsigned>(i) < connClients.size(); ++i )
{
if( connClients[i] == toFind )
return i;
}
return 0xFFFFFFFF;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CNetworkStuff::LoadFirewallEntries()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Loads list of banned IPs from firewall list
//o------------------------------------------------------------------------------------------------o
auto CNetworkStuff::LoadFirewallEntries() -> void
{
std::string token;
std::string fileToUse;
if( !FileExists( "banlist.ini" ))
{
if( FileExists( "firewall.ini" ))
{
fileToUse = "firewall.ini";
}
}
else
{
fileToUse = "banlist.ini";
}
if( !fileToUse.empty() )
{
auto firewallData = std::make_unique<Script>( fileToUse, NUM_DEFS, false );
if( firewallData )
{
SI16 p[4];
for( const auto &[secName, firewallSect] : firewallData->collection() )
{
if( firewallSect )
{
for( const auto &sec : firewallSect->collection() )
{
auto tag = sec->tag;
auto data = sec->data;
if( oldstrutil::upper( tag ) == "IP" )
{
data = oldstrutil::trim( oldstrutil::removeTrailing( data, "//" ));
if( !data.empty() )
{
auto psecs = oldstrutil::sections( data, "." );
if( psecs.size() == 4 ) // Wellformed IP address
{
for( UI08 i = 0; i < 4; ++i )
{
token = psecs[i];
if( token == "*" )
{
p[i] = -1;
}
else
{
p[i] = static_cast<SI16>( std::stoi( token, nullptr, 0 ));
}
}
slEntries.push_back( FirewallEntry_st( p[0], p[1], p[2], p[3] ));
}
else if( data != "\n" && data != "\r" )
{
Console.Error( oldstrutil::format( "Malformed IP address in banlist.ini (line: %s)", data.c_str() ));
}
}
}
}
}
}
}
}
}
void CNetworkStuff::RegisterPacket( UI08 packet, [[maybe_unused]] UI08 subCmd, UI16 scriptId )
{
//UI16 packetId = static_cast<UI16>(( subCmd << 8 ) + packet ); // Registration of subCmd disabled until it can be fully implemented
UI16 packetId = static_cast<UI16>( packet );
packetOverloads[packetId] = scriptId;
}
void CNetworkStuff::CheckConnections( void )
{
CheckConn();
}
void CNetworkStuff::CheckMessages( void )
{
CheckLoginMessage();
CheckMessage();
}
size_t CNetworkStuff::PeakConnectionCount( void ) const
{
return peakConnectionCount;
}