/** @file * * @par History * - 2005/11/25 MuadDib: Added PKTBI_BF::TYPE_SESPAM: to do away with spam. * - 2005/11/23 MuadDib: Altered handle_mode_set for 0x72 Packet. Nows reads * from combat.cfg for warmode_wait object. Sends the * 0x77 Packet and returns 0 when out of timer. * - 2005/12/09 MuadDib: Added TYPE_CLIENT_LANGUAGE for setting member uclang. * - 2006/05/24 Shinigami: Added PKTBI_BF::TYPE_CHARACTER_RACE_CHANGER to support Elfs * - 2006/05/30 Shinigami: Changed params of character_race_changer_handler() * - 2009/07/23 MuadDib: updates for new Enum::Packet IDs * - 2009/08/14 Turley: Added PKTBI_BF::TYPE_SCREEN_SIZE & TYPE_CLOSED_STATUS_GUMP (anti spam) * - 2009/09/03 MuadDib: Relocation of account related cpp/h * - 2009/09/03 MuadDib: Relocation of multi related cpp/h * - 2009/09/06 Turley: Changed Version checks to bitfield client->ClientType * Added 0xE1 packet (UO3D clienttype packet) * - 2009/11/19 Turley: ssopt.core_sends_season & .core_handled_tags - Tomi * - 2009/12/03 Turley: toggle gargoyle flying support */ /* MISCMSG.CPP: Miscellaneous message handlers. Handlers shouldn't stay here long, only until they find a better home - but this is better than putting them in POL.CPP. */ #include #include #include #include #include #include "../bscript/eprog.h" #include "../bscript/impstr.h" #include "../clib/clib_endian.h" #include "../clib/fdump.h" #include "../clib/logfacility.h" #include "../clib/rawtypes.h" #include "../clib/refptr.h" #include "../plib/systemstate.h" #include "../plib/uconst.h" #include "accounts/account.h" #include "cmbtcfg.h" #include "fnsearch.h" #include "gameclck.h" #include "globals/settings.h" #include "globals/uvars.h" #include "guilds.h" #include "mobile/attribute.h" #include "mobile/charactr.h" #include "module/uomod.h" #include "multi/customhouses.h" #include "multi/multi.h" #include "network/cgdata.h" #include "network/client.h" #include "network/clientio.h" #include "network/packethelper.h" #include "network/packets.h" #include "network/pktboth.h" #include "network/pktdef.h" #include "network/pktin.h" #include "network/sockio.h" #include "party.h" #include "polclass.h" #include "realms/realm.h" #include "scrstore.h" #include "spells.h" #include "systems/suspiciousacts.h" #include "tooltips.h" #include "ufunc.h" #include "uobject.h" #include "uoexec.h" #include "uoscrobj.h" namespace Pol { namespace Module { void character_race_changer_handler( Network::Client* client, Core::PKTBI_BF* msg ); } namespace Core { using namespace Network; void handle_unknown_packet( Network::ThreadedClient* session ); void handle_unknown_packet( Network::Client* client ) { handle_unknown_packet( client->session() ); } void party_cmd_handler( Client* client, PKTBI_BF* msg ); void OnGuildButton( Client* client ); void OnQuestButton( Client* client ); void OnChatButton( Client* client ); void handle_bulletin_boards( Client* client, PKTBI_71* /*msg*/ ) { handle_unknown_packet( client ); } void handle_mode_set( Client* client, PKTBI_72* msg ) { if ( client->chr->warmode_wait > read_gameclock() ) { send_move( client, client->chr ); return; } client->chr->warmode_wait = read_gameclock() + settingsManager.combat_config.warmode_delay; bool msg_warmode = msg->warmode ? true : false; // FIXME: Should reply with 0x77 packet!? (so says various docs!) [TJ] transmit( client, msg, sizeof *msg ); client->chr->set_warmode( msg_warmode ); } void handle_keep_alive( Network::Client* client, PKTBI_73* msg ) { transmit( client, msg, sizeof *msg ); } void handle_rename_char( Client* client, PKTIN_75* msg ) { Mobile::Character* chr = find_character( cfBEu32( msg->serial ) ); if ( chr != nullptr ) { if ( client->chr->can_rename( chr ) ) { msg->name[sizeof msg->name - 1] = '\0'; // check for legal characters for ( char* p = msg->name; *p; p++ ) { // only allow: a-z, A-Z & spaces if ( *p != ' ' && !isalpha( *p ) ) { std::string tmp = fmt::format( "Client#{} (account {}) attempted an invalid rename (packet {:#x}):\n{}", client->instance_, ( client->acct != nullptr ) ? client->acct->name() : "unknown", (int)msg->msgtype, msg->name ); Clib::fdump( std::back_inserter( tmp ), msg->name, static_cast( strlen( msg->name ) ) ); POLLOG_INFOLN( tmp ); *p = '\0'; send_sysmessage( client, "Invalid name!" ); return; // dave 12/26 if invalid name, do not apply to chr! } } chr->setname( msg->name ); } else { send_sysmessage( client, "I can't rename that." ); } } else { send_sysmessage( client, "I can't find that." ); } } void handle_msg_B5( Client* client, PKTIN_B5* /*msg*/ ) { OnChatButton( client ); } void handle_char_profile_request( Client* client, PKTBI_B8_IN* msg ) { ref_ptr prog = find_script( "misc/charprofile", true, Plib::systemstate.config.cache_interactive_scripts ); if ( prog.get() != nullptr ) { Mobile::Character* mobile; if ( msg->mode == msg->MODE_REQUEST ) { mobile = system_find_mobile( cfBEu32( msg->profile_request.serial ) ); if ( mobile == nullptr ) return; client->chr->start_script( prog.get(), false, new Module::ECharacterRefObjImp( mobile ), new Bscript::BLong( msg->mode ), new Bscript::BLong( 0 ) ); } else if ( msg->mode == msg->MODE_UPDATE ) { mobile = system_find_mobile( cfBEu32( msg->profile_update.serial ) ); if ( mobile == nullptr ) return; u16* themsg = msg->profile_update.wtext; int intextlen = ( cfBEu16( msg->msglen ) - 12 ) / sizeof( msg->profile_update.wtext[0] ); if ( intextlen < 0 ) intextlen = 0; if ( intextlen > SPEECH_MAX_LEN ) intextlen = SPEECH_MAX_LEN; std::string text = Bscript::String::fromUTF16( themsg, intextlen, true ); client->chr->start_script( prog.get(), false, new Module::ECharacterRefObjImp( mobile ), new Bscript::BLong( msg->mode ), new Bscript::String( text ) ); } } } void handle_msg_BB( Client* client, PKTBI_BB* /*msg*/ ) { handle_unknown_packet( client ); } void handle_client_version( Client* client, PKTBI_BD* msg ) { u16 len = cfBEu16( msg->msglen ) - 3; if ( len < 100 ) { int c = 0; char ch; std::string ver2 = ""; while ( c < len ) { ch = msg->version[c]; if ( ch == 0 ) break; // seems to be null-terminated ver2 += ch; ++c; } client->setversion( ver2 ); VersionDetailStruct vers_det; client->itemizeclientversion( ver2, vers_det ); client->setversiondetail( vers_det ); if ( client->compareVersion( CLIENT_VER_70331 ) ) client->setClientType( CLIENTTYPE_70331 ); else if ( client->compareVersion( CLIENT_VER_70300 ) ) client->setClientType( CLIENTTYPE_70300 ); else if ( client->compareVersion( CLIENT_VER_70130 ) ) client->setClientType( CLIENTTYPE_70130 ); else if ( client->compareVersion( CLIENT_VER_7090 ) ) client->setClientType( CLIENTTYPE_7090 ); else if ( client->compareVersion( CLIENT_VER_7000 ) ) client->setClientType( CLIENTTYPE_7000 ); else if ( client->compareVersion( CLIENT_VER_60142 ) ) client->setClientType( CLIENTTYPE_60142 ); else if ( client->compareVersion( CLIENT_VER_6017 ) ) // Grid-loc support client->setClientType( CLIENTTYPE_6017 ); else if ( client->compareVersion( CLIENT_VER_5020 ) ) client->setClientType( CLIENTTYPE_5020 ); else if ( client->compareVersion( CLIENT_VER_5000 ) ) client->setClientType( CLIENTTYPE_5000 ); else if ( client->compareVersion( CLIENT_VER_4070 ) ) client->setClientType( CLIENTTYPE_4070 ); else if ( client->compareVersion( CLIENT_VER_4000 ) ) client->setClientType( CLIENTTYPE_4000 ); if ( settingsManager.ssopt.core_sends_season ) send_season_info( client ); // Scott 10/11/2007 added for login fixes and handling 1.x clients. // Season info needs to check client version to keep from crashing 1.x // version not set until shortly after login complete. // send_feature_enable(client); //dave commented out 8/21/03, unexpected problems with people // sending B9 via script with this too. if ( client->acctSupports( Plib::ExpansionVersion::AOS ) ) { send_object_cache( client, client->chr ); } } else { POLLOG_INFOLN( "Suspect string length in PKTBI_BD packet: {}", len ); } } void ext_stats_in( Client* client, PKTBI_BF* msg ) { if ( settingsManager.ssopt.core_handled_locks ) { const Mobile::Attribute* attrib = nullptr; switch ( msg->extstatin.stat ) { case PKTBI_BF_1A::STAT_STR: attrib = gamestate.pAttrStrength; break; case PKTBI_BF_1A::STAT_DEX: attrib = gamestate.pAttrDexterity; break; case PKTBI_BF_1A::STAT_INT: attrib = gamestate.pAttrIntelligence; break; default: // sent an illegal stat. Should report to console? return; } if ( attrib == nullptr ) // there's no attribute for this (?) return; u8 state = msg->extstatin.mode; if ( state > 2 ) // FIXME hard-coded value return; client->chr->attribute( attrib->attrid ).lock( state ); } } void handle_msg_BF( Client* client, PKTBI_BF* msg ) { UObject* obj = nullptr; Multi::UMulti* multi = nullptr; Multi::UHouse* house = nullptr; switch ( cfBEu16( msg->subcmd ) ) { case PKTBI_BF::TYPE_CLIENT_LANGUAGE: client->chr->uclang = Clib::strlowerASCII( msg->client_lang ); break; case PKTBI_BF::TYPE_REQ_FULL_CUSTOM_HOUSE: if ( !client->acctSupports( Plib::ExpansionVersion::AOS ) ) return; multi = system_find_multi( cfBEu32( msg->reqfullcustomhouse.house_serial ) ); if ( multi != nullptr ) { house = multi->as_house(); if ( house != nullptr ) { if ( client->acctSupports( Plib::ExpansionVersion::AOS ) ) { send_object_cache( client, (UObject*)( house ) ); } // consider sending working design to certain players, to assist building, or GM help CustomHousesSendFull( house, client, Multi::HOUSE_DESIGN_CURRENT ); } } break; case PKTBI_BF::TYPE_OBJECT_CACHE: if ( !client->acctSupports( Plib::ExpansionVersion::AOS ) ) return; obj = system_find_object( cfBEu32( msg->objectcache.serial ) ); if ( obj != nullptr ) { SendAOSTooltip( client, obj ); } break; case PKTBI_BF::TYPE_SESPAM: return; break; case PKTBI_BF::TYPE_SPELL_SELECT: do_cast( client, cfBEu16( msg->spellselect.selected_spell ) ); break; case PKTBI_BF::TYPE_CHARACTER_RACE_CHANGER: Module::character_race_changer_handler( client, msg ); break; case PKTBI_BF::TYPE_PARTY_SYSTEM: party_cmd_handler( client, msg ); break; case PKTBI_BF::TYPE_EXTENDED_STATS_IN: ext_stats_in( client, msg ); break; case PKTBI_BF::TYPE_CLOSED_STATUS_GUMP: return; break; case PKTBI_BF::TYPE_SCREEN_SIZE: return; break; case PKTBI_BF::TYPE_TOGGLE_FLYING: if ( client->chr->race == Plib::RACE_GARGOYLE ) { // FIXME: add checks if its possible to stand with new movemode client->chr->movemode = (Plib::MOVEMODE)( client->chr->movemode ^ Plib::MOVEMODE_FLY ); send_move_mobile_to_nearby_cansee( client->chr ); send_goxyz( client, client->chr ); } break; case PKTBI_BF::TYPE_CLIENTTYPE: client->UOExpansionFlagClient = ctBEu32( msg->clienttype.clientflag ); break; case PKTBI_BF::TYPE_POPUP_MENU_REQUEST: { ref_ptr prog = find_script( "misc/popupmenu", true, Plib::systemstate.config.cache_interactive_scripts ); if ( prog.get() == nullptr ) break; u32 serial = cfBEu32( msg->serial_request_popup_menu ); if ( IsCharacter( serial ) ) { Pol::Mobile::Character* chr = system_find_mobile( serial ); if ( chr == nullptr ) break; client->chr->start_script( prog.get(), false, new Pol::Module::ECharacterRefObjImp( chr ) ); } else { Pol::Items::Item* item = system_find_item( serial ); if ( item == nullptr ) break; client->chr->start_script( prog.get(), false, item->make_ref() ); } break; } case PKTBI_BF::TYPE_POPUP_ENTRY_SELECT: { if ( client->gd->on_popup_menu_selection == nullptr ) { POLLOG_INFOLN( "{}/{} tried to use a popup menu, but none was active.", client->acct->name(), client->chr->name() ); break; } u32 serial = cfBEu32( msg->popupselect.serial ); u16 id = cfBEu16( msg->popupselect.entry_tag ); client->gd->on_popup_menu_selection( client, serial, id ); break; } case PKTBI_BF::TYPE_BOAT_MOVE: { Mobile::Character* chr = client->chr; multi = chr->realm()->find_supporting_multi( client->chr->pos3d() ); if ( multi == nullptr ) { SuspiciousActs::BoatMoveNoMulti( client ); break; } if ( !multi->script_isa( Core::POLCLASS_BOAT ) ) { SuspiciousActs::BoatMoveNotBoatMulti( client ); break; } Multi::UBoat* boat = static_cast( multi ); if ( boat->pilot() != chr ) { SuspiciousActs::BoatMoveNotPilot( client, multi->serial ); break; } if ( msg->boatmove.direction > 7 || msg->boatmove.speed > 2 ) { SuspiciousActs::BoatMoveOutOfRangeParameters( client, multi->serial, msg->boatmove.direction, msg->boatmove.speed ); break; } Module::UOExecutorModule* process = multi->process(); if ( !process ) { break; } auto relative_direction = static_cast( ( msg->boatmove.direction - boat->boat_facing() + 8 ) & 7 ); process->uoexec().signal_event( new Module::BoatMovementEvent( chr, msg->boatmove.speed, msg->boatmove.direction, relative_direction ) ); break; } default: handle_unknown_packet( client ); } } void handle_unknown_C4( Client* client, PKTOUT_C4* /*msg*/ ) { handle_unknown_packet( client ); } void handle_update_range_change( Client* client, PKTBI_C8* msg ) { client->set_update_range_by_client( msg->range ); } void handle_krrios_packet( Client* client, PKTBI_F0* msg ) { auto* me = client->chr; switch ( msg->subcmd ) { case PKTBI_F0::QUERY_PARTY: { if ( !settingsManager.ssopt.enable_worldmap_packets ) break; if ( me->has_party() ) { Party* party = me->party(); Network::PktHelper::PacketOut outMsg; outMsg->offset += 2; // len outMsg->Write( PKTBI_F0::QUERY_PARTY + 1U ); // sub, response is +1 for ( auto member : party->get_members() ) { if ( member->serial == me->serial ) continue; if ( me->is_visible_to_me( member ) ) continue; outMsg->Write( member->serial_ext ); outMsg->WriteFlipped( member->x() ); outMsg->WriteFlipped( member->y() ); outMsg->Write( member->realm()->getUOMapID() ); } if ( outMsg->offset != 4 ) // only send if there is an update { outMsg->Write( 0U ); // end of list marker u16 len = outMsg->offset; outMsg->offset = 1; outMsg->WriteFlipped( len ); outMsg.Send( client, len ); } } break; } case PKTBI_F0::QUERY_GUILD: { if ( !settingsManager.ssopt.enable_worldmap_packets ) break; Guild* guild = me->guild(); u8 locations = msg->query_guild.include_locations > 0 ? 1U : 0U; if ( guild != nullptr ) { Network::PktHelper::PacketOut outMsg; outMsg->offset += 2; // len outMsg->Write( PKTBI_F0::QUERY_GUILD + 1U ); // sub, response is +1 outMsg->Write( locations ); for ( auto member : guild->get_members() ) { if ( member->serial == me->serial ) continue; if ( locations && me->is_visible_to_me( member ) ) continue; outMsg->Write( member->serial_ext ); if ( locations ) { outMsg->WriteFlipped( member->x() ); outMsg->WriteFlipped( member->y() ); outMsg->Write( member->realm()->getUOMapID() ); if ( member->dead() ) outMsg->Write( 0U ); else { int current = member->vital( networkManager.uoclient_general.hits.id ).current_ones(); int max = member->vital( networkManager.uoclient_general.hits.id ).maximum_ones(); u8 ratio = static_cast( 100 * current / ( max < 1 ? 1 : max ) ); outMsg->Write( ratio ); // hits } } } if ( outMsg->offset != 5 ) // only send if there is an update { outMsg->Write( 0U ); // end of list marker u16 len = outMsg->offset; outMsg->offset = 1; outMsg->WriteFlipped( len ); outMsg.Send( client, len ); } } break; } default: handle_unknown_packet( client ); break; } } void handle_open_uo_store( Client* client, PKTIN_FA* /*msg*/ ) { handle_unknown_packet( client ); } void handle_update_view_public_house_content( Client* client, PKTIN_FB* /*msg*/ ) { handle_unknown_packet( client ); } void handle_allnames( Client* client, PKTBI_98_IN* msg ) { u32 serial = cfBEu32( msg->serial ); Mobile::Character* the_mob = find_character( serial ); if ( the_mob == nullptr ) return; if ( !client->chr->is_visible_to_me( the_mob ) ) return; PktHelper::PacketOut msgOut; msgOut->WriteFlipped( 37u ); // static length msgOut->Write( the_mob->serial_ext ); msgOut->Write( Clib::strUtf8ToCp1252( the_mob->name() ).c_str(), 30, false ); msgOut.Send( client ); } void handle_se_object_list( Client* client, PKTBI_D6_IN* msgin ) { UObject* obj = nullptr; int length = cfBEu16( msgin->msglen ) - 3; if ( length < 0 || ( length % 4 ) != 0 ) return; int count = length / 4; for ( int i = 0; i < count; ++i ) { obj = system_find_object( cfBEu32( msgin->serials[i].serial ) ); if ( obj != nullptr ) SendAOSTooltip( client, obj ); } } void handle_ef_seed( Client* client, PKTIN_EF* msg ) { VersionDetailStruct detail; detail.major = cfBEu32( msg->ver_Major ); detail.minor = cfBEu32( msg->ver_Minor ); detail.rev = cfBEu32( msg->ver_Revision ); detail.patch = cfBEu32( msg->ver_Patch ); client->setversiondetail( detail ); if ( client->compareVersion( CLIENT_VER_70331 ) ) client->setClientType( CLIENTTYPE_70331 ); else if ( client->compareVersion( CLIENT_VER_70300 ) ) client->setClientType( CLIENTTYPE_70300 ); else if ( client->compareVersion( CLIENT_VER_70130 ) ) client->setClientType( CLIENTTYPE_70130 ); else if ( client->compareVersion( CLIENT_VER_7090 ) ) client->setClientType( CLIENTTYPE_7090 ); else if ( client->compareVersion( CLIENT_VER_7000 ) ) client->setClientType( CLIENTTYPE_7000 ); else if ( client->compareVersion( CLIENT_VER_60142 ) ) client->setClientType( CLIENTTYPE_60142 ); else if ( client->compareVersion( CLIENT_VER_6017 ) ) // Grid-loc support client->setClientType( CLIENTTYPE_6017 ); else if ( client->compareVersion( CLIENT_VER_5020 ) ) client->setClientType( CLIENTTYPE_5020 ); else if ( client->compareVersion( CLIENT_VER_5000 ) ) client->setClientType( CLIENTTYPE_5000 ); else if ( client->compareVersion( CLIENT_VER_4070 ) ) client->setClientType( CLIENTTYPE_4070 ); else if ( client->compareVersion( CLIENT_VER_4000 ) ) client->setClientType( CLIENTTYPE_4000 ); // detail->patch is since 5.0.7 always numeric, so no need to make it complicated client->setversion( fmt::format( "{}.{}.{}.{}", detail.major, detail.minor, detail.rev, detail.patch ) ); } void handle_e1_clienttype( Client* client, PKTIN_E1* msg ) { switch ( cfBEu32( msg->clienttype ) ) { case PKTIN_E1::CLIENTTYPE_KR: client->setClientType( CLIENTTYPE_UOKR ); break; case PKTIN_E1::CLIENTTYPE_SA: client->setClientType( CLIENTTYPE_UOSA ); break; default: INFO_PRINTLN( "Unknown client type send with packet 0xE1 : {:#x}", static_cast( cfBEu32( msg->clienttype ) ) ); break; } } /** * Handler for a 0xD7 packet */ void handle_aos_commands( Client* client, PKTBI_D7* msg ) { // nullptr prevention, no need to disturb if client or character is not found if ( client == nullptr || client->chr == nullptr ) return; /// Checks that serial written inside packet matches sending character's serial u32 serial = cfBEu32( msg->serial ); if ( client && client->chr && client->chr->serial != serial ) { INFO_PRINTLN( "Ignoring spoofed packet 0xD7 from character {:#x} trying to spoof {:#x}", client->chr->serial, serial ); return; } switch ( cfBEu16( msg->subcmd ) ) { case PKTBI_D7::CUSTOM_HOUSE_BACKUP: Multi::CustomHousesBackup( msg ); break; case PKTBI_D7::CUSTOM_HOUSE_RESTORE: Multi::CustomHousesRestore( msg ); break; case PKTBI_D7::CUSTOM_HOUSE_COMMIT: Multi::CustomHousesCommit( msg ); break; case PKTBI_D7::CUSTOM_HOUSE_ERASE: Multi::CustomHousesErase( msg ); break; case PKTBI_D7::CUSTOM_HOUSE_ADD: Multi::CustomHousesAdd( msg ); break; case PKTBI_D7::CUSTOM_HOUSE_QUIT: Multi::CustomHousesQuit( msg ); break; case PKTBI_D7::CUSTOM_HOUSE_ADD_MULTI: Multi::CustomHousesAddMulti( msg ); break; case PKTBI_D7::CUSTOM_HOUSE_SYNCH: Multi::CustomHousesSynch( msg ); break; case PKTBI_D7::CUSTOM_HOUSE_CLEAR: Multi::CustomHousesClear( msg ); break; case PKTBI_D7::CUSTOM_HOUSE_SELECT_FLOOR: Multi::CustomHousesSelectFloor( msg ); break; case PKTBI_D7::CUSTOM_HOUSE_REVERT: Multi::CustomHousesRevert( msg ); break; case PKTBI_D7::CUSTOM_HOUSE_SELECT_ROOF: Multi::CustomHousesRoofSelect( msg ); break; case PKTBI_D7::CUSTOM_HOUSE_DELETE_ROOF: Multi::CustomHousesRoofRemove( msg ); break; case PKTBI_D7::GUILD_BUTTON: OnGuildButton( client ); break; case PKTBI_D7::QUEST_BUTTON: OnQuestButton( client ); break; // missing combat book abilities default: handle_unknown_packet( client ); } } void OnGuildButton( Client* client ) { ref_ptr prog = find_script( "misc/guildbutton", true, Plib::systemstate.config.cache_interactive_scripts ); if ( prog.get() != nullptr ) { client->chr->start_script( prog.get(), false ); } } void OnQuestButton( Client* client ) { ref_ptr prog = find_script( "misc/questbutton", true, Plib::systemstate.config.cache_interactive_scripts ); if ( prog.get() != nullptr ) { client->chr->start_script( prog.get(), false ); } } void OnChatButton( Client* client ) { ref_ptr prog = find_script( "misc/chatbutton", true, Plib::systemstate.config.cache_interactive_scripts ); if ( prog.get() != nullptr ) { client->chr->start_script( prog.get(), false ); } } } // namespace Core } // namespace Pol