diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 3f8b29934..d275729d0 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -93,6 +93,8 @@ set(common_sources patches/sod_limits.cpp patches/sof.cpp patches/sof_limits.cpp + patches/steam_latest.cpp + patches/steam_latest_limits.cpp patches/titanium.cpp patches/titanium_limits.cpp patches/uf.cpp @@ -674,6 +676,10 @@ set(common_headers patches/sof_limits.h patches/sof_ops.h patches/sof_structs.h + patches/steam_latest.h + patches/steam_latest_limits.h + patches/steam_latest_ops.h + patches/steam_latest_structs.h patches/ss_declare.h patches/ss_define.h patches/ss_register.h diff --git a/common/base_packet.h b/common/base_packet.h index 607e04576..f0acd3f19 100644 --- a/common/base_packet.h +++ b/common/base_packet.h @@ -50,6 +50,7 @@ public: void WriteUInt8(uint8 value) { *(uint8 *)(pBuffer + _wpos) = value; _wpos += sizeof(uint8); } void WriteUInt32(uint32 value) { *(uint32 *)(pBuffer + _wpos) = value; _wpos += sizeof(uint32); } void WriteUInt64(uint64 value) { *(uint64 *)(pBuffer + _wpos) = value; _wpos += sizeof(uint64); } + void WriteSInt16(int32 value) { *(int16*)(pBuffer + _wpos) = value; _wpos += sizeof(int16); } void WriteUInt16(uint32 value) { *(uint16 *)(pBuffer + _wpos) = value; _wpos += sizeof(uint16); } void WriteSInt32(int32 value) { *(int32 *)(pBuffer + _wpos) = value; _wpos += sizeof(int32); } void WriteFloat(float value) { *(float *)(pBuffer + _wpos) = value; _wpos += sizeof(float); } diff --git a/common/classes.h b/common/classes.h index 5d6467fd5..8c39ee4e7 100644 --- a/common/classes.h +++ b/common/classes.h @@ -71,6 +71,9 @@ namespace Class { constexpr uint8 FellowshipMaster = 69; constexpr uint8 AlternateCurrencyMerchant = 70; constexpr uint8 MercenaryLiaison = 71; + constexpr uint8 RealEstateMerchant = 72; + constexpr uint8 LoyaltyMerchant = 73; + constexpr uint8 TributeMaster2 = 74; constexpr uint8 PLAYER_CLASS_COUNT = 16; constexpr uint16 ALL_CLASSES_BITMASK = 65535; diff --git a/common/crc32.cpp b/common/crc32.cpp index 54e66dfe7..cc5898774 100644 --- a/common/crc32.cpp +++ b/common/crc32.cpp @@ -99,6 +99,24 @@ uint32 CRC32::GenerateNoFlip(const uint8* buf, uint32 bufsize) { return Update(buf, bufsize); } +unsigned long CRC32::GetEQChecksum(uchar* in_data, uint32 in_length, uint32 start_at) +{ + unsigned long data; + unsigned long check = 0xffffffff; + + for (uint32 i = start_at; i < in_length; i++) + { + data = in_data[i]; + data = data ^ (check); + data = data & 0x000000ff; + check = check >> 8; + data = CRC32Table[data]; + check = check ^ data; + } + + return check; +} + void CRC32::SetEQChecksum(uchar* in_data, uint32 in_length, uint32 start_at) { unsigned long data; diff --git a/common/crc32.h b/common/crc32.h index 50dfe9901..de27432fd 100644 --- a/common/crc32.h +++ b/common/crc32.h @@ -25,6 +25,7 @@ public: static uint32 Generate(const uint8* buf, uint32 bufsize); static uint32 GenerateNoFlip(const uint8* buf, uint32 bufsize); // Same as Generate(), but without the ~ static void SetEQChecksum(uchar* in_data, uint32 in_length, uint32 start_at=4); + static unsigned long GetEQChecksum(uchar* in_data, uint32 in_length, uint32 start_at = 4); // Multiple buffer CRC32 static uint32 Update(const uint8* buf, uint32 bufsize, uint32 crc32 = 0xFFFFFFFF); diff --git a/common/emu_oplist.h b/common/emu_oplist.h index 1f8366662..22972f799 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -18,8 +18,6 @@ // system use N(OP_ExploreUnknown), // start (please add new opcodes in descending order and re-order any name changes where applicable) -N(OP_0x0193), -N(OP_0x0347), N(OP_AAAction), N(OP_AAExpUpdate), N(OP_AcceptNewTask), @@ -380,6 +378,7 @@ N(OP_MercenaryTimer), N(OP_MercenaryTimerRequest), N(OP_MercenaryUnknown1), N(OP_MercenaryUnsuspendResponse), +N(OP_MerchantBulkItems), N(OP_MobEnduranceUpdate), N(OP_MobHealth), N(OP_MobManaUpdate), @@ -398,6 +397,7 @@ N(OP_MultiLineMsg), N(OP_NewSpawn), N(OP_NewTitlesAvailable), N(OP_NewZone), +N(OP_NPCMoveUpdate), N(OP_OnLevelMessage), N(OP_OpenContainer), N(OP_OpenDiscordMerchant), diff --git a/common/emu_versions.cpp b/common/emu_versions.cpp index 8d724f4fc..774762bbb 100644 --- a/common/emu_versions.cpp +++ b/common/emu_versions.cpp @@ -54,6 +54,8 @@ const char* EQ::versions::ClientVersionName(ClientVersion client_version) return "RoF"; case ClientVersion::RoF2: return "RoF2"; + case ClientVersion::SteamLatest: + return "SteamLatest"; default: return "Invalid Version"; }; @@ -74,6 +76,8 @@ uint32 EQ::versions::ConvertClientVersionToClientVersionBit(ClientVersion client return bitRoF; case ClientVersion::RoF2: return bitRoF2; + case ClientVersion::SteamLatest: + return bitSteamLatest; default: return bitUnknown; } @@ -94,6 +98,8 @@ EQ::versions::ClientVersion EQ::versions::ConvertClientVersionBitToClientVersion return ClientVersion::RoF; case ((uint32)1 << (static_cast(ClientVersion::RoF2) - 1)) : return ClientVersion::RoF2; + case ((uint32)1 << (static_cast(ClientVersion::SteamLatest) - 1)) : + return ClientVersion::SteamLatest; default: return ClientVersion::Unknown; } @@ -182,6 +188,8 @@ const char* EQ::versions::MobVersionName(MobVersion mob_version) return "RoF"; case MobVersion::RoF2: return "RoF2"; + case MobVersion::SteamLatest: + return "SteamLatest"; case MobVersion::NPC: return "NPC"; case MobVersion::NPCMerchant: @@ -210,6 +218,8 @@ const char* EQ::versions::MobVersionName(MobVersion mob_version) return "Offline RoF"; case MobVersion::OfflineRoF2: return "Offline RoF2"; + case MobVersion::OfflineSteamLatest: + return "Offline Steam Latest"; default: return "Invalid Version"; }; @@ -233,6 +243,8 @@ EQ::versions::ClientVersion EQ::versions::ConvertMobVersionToClientVersion(MobVe return ClientVersion::RoF; case MobVersion::RoF2: return ClientVersion::RoF2; + case MobVersion::SteamLatest: + return ClientVersion::SteamLatest; default: return ClientVersion::Unknown; } @@ -256,6 +268,8 @@ EQ::versions::MobVersion EQ::versions::ConvertClientVersionToMobVersion(ClientVe return MobVersion::RoF; case ClientVersion::RoF2: return MobVersion::RoF2; + case ClientVersion::SteamLatest: + return MobVersion::SteamLatest; default: return MobVersion::Unknown; } @@ -276,6 +290,8 @@ EQ::versions::MobVersion EQ::versions::ConvertPCMobVersionToOfflinePCMobVersion( return MobVersion::OfflineRoF; case MobVersion::RoF2: return MobVersion::OfflineRoF2; + case MobVersion::SteamLatest: + return MobVersion::OfflineSteamLatest; default: return MobVersion::Unknown; } @@ -296,6 +312,8 @@ EQ::versions::MobVersion EQ::versions::ConvertOfflinePCMobVersionToPCMobVersion( return MobVersion::RoF; case MobVersion::OfflineRoF2: return MobVersion::RoF2; + case MobVersion::OfflineSteamLatest: + return MobVersion::SteamLatest; default: return MobVersion::Unknown; } @@ -316,6 +334,8 @@ EQ::versions::ClientVersion EQ::versions::ConvertOfflinePCMobVersionToClientVers return ClientVersion::RoF; case MobVersion::OfflineRoF2: return ClientVersion::RoF2; + case MobVersion::OfflineSteamLatest: + return ClientVersion::SteamLatest; default: return ClientVersion::Unknown; } @@ -336,6 +356,8 @@ EQ::versions::MobVersion EQ::versions::ConvertClientVersionToOfflinePCMobVersion return MobVersion::OfflineRoF; case ClientVersion::RoF2: return MobVersion::OfflineRoF2; + case ClientVersion::SteamLatest: + return MobVersion::OfflineSteamLatest; default: return MobVersion::Unknown; } @@ -386,6 +408,28 @@ const char* EQ::expansions::ExpansionName(Expansion expansion) return "Rain of Fear"; case Expansion::CotF: return "Call of the Forsaken"; + case Expansion::TDS: + return "The Darkened Sea"; + case Expansion::TBM: + return "The Broken Mirror"; + case Expansion::EoK: + return "Empires of Kunark"; + case Expansion::RoS: + return "Ring of Scale"; + case Expansion::TBL: + return "The Burning Lands"; + case Expansion::ToV: + return "Torment of Velious"; + case Expansion::CoV: + return "Claws of Veeshan"; + case Expansion::ToL: + return "Terror of Luclin"; + case Expansion::NoS: + return "Night of Shadows"; + case Expansion::LS: + return "Laurion's Song"; + case Expansion::TOB: + return "The Outer Brood"; default: return "Invalid Expansion"; } @@ -439,6 +483,29 @@ uint32 EQ::expansions::ConvertExpansionToExpansionBit(Expansion expansion) return bitRoF; case Expansion::CotF: return bitCotF; + case Expansion::TDS: + return bitTDS; + case Expansion::TBM: + return bitTBM; + case Expansion::EoK: + return bitEoK; + case Expansion::RoS: + return bitRoS; + case Expansion::TBL: + return bitTBL; + case Expansion::ToV: + return bitToV; + case Expansion::CoV: + return bitCoV; + case Expansion::ToL: + return bitToL; + case Expansion::NoS: + return bitNoS; + case Expansion::LS: + return bitLS; + case Expansion::TOB: + return bitTOB; + default: return bitEverQuest; } @@ -487,6 +554,28 @@ EQ::expansions::Expansion EQ::expansions::ConvertExpansionBitToExpansion(uint32 return Expansion::RoF; case bitCotF: return Expansion::CotF; + case bitTDS: + return Expansion::TDS; + case bitTBM: + return Expansion::TBM; + case bitEoK: + return Expansion::EoK; + case bitRoS: + return Expansion::RoS; + case bitTBL: + return Expansion::TBL; + case bitToV: + return Expansion::ToV; + case bitCoV: + return Expansion::CoV; + case bitToL: + return Expansion::ToL; + case bitNoS: + return Expansion::NoS; + case bitLS: + return Expansion::LS; + case bitTOB: + return Expansion::TOB; default: return Expansion::EverQuest; } @@ -535,6 +624,28 @@ uint32 EQ::expansions::ConvertExpansionToExpansionsMask(Expansion expansion) return maskRoF; case Expansion::CotF: return maskCotF; + case Expansion::TDS: + return maskTDS; + case Expansion::TBM: + return maskTBM; + case Expansion::EoK: + return maskEoK; + case Expansion::RoS: + return maskRoS; + case Expansion::TBL: + return maskTBL; + case Expansion::ToV: + return maskToV; + case Expansion::CoV: + return maskCoV; + case Expansion::ToL: + return maskToL; + case Expansion::NoS: + return maskNoS; + case Expansion::LS: + return maskLS; + case Expansion::TOB: + return maskTOB; default: return maskEverQuest; } diff --git a/common/emu_versions.h b/common/emu_versions.h index 522fe3c9b..20dedc7ff 100644 --- a/common/emu_versions.h +++ b/common/emu_versions.h @@ -32,7 +32,8 @@ namespace EQ SoD, // Build: 'Dec 19 2008 15:22:49' UF, // Build: 'Jun 8 2010 16:44:32' RoF, // Build: 'Dec 10 2012 17:35:44' - RoF2 // Build: 'May 10 2013 23:30:08' + RoF2, // Build: 'May 10 2013 23:30:08' + SteamLatest // Build: 'Sep 11 2025 11:54:10' }; enum ClientVersionBitmask : uint32 { @@ -44,6 +45,7 @@ namespace EQ bitUF = 0x00000010, bitRoF = 0x00000020, bitRoF2 = 0x00000040, + bitSteamLatest = 0x00000080, maskUnknown = 0x00000000, maskTitaniumAndEarlier = 0x00000003, maskSoFAndEarlier = 0x00000007, @@ -55,10 +57,11 @@ namespace EQ maskUFAndLater = 0xFFFFFFF0, maskRoFAndLater = 0xFFFFFFE0, maskRoF2AndLater = 0xFFFFFFC0, + maskSteamLatestAndLater = 0xFFFFFF80, maskAllClients = 0xFFFFFFFF }; - const ClientVersion LastClientVersion = ClientVersion::RoF2; + const ClientVersion LastClientVersion = ClientVersion::SteamLatest; const size_t ClientVersionCount = (static_cast(LastClientVersion) + 1); bool IsValidClientVersion(ClientVersion client_version); @@ -76,6 +79,7 @@ namespace EQ UF, RoF, RoF2, + SteamLatest NPC, NPCMerchant, Merc, @@ -89,13 +93,14 @@ namespace EQ OfflineSoD, OfflineUF, OfflineRoF, - OfflineRoF2 + OfflineRoF2, + OfflineSteamLatest }; - const MobVersion LastMobVersion = MobVersion::OfflineRoF2; - const MobVersion LastPCMobVersion = MobVersion::RoF2; + const MobVersion LastMobVersion = MobVersion::OfflineSteamLatest; + const MobVersion LastPCMobVersion = MobVersion::SteamLatest; const MobVersion LastNonPCMobVersion = MobVersion::BotPet; - const MobVersion LastOfflinePCMobVersion = MobVersion::OfflineRoF2; + const MobVersion LastOfflinePCMobVersion = MobVersion::OfflineSteamLatest; const size_t MobVersionCount = (static_cast(LastMobVersion) + 1); bool IsValidMobVersion(MobVersion mob_version); @@ -127,7 +132,8 @@ namespace EQ ucsSoDCombined = 'D', ucsUFCombined = 'E', ucsRoFCombined = 'F', - ucsRoF2Combined = 'G' + ucsRoF2Combined = 'G', + ucsSteamLatestCombined = 'H' }; } /*versions*/ @@ -154,7 +160,18 @@ namespace EQ HoT, VoA, RoF, - CotF + CotF, + TDS, + TBM, + EoK, + RoS, + TBL, + ToV, + CoV, + ToL, + NoS, + LS, + TOB }; enum ExpansionBitmask : uint32 { @@ -179,6 +196,17 @@ namespace EQ bitVoA = 0x00020000, bitRoF = 0x00040000, bitCotF = 0x00080000, + bitTDS = 0x00100000, + bitTBM = 0x00200000, + bitEoK = 0x00400000, + bitRoS = 0x00800000, + bitTBL = 0x01000000, + bitToV = 0x02000000, + bitCoV = 0x04000000, + bitToL = 0x08000000, + bitNoS = 0x10000000, + bitLS = 0x20000000, + bitTOB = 0x40000000, maskEverQuest = 0x00000000, maskRoK = 0x00000001, maskSoV = 0x00000003, @@ -200,6 +228,18 @@ namespace EQ maskVoA = 0x0003FFFF, maskRoF = 0x0007FFFF, maskCotF = 0x000FFFFF + maskCotF = 0x000FFFFF, + maskTDS = 0x001FFFFF, + maskTBM = 0x003FFFFF, + maskEoK = 0x007FFFFF, + maskRoS = 0x00FFFFFF, + maskTBL = 0x01FFFFFF, + maskToV = 0x03FFFFFF, + maskCoV = 0x07FFFFFF, + maskToL = 0x0FFFFFFF, + maskNoS = 0x1FFFFFFF, + maskLS = 0x3FFFFFFF, + maskTOB = 0x7FFFFFFF, }; const char* ExpansionName(Expansion expansion); diff --git a/common/eq_constants.h b/common/eq_constants.h index 6e621b0d8..5ca026096 100644 --- a/common/eq_constants.h +++ b/common/eq_constants.h @@ -759,6 +759,46 @@ typedef enum { FilterStrikethrough = 26, //0=show, 1=hide // RoF2 Confirmed FilterStuns = 27, //0=show, 1=hide // RoF2 Confirmed FilterBardSongsOnPets = 28, //0=show, 1=hide // RoF2 Confirmed + FilterSwarmPetDeath = 29, + FilterFellowshipChat = 30, + FilterMercenaryMessages = 31, + FilterSpam = 32, + FilterAchievements = 33, + FilterPvPMessages = 34, + FilterSpellNameInCast = 35, + FilterRandomMine = 36, + FilterRandomGroupRaid = 37, + FilterRandomOthers = 38, + FilterEnvironmentalDamage = 39, + FilterMessages = 40, + FilterOverwriteDetrimental = 41, + FilterOverwriteBeneficial = 42, + FilterCantUseCommand = 43, + FilterCombatAbilityReuse = 44, + FilterAAAbilityReuse = 45, + FilterProcBeginCasting = 46, + FilterDestroyedItems = 47, + FilterYourAuras = 48, + FilterOtherAuras = 49, + FilterYourHeals = 50, + FilterOtherHeals = 51, + FilterYourDoTs = 52, + FilterOtherDoTs = 53, + FilterOtherDirectDamage = 54, + FilterSpellEmotes = 55, + FilterFactionMessages = 56, + FilterTauntMessages = 57, + FilterYourDisciplines = 58, + FilterOtherDisplines = 59, + FilterAchievementsOthers = 60, + FilterRaidVictory = 61, + FilterOtherDirectDamageCrits = 62, + FilterDoTYoursCritical = 63, + FilterDoTOthersCritical = 64, + FilterDoTDamageTaken = 65, + FilterHealsReceived = 66, + FilterHealsYoursCritical = 67, + FilterHealsOthersCritical = 68, _FilterCount } eqFilterType; diff --git a/common/eq_limits.cpp b/common/eq_limits.cpp index 281308af1..97822777e 100644 --- a/common/eq_limits.cpp +++ b/common/eq_limits.cpp @@ -110,6 +110,14 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers RoF2::constants::CHARACTER_CREATION_LIMIT, RoF2::constants::SAY_LINK_BODY_SIZE, RoF2::constants::MAX_BAZAAR_TRADERS + ), + /*[ClientVersion::SteamLatest] =*/ + EQ::constants::LookupEntry( + SteamLatest::constants::EXPANSION, + SteamLatest::constants::EXPANSION_BIT, + SteamLatest::constants::EXPANSIONS_MASK, + SteamLatest::constants::CHARACTER_CREATION_LIMIT, + SteamLatest::constants::SAY_LINK_BODY_SIZE ) }; @@ -376,6 +384,34 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers RoF2::inventory::ConcatenateInvTypeLimbo, RoF2::inventory::AllowOverLevelEquipment ), + /*[MobVersion::SteamLatest] =*/ + //SteamLatestTodo: These need to be set to the latest values not just use RoF2 + EQ::inventory::LookupEntry( + EQ::inventory::LookupEntry::InventoryTypeSize_Struct( + EQ::invtype::POSSESSIONS_SIZE, RoF2::invtype::BANK_SIZE, RoF2::invtype::SHARED_BANK_SIZE, + RoF2::invtype::TRADE_SIZE, RoF2::invtype::WORLD_SIZE, RoF2::invtype::LIMBO_SIZE, + RoF2::invtype::TRIBUTE_SIZE, RoF2::invtype::TROPHY_TRIBUTE_SIZE, RoF2::invtype::GUILD_TRIBUTE_SIZE, + RoF2::invtype::MERCHANT_SIZE, RoF2::invtype::DELETED_SIZE, RoF2::invtype::CORPSE_SIZE, + RoF2::invtype::BAZAAR_SIZE, RoF2::invtype::INSPECT_SIZE, RoF2::invtype::REAL_ESTATE_SIZE, + RoF2::invtype::VIEW_MOD_PC_SIZE, RoF2::invtype::VIEW_MOD_BANK_SIZE, RoF2::invtype::VIEW_MOD_SHARED_BANK_SIZE, + RoF2::invtype::VIEW_MOD_LIMBO_SIZE, RoF2::invtype::ALT_STORAGE_SIZE, RoF2::invtype::ARCHIVED_SIZE, + RoF2::invtype::MAIL_SIZE, RoF2::invtype::GUILD_TROPHY_TRIBUTE_SIZE, RoF2::invtype::KRONO_SIZE, + RoF2::invtype::OTHER_SIZE + ), + + RoF2::invslot::EQUIPMENT_BITMASK, + RoF2::invslot::GENERAL_BITMASK, + RoF2::invslot::CURSOR_BITMASK, + RoF2::invslot::POSSESSIONS_BITMASK, + RoF2::invslot::CORPSE_BITMASK, + RoF2::invbag::SLOT_COUNT, + RoF2::invaug::SOCKET_COUNT, + + RoF2::inventory::AllowEmptyBagInBag, + RoF2::inventory::AllowClickCastFromBag, + RoF2::inventory::ConcatenateInvTypeLimbo, + RoF2::inventory::AllowOverLevelEquipment + ), /*[MobVersion::NPC] =*/ EQ::inventory::LookupEntry( EQ::inventory::LookupEntry::InventoryTypeSize_Struct( @@ -748,6 +784,35 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers RoF2::INULL, RoF2::invbag::SLOT_COUNT, RoF2::invaug::SOCKET_COUNT, + + false, + false, + false, + false + ), + /*[MobVersion::OfflineSteamLatest] =*/ + //SteamLatestTodo: Need to use their own values instead of RoF2 + EQ::inventory::LookupEntry( + EQ::inventory::LookupEntry::InventoryTypeSize_Struct( + RoF2::INULL, RoF2::INULL, RoF2::INULL, + RoF2::invtype::TRADE_SIZE, RoF2::INULL, RoF2::INULL, + RoF2::INULL, RoF2::INULL, RoF2::INULL, + RoF2::invtype::MERCHANT_SIZE, RoF2::INULL, RoF2::INULL, + RoF2::invtype::BAZAAR_SIZE, RoF2::invtype::INSPECT_SIZE, RoF2::INULL, + RoF2::invtype::VIEW_MOD_PC_SIZE, RoF2::invtype::VIEW_MOD_BANK_SIZE, RoF2::invtype::VIEW_MOD_SHARED_BANK_SIZE, + RoF2::invtype::VIEW_MOD_LIMBO_SIZE, RoF2::INULL, RoF2::INULL, + RoF2::INULL, RoF2::INULL, RoF2::INULL, + RoF2::INULL + ), + + RoF2::INULL, + RoF2::INULL, + RoF2::INULL, + RoF2::INULL, + RoF2::INULL, + RoF2::invbag::SLOT_COUNT, + RoF2::invaug::SOCKET_COUNT, + false, false, @@ -1000,6 +1065,11 @@ static const EQ::behavior::LookupEntry behavior_static_lookup_entries[EQ::versio EQ::behavior::LookupEntry( RoF2::behavior::CoinHasWeight ), + /*[MobVersion::SteamLatest] =*/ + //SteamLatestTodo: We need this value set properly + EQ::behavior::LookupEntry( + RoF2::behavior::CoinHasWeight + ), /*[MobVersion::NPC] =*/ EQ::behavior::LookupEntry( EQ::behavior::CoinHasWeight @@ -1053,6 +1123,11 @@ static const EQ::behavior::LookupEntry behavior_static_lookup_entries[EQ::versio RoF::behavior::CoinHasWeight ), /*[MobVersion::OfflineRoF2] =*/ + EQ::behavior::LookupEntry( + RoF2::behavior::CoinHasWeight + ), + /*[MobVersion::OfflineSteamLatest] =*/ + //SteamLatestTodo: We need this value set properly EQ::behavior::LookupEntry( RoF2::behavior::CoinHasWeight ) @@ -1209,6 +1284,19 @@ static const EQ::spells::LookupEntry spells_static_lookup_entries[EQ::versions:: RoF2::spells::PET_BUFFS, RoF2::spells::MERC_BUFFS ) + /*[ClientVersion::SteamLatest] =*/ + EQ::spells::LookupEntry( + SteamLatest::spells::SPELL_ID_MAX, + SteamLatest::spells::SPELLBOOK_SIZE, + UF::spells::SPELL_GEM_COUNT, // client translators are setup to allow the max value a client supports..however, the top 4 indices are not valid in this case + SteamLatest::spells::LONG_BUFFS, + SteamLatest::spells::SHORT_BUFFS, + SteamLatest::spells::DISC_BUFFS, + SteamLatest::spells::TOTAL_BUFFS, + SteamLatest::spells::NPC_BUFFS, + SteamLatest::spells::PET_BUFFS, + SteamLatest::spells::MERC_BUFFS + ) }; static bool spells_dictionary_init = false; diff --git a/common/eq_limits.h b/common/eq_limits.h index 0d79e3041..76260f1af 100644 --- a/common/eq_limits.h +++ b/common/eq_limits.h @@ -23,6 +23,7 @@ #include "common/patches/rof2_limits.h" #include "common/patches/sod_limits.h" #include "common/patches/sof_limits.h" +#include "common/patches/steam_latest_limits.h" #include "common/patches/titanium_limits.h" #include "common/patches/uf_limits.h" #include "common/types.h" diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index c11945cc4..14b2b92c2 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -47,6 +47,13 @@ static const uint32 ADVANCED_LORE_LENGTH = 8192; #pragma pack(push) #pragma pack(1) +struct EqGuid +{ + uint32_t Id; + uint16_t WorldId; + uint16_t Reserved; +}; + struct LoginInfo { /*000*/ char login_info[64]; /*064*/ uint8 unknown064[124]; @@ -326,6 +333,7 @@ union bool buyer; bool untargetable; uint32 npc_tint_id; + EqGuid CharacterGuid; }; struct PlayerState_Struct { diff --git a/common/patches/patches.cpp b/common/patches/patches.cpp index 7e4876a04..94b7a5b53 100644 --- a/common/patches/patches.cpp +++ b/common/patches/patches.cpp @@ -21,6 +21,7 @@ #include "common/patches/rof2.h" #include "common/patches/sod.h" #include "common/patches/sof.h" +#include "common/patches/steam_latest.h" #include "common/patches/titanium.h" #include "common/patches/uf.h" @@ -33,6 +34,7 @@ void RegisterAllPatches(EQStreamIdentifier &into) UF::Register(into); RoF::Register(into); RoF2::Register(into); + SteamLatest::Register(into); } void ReloadAllPatches() @@ -43,4 +45,5 @@ void ReloadAllPatches() UF::Reload(); RoF::Reload(); RoF2::Reload(); + SteamLatest::Reload(); } diff --git a/common/patches/steam_latest.cpp b/common/patches/steam_latest.cpp new file mode 100644 index 000000000..e6ebb4d99 --- /dev/null +++ b/common/patches/steam_latest.cpp @@ -0,0 +1,5750 @@ +#include "../global_define.h" +#include "../eqemu_config.h" +#include "../eqemu_logsys.h" +#include "steam_latest.h" +#include "../opcodemgr.h" + +#include "../eq_stream_ident.h" +#include "../crc32.h" + +#include "../eq_packet_structs.h" +#include "../misc_functions.h" +#include "../strings.h" +#include "../inventory_profile.h" +#include "steam_latest_structs.h" +#include "../rulesys.h" +#include "../path_manager.h" +#include "../classes.h" +#include "../races.h" +#include "../raid.h" + +#include +#include +#include +#include +#include + +namespace SteamLatest +{ + static const char* name = "SteamLatest"; + static OpcodeManager* opcodes = nullptr; + static Strategy struct_strategy; + + void SerializeItem(SerializeBuffer &buffer, const EQ::ItemInstance* inst, int16 slot_id, uint8 depth, ItemPacketType packet_type); + + // message link converters + static inline void ServerToSteamLatestConvertLinks(std::string& message_out, const std::string& message_in); + static inline void SteamLatestToServerConvertLinks(std::string& message_out, const std::string& message_in); + + // SpawnAppearance + static inline uint32 ServerToSteamLatestSpawnAppearanceType(uint32 server_type); + static inline uint32 SteamLatestToServerSpawnAppearanceType(uint32 steam_latest_type); + + // server to client inventory location converters + static inline structs::InventorySlot_Struct ServerToSteamLatestSlot(uint32 server_slot); + static inline structs::InventorySlot_Struct ServerToSteamLatestCorpseSlot(uint32 server_corpse_slot); + static inline uint32 ServerToSteamLatestCorpseMainSlot(uint32 server_corpse_slot); + static inline structs::TypelessInventorySlot_Struct ServerToSteamLatestTypelessSlot(uint32 server_slot, int16 server_type); + + // client to server inventory location converters + static inline uint32 SteamLatestToServerSlot(structs::InventorySlot_Struct steam_latest_slot); + static inline uint32 SteamLatestToServerCorpseSlot(structs::InventorySlot_Struct steam_latest_corpse_slot); + static inline uint32 SteamLatestToServerCorpseMainSlot(uint32 steam_latest_corpse_slot); + static inline uint32 SteamLatestToServerTypelessSlot(structs::TypelessInventorySlot_Struct steam_latest_slot, int16 steam_latest_type); + static inline structs::InventorySlot_Struct SteamLatestCastingInventorySlotToInventorySlot(structs::CastSpellInventorySlot_Struct steam_latest_slot); + static inline structs::CastSpellInventorySlot_Struct SteamLatestInventorySlotToCastingInventorySlot(structs::InventorySlot_Struct steam_latest_slot); + + // Item packet types + static item::ItemPacketType ServerToSteamLatestItemPacketType(ItemPacketType steam_latest_type); + + // casting slots + static inline spells::CastingSlot ServerToSteamLatestCastingSlot(EQ::spells::CastingSlot slot); + static inline EQ::spells::CastingSlot SteamLatestToServerCastingSlot(spells::CastingSlot slot); + + // buff slots + static inline int ServerToSteamLatestBuffSlot(int index); + static inline int SteamLatestToServerBuffSlot(int index); + + void Register(EQStreamIdentifier& into) + { + //create our opcode manager if we havent already + if (opcodes == nullptr) { + + std::string opfile = fmt::format("{}/patch_{}.conf", path.GetPatchPath(), name); + + //load up the opcode manager. + //TODO: figure out how to support shared memory with multiple patches... + opcodes = new RegularOpcodeManager(); + if (!opcodes->LoadOpcodes(opfile.c_str())) { + LogNetcode("[OPCODES] Error loading opcodes file [{}]. Not registering patch [{}]", opfile.c_str(), name); + return; + } + } + + //ok, now we have what we need to register. + + EQStreamInterface::Signature signature; + std::string pname; + + //register our world signature. + pname = std::string(name) + "_world"; + signature.ignore_eq_opcode = 0; + signature.first_length = sizeof(structs::LoginInfo_Struct); + signature.first_eq_opcode = opcodes->EmuToEQ(OP_SendLoginInfo); + into.RegisterPatch(signature, pname.c_str(), &opcodes, &struct_strategy); + + //register our zone signature. + pname = std::string(name) + "_zone"; + signature.ignore_eq_opcode = opcodes->EmuToEQ(OP_AckPacket); + signature.first_length = sizeof(structs::ClientZoneEntry_Struct); + signature.first_eq_opcode = opcodes->EmuToEQ(OP_ZoneEntry); + into.RegisterPatch(signature, pname.c_str(), &opcodes, &struct_strategy); + LogNetcode("[StreamIdentify] Registered patch [{}]", name); + } + + void Reload() + { + //we have a big problem to solve here when we switch back to shared memory + //opcode managers because we need to change the manager pointer, which means + //we need to go to every stream and replace it's manager. + + if (opcodes != nullptr) { + std::string opfile = fmt::format("{}/patch_{}.conf", path.GetPatchPath(), name); + if (!opcodes->ReloadOpcodes(opfile.c_str())) { + LogNetcode("[OPCODES] Error reloading opcodes file [{}] for patch [{}]", opfile.c_str(), name); + return; + } + LogNetcode("[OPCODES] Reloaded opcodes for patch [{}]", name); + } + } + + Strategy::Strategy() : StructStrategy() + { + //all opcodes default to passthrough. +#include "ss_register.h" +#include "steam_latest_ops.h" + } + + std::string Strategy::Describe() const + { + std::string r; + r += "Patch "; + r += name; + return(r); + } + + const EQ::versions::ClientVersion Strategy::ClientVersion() const + { + return EQ::versions::ClientVersion::SteamLatest; + } + +#include "ss_define.h" + + // ENCODE methods + ENCODE(OP_Action) { + ENCODE_LENGTH_EXACT(Action_Struct); + SETUP_DIRECT_ENCODE(Action_Struct, structs::MissileHitInfo); + + //This is mostly figured out; there's two unknowns, only unknown1 is read by the client + OUT(target); + OUT(source); + eq->spell_id = emu->spell; + eq->effect_type = emu->effect_flag; + eq->effective_casting_level = 0; //if you set this to != 0 it will use this level instead of calculating it + eq->unknown1 = 0; + eq->unknown2 = 0; + eq->damage = 0; //client doesn't read this but live sends it here, can just set 0 + eq->modifier = 1.0f + (emu->instrument_mod - 10) / 10.0f; + OUT(force); + OUT(hit_heading); + OUT(hit_pitch); + eq->skill = emu->type; + OUT(level); + + FINISH_ENCODE(); + } + + ENCODE(OP_Animation) + { + ENCODE_LENGTH_EXACT(Animation_Struct); + SETUP_DIRECT_ENCODE(Animation_Struct, structs::Animation_Struct); + + OUT(spawnid); + OUT(action); + OUT(speed); + + FINISH_ENCODE(); + } + + ENCODE(OP_ApplyPoison) + { + ENCODE_LENGTH_EXACT(ApplyPoison_Struct); + SETUP_DIRECT_ENCODE(ApplyPoison_Struct, structs::ApplyPoison_Struct); + + eq->inventorySlot = ServerToSteamLatestTypelessSlot(emu->inventorySlot, EQ::invtype::typePossessions); + OUT(success); + + FINISH_ENCODE(); + } + + ENCODE(OP_AugmentInfo) + { + ENCODE_LENGTH_EXACT(AugmentInfo_Struct); + SETUP_DIRECT_ENCODE(AugmentInfo_Struct, structs::AugmentInfo_Struct); + + OUT(itemid); + OUT(window); + strn0cpy(eq->augment_info, emu->augment_info, 64); + + FINISH_ENCODE(); + } + + ENCODE(OP_BeginCast) + { + ENCODE_LENGTH_EXACT(BeginCast_Struct); + SETUP_DIRECT_ENCODE(BeginCast_Struct, structs::BeginCast_Struct); + + OUT(spell_id); + OUT(caster_id); + OUT(cast_time); + eq->unknown0e = 1; //not sure what this is; but its usually 1 on live + + FINISH_ENCODE(); + } + + ENCODE(OP_BlockedBuffs) + { + ENCODE_LENGTH_EXACT(BlockedBuffs_Struct); + SETUP_DIRECT_ENCODE(BlockedBuffs_Struct, structs::BlockedBuffs_Struct); + + for (uint32 i = 0; i < BLOCKED_BUFF_COUNT; ++i) + eq->SpellID[i] = emu->SpellID[i]; + + for (uint32 i = BLOCKED_BUFF_COUNT; i < structs::BLOCKED_BUFF_COUNT; ++i) + eq->SpellID[i] = -1; + + OUT(Count); + OUT(Pet); + OUT(Initialise); + OUT(Flags); + + FINISH_ENCODE(); + } + + ENCODE(OP_Buff) + { + ENCODE_LENGTH_EXACT(SpellBuffPacket_Struct); + SETUP_DIRECT_ENCODE(SpellBuffPacket_Struct, structs::EQAffectPacket_Struct); + + eq->entity_id = emu->entityid; + eq->unknown004 = 0; + + //fill in affect info + eq->affect.caster_id.Id = emu->buff.player_id; + eq->affect.flags = 0; + eq->affect.spell_id = emu->buff.spellid; + eq->affect.duration = emu->buff.duration; + eq->affect.initial_duration = emu->buff.duration; + eq->affect.hit_count = emu->buff.num_hits; + eq->affect.viral_timer = 0; + eq->affect.modifier = emu->buff.bard_modifier == 10 ? 1.0f : emu->buff.bard_modifier / 10.0f; + eq->affect.y = emu->buff.y; + eq->affect.x = emu->buff.x; + eq->affect.z = emu->buff.z; + eq->affect.level = emu->buff.level; + + eq->slot_id = ServerToSteamLatestBuffSlot(emu->slotid); + if (emu->bufffade == 1) + { + eq->buff_fade = 1; + } + else + { + eq->buff_fade = 2; + } + + EQApplicationPacket* outapp = nullptr; + if (emu->bufffade == 1) + { + // Bit of a hack. OP_Buff appears to add/remove the buff while OP_BuffCreate adds/removes the actual buff icon + outapp = new EQApplicationPacket(OP_BuffCreate, 30); + outapp->WriteUInt32(emu->entityid); + outapp->WriteUInt32(0); // tic timer + outapp->WriteUInt8(0); // Type of OP_BuffCreate packet ? + outapp->WriteUInt16(1); // 1 buff in this packet + outapp->WriteUInt32(ServerToSteamLatestBuffSlot(emu->slotid)); + outapp->WriteUInt32(0xffffffff); // SpellID (0xffff to remove) + outapp->WriteUInt32(0); // Duration + outapp->WriteUInt32(0); // numhits + outapp->WriteUInt8(0); // Caster name + outapp->WriteUInt8(0); // Type + outapp->WriteUInt8(0); // Type + } + + FINISH_ENCODE(); + + if (outapp) { + dest->FastQueuePacket(&outapp); + } + } + + ENCODE(OP_BuffCreate) + { + SETUP_VAR_ENCODE(BuffIcon_Struct); + + //SteamLatest has one extra 0x00 byte before the end byte + uint32 sz = 13 + (17 * emu->count) + emu->name_lengths; // 17 includes nullterm + __packet->size = sz; + __packet->pBuffer = new unsigned char[sz]; + memset(__packet->pBuffer, 0, sz); + + __packet->WriteUInt32(emu->entity_id); + __packet->WriteUInt32(emu->tic_timer); + __packet->WriteUInt8(emu->all_buffs); // 1 indicates all buffs on the player (0 to add or remove a single buff) + __packet->WriteUInt16(emu->count); + + for (int i = 0; i < emu->count; ++i) + { + __packet->WriteUInt32(emu->type == 0 ? ServerToSteamLatestBuffSlot(emu->entries[i].buff_slot) : emu->entries[i].buff_slot); + __packet->WriteUInt32(emu->entries[i].spell_id); + __packet->WriteUInt32(emu->entries[i].tics_remaining); + __packet->WriteUInt32(emu->entries[i].num_hits); // Unknown + __packet->WriteString(emu->entries[i].caster); + } + __packet->WriteUInt8(0); // Unknown1 + __packet->WriteUInt8(emu->type); // Unknown2 + + FINISH_ENCODE(); + } + + ENCODE(OP_CancelTrade) + { + ENCODE_LENGTH_EXACT(CancelTrade_Struct); + SETUP_DIRECT_ENCODE(CancelTrade_Struct, structs::CancelTrade_Struct); + + OUT(fromid); + OUT(action); + + FINISH_ENCODE(); + } + + ENCODE(OP_CastSpell) + { + ENCODE_LENGTH_EXACT(CastSpell_Struct); + SETUP_DIRECT_ENCODE(CastSpell_Struct, structs::CastSpell_Struct); + + eq->slot = static_cast(ServerToSteamLatestCastingSlot(static_cast(emu->slot))); + + OUT(spell_id); + //we should double check this cause it feels wrong + eq->inventory_slot = SteamLatestInventorySlotToCastingInventorySlot(ServerToSteamLatestSlot(emu->inventoryslot)); + //OUT(inventoryslot); + OUT(target_id); + + FINISH_ENCODE(); + } + + ENCODE(OP_ChannelMessage) + { + EQApplicationPacket* in = *p; + *p = nullptr; + + ChannelMessage_Struct* emu = (ChannelMessage_Struct*)in->pBuffer; + + unsigned char* __emu_buffer = in->pBuffer; + + std::string old_message = emu->message; + std::string new_message; + ServerToSteamLatestConvertLinks(new_message, old_message); + + in->size = strlen(emu->sender) + strlen(emu->targetname) + new_message.length() + 43; + + in->pBuffer = new unsigned char[in->size]; + + char* OutBuffer = (char*)in->pBuffer; + + VARSTRUCT_ENCODE_STRING(OutBuffer, emu->sender); + VARSTRUCT_ENCODE_STRING(OutBuffer, emu->targetname); + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Unknown + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Unknown + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->language); + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->chan_num); + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Unknown + VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, 0); // Unknown + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->skill_in_language); + VARSTRUCT_ENCODE_STRING(OutBuffer, new_message.c_str()); + + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Unknown + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Unknown + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Unknown + VARSTRUCT_ENCODE_TYPE(uint16, OutBuffer, 0); // Unknown + VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, 0); // Unknown + + delete[] __emu_buffer; + dest->FastQueuePacket(&in, ack_req); + } + + ENCODE(OP_CharInventory) { + //consume the packet + EQApplicationPacket* in = *p; + *p = nullptr; + + if (!in->size) { + in->size = 4; + in->pBuffer = new uchar[in->size]; + memset(in->pBuffer, 0, in->size); + + dest->FastQueuePacket(&in, ack_req); + return; + } + + //store away the emu struct + uchar* __emu_buffer = in->pBuffer; + + int item_count = in->size / sizeof(EQ::InternalSerializedItem_Struct); + if (!item_count || (in->size % sizeof(EQ::InternalSerializedItem_Struct)) != 0) { + Log(Logs::General, Logs::Netcode, "[STRUCTS] Wrong size on outbound %s: Got %d, expected multiple of %d", + opcodes->EmuToName(in->GetOpcode()), in->size, sizeof(EQ::InternalSerializedItem_Struct)); + + delete in; + return; + } + + EQ::InternalSerializedItem_Struct* eq = (EQ::InternalSerializedItem_Struct*)in->pBuffer; + SerializeBuffer buffer; + buffer.WriteUInt32(item_count); + + for (int index = 0; index < item_count; ++index, ++eq) { + SerializeItem(buffer, (const EQ::ItemInstance*)eq->inst, eq->slot_id, 0, ItemPacketCharInventory); + } + + in->size = buffer.size(); + in->pBuffer = new unsigned char[buffer.size()]; + memcpy(in->pBuffer, buffer.buffer(), buffer.size()); + + delete[] __emu_buffer; + + dest->FastQueuePacket(&in, ack_req); + } + + ENCODE(OP_ClickObjectAction) + { + ENCODE_LENGTH_EXACT(ClickObjectAction_Struct); + SETUP_DIRECT_ENCODE(ClickObjectAction_Struct, structs::ClickObjectAction_Struct); + + OUT(drop_id); + eq->unknown04 = -1; + eq->unknown08 = -1; + OUT(type); + OUT(icon); + eq->unknown16 = 0; + OUT_str(object_name); + + FINISH_ENCODE(); + } + + ENCODE(OP_ClientUpdate) + { + ENCODE_LENGTH_EXACT(PlayerPositionUpdateServer_Struct); + SETUP_DIRECT_ENCODE(PlayerPositionUpdateServer_Struct, structs::PlayerPositionUpdateServer_Struct); + + OUT(spawn_id); + OUT(vehicle_id); + eq->position.x = emu->x_pos; + eq->position.y = emu->y_pos; + eq->position.z = emu->z_pos; + eq->position.heading = emu->heading; + eq->position.deltaX = emu->delta_x; + eq->position.deltaY = emu->delta_y; + eq->position.deltaZ = emu->delta_z; + eq->position.deltaHeading = emu->delta_heading; + eq->position.animation = emu->animation; + + FINISH_ENCODE(); + } + + ENCODE(OP_Consider) + { + ENCODE_LENGTH_EXACT(Consider_Struct); + SETUP_DIRECT_ENCODE(Consider_Struct, structs::Consider_Struct); + + OUT(playerid); + OUT(targetid); + OUT(faction); + OUT(level); + + FINISH_ENCODE(); + } + + ENCODE(OP_Damage) { + SETUP_DIRECT_ENCODE(CombatDamage_Struct, structs::CombatDamage_Struct); + + OUT(target); + OUT(source); + OUT(type); + OUT(spellid); + OUT(damage); + OUT(force); + OUT(hit_heading); + OUT(hit_pitch); + OUT(special); + + FINISH_ENCODE(); + } + + ENCODE(OP_Death) + { + ENCODE_LENGTH_EXACT(Death_Struct); + SETUP_DIRECT_ENCODE(Death_Struct, structs::Death_Struct); + + OUT(spawn_id); + OUT(killer_id); + OUT(spell_id); + OUT(attack_skill); + OUT(damage); + + //This is a hack, we need to actually fix the ordering in source as this wont respect filters etc + if (emu->attack_skill != 231) { + auto combat_packet = new EQApplicationPacket(OP_Damage, sizeof(structs::CombatDamage_Struct)); + structs::CombatDamage_Struct* cds = (structs::CombatDamage_Struct*)combat_packet->pBuffer; + + cds->target = emu->spawn_id; + cds->source = emu->killer_id; + cds->type = emu->attack_skill; + cds->damage = emu->damage; + cds->spellid = -1; + + dest->FastQueuePacket(&combat_packet, ack_req); + } + + FINISH_ENCODE(); + } + + ENCODE(OP_DeleteCharge) + { + Log(Logs::Detail, Logs::Netcode, "SteamLatest::ENCODE(OP_DeleteCharge)"); + + ENCODE_FORWARD(OP_MoveItem); + } + + ENCODE(OP_DeleteItem) + { + ENCODE_LENGTH_EXACT(DeleteItem_Struct); + SETUP_DIRECT_ENCODE(DeleteItem_Struct, structs::DeleteItem_Struct); + + eq->from_slot = ServerToSteamLatestSlot(emu->from_slot); + eq->to_slot = ServerToSteamLatestSlot(emu->to_slot); + OUT(number_in_stack); + + FINISH_ENCODE(); + } + + ENCODE(OP_DeleteSpawn) + { + ENCODE_LENGTH_EXACT(DeleteSpawn_Struct); + SETUP_DIRECT_ENCODE(DeleteSpawn_Struct, structs::DeleteSpawn_Struct); + + OUT(spawn_id); + eq->unknown04 = 1; // Observed + + FINISH_ENCODE(); + } + + ENCODE(OP_DisciplineUpdate) + { + ENCODE_LENGTH_EXACT(Disciplines_Struct); + SETUP_DIRECT_ENCODE(Disciplines_Struct, structs::Disciplines_Struct); + + memcpy(&eq->values, &emu->values, sizeof(Disciplines_Struct)); + + FINISH_ENCODE(); + } + + ENCODE(OP_ExpansionInfo) + { + ENCODE_LENGTH_EXACT(ExpansionInfo_Struct); + SETUP_DIRECT_ENCODE(ExpansionInfo_Struct, structs::ExpansionInfo_Struct); + + OUT(Expansions); + + FINISH_ENCODE(); + } + + ENCODE(OP_ExpUpdate) + { + SETUP_DIRECT_ENCODE(ExpUpdate_Struct, structs::ExpUpdate_Struct); + + //later we should change the underlying server to use this more accurate value + //and encode the 330 in the other patches + eq->exp = emu->exp * 100000 / 330; + + FINISH_ENCODE(); + } + + ENCODE(OP_FormattedMessage) + { + EQApplicationPacket* in = *p; + *p = nullptr; + + FormattedMessage_Struct* emu = (FormattedMessage_Struct*)in->pBuffer; + + char* old_message_ptr = (char*)in->pBuffer; + old_message_ptr += sizeof(FormattedMessage_Struct); + + std::string old_message_array[9]; + + for (int i = 0; i < 9; ++i) { + if (*old_message_ptr == 0) { break; } + old_message_array[i] = old_message_ptr; + old_message_ptr += old_message_array[i].length() + 1; + } + + SerializeBuffer buffer; + buffer.WriteUInt32(emu->unknown0); + buffer.WriteUInt8(0); // Observed + buffer.WriteUInt32(emu->string_id); + buffer.WriteUInt32(emu->type); + + for (int i = 0; i < 9; ++i) { + std::string new_message; + ServerToSteamLatestConvertLinks(new_message, old_message_array[i]); + buffer.WriteLengthString(new_message); + } + + auto outapp = new EQApplicationPacket(OP_FormattedMessage, buffer.size()); + outapp->WriteData(buffer.buffer(), buffer.size()); + dest->FastQueuePacket(&outapp, ack_req); + + delete in; + } + + ENCODE(OP_GMTraining) { + ENCODE_LENGTH_EXACT(GMTrainee_Struct); + SETUP_DIRECT_ENCODE(GMTrainee_Struct, structs::GMTrainee_Struct); + + OUT(npcid); + OUT(playerid); + + for (int i = 0; i < 100; ++i) { + OUT(skills[i]); + } + + eq->unknown408[0] = 1; + eq->unknown408[1] = 0xC9; + eq->unknown408[2] = 0xC9; + eq->unknown408[3] = 0xC9; + eq->unknown408[4] = 0xC9; + eq->unknown408[5] = 0xC9; + eq->unknown408[6] = 0xC9; + eq->unknown408[7] = 0xC9; + eq->unknown408[8] = 0xC9; + eq->unknown408[9] = 0xC9; + eq->unknown408[10] = 0xC9; + eq->unknown408[11] = 0xC9; + eq->unknown408[12] = 0xC9; + eq->unknown408[13] = 0xC9; + eq->unknown408[14] = 0xC9; + eq->unknown408[15] = 0xC9; + eq->unknown408[16] = 0xC9; + eq->unknown408[17] = 0xC9; + eq->unknown408[18] = 0xC9; + eq->unknown408[19] = 0xC9; + eq->unknown408[20] = 0xC9; + eq->unknown408[21] = 0xC9; + eq->unknown408[22] = 0xC9; + eq->unknown408[23] = 0xC9; + eq->unknown408[24] = 0xC9; + eq->unknown408[25] = 0xC9; + eq->unknown408[26] = 0xC9; + eq->unknown408[27] = 0xC9; + eq->unknown408[28] = 0xC9; + eq->unknown408[29] = 0xC9; + eq->unknown408[30] = 0xC9; + eq->unknown408[31] = 0xC9; + eq->unknown408[32] = 0xC9; + eq->unknown408[33] = 0xCA; //the client far as I can tell doesn't read past the 1 byte + 32 0xc9s, but still setting it to what we saw + eq->unknown408[34] = 0x8C; + eq->unknown408[35] = 0xEC; + + FINISH_ENCODE(); + } + + ENCODE(OP_GMTrainSkillConfirm) + { + ENCODE_LENGTH_EXACT(GMTrainSkillConfirm_Struct); + SETUP_DIRECT_ENCODE(GMTrainSkillConfirm_Struct, structs::GMTrainSkillConfirm_Struct); + + OUT(SkillID); + OUT(Cost); + OUT(NewSkill); + OUT_str(TrainerName); + + FINISH_ENCODE(); + } + + ENCODE(OP_GroundSpawn) + { + EQApplicationPacket* in = *p; + *p = nullptr; + Object_Struct* emu = (Object_Struct*)in->pBuffer; + + SerializeBuffer buffer; + buffer.WriteUInt32(emu->drop_id); + buffer.WriteString(emu->object_name); + buffer.WriteUInt16(emu->zone_id); + buffer.WriteUInt16(emu->zone_instance); + buffer.WriteUInt32(emu->drop_id); //this is some other sub but it's okay to duplicate + buffer.WriteUInt32(0); //expires + buffer.WriteFloat(emu->heading); + buffer.WriteFloat(emu->tilt_x); + buffer.WriteFloat(emu->tilt_y); + buffer.WriteFloat(emu->size != 0 && (float)emu->size < 5000.f ? (float)((float)emu->size / 100.0f) : 1.f); //size, with weird peq hack + buffer.WriteFloat(emu->y); + buffer.WriteFloat(emu->x); + buffer.WriteFloat(emu->z); + buffer.WriteFloat(emu->object_type); //weight + + auto outapp = new EQApplicationPacket(OP_GroundSpawn, buffer.size()); + outapp->WriteData(buffer.buffer(), buffer.size()); + dest->FastQueuePacket(&outapp, ack_req); + + delete in; + } + + ENCODE(OP_HPUpdate) + { + SETUP_DIRECT_ENCODE(SpawnHPUpdate_Struct, structs::SpawnHPUpdate_Struct); + + OUT(spawn_id); + OUT(cur_hp); + OUT(max_hp); + + FINISH_ENCODE(); + } + + ENCODE(OP_Illusion) + { + ENCODE_LENGTH_EXACT(Illusion_Struct); + SETUP_DIRECT_ENCODE(Illusion_Struct, structs::Illusion_Struct); + + OUT(spawnid); + OUT_str(charname); + OUT(race); + eq->unknown006[0] = 0; + eq->unknown006[1] = 0; + OUT(gender); + OUT(texture); + OUT(helmtexture); + OUT(face); + OUT(hairstyle); + OUT(haircolor); + OUT(beard); + OUT(beardcolor); + OUT(size); + OUT(drakkin_heritage); + OUT(drakkin_tattoo); + OUT(drakkin_details); + + FINISH_ENCODE(); + } + + ENCODE(OP_ItemPacket) + { + EQApplicationPacket* in = *p; + *p = nullptr; + uchar* __emu_buffer = in->pBuffer; + ItemPacket_Struct* old_item_pkt = (ItemPacket_Struct*)__emu_buffer; + + auto type = ServerToSteamLatestItemPacketType(old_item_pkt->PacketType); + if (type == item::ItemPacketType::ItemPacketInvalid) { + delete in; + return; + } + + switch (type) + { + case item::ItemPacketType::ItemPacketParcel: { + ParcelMessaging_Struct pms{}; + EQ::Util::MemoryStreamReader ss(reinterpret_cast(in->pBuffer), in->size); + cereal::BinaryInputArchive ar(ss); + ar(pms); + + uint32 player_name_length = pms.player_name.length(); + uint32 note_length = pms.note.length(); + + auto* int_struct = (EQ::InternalSerializedItem_Struct*)pms.serialized_item.data(); + + SerializeBuffer buffer; + buffer.WriteInt32((int32_t)type); + SerializeItem(buffer, (const EQ::ItemInstance*)int_struct->inst, int_struct->slot_id, 0, old_item_pkt->PacketType); + + buffer.WriteUInt32(pms.sent_time); + buffer.WriteLengthString(pms.player_name); + buffer.WriteLengthString(pms.note); + + auto outapp = new EQApplicationPacket(OP_ItemPacket, buffer.size()); + outapp->WriteData(buffer.buffer(), buffer.size()); + dest->FastQueuePacket(&outapp, ack_req); + break; + } + default: { + EQ::InternalSerializedItem_Struct* int_struct = (EQ::InternalSerializedItem_Struct*)(&__emu_buffer[4]); + SerializeBuffer buffer; + buffer.WriteInt32((int32_t)type); + SerializeItem(buffer, (const EQ::ItemInstance*)int_struct->inst, int_struct->slot_id, 0, old_item_pkt->PacketType); + + auto outapp = new EQApplicationPacket(OP_ItemPacket, buffer.size()); + outapp->WriteData(buffer.buffer(), buffer.size()); + dest->FastQueuePacket(&outapp, ack_req); + } + } + + delete in; + } + + ENCODE(OP_LogServer) { + SETUP_VAR_ENCODE(LogServer_Struct); + ALLOC_LEN_ENCODE(1840); + + //pvp + if (emu->enable_pvp) { + *(char*)&__packet->pBuffer[0x04] = 1; + } + + if (emu->enable_FV) { + //FV sets these both to 1 + //one appears to enable the no drop flag the other just marks the server as special? + *(char*)&__packet->pBuffer[0x08] = 1; + *(char*)&__packet->pBuffer[0x0a] = 1; + } + + //This has something to do with heirloom and prestige items but im not sure what it does + //Seems to sit at 0 + *(char*)&__packet->pBuffer[0x71d] = 0; + + //not sure what this does, something to do with server select + *(char*)&__packet->pBuffer[0x09] = 0; + + //this appears to have some effect on the tradeskill system; disabling made by tags perhaps? + *(char*)&__packet->pBuffer[0x0b] = 0; + + //not sure, setting it to the value ive seen + *(char*)&__packet->pBuffer[0x0c] = 1; + + //Something to do with languages + *(char*)&__packet->pBuffer[0x0d] = 1; + + //These seem to affect if server has betabuff enabled + *(char*)&__packet->pBuffer[0x5c0] = 0; + *(char*)&__packet->pBuffer[0x5c1] = 0; + //This is set on test so it's probably indicating this is a test server + *(char*)&__packet->pBuffer[0x5c2] = 0; + + //not sure, but it's grouped with the beta and test stuff + *(char*)&__packet->pBuffer[0x5c3] = 0; + + //world short name + strncpy((char*)&__packet->pBuffer[0x15], emu->worldshortname, 32); + + //not sure, affects some player calculation but didn't care to look more + *(char*)&__packet->pBuffer[0x5c2] = 0; + + //Looks right + if (emu->enablemail) { + *(char*)&__packet->pBuffer[0x5b5] = 1; + } + + //Looks right + if (emu->enablevoicemacros) { + *(char*)&__packet->pBuffer[0x5b4] = 1; + } + + //Not sure, sending what we've seen + *(char*)&__packet->pBuffer[0x5b6] = 0; + + //Not sure sending what we've seen + *(char*)&__packet->pBuffer[0x5b8] = 1; + + //Not sure sending what we've seen + *(int32_t*)&__packet->pBuffer[0x5fc] = -1; + + //Test sets this to 1, everyone else seems to set it to 0 + *(int32_t*)&__packet->pBuffer[0x600] = 0; + + //Disassembly puts it next to code dealing with commands, ive not seen anyone send anything but 0 + *(char*)&__packet->pBuffer[0x705] = 0; + + //Something about item restrictions, seems to always be set to 1 + *(char*)&__packet->pBuffer[0x710] = 1; + + //This and 0x724 are often multiplied together in guild favor calcs, live and test send 1.0f + *(float*)&__packet->pBuffer[0x720] = 1.0f; + *(float*)&__packet->pBuffer[0x724] = 1.0f; + + //This and 0x72c are often multiplied together in non-guild favor calcs, live and test send 1.0f + *(float*)&__packet->pBuffer[0x728] = 1.0f; + *(float*)&__packet->pBuffer[0x72c] = 1.0f; + + FINISH_ENCODE(); + } + + ENCODE(OP_ManaChange) { + ENCODE_LENGTH_EXACT(ManaChange_Struct); + SETUP_DIRECT_ENCODE(ManaChange_Struct, structs::ManaChange_Struct); + + OUT(new_mana); + OUT(stamina); + OUT(spell_id); + OUT(keepcasting); + OUT(slot); + + FINISH_ENCODE(); + } + + ENCODE(OP_MobHealth) { + ENCODE_LENGTH_EXACT(SpawnHPUpdate_Struct2); + SETUP_DIRECT_ENCODE(SpawnHPUpdate_Struct2, structs::MobHealth_Struct); + + OUT(spawn_id); + OUT(hp); + + FINISH_ENCODE(); + } + + ENCODE(OP_MoneyOnCorpse) + { + ENCODE_LENGTH_EXACT(moneyOnCorpseStruct); + SETUP_DIRECT_ENCODE(moneyOnCorpseStruct, structs::moneyOnCorpseStruct); + + eq->type = emu->response; + OUT(platinum); + OUT(gold); + OUT(silver); + OUT(copper); + eq->flags = 0; + + FINISH_ENCODE(); + } + + ENCODE(OP_MoveItem) + { + ENCODE_LENGTH_EXACT(MoveItem_Struct); + SETUP_DIRECT_ENCODE(MoveItem_Struct, structs::MoveItem_Struct); + + Log(Logs::Detail, Logs::Netcode, "SteamLatest::ENCODE(OP_MoveItem)"); + + eq->from_slot = ServerToSteamLatestSlot(emu->from_slot); + eq->to_slot = ServerToSteamLatestSlot(emu->to_slot); + OUT(number_in_stack); + + FINISH_ENCODE(); + } + + ENCODE(OP_NewSpawn) { ENCODE_FORWARD(OP_ZoneSpawns); } + + ENCODE(OP_NewZone) { + EQApplicationPacket* in = *p; + *p = nullptr; + + unsigned char* __emu_buffer = in->pBuffer; + NewZone_Struct* emu = (NewZone_Struct*)__emu_buffer; + + SerializeBuffer buffer; + /* + char Shortname[]; + char Longname[]; + char WeatherType[]; + char WeatherTypeOverride[]; + char SkyType[]; + char SkyTypeOverride[]; + */ + buffer.WriteString(emu->zone_short_name); + buffer.WriteString(emu->zone_long_name); + buffer.WriteString(""); + buffer.WriteString(""); + buffer.WriteString(""); + buffer.WriteString(""); + + /* + s32 ZoneType; + s16 ZoneId; + s16 ZoneInstance; + float ZoneExpModifier; + s32 GroupLvlExpRelated; + s32 FilterID; + s32 Unknown1; + */ + buffer.WriteInt32(emu->ztype); + buffer.WriteInt16(emu->zone_id); + buffer.WriteInt16(emu->zone_instance); + buffer.WriteFloat(emu->zone_exp_multiplier); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + + //float FogDensity; + buffer.WriteFloat(emu->fog_density); + + //WeatherState state[4]; + for (int i = 0; i < 4; ++i) { + /* + float FogStart; + float FogEnd; + u8 FogRed; + u8 FogGreen; + u8 FogBlue; + u8 RainChance; + u8 RainDuration; + u8 SnowChance; + u8 SnowDuration; + */ + + buffer.WriteFloat(emu->fog_minclip[i]); + buffer.WriteFloat(emu->fog_maxclip[i]); + buffer.WriteUInt8(emu->fog_red[i]); + buffer.WriteUInt8(emu->fog_green[i]); + buffer.WriteUInt8(emu->fog_blue[i]); + buffer.WriteUInt8(emu->rain_chance[i]); + buffer.WriteUInt8(emu->rain_duration[i]); + buffer.WriteUInt8(emu->snow_chance[i]); + buffer.WriteUInt8(emu->snow_duration[i]); + + } + + /* + u8 PrecipitationType; + float BloomIntensity; + float ZoneGravity; + s32 LavaDamage; + s32 MinLavaDamage; + */ + buffer.WriteUInt8(emu->sky); + buffer.WriteFloat(1.0f); + buffer.WriteFloat(emu->gravity); + buffer.WriteInt32(emu->lava_damage); + buffer.WriteInt32(emu->min_lava_damage); + + /* + s32 TimeStringID; + s32 Unknown3; + s32 SkyLock; + s32 SkyLockOverride; + */ + buffer.WriteInt32(0); + buffer.WriteInt32(1); + buffer.WriteInt32(0); + buffer.WriteInt32(-1); + + /* + float SafeY; + float SafeX; + float SafeZ; + float SafeHeading; + float Ceiling; + float Floor; + */ + + buffer.WriteFloat(emu->safe_y); + buffer.WriteFloat(emu->safe_x); + buffer.WriteFloat(emu->safe_z); + buffer.WriteFloat(emu->safe_heading); + buffer.WriteFloat(emu->max_z); + buffer.WriteFloat(emu->underworld); + + /* + float MinClip; + float MaxClip; + s32 FallThroughWorldTeleportID; + */ + buffer.WriteFloat(emu->minclip); + buffer.WriteFloat(emu->maxclip); + buffer.WriteInt32(emu->underworld_teleport_index); + + /* + s32 Unknown4; + s32 ScriptIDHour; + s32 ScriptIDMinute; + s32 ScriptIDTick; + s32 ScriptIDOnPlayerDeath; + s32 ScriptIDOnNPCDeath; + s32 ScriptIDPlayerEnteringZone; + s32 ScriptIDOnZonePop; + s32 ScriptIDNPCLoot; + s32 Unknown4b; + s32 ScriptIDOnFishing; + s32 ScriptIDOnForage; + s32 Unknown4c; + */ + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + + /* + s32 NPCAgroMaxDist; + */ + buffer.WriteInt32(600); + + /* + s32 ForageLow; + s32 ForageMedium; + s32 ForageHigh; + s32 ForageSpecial; + s32 FishingLow; + s32 FishingMedium; + s32 FishingHigh; + s32 FishingRelated; + */ + buffer.WriteInt32(-1); + buffer.WriteInt32(-1); + buffer.WriteInt32(-1); + buffer.WriteInt32(-1); + buffer.WriteInt32(-1); + buffer.WriteInt32(-1); + buffer.WriteInt32(-1); + buffer.WriteInt32(-1); + + /* + s32 CanPlaceCampsite; + s32 CanPlaceGuildBanner; + s32 Unknown4d; + */ + + buffer.WriteInt32(2); + buffer.WriteInt32(2); + buffer.WriteInt32(0); + + /* + s32 FastRegenHP; + s32 FastRegenMana; + s32 FastRegenEndurance; + */ + buffer.WriteInt32(emu->fast_regen_hp); + buffer.WriteInt32(emu->fast_regen_mana); + buffer.WriteInt32(emu->fast_regen_endurance); + + /* + u8 NewEngineZone; + u8 SkyEnabled; + u8 FogOnOff; + u8 ClimateType; + u8 bNoPlayerLight; + */ + buffer.WriteUInt8(0); //not sure what happens if we set this incorrectly but we probably need to add this to the zone database + buffer.WriteUInt8(1); + buffer.WriteUInt8(1); + buffer.WriteUInt8(0); + buffer.WriteUInt8(0); + + /* + u8 bUnknown5; + u8 bNoAttack; + u8 bAllowPVP; + u8 bNoEncumber; + u8 Unknown6; + u8 Unknown7; + */ + buffer.WriteUInt8(1); + buffer.WriteUInt8(0); + buffer.WriteUInt8(1); + buffer.WriteUInt8(0); + buffer.WriteUInt8(0); + buffer.WriteUInt8(0); + + /* + u8 bNoLevitate; + u8 bNoBuffExpiration; + u8 bDisallowManaStone; + u8 bNoBind; + u8 bNoCallOfTheHero; + u8 bUnknown8; + u8 bNoFear; + u8 bUnknown9; + */ + + buffer.WriteUInt8(0); + buffer.WriteUInt8(emu->suspend_buffs); + buffer.WriteUInt8(1); + buffer.WriteUInt8(0); + buffer.WriteUInt8(0); + buffer.WriteUInt8(0); + buffer.WriteUInt8(0); + buffer.WriteUInt8(0); + + auto outapp = new EQApplicationPacket(OP_NewZone, buffer.size()); + outapp->WriteData(buffer.buffer(), buffer.size()); + dest->FastQueuePacket(&outapp, ack_req); + + delete in; + } + + ENCODE(OP_OnLevelMessage) + { + EQApplicationPacket* in = *p; + *p = nullptr; + OnLevelMessage_Struct* emu = (OnLevelMessage_Struct*)in->pBuffer; + SerializeBuffer buffer; + + buffer.WriteLengthString(emu->Title); + buffer.WriteLengthString(emu->Text); + buffer.WriteLengthString(emu->ButtonName0); + buffer.WriteLengthString(emu->ButtonName1); + buffer.WriteUInt8(emu->Buttons); + buffer.WriteUInt8(emu->SoundControls); + buffer.WriteUInt32(emu->Duration); + buffer.WriteUInt32(emu->PopupID); + buffer.WriteUInt32(emu->NegativeID); + buffer.WriteUInt32(0); //seen -1 & 0 + buffer.WriteUInt32(0); //seen 0 + + auto outapp = new EQApplicationPacket(OP_OnLevelMessage, buffer.size()); + outapp->WriteData(buffer.buffer(), buffer.size()); + dest->FastQueuePacket(&outapp, ack_req); + + delete in; + } + + ENCODE(OP_PlayerProfile) { + EQApplicationPacket* in = *p; + *p = nullptr; + + unsigned char* __emu_buffer = in->pBuffer; + PlayerProfile_Struct* emu = (PlayerProfile_Struct*)__emu_buffer; + + SerializeBuffer out; + + /* + u32 crc; + u32 length; + */ + out.WriteUInt32(0); + out.WriteUInt32(0); + + //PcProfile begin + /* + u32 profile_type; + u32 profile_id; + u32 shroud_template_id; + */ + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + + /* + u8 gender; + u32 race; + u32 class; + u8 level; + u8 level1; + */ + out.WriteUInt8(emu->gender); + out.WriteUInt32(emu->race); + out.WriteUInt32(emu->class_); + out.WriteUInt8(emu->level); + out.WriteUInt8(emu->level); + + //u32 bind_count; + out.WriteUInt32(5); + + for (int r = 0; r < 5; r++) + { + /* + u32 zoneid; + float x; + float y; + float z; + float heading; + */ + out.WriteUInt32(emu->binds[r].zone_id); + out.WriteFloat(emu->binds[r].x); + out.WriteFloat(emu->binds[r].y); + out.WriteFloat(emu->binds[r].z); + out.WriteFloat(emu->binds[r].heading); + } + + /* + u32 deity; + u32 intoxication; + */ + out.WriteUInt32(emu->deity); + out.WriteUInt32(emu->intoxication); + + //u32 property_count; + out.WriteUInt32(10); // properties count + + //u32 properties[property_count]; + for (int i = 0; i < 10; i++) { + out.WriteUInt32(0); + } + + //u32 armor_prop_count; + out.WriteUInt32(22); //armor count + for (int i = 0; i < 22; ++i) { + /* + s32 type; + s32 variation; + s32 material; + s32 newArmorId; + s32 newArmorType; + */ + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + } + + //u32 base_armor_prop_count; + out.WriteUInt32(9); //base armor count + for (int i = 0; i < 9; ++i) { + /* + s32 type; + s32 variation; + s32 material; + s32 newArmorId; + s32 newArmorType; + */ + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + } + + //u32 body_tint_count; + out.WriteUInt32(9); //body_tint_count + //u32 body_tints[body_tint_count]; + for (int i = 0; i < 9; ++i) { + out.WriteUInt32(0); + } + + //u32 equip_tint_count; + out.WriteUInt32(9); //equip_tint_count + //u32 equip_tints[equip_tint_count]; + for (int i = 0; i < 9; ++i) { + out.WriteUInt32(0); + } + + /* + u8 hair_color; + u8 facial_hair_color; + u32 npc_tint_index; + u8 eye_color1; + u8 eye_color2; + u8 hair_style; + u8 facial_hair; + u8 face; + u8 old_face; + u32 heritage; + u32 tattoo; + u32 details; + */ + out.WriteUInt8(emu->haircolor); + out.WriteUInt8(emu->beardcolor); + out.WriteUInt32(0); //npc tint index + out.WriteUInt8(emu->eyecolor1); + out.WriteUInt8(emu->eyecolor2); + out.WriteUInt8(emu->hairstyle); + out.WriteUInt8(emu->beard); + out.WriteUInt8(emu->face); + out.WriteUInt8(0); //old face + out.WriteUInt32(emu->drakkin_heritage); + out.WriteUInt32(emu->drakkin_tattoo); + out.WriteUInt32(emu->drakkin_details); + + /* + u8 texture_type; + u8 material; + u8 variation; + */ + out.WriteUInt8(0); + out.WriteUInt8(0); + out.WriteUInt8(0); + + /* + float height; + float width; + float length; + float view_height; + */ + out.WriteFloat(5.0f); + out.WriteFloat(3.0f); + out.WriteFloat(2.5f); + out.WriteFloat(5.5f); + + /* + u32 primary; + u32 secondary; + */ + out.WriteUInt32(0); + out.WriteUInt32(0); + + /* + u32 practices; + u32 base_mana; + u32 base_hp; + u32 base_str; + u32 base_sta; + u32 base_cha; + u32 base_dex; + u32 base_int; + u32 base_agi; + u32 base_wis; + u32 base_heroic_str; + u32 base_heroic_sta; + u32 base_heroic_cha; + u32 base_heroic_dex; + u32 base_heroic_int; + u32 base_heroic_agi; + u32 base_heroic_wis; + */ + out.WriteUInt32(emu->points); + out.WriteUInt32(emu->mana); + out.WriteUInt32(emu->cur_hp); + out.WriteUInt32(emu->STR); + out.WriteUInt32(emu->STA); + out.WriteUInt32(emu->CHA); + out.WriteUInt32(emu->DEX); + out.WriteUInt32(emu->INT); + out.WriteUInt32(emu->AGI); + out.WriteUInt32(emu->WIS); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + + //u32 aa_count; + out.WriteUInt32(300); + for (int i = 0; i < 240; ++i) { + /* + s32 index; + s32 points_spent; + s32 charges_spent; + u8 unknown1; + */ + out.WriteUInt32(emu->aa_array[i].AA); + out.WriteUInt32(emu->aa_array[i].value); + out.WriteUInt32(emu->aa_array[i].charges); + out.WriteUInt8(0); + } + + for (int i = 0; i < 60; ++i) { + /* + s32 index; + s32 points_spent; + s32 charges_spent; + u8 unknown1; //not sure about this one + */ + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt8(0); + } + + /*u32 skill_count;*/ + out.WriteUInt32(100); + //s32 skills[skill_count]; + for (int i = 0; i < 100; ++i) { + out.WriteUInt32(emu->skills[i]); + } + + //u32 innate_skill_count; + out.WriteUInt32(25); + //s32 innate_skills[innate_skill_count]; + for (int i = 0; i < 25; ++i) { + out.WriteUInt32(emu->InnateSkills[i]); + } + + /* + u32 combat_ability_count; + */ + out.WriteUInt32(300); + //s32 combat_abilities[combat_ability_count]; + for (int i = 0; i < 100; ++i) { + out.WriteUInt32(emu->disciplines.values[i]); + } + + for (int i = 0; i < 200; ++i) { + out.WriteUInt32(0); + } + + //u32 combat_ability_timer_count; + out.WriteUInt32(25); + //s32 combat_ability_timers[combat_ability_timer_count]; + for (int i = 0; i < 20; ++i) { + out.WriteUInt32(emu->disciplines.values[i]); + } + + for (int i = 0; i < 5; ++i) { + out.WriteUInt32(0); + } + + //u32 unk_ability_count; + out.WriteUInt32(0); + + //u32 linked_spell_timer_count; + out.WriteUInt32(25); + //s32 linked_spell_timers[linked_spell_timer_count]; + for (int i = 0; i < 25; ++i) { + out.WriteUInt32(0); + } + + //u32 item_recast_timer_count; + out.WriteUInt32(100); + //s32 item_recast_timers[item_recast_timer_count]; + for (int i = 0; i < 100; ++i) { + out.WriteUInt32(0); + } + + //u32 spell_book_slot_count; + out.WriteUInt32(1120); + + //s32 spell_book_slots[spell_book_slot_count]; + for (int i = 0; i < 720; ++i) { + out.WriteUInt32(emu->spell_book[i]); + } + + for (int i = 0; i < 400; ++i) { + out.WriteUInt32(0xFFFFFFFF); + } + + //u32 spell_gem_count; + out.WriteUInt32(18); + //s32 spell_gems[spell_gem_count]; + for (int i = 0; i < 12; ++i) { + out.WriteUInt32(emu->mem_spells[i]); + } + + for (int i = 0; i < 6; ++i) { + out.WriteUInt32(0xFFFFFFFF); + } + + /* + u32 spell_recast_timer_count; + */ + out.WriteUInt32(15); + + //s32 spell_recast_timers[spell_recast_timer_count]; + for (int i = 0; i < 12; ++i) { + out.WriteUInt32(emu->spellSlotRefresh[i]); + } + + for (int i = 0; i < 3; ++i) { + out.WriteUInt32(0); + } + + //u8 max_allowed_spell_slots; + out.WriteUInt8(0); + + //u32 buff_count; + out.WriteUInt32(42); + + //PackedEQAffect buffs[buff_count]; + //todo: fix + for (int i = 0; i < 42; ++i) { + /* + struct EQAffect + { + float modifier; + EqGuid caster; + u32 duration; + u32 max_duration; + u8 level; + s32 spell_id; + s32 hitcount; + u32 flags; + u32 viral_timer; + u8 type; + SlotData slots[6]; + }; + + */ + out.WriteFloat(1.0f); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt8(0); + out.WriteUInt32(0xFFFFFFFF); + out.WriteUInt32(0); + out.WriteUInt8(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + //SlotData slots[6]; + for (int j = 0; j < 6; ++j) { + /* + s32 slot_id; + s64 value; + */ + out.WriteInt32(-1); + out.WriteUInt64(0); + } + } + + //Coin coin; + /* + u32 platinum; + u32 gold; + u32 silver; + u32 copper; + */ + out.WriteUInt32(emu->platinum); + out.WriteUInt32(emu->gold); + out.WriteUInt32(emu->silver); + out.WriteUInt32(emu->copper); + + //Coin cursor_coin; + /* + u32 platinum; + u32 gold; + u32 silver; + u32 copper; + */ + out.WriteUInt32(emu->platinum_cursor); + out.WriteUInt32(emu->gold_cursor); + out.WriteUInt32(emu->silver_cursor); + out.WriteUInt32(emu->copper_cursor); + + /* + u32 disc_timer; + u32 mend_timer; + u32 forage_timer; + u32 thirst; + u32 hunger; + */ + + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(emu->thirst_level); + out.WriteUInt32(emu->hunger_level); + + //u32 aa_spent; + out.WriteUInt32(emu->aapoints_spent); + + //u32 aa_window_count; + out.WriteUInt32(6); + //u32 aa_window_stats[aa_window_count]; + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + + //u32 aa_points_unspent; + out.WriteUInt32(emu->aapoints); + + /* + u8 sneak; + u8 hide; + */ + out.WriteUInt8(0); + out.WriteUInt8(0); + + //u32 bandolier_count; + out.WriteUInt32(20); + + //BandolierSet bandolier_sets[bandolier_count]; + for (int i = 0; i < 20; ++i) { + //char name[]; + out.WriteString(emu->bandoliers[i].Name); + + //BandolierItemInfo items[4]; + for (int j = 0; j < 4; ++j) { + //char name[]; + out.WriteString(emu->bandoliers[i].Items[j].Name); + //s32 item_id; + out.WriteUInt32(emu->bandoliers[i].Items[j].ID); + //s32 icon; + out.WriteUInt32(emu->bandoliers[i].Items[j].Icon); + } + } + + //u32 invslot_bitmask; + out.WriteUInt32(0xFFFFFFFF); + + /* + u32 basedata_hp; + u32 basedata_mana; + u32 basedata_endur; + u32 basedata_mr; + u32 basedata_fr; + u32 basedata_cr; + u32 basedata_pr; + u32 basedata_dr; + u32 basedata_corrupt; + u32 basedata_phr; + */ + + out.WriteUInt32(5); + out.WriteUInt32(5); + out.WriteUInt32(5); + out.WriteUInt32(25); + out.WriteUInt32(25); + out.WriteUInt32(25); + out.WriteUInt32(15); + out.WriteUInt32(15); + out.WriteUInt32(15); + out.WriteUInt32(15); + + /* + float basedata_walkspeed; + float basedata_runspeed; + */ + + out.WriteFloat(0.46f); + out.WriteFloat(0.7f); + + /* + u32 basedata_hpregen; + u32 basedata_manaregen; + u32 basedata_mountmanaregen; + u32 basedata_endurregen; + u32 basedata_ac; + u32 basedata_atk; + u32 basedata_dmg; + u32 basedata_delay; + u32 endurance; + u32 heroic_type; + */ + + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(emu->endurance); + out.WriteUInt32(0); + + //ItemIndex keyring_item_index[5]; + for (int i = 0; i < 5; ++i) { + /* + s16 slot1; + s16 slot2; + s16 slot3; + */ + + out.WriteInt16(-1); + out.WriteInt16(-1); + out.WriteInt16(-1); + } + + /* + u64 exp; + u32 aa_exp; + */ + out.WriteUInt64(emu->exp); + out.WriteUInt32(emu->expAA); + + //PcClient begin + /* + u32 character_id; + u16 world_id; + u16 reserved; + */ + out.WriteUInt32(emu->char_id); + out.WriteUInt16(RuleI(World, Id)); + out.WriteUInt16(0); + + /*u32 name_length;*/ + /*char name[name_length];*/ + out.WriteLengthString(64, emu->name); + + //u32 last_name_length; + //char last_name[last_name_length]; + out.WriteLengthString(32, emu->last_name); + + /* + u32 creation_time; + u32 account_creation_time; + u32 last_played_time; + u32 played_minutes; + u32 entitled_days; + u32 expansion_flags; + */ + + out.WriteUInt32(emu->birthday); + out.WriteUInt32(emu->birthday); + out.WriteUInt32(emu->lastlogin); + out.WriteUInt32(5000); + out.WriteUInt32(6000); + out.WriteUInt32(0x3FFFFFFF); + + //u32 language_count; + out.WriteUInt32(32); + for (int i = 0; i < 28; i++) + { + //u8 languages[language_count]; + out.WriteUInt8(emu->languages[i]); + } + + for (int i = 0; i < 4; i++) + { + out.WriteUInt8(0); + } + + /* + u32 current_zone; + float current_x; + float current_y; + float current_z; + float current_heading; + */ + out.WriteUInt16(emu->zone_id); + out.WriteUInt16(emu->zoneInstance); + out.WriteFloat(emu->x); + out.WriteFloat(emu->y); + out.WriteFloat(emu->z); + out.WriteFloat(emu->heading); + + /* + u8 animation; + u8 pvp; + u8 anon; + u8 gm; + */ + out.WriteUInt8(100); + out.WriteUInt8(emu->pvp); + out.WriteUInt8(emu->anon); + out.WriteUInt8(emu->gm); + + /* + u64 guild_id; + u8 guild_show_sprite; + u8 status; + */ + + out.WriteInt32(emu->guild_id); + out.WriteUInt32(0); + out.WriteUInt8(0); + out.WriteUInt8(5); + + //Coin coin; + out.WriteUInt32(emu->platinum); + out.WriteUInt32(emu->gold); + out.WriteUInt32(emu->silver); + out.WriteUInt32(emu->copper); + + //Coin bank; + out.WriteUInt32(emu->platinum_bank); + out.WriteUInt32(emu->gold_bank); + out.WriteUInt32(emu->silver_bank); + out.WriteUInt32(emu->copper_bank); + + //u32 bank_shared_plat; + out.WriteUInt32(emu->platinum_shared); + + //u32 claim_count; + out.WriteUInt32(0); + //Claim claims[claim_count]; + + //Tribute tribute; + /* + u32 BenefitTimer; + s32 unknown1; + s32 current_favor; + s32 unknown2; + s32 all_time_favor; + s32 unknown3; //some of these are probably the bools on the pcclient; + u16 unknown4; + */ + out.WriteUInt32(600000); + out.WriteInt32(-1); + out.WriteUInt32(emu->tribute_points); + out.WriteUInt32(0); + out.WriteUInt32(emu->career_tribute_points); + out.WriteUInt32(0); + out.WriteUInt16(0); + + //u32 tribute_benefit_count + out.WriteUInt32(5); + + //TributeBenefit tribute_benefits[tribute_benefit_count]; + for (int i = 0; i < 5; ++i) { + /* + s32 benefit_id; + s32 benefit_tier; + */ + + out.WriteUInt32(emu->tributes[i].tribute); + out.WriteUInt32(emu->tributes[i].tier); + } + + //u32 trophy_tribute_benefit_count; + out.WriteUInt32(10); + + //TributeBenefit trophy_tribute_benefit[trophy_tribute_benefit_count]; + for (int i = 0; i < 10; ++i) { + /* + s32 benefit_id; + s32 benefit_tier; + */ + + out.WriteUInt32(0xFFFFFFFF); + out.WriteUInt32(0); + } + const uint8_t task_data[137] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, + }; + + //u8 tasks[137]; //on live and on xac's capture from 12/28/23 these both are the same size; + for (int i = 0; i < 137; i++) + { + out.WriteUInt8(task_data[i]); + } + + /* + u32 good_points_available; + u32 good_points_earned; + u32 bad_points_available; + u32 bad_points_earned; + */ + + out.WriteUInt32(emu->currentRadCrystals); + out.WriteUInt32(emu->careerRadCrystals); + out.WriteUInt32(emu->currentEbonCrystals); + out.WriteUInt32(emu->careerEbonCrystals); + + /* + u32 momentum_balance; + u32 loyalty_reward_balance; + u32 parcel_status; + */ + + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + + /* + u32 vehicle_name_length; + char vehicle_name[vehicle_name_length]; + */ + + out.WriteUInt32(64); + for (int i = 0; i < 64; ++i) { + out.WriteUInt8(0); + } + + /* + u8 super_pkill; + u8 unclone; + u8 dead; + */ + + out.WriteUInt8(0); + out.WriteUInt8(0); + out.WriteUInt8(0); + + /* + u32 ld_timer; + u32 spell_interrupt_count; + u8 autosplit; + u8 tells_off; + */ + + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt8(emu->autosplit); + out.WriteUInt8(0); + + /* + u8 gm_invis; + u32 kill_me; + u8 cheater_ld_flag; + u8 norent; + u8 corpse; + u8 client_gm_flag_set; + u32 mentor_pct; + */ + + out.WriteUInt8(0); + out.WriteUInt32(0); + out.WriteUInt8(0); + out.WriteUInt8(0); + out.WriteUInt8(0); + out.WriteUInt8(0); + out.WriteUInt32(0); + + //RaidData raid; + /* + u32 main_assist1; + u32 main_assist2; + u32 main_assist3; + char main_assist_name1[]; + char main_assist_name2[]; + char main_assist_name3[]; + u32 main_marker1; + u32 main_marker2; + u32 main_marker3; + u32 master_looter; + */ + + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt8(0); + out.WriteUInt8(0); + out.WriteUInt8(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + + //u32 unique_player_id; + out.WriteUInt32(emu->char_id); + + //LdonData ldon_data; + /* + u32 count; + u32 ldon_categories[count]; + u32 ldon_points_available; + */ + + out.WriteUInt32(6); + out.WriteUInt32(0); + out.WriteUInt32(emu->ldon_points_guk); + out.WriteUInt32(emu->ldon_points_mir); + out.WriteUInt32(emu->ldon_points_mmc); + out.WriteUInt32(emu->ldon_points_ruj); + out.WriteUInt32(emu->ldon_points_tak); + out.WriteUInt32(emu->ldon_points_available); + + //u32 air_supply; + out.WriteUInt32(emu->air_remaining); + + //PvPData pvp_data; + /* + u32 kills; + u32 deaths; + u32 current_points; + u32 career_points; + u32 best_kill_streak; + u32 worst_death_streak; + u32 current_kill_streak; + */ + + out.WriteUInt32(emu->PVPKills); + out.WriteUInt32(emu->PVPDeaths); + out.WriteUInt32(emu->PVPCurrentPoints); + out.WriteUInt32(emu->PVPCareerPoints); + out.WriteUInt32(emu->PVPBestKillStreak); + out.WriteUInt32(emu->PVPWorstDeathStreak); + out.WriteUInt32(emu->PVPCurrentKillStreak); + + //PvPKill last_kill; + /* + char name[]; + u32 level; + u32 unknown1; //not sure + u32 unknown2; //not sure + u32 race; + u32 class; + u32 zone; + u32 time; + u32 points; + */ + out.WriteString(emu->PVPLastKill.Name); + out.WriteUInt32(emu->PVPLastKill.Level); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(emu->PVPLastKill.Race); + out.WriteUInt32(emu->PVPLastKill.Class); + out.WriteUInt32(emu->PVPLastKill.Zone); + out.WriteUInt32(emu->PVPLastKill.Time); + out.WriteUInt32(emu->PVPLastKill.Points); + + //PvPDeath last_death; + /* + char name[]; + u32 level; + u32 race; + u32 class; + u32 zone; + u32 time; + u32 points; + */ + + out.WriteString(emu->PVPLastDeath.Name); + out.WriteUInt32(emu->PVPLastDeath.Level); + out.WriteUInt32(emu->PVPLastDeath.Race); + out.WriteUInt32(emu->PVPLastDeath.Class); + out.WriteUInt32(emu->PVPLastDeath.Zone); + out.WriteUInt32(emu->PVPLastDeath.Time); + out.WriteUInt32(emu->PVPLastDeath.Points); + + /* + u32 kills_in_past_24_hours; + */ + + out.WriteUInt32(emu->PVPNumberOfKillsInLast24Hours); + + //u32 kill_list_count; + out.WriteUInt32(50); + + //PvPKill kill_list[kill_list_count]; + for (int i = 0; i < 50; ++i) { + /* + char name[]; + u32 level; + u32 unknown1; //not sure + u32 unknown2; //not sure + u32 race; + u32 class; + u32 zone; + u32 time; + u32 points; + */ + out.WriteString(emu->PVPRecentKills[i].Name); + out.WriteUInt32(emu->PVPRecentKills[i].Level); + out.WriteUInt32(0); + out.WriteUInt32(0); + out.WriteUInt32(emu->PVPRecentKills[i].Race); + out.WriteUInt32(emu->PVPRecentKills[i].Class); + out.WriteUInt32(emu->PVPRecentKills[i].Zone); + out.WriteUInt32(emu->PVPRecentKills[i].Time); + out.WriteUInt32(emu->PVPRecentKills[i].Points); + } + + /* + u32 pvp_infamy_level; + u32 pvp_vitality; + */ + out.WriteUInt32(0); + out.WriteUInt32(0); + + /* + u32 cursor_krono; + u32 krono; + */ + out.WriteUInt32(0); + out.WriteUInt32(0); + + /* + u8 autoconsent_group; + u8 autoconsent_raid; + u8 autoconsent_guild; + u8 autoconsent_fellowship; + */ + + out.WriteUInt8(emu->groupAutoconsent); + out.WriteUInt8(emu->raidAutoconsent); + out.WriteUInt8(emu->guildAutoconsent); + out.WriteUInt8(1); + + /* + u8 private_for_eq_players; + u32 main_level; + u8 show_helm; + u32 downtime; + */ + out.WriteUInt8(1); + out.WriteUInt32(emu->level); + out.WriteUInt8(emu->showhelm); + out.WriteUInt32(emu->RestTimer); + + //AltCurrency alt_currency; + /* + u32 alt_currency_str_length; + u32 unknown1; + char alt_currency_string[alt_currency_str_length]; + */ + out.WriteUInt32(1); + out.WriteUInt32(0); + out.WriteUInt8(0x31); + + //u32 completed_event_subcomponent_count; + out.WriteUInt32(0); + //AchivementSubComponentData completed_event_subcomponents[completed_event_subcomponent_count]; + + //u32 inprogress_event_subcomponent_count; + out.WriteUInt32(0); + //AchivementSubComponentData inprogress_event_subcomponents[inprogress_event_subcomponent_count]; + + /* + u64 merc_aa_exp; + u32 merc_aa_points; + u32 merc_aa_spent; + */ + out.WriteUInt64(0); + out.WriteUInt32(0); + out.WriteUInt32(0); + + //u32 starting_city_zone_id; + //we don't actually support this yet + out.WriteUInt32(394); + + /* + u8 use_advanced_looting; + u8 is_master_loot_candidate; + */ + + out.WriteUInt8(1); + out.WriteUInt8(1); + + //alchemy_bonus_list_count + out.WriteUInt32(0); + //AlchemyBonusSkillData alchemy_bonus_list[alchemy_bonus_list_count]; + + //u32 persona_count; + out.WriteUInt32(0); + //PersonaEquipmentSet persona_equipment_set[persona_count]; + + //u8 term; + out.WriteUInt8(0); + + auto outapp = new EQApplicationPacket(OP_PlayerProfile, out.length()); + outapp->WriteData(out.buffer(), out.length()); + outapp->SetWritePosition(4); + outapp->WriteUInt32(outapp->size - 9); + CRC32::SetEQChecksum(outapp->pBuffer, outapp->size - 1, 8); + dest->FastQueuePacket(&outapp, ack_req); + delete in; + } + + ENCODE(OP_RecipeAutoCombine) + { + ENCODE_LENGTH_EXACT(RecipeAutoCombine_Struct); + SETUP_DIRECT_ENCODE(RecipeAutoCombine_Struct, structs::RecipeAutoCombine_Struct); + + OUT(object_type); + OUT(some_id); + eq->container_slot = ServerToSteamLatestSlot(emu->unknown1); + structs::InventorySlot_Struct SteamLatestSlot; + SteamLatestSlot.Type = 8; // Observed + SteamLatestSlot.Padding1 = 0; + SteamLatestSlot.Slot = 0xffff; + SteamLatestSlot.SubIndex = 0xffff; + SteamLatestSlot.AugIndex = 0xffff; + SteamLatestSlot.Padding2 = 0; + eq->unknown_slot = SteamLatestSlot; + OUT(recipe_id); + OUT(reply_code); + + FINISH_ENCODE(); + } + + ENCODE(OP_RemoveBlockedBuffs) { ENCODE_FORWARD(OP_BlockedBuffs); } + + ENCODE(OP_RespondAA) + { + SETUP_DIRECT_ENCODE(AATable_Struct, structs::AATable_Struct); + + eq->aa_spent = emu->aa_spent; + // These fields may need to be correctly populated at some point + eq->aapoints_assigned[0] = emu->aa_spent; + eq->aapoints_assigned[1] = 0; + eq->aapoints_assigned[2] = 0; + eq->aapoints_assigned[3] = 0; + eq->aapoints_assigned[4] = 0; + eq->aapoints_assigned[5] = 0; + + for (uint32 i = 0; i < MAX_PP_AA_ARRAY; ++i) + { + eq->aa_list[i].AA = emu->aa_list[i].AA; + eq->aa_list[i].value = emu->aa_list[i].value; + eq->aa_list[i].charges = emu->aa_list[i].charges; + eq->aa_list[i].bUnknown0x0c = false; + } + + FINISH_ENCODE(); + } + + ENCODE(OP_RequestClientZoneChange) + { + ENCODE_LENGTH_EXACT(RequestClientZoneChange_Struct); + SETUP_DIRECT_ENCODE(RequestClientZoneChange_Struct, structs::RequestClientZoneChange_Struct); + + OUT(zone_id); + OUT(instance_id); + OUT(y); + OUT(x); + OUT(z); + OUT(heading); + eq->type = 0x0b; + eq->unknown004 = 0xffffffff; + eq->unknown172 = 0x0168b500; + + FINISH_ENCODE(); + } + + ENCODE(OP_SendAATable) + { + EQApplicationPacket* in = *p; + *p = nullptr; + AARankInfo_Struct* emu = (AARankInfo_Struct*)in->pBuffer; + + std::vector skill; + std::vector points; + in->SetReadPosition(sizeof(AARankInfo_Struct) + emu->total_effects * sizeof(AARankEffect_Struct)); + for (auto i = 0; i < emu->total_prereqs; ++i) { + skill.push_back(in->ReadUInt32()); + points.push_back(in->ReadUInt32()); + } + + SerializeBuffer buffer; + + /* + s32 AbilityId; + u8 ShowInAbilityWindow; + s32 ShortName; + s32 ShortName2; + s32 Name; + s32 Desc; + */ + + buffer.WriteUInt32(emu->id); + buffer.WriteUInt8(1); + buffer.WriteInt32(emu->upper_hotkey_sid); + buffer.WriteInt32(emu->lower_hotkey_sid); + buffer.WriteInt32(emu->title_sid); + buffer.WriteInt32(emu->desc_sid); + + /* + s32 MinLevel; + s32 Cost; + s32 GroupID; + s32 CurrentRank; + */ + buffer.WriteInt32(emu->level_req); + buffer.WriteInt32(emu->cost); + buffer.WriteUInt32(emu->seq); + buffer.WriteUInt32(emu->current_level); + + /* + u32 PrereqSkillCount; + s32 PrereqSkills[PrereqSkillCount]; + u32 PrereqLevelCount; + s32 PrereqLevels[PrereqLevelCount]; + */ + + if (emu->total_prereqs) { + buffer.WriteUInt32(emu->total_prereqs); + for (auto& e : skill) + buffer.WriteInt32(e); + buffer.WriteUInt32(emu->total_prereqs); + for (auto& e : points) + buffer.WriteInt32(e); + } + else { + buffer.WriteUInt32(1); + buffer.WriteUInt32(0); + buffer.WriteUInt32(1); + buffer.WriteUInt32(0); + } + + /* + u32 Type; + s32 SpellId; + */ + buffer.WriteInt32(emu->type); + buffer.WriteInt32(emu->spell); + + /* + u32 TimerIdCount; + s32 TimerIds[TimerIdCount]; + s32 ReuseTimer; + u32 Classes; + */ + buffer.WriteInt32(1); + buffer.WriteInt32(emu->spell_type); + buffer.WriteInt32(emu->spell_refresh); + buffer.WriteInt32(emu->classes); + + /* + s32 MaxRank; + s32 PrevAbilityId; + s32 NextAbilityId; + s32 TotalPoints; + */ + + buffer.WriteInt32(emu->max_level); + buffer.WriteInt32(emu->prev_id); + buffer.WriteInt32(emu->next_id); + buffer.WriteInt32(emu->total_cost); + + /* + u8 bRefund; + s32 QuestOnly; + u8 bIgnoreDeLevel; + */ + buffer.WriteUInt8(0); + buffer.WriteUInt32(emu->grant_only); + buffer.WriteUInt8(0); + + /* + s32 Charges; + s32 Expansion; + s32 Category; + */ + buffer.WriteUInt32(emu->charges); + buffer.WriteInt32(emu->expansion); + buffer.WriteInt32(emu->category); + + /* + u8 bShroud; + u8 bBetaOnly; + u8 bResetOnDeath; + u8 AutoGrant; + */ + buffer.WriteUInt8(0); + buffer.WriteUInt8(0); + buffer.WriteUInt8(0); + buffer.WriteUInt8(0); + + /* + s32 AutoGrantExpansion; + s32 Unknown098; + u8 Unknown09C; + */ + + buffer.WriteInt32(emu->expansion); + buffer.WriteInt32(0); + buffer.WriteUInt8(0); + + //u32 TotalEffects; + buffer.WriteUInt32(emu->total_effects); + in->SetReadPosition(sizeof(AARankInfo_Struct)); + for (auto i = 0; i < emu->total_effects; ++i) { + auto skill_id = in->ReadUInt32(); + auto base1 = in->ReadUInt32(); + auto base2 = in->ReadUInt32(); + auto slot = in->ReadUInt32(); + + /* + u32 effect_id; + s64 base1; + s64 base2; + u32 slot; + */ + + buffer.WriteUInt32(skill_id); + buffer.WriteInt64(base1); + buffer.WriteInt64(base2); + buffer.WriteUInt32(slot); + } + + auto outapp = new EQApplicationPacket(OP_SendAATable, buffer.size()); + outapp->WriteData(buffer.buffer(), buffer.size()); + dest->FastQueuePacket(&outapp, ack_req); + + delete in; + } + + ENCODE(OP_SendCharInfo) { + ENCODE_LENGTH_ATLEAST(CharacterSelect_Struct); + SETUP_VAR_ENCODE(CharacterSelect_Struct); + + // Zero-character count shunt + if (emu->CharCount == 0) { + ALLOC_VAR_ENCODE(structs::CharacterSelect_Struct, sizeof(structs::CharacterSelect_Struct)); + eq->CharCount = emu->CharCount; + + FINISH_ENCODE(); + return; + } + + unsigned char* emu_ptr = __emu_buffer; + emu_ptr += sizeof(CharacterSelect_Struct); + CharacterSelectEntry_Struct* emu_cse = (CharacterSelectEntry_Struct*)nullptr; + + size_t names_length = 0; + size_t character_count = 0; + for (; character_count < emu->CharCount; ++character_count) { + emu_cse = (CharacterSelectEntry_Struct*)emu_ptr; + names_length += strlen(emu_cse->Name); + emu_ptr += sizeof(CharacterSelectEntry_Struct); + } + + size_t total_length = sizeof(structs::CharacterSelect_Struct) + + character_count * sizeof(structs::CharacterSelectEntry_Struct) + + names_length; + + ALLOC_VAR_ENCODE(structs::CharacterSelect_Struct, total_length); + structs::CharacterSelectEntry_Struct* eq_cse = (structs::CharacterSelectEntry_Struct*)nullptr; + + eq->CharCount = character_count; + + emu_ptr = __emu_buffer; + emu_ptr += sizeof(CharacterSelect_Struct); + + unsigned char* eq_ptr = __packet->pBuffer; + eq_ptr += sizeof(structs::CharacterSelect_Struct); + + for (int counter = 0; counter < character_count; ++counter) { + emu_cse = (CharacterSelectEntry_Struct*)emu_ptr; + eq_cse = (structs::CharacterSelectEntry_Struct*)eq_ptr; // base address + + strcpy(eq_cse->Name, emu_cse->Name); + eq_ptr += strlen(emu_cse->Name); + + eq_cse = (structs::CharacterSelectEntry_Struct*)eq_ptr; + eq_cse->Name[0] = '\0'; + + eq_cse->Class = emu_cse->Class; + eq_cse->Race = emu_cse->Race; + eq_cse->Level = emu_cse->Level; + eq_cse->ShroudClass = emu_cse->ShroudClass; + eq_cse->ShroudRace = emu_cse->ShroudRace; + eq_cse->Zone = emu_cse->Zone; + eq_cse->Instance = emu_cse->Instance; + eq_cse->Gender = emu_cse->Gender; + eq_cse->Face = emu_cse->Face; + + for (int equip_index = 0; equip_index < EQ::textures::materialCount; equip_index++) { + eq_cse->Equip[equip_index].Material = emu_cse->Equip[equip_index].Material; + eq_cse->Equip[equip_index].Unknown1 = emu_cse->Equip[equip_index].Unknown1; + eq_cse->Equip[equip_index].EliteMaterial = emu_cse->Equip[equip_index].EliteModel; + eq_cse->Equip[equip_index].HeroForgeModel = emu_cse->Equip[equip_index].HerosForgeModel; + eq_cse->Equip[equip_index].Material2 = emu_cse->Equip[equip_index].Unknown2; + eq_cse->Equip[equip_index].Color = emu_cse->Equip[equip_index].Color; + } + + eq_cse->Unknown1 = 255; + eq_cse->Unknown2 = 0; + eq_cse->DrakkinTattoo = emu_cse->DrakkinTattoo; + eq_cse->DrakkinDetails = emu_cse->DrakkinDetails; + eq_cse->Deity = emu_cse->Deity; + eq_cse->PrimaryIDFile = emu_cse->PrimaryIDFile; + eq_cse->SecondaryIDFile = emu_cse->SecondaryIDFile; + eq_cse->HairColor = emu_cse->HairColor; + eq_cse->BeardColor = emu_cse->BeardColor; + eq_cse->EyeColor1 = emu_cse->EyeColor1; + eq_cse->EyeColor2 = emu_cse->EyeColor2; + eq_cse->HairStyle = emu_cse->HairStyle; + eq_cse->Beard = emu_cse->Beard; + eq_cse->GoHome = emu_cse->GoHome; + eq_cse->Tutorial = emu_cse->Tutorial; + eq_cse->DrakkinHeritage = emu_cse->DrakkinHeritage; + eq_cse->Enabled = emu_cse->Enabled; + eq_cse->LastLogin = emu_cse->LastLogin; + eq_cse->Unknown3 = 0; + eq_cse->Unknown4 = 0; + eq_cse->Unknown5 = 0; + eq_cse->Unknown6 = 0; + eq_cse->Unknown7 = 0; + eq_cse->CharacterId = 0; + eq_cse->Unknown8 = 1; + + emu_ptr += sizeof(CharacterSelectEntry_Struct); + eq_ptr += sizeof(structs::CharacterSelectEntry_Struct); + } + + DumpPacket(__packet); + + FINISH_ENCODE(); + } + + ENCODE(OP_SendMaxCharacters) { + ENCODE_LENGTH_EXACT(MaxCharacters_Struct); + SETUP_DIRECT_ENCODE(MaxCharacters_Struct, structs::MaxCharacters_Struct); + + //OUT(max_chars); + eq->max_chars = 8; //needs to be fixed + eq->marketplace_chars = 0; + eq->unknown008 = -1; + eq->unknown00c = 196608; + eq->unknown010 = 0; + eq->unknown014 = 0; + eq->unknown018 = 0; + eq->unknown01c = 0; + eq->unknown020 = -1; + eq->unknown024 = 0; + eq->unknown028 = 0; + eq->unknown02c = 0; + eq->unknown030 = 0; + eq->unknown034 = 0; + + FINISH_ENCODE(); + } + + ENCODE(OP_SendMembership) { + ENCODE_LENGTH_EXACT(Membership_Struct); + SETUP_DIRECT_ENCODE(Membership_Struct, structs::Membership_Struct); + + eq->membership = emu->membership; + eq->races = emu->races; + eq->classes = emu->classes; + eq->entrysize = 33; + eq->entries[0] = -1; // Max AA Restriction + eq->entries[1] = -1; // Max Level Restriction + eq->entries[2] = -1; // Max Char Slots per Account (not used by client?) + eq->entries[3] = -1; // SpellTier + eq->entries[4] = -1; // Main Inventory Size + eq->entries[5] = -1; // Max Platinum per level + eq->entries[6] = 1; // Send Mail + eq->entries[7] = 1; // Use Parcels + eq->entries[8] = 1; // Loyalty + eq->entries[9] = -1; // Merc Tiers + eq->entries[10] = 1; // Housing + eq->entries[11] = -1; // Shared Bank Slots + eq->entries[12] = -1; // Max Journal Quests + eq->entries[13] = 1; // CreateGuild + eq->entries[14] = 1; // Bazaar + eq->entries[15] = 1; // Barter + eq->entries[16] = 1; // Chat + eq->entries[17] = 1; // Petition + eq->entries[18] = 1; // Advertising + eq->entries[19] = -1; // UseItem + eq->entries[20] = -1; // StartingCity + eq->entries[21] = 1; // Ornament + eq->entries[22] = 0; // HeroicCharacter + eq->entries[23] = 0; // AutoGrantAA + eq->entries[24] = 0; // MountKeyRingSlots + eq->entries[25] = 0; // IllusionKeyRingSlots + eq->entries[26] = 0; // FamiliarKeyRingSlots + eq->entries[27] = 0; // FamiliarAutoLeave + eq->entries[28] = 0; // HeroForgeKeyRingSlots + eq->entries[29] = 0; // DragonHoardSlots + eq->entries[30] = 0; // TeleportKeyRingSlots + eq->entries[31] = 0; // PersonalDepotSlots + eq->entries[32] = 0; + + FINISH_ENCODE(); + } + + ENCODE(OP_SendMembershipDetails) { + ENCODE_LENGTH_EXACT(Membership_Details_Struct); + SETUP_DIRECT_ENCODE(Membership_Details_Struct, structs::Membership_Details_Struct); + + int32 settings[96][3] = { + { 0, 0, 250 }, { 1, 0, 1000 }, { 0, 1, -1 }, { 1, 1, -1 }, + { 0, 2, 2 }, { 2, 0, -1 }, { 3, 0, -1 }, { 1, 2, 4 }, + { 0, 3, 1 }, { 2, 1, -1 }, { 3, 1, -1 }, { 1, 3, 1 }, + { 0, 4, -1 }, { 2, 2, -1 }, { 3, 2, -1 }, { 1, 4, -1 }, + { 0, 5, -1 }, { 2, 3, -1 }, { 3, 3, -1 }, { 1, 5, -1 }, + { 0, 6, 0 }, { 2, 4, -1 }, { 3, 4, -1 }, { 1, 6, 0 }, + { 0, 7, 1 }, { 2, 5, -1 }, { 3, 5, -1 }, { 1, 7, 1 }, + { 0, 8, 1 }, { 2, 6, 1 }, { 3, 6, 1 }, { 1, 8, 1 }, + { 0, 9, 5 }, { 2, 7, 1 }, { 3, 7, 1 }, { 1, 9, 5 }, + { 0, 10, 0 }, { 2, 8, 1 }, { 3, 8, 1 }, { 0, 11, -1 }, + { 1, 10, 1 }, { 2, 9, -1 }, { 3, 9, -1 }, { 0, 12, -1 }, + { 1, 11, -1 }, { 2, 10, 1 }, { 3, 10, 1 }, { 0, 13, 0 }, + { 1, 12, -1 }, { 2, 11, -1 }, { 3, 11, -1 }, { 0, 14, 0 }, + { 1, 13, 1 }, { 2, 12, -1 }, { 3, 12, -1 }, { 0, 15, 0 }, + { 1, 14, 0 }, { 2, 13, 1 }, { 3, 13, 1 }, { 0, 16, 0 }, + { 1, 15, 0 }, { 2, 14, 1 }, { 3, 14, 1 }, { 0, 17, 0 }, + { 1, 16, 1 }, { 2, 15, 1 }, { 3, 15, 1 }, { 0, 18, 0 }, + { 1, 17, 0 }, { 2, 16, 1 }, { 3, 16, 1 }, { 0, 19, 0 }, + { 1, 18, 0 }, { 2, 17, 1 }, { 3, 17, 1 }, { 0, 20, 0 }, + { 1, 19, 0 }, { 2, 18, 1 }, { 3, 18, 1 }, { 0, 21, 0 }, + { 1, 20, 0 }, { 2, 19, -1 }, { 3, 19, -1 }, { 0, 22, 0 }, + { 1, 21, 0 }, { 2, 20, -1 }, { 3, 20, -1 }, { 2, 21, 0 }, + { 0, 23, 0 }, { 1, 22, 0 }, { 3, 21, 0 }, { 2, 22, 0 }, + { 1, 23, 0 }, { 3, 22, 0 }, { 2, 23, 0 }, { 3, 23, 0 } + }; + + uint32 races[17][2] = { + { 1, 131071 }, + { 333, 131071 }, + { 90287, 131071 }, + { 90289, 16 }, + { 90290, 32 }, + { 90291, 64 }, + { 90292, 128 }, + { 90293, 256 }, + { 90294, 512 }, + { 90295, 1024 }, + { 90296, 2048 }, + { 90297, 8192 }, + { 90298, 16384 }, + { 90299, 32768 }, + { 90300, 65536 }, + { 2012271, 131071 }, + { 2012277, 131071 } + }; + + uint32 classes[17][2] = { + { 1, 131071 }, + { 333, 131071 }, + { 90287, 131071 }, + { 90301, 8 }, + { 90302, 16 }, + { 90303, 32 }, + { 90304, 64 }, + { 90305, 128 }, + { 90306, 256 }, + { 90307, 1024 }, + { 90308, 2048 }, + { 90309, 8192 }, + { 90310, 16384 }, + { 90311, 32768 }, + { 90312, 65536 }, + { 2012271, 131071 }, + { 2012277, 131071 } + }; + + eq->membership_setting_count = 96; + + for (int i = 0; i < 96; ++i) { + eq->settings[i].setting_index = (int8)settings[i][0]; + eq->settings[i].setting_id = settings[i][1]; + eq->settings[i].setting_value = settings[i][2]; + } + + eq->class_entry_count = 17; + for (int i = 0; i < 17; ++i) { + eq->membership_classes[i].purchase_id = classes[i][0]; + eq->membership_classes[i].bitwise_entry = classes[i][1]; + } + + eq->race_entry_count = 17; + for (int i = 0; i < 17; ++i) { + eq->membership_races[i].purchase_id = races[i][0]; + eq->membership_races[i].bitwise_entry = races[i][1]; + } + + eq->exit_url_length = 0; + + FINISH_ENCODE(); + } + + ENCODE(OP_SendZonepoints) + { + SETUP_VAR_ENCODE(ZonePoints); + ALLOC_VAR_ENCODE(structs::ZonePoints, sizeof(structs::ZonePoints) + sizeof(structs::ZonePoint_Entry) * (emu->count + 1)); + + eq->count = emu->count; + for (uint32 i = 0; i < emu->count; ++i) + { + eq->zpe[i].iterator = emu->zpe[i].iterator; + eq->zpe[i].x = emu->zpe[i].x; + eq->zpe[i].y = emu->zpe[i].y; + eq->zpe[i].z = emu->zpe[i].z; + eq->zpe[i].heading = emu->zpe[i].heading; + eq->zpe[i].zoneid = emu->zpe[i].zoneid; + eq->zpe[i].zoneinstance = emu->zpe[i].zoneinstance; + } + + FINISH_ENCODE(); + } + + ENCODE(OP_ShopPlayerBuy) + { + ENCODE_LENGTH_EXACT(Merchant_Sell_Struct); + SETUP_DIRECT_ENCODE(Merchant_Sell_Struct, structs::Merchant_Sell_Response_Struct); + + OUT(npcid); + OUT(playerid); + OUT(itemslot); + OUT(quantity); + OUT(price); + + FINISH_ENCODE(); + } + + ENCODE(OP_ShopPlayerSell) + { + ENCODE_LENGTH_EXACT(Merchant_Purchase_Struct); + SETUP_DIRECT_ENCODE(Merchant_Purchase_Struct, structs::Merchant_Purchase_Response_Struct); + + OUT(npcid); + eq->inventory_slot = ServerToSteamLatestTypelessSlot(emu->itemslot, EQ::invtype::typePossessions); + OUT(quantity); + OUT(price); + + FINISH_ENCODE(); + } + + ENCODE(OP_ShopRequest) + { + ENCODE_LENGTH_EXACT(MerchantClick_Struct); + SETUP_DIRECT_ENCODE(MerchantClick_Struct, structs::MerchantClickResponse_Struct); + + if (emu->command == 0) { + OUT(player_id); + eq->npc_id = 0; + } + else { + OUT(npc_id); + OUT(player_id); + OUT(rate); + OUT(tab_display); + eq->unknown028 = 256; + } + + FINISH_ENCODE(); + } + + ENCODE(OP_SkillUpdate) + { + ENCODE_LENGTH_EXACT(SkillUpdate_Struct); + SETUP_DIRECT_ENCODE(SkillUpdate_Struct, structs::SkillUpdate_Struct); + + OUT(skillId); + OUT(value); + eq->active = 1; + + FINISH_ENCODE(); + } + + ENCODE(OP_SpecialMesg) + { + EQApplicationPacket* in = *p; + *p = nullptr; + + SerializeBuffer buf(in->size); + buf.WriteInt8(in->ReadUInt8()); // speak mode + buf.WriteInt8(in->ReadUInt8()); // journal mode + buf.WriteInt8(in->ReadUInt8()); // language + buf.WriteInt32(in->ReadUInt32()); // message type + buf.WriteInt32(in->ReadUInt32()); // target spawn id + + std::string name; + in->ReadString(name); // NPC names max out at 63 chars + + buf.WriteString(name); + + buf.WriteInt32(in->ReadUInt32()); // loc + buf.WriteInt32(in->ReadUInt32()); + buf.WriteInt32(in->ReadUInt32()); + + std::string old_message; + std::string new_message; + + in->ReadString(old_message); + + ServerToSteamLatestConvertLinks(new_message, old_message); + + buf.WriteString(new_message); + + auto outapp = new EQApplicationPacket(OP_SpecialMesg, buf); + + dest->FastQueuePacket(&outapp, ack_req); + delete in; + } + + ENCODE(OP_SpawnAppearance) + { + EQApplicationPacket* in = *p; + *p = nullptr; + + unsigned char* emu_buffer = in->pBuffer; + + SpawnAppearance_Struct* sas = (SpawnAppearance_Struct*)emu_buffer; + + if (sas->type != AppearanceType::Size) + { + //steam_latest struct is different than rof2's but the idea is the same + //we will probably want to better implement SteamLatest's structure later + auto outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(structs::SpawnAppearance_Struct)); + structs::SpawnAppearance_Struct *eq = (structs::SpawnAppearance_Struct*)outapp->pBuffer; + + eq->spawn_id = sas->spawn_id; + eq->type = ServerToSteamLatestSpawnAppearanceType(sas->type); + eq->parameter = sas->parameter; + + dest->FastQueuePacket(&outapp, ack_req); + delete in; + return; + } + + auto outapp = new EQApplicationPacket(OP_ChangeSize, sizeof(ChangeSize_Struct)); + + ChangeSize_Struct* css = (ChangeSize_Struct*)outapp->pBuffer; + + css->EntityID = sas->spawn_id; + css->Size = (float)sas->parameter; + css->Unknown08 = 0; + css->Unknown12 = 1.0f; + + dest->FastQueuePacket(&outapp, ack_req); + delete in; + } + + ENCODE(OP_SpawnDoor) + { + SETUP_VAR_ENCODE(Door_Struct); + int door_count = __packet->size / sizeof(Door_Struct); + int total_length = door_count * sizeof(structs::Door_Struct); + ALLOC_VAR_ENCODE(structs::Door_Struct, total_length); + + int r; + for (r = 0; r < door_count; r++) { + strncpy(eq[r].name, emu[r].name, 32); + eq[r].DefaultY = emu[r].yPos; + eq[r].DefaultX = emu[r].xPos; + eq[r].DefaultZ = emu[r].zPos; + eq[r].DefaultHeading = emu[r].heading; + eq[r].DefaultDoorAngle = emu[r].incline; + eq[r].Y = emu[r].yPos; + eq[r].X = emu[r].xPos; + eq[r].Z = emu[r].zPos; + eq[r].Heading = emu[r].heading; + //there's a door angle here but im not sure if / what we set it to since ive literally never seen it as anything but 0 on live + //based on pattern it probably is supposed to match the default angle? + //I'm not 100% sure this is a float it might be a uint32 + eq[r].DoorAngle = emu[r].incline; + eq[r].ScaleFactor = emu[r].size; + eq[r].Id = emu[r].doorId; + eq[r].Type = emu[r].opentype; + eq[r].State = emu[r].state_at_spawn; + eq[r].DefaultState = emu[r].invert_state; + eq[r].Param = emu[r].door_param; + eq[r].bVisible = 1; + eq[r].bUsable = 1; + } + + FINISH_ENCODE(); + } + + ENCODE(OP_Stun) + { + ENCODE_LENGTH_EXACT(Stun_Struct); + SETUP_DIRECT_ENCODE(Stun_Struct, structs::Stun_Struct); + + OUT(duration); + eq->unknown005 = 163; + eq->unknown006 = 67; + + FINISH_ENCODE(); + } + + ENCODE(OP_WearChange) + { + ENCODE_LENGTH_EXACT(WearChange_Struct); + SETUP_DIRECT_ENCODE(WearChange_Struct, structs::WearChange_Struct); + + OUT(spawn_id); + eq->wear_slot_id = emu->wear_slot_id; + eq->armor_id = emu->material; + eq->variation = emu->unknown06; + eq->material = emu->elite_material; + eq->new_armor_id = emu->hero_forge_model; + eq->new_armor_type = emu->unknown18; + eq->color = emu->color.Color; + + FINISH_ENCODE(); + } + + ENCODE(OP_ZoneChange) + { + ENCODE_LENGTH_EXACT(ZoneChange_Struct); + SETUP_DIRECT_ENCODE(ZoneChange_Struct, structs::ZoneChange_Struct); + + memcpy(eq->char_name, emu->char_name, sizeof(emu->char_name)); + OUT(zoneID); + OUT(instanceID); + OUT(y); + OUT(x); + OUT(z) + OUT(zone_reason); + OUT(success); + + if (eq->success < 0) + eq->success -= 1; + + FINISH_ENCODE(); + } + + ENCODE(OP_ZoneEntry) { ENCODE_FORWARD(OP_ZoneSpawns); } + + ENCODE(OP_ZonePlayerToBind) + { + SETUP_VAR_ENCODE(ZonePlayerToBind_Struct); + ALLOC_LEN_ENCODE(sizeof(structs::ZonePlayerToBind_Struct) + strlen(emu->zone_name)); + + __packet->SetWritePosition(0); + __packet->WriteUInt16(emu->bind_zone_id); + __packet->WriteUInt16(emu->bind_instance_id); + __packet->WriteFloat(emu->x); + __packet->WriteFloat(emu->y); + __packet->WriteFloat(emu->z); + __packet->WriteFloat(emu->heading); + __packet->WriteString(emu->zone_name); + __packet->WriteUInt32(60); + __packet->WriteUInt32(0); + __packet->WriteUInt32(51); + __packet->WriteUInt32(41); + + FINISH_ENCODE(); + } + + ENCODE(OP_ZoneSpawns) + { + EQApplicationPacket* in = *p; + *p = nullptr; + + //store away the emu struct + unsigned char* __emu_buffer = in->pBuffer; + Spawn_Struct* emu = (Spawn_Struct*)__emu_buffer; + + int entrycount = in->size / sizeof(Spawn_Struct); + if (entrycount == 0 || (in->size % sizeof(Spawn_Struct)) != 0) { + LogNetcode("[STRUCTS] Wrong size on outbound [{}]: Got [{}], expected multiple of [{}]", opcodes->EmuToName(in->GetOpcode()), in->size, sizeof(Spawn_Struct)); + delete in; + return; + } + + for (int i = 0; i < entrycount; i++, emu++) { + SerializeBuffer buffer; + + auto SpawnSize = emu->size; + if (!((emu->NPC == 0) || (emu->race <= Race::Gnome) || (emu->race == Race::Iksar) || + (emu->race == Race::VahShir) || (emu->race == Race::Froglok2) || (emu->race == Race::Drakkin)) + ) + { + if (emu->size == 0) + { + emu->size = 6; + SpawnSize = 6; + } + } + + if (SpawnSize == 0) + { + SpawnSize = 3; + } + + /* + char Name[]; + u32 SpawnId; + u8 Level; + float MeleeRadius; + */ + buffer.WriteString(emu->name); + buffer.WriteUInt32(emu->spawnId); + buffer.WriteUInt8(emu->level); + if (emu->DestructibleObject) //bounding radius: we should consider supporting this officially in the future + { + buffer.WriteFloat(10.0f); + } + else + { + + buffer.WriteFloat(SpawnSize - 0.7f); + } + + /* + EqGuid HashKey; + */ + buffer.WriteUInt32(emu->CharacterGuid.Id); + buffer.WriteUInt16(emu->CharacterGuid.WorldId); + buffer.WriteUInt16(0); + + /* + u8 Type; + ActorFlags Flags; + */ + buffer.WriteUInt8(emu->NPC); + + structs::Spawn_Struct_Bitfields flags; + memset(&flags, 0, sizeof(structs::Spawn_Struct_Bitfields)); + + flags.gender = emu->gender; + flags.ispet = emu->is_pet; + flags.afk = emu->afk; + flags.anon = emu->anon; + flags.gm = emu->gm; + flags.sneak = 0; + flags.lfg = emu->lfg; + flags.invis = emu->invis; //we need to implement this + flags.linkdead = 0; //on live I often see this as 1 for npcs, maybe consider adding this in the future + flags.showhelm = emu->showhelm; + flags.trader = emu->trader ? 1 : 0; + flags.targetable = 1; + flags.targetable_with_hotkey = emu->targetable_with_hotkey ? 1 : 0; + flags.showname = emu->show_name; + flags.buyer = emu->buyer ? 1 : 0; + flags.title = strlen(emu->title) > 0 ? 1 : 0; + flags.suffix = strlen(emu->suffix) > 0 ? 1 : 0; + + if (emu->DestructibleObject || emu->class_ == Class::LDoNTreasure) { + flags.interactiveobject = 1; + } + + //write flags + //buffer.WriteStructure(flags); + for (int j = 0; j < 5; ++j) { + buffer.WriteUInt8(flags.raw[j]); + } + + /* + float EmitterScalingRadius; + s32 DefaultEmitterID; + */ + + //we don't support this atm; wouldn't be hard to add I don't think + //RoF also supports this so might be worth implementing. + buffer.WriteFloat(1.0f); + buffer.WriteInt32(-1); + + if (emu->DestructibleObject || emu->class_ == Class::LDoNTreasure) + { + /* + char InteractiveObjectModelName[]; + char InteractiveObjectName[]; + char InteractiveObjectOtherName[]; + */ + + buffer.WriteString(emu->DestructibleModel); + buffer.WriteString(emu->DestructibleName2); + buffer.WriteString(emu->DestructibleString); + + /* + s32 CurrIOState; + s32 ObjectAnimationID; + */ + buffer.WriteUInt32(emu->DestructibleAppearance); + buffer.WriteUInt32(emu->DestructibleUnk1); + + /* + s32 SoundId[10]; + */ + buffer.WriteUInt32(emu->DestructibleID1); + buffer.WriteUInt32(emu->DestructibleID2); + buffer.WriteUInt32(emu->DestructibleID3); + buffer.WriteUInt32(emu->DestructibleID4); + buffer.WriteUInt32(emu->DestructibleUnk2); + buffer.WriteUInt32(emu->DestructibleUnk3); + buffer.WriteUInt32(emu->DestructibleUnk4); + buffer.WriteUInt32(emu->DestructibleUnk5); + buffer.WriteUInt32(emu->DestructibleUnk6); + buffer.WriteUInt32(emu->DestructibleUnk7); + /* + s8 Collidable; + s8 ObjectType; + */ + buffer.WriteUInt8(emu->DestructibleUnk8); + buffer.WriteUInt8(emu->DestructibleUnk9); + } + + /* + u8 PropertyCount; + u32 Properties[PropertyCount]; + */ + //We don't actually support multiple body types yet, but we should consider it in the future + + if (!emu->DestructibleObject) + { + buffer.WriteUInt8(1); + buffer.WriteUInt32(emu->bodytype); + } + else + { + buffer.WriteUInt8(0); + } + + /* + u8 HPCurrentPct; + */ + buffer.WriteUInt8(emu->curHp); + + /* + s8 HairColor; + s8 FacialHairColor; + s8 EyeColor1; + s8 EyeColor2; + s8 HairStyle; + s8 FacialHair; + s32 Heritage; + s32 Tattoo; + s32 Details; + */ + + buffer.WriteUInt8(emu->haircolor); + buffer.WriteUInt8(emu->beardcolor); + buffer.WriteUInt8(emu->eyecolor1); + buffer.WriteUInt8(emu->eyecolor2); + buffer.WriteUInt8(emu->hairstyle); + buffer.WriteUInt8(emu->beard); + buffer.WriteUInt32(emu->drakkin_heritage); + buffer.WriteUInt32(emu->drakkin_tattoo); + buffer.WriteUInt32(emu->drakkin_details); + + /* + s8 TextureType; + s8 Material; + s8 Variation; + s8 HeadType; + */ + buffer.WriteUInt8(emu->equip_chest2); + buffer.WriteUInt8(0); + buffer.WriteUInt8(0); + buffer.WriteUInt8(emu->helm); + + /* + float Height; + s8 FaceStyle; + float MyWalkSpeed; + float RunSpeed; + s32 Race; + */ + + buffer.WriteFloat(emu->size); + buffer.WriteInt8(emu->face); + buffer.WriteFloat(emu->walkspeed); + buffer.WriteFloat(emu->runspeed); + buffer.WriteInt32(emu->race); + + /* + u8 HoldingAnimation; + u32 Deity; + EqGuid GuildID; + u32 Class; + */ + + buffer.WriteUInt8(0); + buffer.WriteUInt32(emu->deity); + if (emu->NPC) { + buffer.WriteInt32(-1); + buffer.WriteUInt32(0); + } + else { //guilds will probably need a ton of work + buffer.WriteUInt32(emu->guildID); + buffer.WriteUInt32(0); + } + buffer.WriteUInt32(emu->class_); + + /* + u8 PvP; + u8 StandState; + u8 Light; + u8 GravityBehavior; + */ + + buffer.WriteUInt8(0); + buffer.WriteUInt8(emu->StandState); + buffer.WriteUInt8(emu->light); + buffer.WriteUInt8(emu->flymode); + + /* + char LastName[]; + */ + buffer.WriteString(emu->lastName); + + /* + u8 bGuildShowAnim; + u8 bTempPet; + u32 MasterID; + u8 FindBits; + */ + + buffer.WriteUInt8(emu->guild_show); + buffer.WriteUInt8(0); + buffer.WriteUInt32(emu->petOwnerId); + buffer.WriteUInt8(0); + + /* + u32 PlayerState; + u32 NpcTintIndex; + u32 PrimaryTintIndex; + u32 SecondaryTintIndex; + */ + + buffer.WriteUInt32(emu->PlayerState); + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + + /* + u32 EncounterLockState; + u64 LockID; + */ + + buffer.WriteUInt32(0); + buffer.WriteUInt64(0); + + //u32 SeeInvis[3]; + if (emu->NPC == 1) { + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + } + + /* + s32 Primary; + s32 Secondary; + */ + buffer.WriteUInt32(0xffffffff); + buffer.WriteUInt32(0xffffffff); + + if ((emu->NPC == 0) || (emu->race <= Race::Gnome) || (emu->race == Race::Iksar) || + (emu->race == Race::VahShir) || (emu->race == Race::Froglok2) || (emu->race == Race::Drakkin) + ) + { + /* + u32 ArmorColor[9]; + */ + for (int k = EQ::textures::textureBegin; k < EQ::textures::materialCount; ++k) + { + buffer.WriteUInt32(emu->equipment_tint.Slot[k].Color); + } + + /* + Armor Armor[9]; + */ + for (int k = EQ::textures::textureBegin; k < EQ::textures::materialCount; k++) { + buffer.WriteUInt32(emu->equipment.Slot[k].Material); + buffer.WriteUInt32(emu->equipment.Slot[k].Unknown1); + buffer.WriteUInt32(emu->equipment.Slot[k].EliteModel); + buffer.WriteUInt32(emu->equipment.Slot[k].HerosForgeModel); + buffer.WriteUInt32(emu->equipment.Slot[k].Unknown2); + } + } + else + { + //Armor Armor[3]; + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + + buffer.WriteUInt32(emu->equipment.Primary.Material); + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + + buffer.WriteUInt32(emu->equipment.Secondary.Material); + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + } + + //u8 CPhysicsData[20]; + structs::Spawn_Struct_Position position; + memset(&position, 0, sizeof(structs::Spawn_Struct_Position)); + + position.y = emu->y; + position.deltaZ = emu->deltaZ; + position.deltaX = emu->deltaX; + position.x = emu->x; + position.heading = emu->heading; + position.deltaHeading = emu->deltaHeading; + position.z = emu->z; + position.animation = emu->animation; + position.deltaY = emu->deltaY; + + //buffer.WriteStructure(position); + for (int j = 0; j < 5; ++j) { + buffer.WriteUInt32(position.raw[j]); + } + + /* + if(Flags.title) { + char Title[]; + } + */ + if (flags.title) { + buffer.WriteString(emu->title); + } + + /* + if(Flags.suffix) { + char Suffix[]; + } + */ + if (flags.suffix) { + buffer.WriteString(emu->suffix); + } + + /* + u32 Unknown0x0164; + s32 SplineID; + */ + + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + + /* + u8 Mercenary; + */ + buffer.WriteUInt8(emu->IsMercenary); + + /* + char realEstateItemGuid[]; + s32 RealEstateID; + s32 RealEstateItemId; + */ + + buffer.WriteString("0000000000000000"); + buffer.WriteInt32(-1); + buffer.WriteInt32(-1); + + /* + s32 MercId; + s32 ContractorID; + u32 Birthdate; + u8 bAlwaysShowAura; + */ + + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteUInt32(0); + buffer.WriteUInt8(0); + + /* + u32 physicsEffectCount; + PhysicsEffect physicsEffects[physicsEffectCount]; + */ + buffer.WriteUInt32(0); + + //s32 SpawnStatus[6]; + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + + if (flags.interactiveobject && emu->DestructibleUnk9 == 4) { + /* + s32 BannerIndex0; + s32 BannerIndex1; + s32 BannerTint0; + s32 BannerTint1; + */ + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + buffer.WriteUInt32(0); + } + + auto outapp = new EQApplicationPacket(OP_ZoneEntry, buffer.size()); + outapp->WriteData(buffer.buffer(), buffer.size()); + dest->FastQueuePacket(&outapp, ack_req); + } + + delete in; + } + + // DECODE methods + DECODE(OP_Animation) + { + DECODE_LENGTH_EXACT(structs::Animation_Struct); + SETUP_DIRECT_DECODE(Animation_Struct, structs::Animation_Struct); + + IN(spawnid); + IN(action); + IN(speed); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ApplyPoison) + { + DECODE_LENGTH_EXACT(structs::ApplyPoison_Struct); + SETUP_DIRECT_DECODE(ApplyPoison_Struct, structs::ApplyPoison_Struct); + + emu->inventorySlot = SteamLatestToServerTypelessSlot(eq->inventorySlot, invtype::typePossessions); + IN(success); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_AugmentInfo) + { + DECODE_LENGTH_EXACT(structs::AugmentInfo_Struct); + SETUP_DIRECT_DECODE(AugmentInfo_Struct, structs::AugmentInfo_Struct); + + IN(itemid); + IN(window); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_AugmentItem) + { + DECODE_LENGTH_EXACT(structs::AugmentItem_Struct); + SETUP_DIRECT_DECODE(AugmentItem_Struct, structs::AugmentItem_Struct); + + emu->container_slot = SteamLatestToServerSlot(eq->container_slot); + emu->augment_slot = SteamLatestToServerSlot(eq->augment_slot); + emu->container_index = eq->container_index; + emu->augment_index = eq->augment_index; + emu->dest_inst_id = eq->dest_inst_id; + emu->augment_action = eq->augment_action; + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_BlockedBuffs) + { + DECODE_LENGTH_EXACT(structs::BlockedBuffs_Struct); + SETUP_DIRECT_DECODE(BlockedBuffs_Struct, structs::BlockedBuffs_Struct); + + for (uint32 i = 0; i < BLOCKED_BUFF_COUNT; ++i) + emu->SpellID[i] = eq->SpellID[i]; + + IN(Count); + IN(Pet); + IN(Initialise); + IN(Flags); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_CastSpell) + { + DECODE_LENGTH_EXACT(structs::CastSpell_Struct); + SETUP_DIRECT_DECODE(CastSpell_Struct, structs::CastSpell_Struct); + + emu->slot = static_cast(SteamLatestToServerCastingSlot(static_cast(eq->slot))); + + IN(spell_id); + emu->inventoryslot = -1; + IN(target_id); + IN(y_pos); + IN(x_pos); + IN(z_pos); + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ChannelMessage) + { + unsigned char* __eq_buffer = __packet->pBuffer; + + char* InBuffer = (char*)__eq_buffer; + + char Sender[64]; + char Target[64]; + + VARSTRUCT_DECODE_STRING(Sender, InBuffer); + VARSTRUCT_DECODE_STRING(Target, InBuffer); + + //packet seems the same as rof2 with 4 more empty bytes before language + InBuffer += 8; + + uint32 Language = VARSTRUCT_DECODE_TYPE(uint32, InBuffer); + uint32 Channel = VARSTRUCT_DECODE_TYPE(uint32, InBuffer); + + InBuffer += 5; + + uint32 Skill = VARSTRUCT_DECODE_TYPE(uint32, InBuffer); + + std::string old_message = InBuffer; + std::string new_message; + SteamLatestToServerConvertLinks(new_message, old_message); + + __packet->size = sizeof(ChannelMessage_Struct) + new_message.length() + 1; + __packet->pBuffer = new unsigned char[__packet->size]; + ChannelMessage_Struct* emu = (ChannelMessage_Struct*)__packet->pBuffer; + + strn0cpy(emu->targetname, Target, sizeof(emu->targetname)); + strn0cpy(emu->sender, Target, sizeof(emu->sender)); + emu->language = Language; + emu->chan_num = Channel; + emu->skill_in_language = Skill; + strcpy(emu->message, new_message.c_str()); + + delete[] __eq_buffer; + } + + DECODE(OP_ClickDoor) + { + DECODE_LENGTH_EXACT(structs::ClickDoor_Struct); + SETUP_DIRECT_DECODE(ClickDoor_Struct, structs::ClickDoor_Struct); + + IN(doorid); + IN(player_id); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ClientUpdate) + { + // for some odd reason, there is an extra byte on the end of this on occasion.. + DECODE_LENGTH_ATLEAST(structs::PlayerPositionUpdateClient_Struct); + SETUP_DIRECT_DECODE(PlayerPositionUpdateClient_Struct, structs::PlayerPositionUpdateClient_Struct); + + IN(spawn_id); + IN(vehicle_id); + IN(sequence); + emu->x_pos = eq->position.x; + emu->y_pos = eq->position.y; + emu->z_pos = eq->position.z; + emu->heading = eq->position.heading; + emu->delta_x = eq->position.delta_x; + emu->delta_y = eq->position.delta_y; + emu->delta_z = eq->position.delta_z; + emu->delta_heading = eq->position.delta_heading; + emu->animation = eq->position.animation; + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_Consider) + { + DECODE_LENGTH_EXACT(structs::Consider_Struct); + SETUP_DIRECT_DECODE(Consider_Struct, structs::Consider_Struct); + + IN(playerid); + IN(targetid); + IN(faction); + IN(level); + //emu->cur_hp = 1; + //emu->max_hp = 2; + //emu->pvpcon = 0; + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ConsiderCorpse) { DECODE_FORWARD(OP_Consider); } + + DECODE(OP_DeleteItem) + { + DECODE_LENGTH_EXACT(structs::DeleteItem_Struct); + SETUP_DIRECT_DECODE(DeleteItem_Struct, structs::DeleteItem_Struct); + + emu->from_slot = SteamLatestToServerSlot(eq->from_slot); + emu->to_slot = SteamLatestToServerSlot(eq->to_slot); + IN(number_in_stack); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_EnterWorld) + { + DECODE_LENGTH_EXACT(structs::EnterWorld_Struct); + SETUP_DIRECT_DECODE(EnterWorld_Struct, structs::EnterWorld_Struct); + + memcpy(emu->name, eq->name, sizeof(emu->name)); + emu->return_home = 0; + emu->tutorial = 0; + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_GMTraining) + { + DECODE_LENGTH_EXACT(structs::GMTrainee_Struct); + SETUP_DIRECT_DECODE(GMTrainee_Struct, structs::GMTrainee_Struct); + + IN(npcid); + IN(playerid); + + for (int i = 0; i < 100; ++i) { + IN(skills[i]); + } + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_GroupDisband) + { + DECODE_LENGTH_EXACT(structs::GroupGeneric_Struct); + SETUP_DIRECT_DECODE(GroupGeneric_Struct, structs::GroupGeneric_Struct); + + memcpy(emu->name1, eq->name1, sizeof(emu->name1)); + memcpy(emu->name2, eq->name2, sizeof(emu->name2)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_GroupInvite) + { + DECODE_LENGTH_EXACT(structs::GroupGeneric_Struct); + SETUP_DIRECT_DECODE(GroupGeneric_Struct, structs::GroupGeneric_Struct); + + memcpy(emu->name1, eq->name1, sizeof(emu->name1)); + memcpy(emu->name2, eq->name2, sizeof(emu->name2)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_GroupInvite2) + { + DECODE_FORWARD(OP_GroupInvite); + } + + DECODE(OP_MoveItem) + { + DECODE_LENGTH_EXACT(structs::MoveItem_Struct); + SETUP_DIRECT_DECODE(MoveItem_Struct, structs::MoveItem_Struct); + + Log(Logs::Detail, Logs::Netcode, "SteamLatest::DECODE(OP_MoveItem)"); + + emu->from_slot = SteamLatestToServerSlot(eq->from_slot); + emu->to_slot = SteamLatestToServerSlot(eq->to_slot); + IN(number_in_stack); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_RemoveBlockedBuffs) { DECODE_FORWARD(OP_BlockedBuffs); } + + DECODE(OP_SetServerFilter) + { + DECODE_LENGTH_EXACT(structs::SetServerFilter_Struct); + SETUP_DIRECT_DECODE(SetServerFilter_Struct, structs::SetServerFilter_Struct); + + int r; + for (r = 0; r < 29; r++) { + // Size 68 in SteamLatest + IN(filters[r]); + } + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ShopPlayerBuy) + { + DECODE_LENGTH_EXACT(structs::Merchant_Sell_Request_Struct); + SETUP_DIRECT_DECODE(Merchant_Sell_Struct, structs::Merchant_Sell_Request_Struct); + + IN(npcid); + IN(playerid); + IN(itemslot); + IN(quantity); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ShopPlayerSell) + { + DECODE_LENGTH_EXACT(structs::Merchant_Purchase_Request_Struct); + SETUP_DIRECT_DECODE(Merchant_Purchase_Struct, structs::Merchant_Purchase_Request_Struct); + + IN(npcid); + emu->itemslot = SteamLatestToServerTypelessSlot(eq->inventory_slot, invtype::typePossessions); + IN(quantity); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ShopRequest) + { + DECODE_LENGTH_EXACT(structs::MerchantClickRequest_Struct); + SETUP_DIRECT_DECODE(MerchantClick_Struct, structs::MerchantClickRequest_Struct); + + IN(npc_id); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_SpawnAppearance) { + DECODE_LENGTH_EXACT(structs::SpawnAppearance_Struct); + SETUP_DIRECT_DECODE(SpawnAppearance_Struct, structs::SpawnAppearance_Struct); + + IN(spawn_id); + emu->type = SteamLatestToServerSpawnAppearanceType(eq->type); + IN(parameter); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_TradeSkillCombine) + { + DECODE_LENGTH_EXACT(structs::NewCombine_Struct); + SETUP_DIRECT_DECODE(NewCombine_Struct, structs::NewCombine_Struct); + + emu->container_slot = SteamLatestToServerSlot(eq->container_slot); + emu->guildtribute_slot = SteamLatestToServerSlot(eq->guildtribute_slot); // this should only return INVALID_INDEX until implemented + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_WearChange) + { + DECODE_LENGTH_EXACT(structs::WearChange_Struct); + SETUP_DIRECT_DECODE(WearChange_Struct, structs::WearChange_Struct); + + IN(spawn_id); + emu->wear_slot_id = eq->wear_slot_id; + emu->material = eq->armor_id; + emu->unknown06 = eq->variation; + emu->elite_material = eq->material; + emu->hero_forge_model = eq->new_armor_id; + emu->unknown18 = eq->new_armor_type; + emu->color.Color = eq->color; + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ZoneEntry) + { + DECODE_LENGTH_EXACT(structs::ClientZoneEntry_Struct); + SETUP_DIRECT_DECODE(ClientZoneEntry_Struct, structs::ClientZoneEntry_Struct); + + memcpy(emu->char_name, eq->char_name, sizeof(emu->char_name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_ZoneChange) + { + DECODE_LENGTH_EXACT(structs::ZoneChange_Struct); + SETUP_DIRECT_DECODE(ZoneChange_Struct, structs::ZoneChange_Struct); + + memcpy(emu->char_name, eq->char_name, sizeof(emu->char_name)); + IN(zoneID); + IN(instanceID); + IN(y); + IN(x); + IN(z) + IN(zone_reason); + IN(success); + + FINISH_DIRECT_DECODE(); + } + + //Naive version but should work well enough for now + int ExtractIDFile(const std::string& input) { + std::string number; + for (char ch : input) { + if (std::isdigit(static_cast(ch))) { + number += ch; + } + } + + if (number.empty()) { + return 0; + } + + return std::stoi(number); + } + + // Helper Functions + void SerializeItemDefinition(SerializeBuffer& buffer, const EQ::ItemData* item) { + //u8 Type; + buffer.WriteUInt8(item->ItemClass); + //char Name[]; + buffer.WriteString(item->Name); + + //char LoreName[]; + buffer.WriteString(item->Lore); + + //we need to parse id file + //s32 IDFile; + int32 idfile = ExtractIDFile(item->IDFile); + buffer.WriteUInt32(idfile); + + //s32 IDFile2; + buffer.WriteUInt32(0); //unsupported atm + + /* + ibs.id = item->ID; + ibs.weight = item->Weight; + ibs.norent = item->NoRent; + ibs.nodrop = item->NoDrop; + ibs.attune = item->Attuneable; + ibs.size = item->Size; + ibs.slots = item->Slots; + ibs.price = item->Price; + ibs.icon = item->Icon; + */ + + //s32 ItemNumber; + buffer.WriteUInt32(item->ID); + + //s32 Weight; + buffer.WriteInt32(item->Weight); + + //bool NoRent; + buffer.WriteUInt8(item->NoRent); + + //bool IsDroppable; + buffer.WriteUInt8(item->NoDrop); + + //bool Attuneable; + buffer.WriteUInt8(item->Attuneable); + + //u8 Size; + buffer.WriteUInt8(item->Size); + + //u32 EquipSlots; + buffer.WriteUInt32(item->Slots); + + //u32 Cost; + buffer.WriteUInt32(item->Price); + + //s32 IconNumber; + buffer.WriteInt32(item->Icon); + + //bool eGMRequirement; + buffer.WriteUInt8(0); //unsupported atm + + //bool Tradeskills; + buffer.WriteUInt8(0); //unsupported atm + + //s8 SvCold; + //s8 SvDisease; + //s8 SvPoison; + //s8 SvMagic; + //s8 SvFire; + //s8 SvCorruption; + buffer.WriteInt8(item->CR); + buffer.WriteInt8(item->DR); + buffer.WriteInt8(item->PR); + buffer.WriteInt8(item->MR); + buffer.WriteInt8(item->FR); + buffer.WriteInt8(item->SVCorruption); + + //s8 STR; + //s8 STA; + //s8 AGI; + //s8 DEX; + //s8 CHA; + //s8 INT; + //s8 WIS; + buffer.WriteInt8(item->AStr); + buffer.WriteInt8(item->ASta); + buffer.WriteInt8(item->AAgi); + buffer.WriteInt8(item->ADex); + buffer.WriteInt8(item->ACha); + buffer.WriteInt8(item->AInt); + buffer.WriteInt8(item->AWis); + + //s32 HP; + //s32 Mana; + //s32 Endurance; + //s32 AC; + buffer.WriteInt32(item->HP); + buffer.WriteInt32(item->Mana); + buffer.WriteInt32(item->Endur); + buffer.WriteInt32(item->AC); + + //s32 HPRegen; + //s32 ManaRegen; + //s32 EnduranceRegen; + buffer.WriteInt32(item->Regen); + buffer.WriteInt32(item->ManaRegen); + buffer.WriteInt32(item->EnduranceRegen); + + //u32 Classes; + //u32 Races; + //u32 Deity; + buffer.WriteUInt32(item->Classes); + buffer.WriteUInt32(item->Races); + buffer.WriteUInt32(item->Deity); + + //u32 SkillModValue; + //u32 SkillModMax; + //s32 SkillModType; + //s32 SkillModBonus; + buffer.WriteInt32(item->SkillModValue); + buffer.WriteUInt32(item->SkillModMax); + buffer.WriteInt32(item->SkillModType); + buffer.WriteInt32(0); //unsupported atm + + //s32 BaneDMGRace; + //s32 BaneDMGBodyType; + //s32 BaneDMGRaceValue; + //s32 BaneDMGBodyTypeValue; + buffer.WriteInt32(item->BaneDmgRace); + buffer.WriteInt32(item->BaneDmgBody); + buffer.WriteInt32(item->BaneDmgRaceAmt); + buffer.WriteInt32(item->BaneDmgAmt); + + //bool Magic; + buffer.WriteUInt8(item->Magic); + + //s32 FoodDuration; + buffer.WriteInt32(item->CastTime_); + + //s32 RequiredLevel; + buffer.WriteInt32(item->ReqLevel > 125 ? 125 : item->ReqLevel); + + //s32 RecommendedLevel; + buffer.WriteInt32(item->RecLevel > 125 ? 125 : item->RecLevel); + + //s32 InstrumentType; + //s32 InstrumentMod; + buffer.WriteInt32(item->BardType); + buffer.WriteInt32(item->BardValue); + + //u8 Light; + buffer.WriteUInt8(item->Light); + + //u8 Delay; + buffer.WriteUInt8(item->Delay); + + //u8 ElementalFlag; + //u8 ElementalDamage; + buffer.WriteUInt8(item->ElemDmgType); + buffer.WriteUInt8(item->ElemDmgAmt); + + //u8 Range; + buffer.WriteUInt8(item->Range); + + //u32 Damage; + buffer.WriteUInt32(item->Damage); + + //u32 MaterialTintIndex; + //u32 Prestige; + buffer.WriteUInt32(item->Color); + buffer.WriteUInt32(0); //unsupported atm + + //u8 ItemClass; + buffer.WriteUInt8(item->ItemType); + + //ArmorProperties properties; + //s32 Type; + //s32 Material; + //s32 Variation; + //s32 NewArmorId; + //s32 NewArmorType; + buffer.WriteUInt32(item->Material); + buffer.WriteUInt32(0); //unsupported atm + buffer.WriteUInt32(item->EliteMaterial); + buffer.WriteUInt32(item->HerosForgeModel); + buffer.WriteUInt32(0); //unsupported atm + + //float MerchantGreedMod; + buffer.WriteFloat(item->SellRate); + + //s32 DmgBonusSkill; + //s32 DmgBonusValue; + buffer.WriteInt32(item->ExtraDmgSkill); + buffer.WriteInt32(item->ExtraDmgAmt); + + //s32 ScriptID; + //char CharmFile[]; + buffer.WriteUInt32(item->CharmFileID); + buffer.WriteString(item->CharmFile); + + //s32 AugType; + //u32 AugSkinTypeMask; + //u32 AugRestrictions; + buffer.WriteUInt32(item->AugType); + buffer.WriteInt32(-1); //unsupported atm + buffer.WriteUInt32(item->AugRestrict); + + //ItemAugmentationSocket AugData[6]; + for (int j = 0; j < 6; ++j) { + /* + s32 Type; + bool Visible; + bool Infusible; + */ + + buffer.WriteInt32(item->AugSlotType[j]); + buffer.WriteUInt8(item->AugSlotVisible[j]); + buffer.WriteUInt8(item->AugSlotUnk2[j]); //not entirely supported atm + } + + //s32 LDType; + //s32 LDTheme; + //s32 LDCost; + //s32 PointBuyBackPercent; + //s32 NeedAdventureCompleted; + buffer.WriteUInt32(item->PointType); + buffer.WriteUInt32(item->LDoNTheme); + buffer.WriteUInt32(item->LDoNPrice); + buffer.WriteUInt32(item->LDoNSellBackRate); + buffer.WriteUInt32(item->LDoNSold); + + //u8 ContainerType; + //u8 Slots; + //u8 SizeCapacity; + //u8 WeightReduction; + buffer.WriteUInt8(item->BagType); + buffer.WriteUInt8(item->BagSlots); + buffer.WriteUInt8(item->BagSize); + buffer.WriteUInt8(item->BagWR); + + //u8 BookType; + //u8 BookLang; + //char BookFile[]; + buffer.WriteUInt8(item->Book); + buffer.WriteUInt8(item->BookType); //doesn't match the name for eqlib + buffer.WriteString(item->Filename); + + //s32 Lore; + buffer.WriteInt32(item->LoreGroup); + + //bool Artifact; + buffer.WriteUInt8(item->ArtifactFlag); + + //s32 Favor; + buffer.WriteUInt32(item->Favor); + + //bool bIsFVNoDrop; + buffer.WriteUInt8(item->FVNoDrop); + + //s32 Attack; + //s32 Haste; + buffer.WriteInt32(item->Attack); + buffer.WriteInt32(item->Haste); + + //s32 GuildFavor; + buffer.WriteUInt32(item->GuildFavor); + + //s32 SolventItemID; + buffer.WriteUInt32(item->AugDistiller); + + //s32 AnimationOverride; + buffer.WriteInt32(-1); //unsupported atm + + //u32 PaletteTintIndex; + buffer.WriteInt32(0); //unsupported atm + + //bool bNoPetGive; + buffer.WriteUInt8(item->NoPet); + + //bool bSomeProfile; + buffer.WriteUInt8(0); //unsupported atm + + //u32 StackSize; + buffer.WriteUInt32(item->ID == PARCEL_MONEY_ITEM_ID ? 0x7FFFFFFF : ((item->Stackable ? item->StackSize : 0))); + + //bool bNoStorage; + buffer.WriteUInt8(item->NoTransfer); + + //bool Expendable; + uint8 expendable = item->ExpendableArrow; + + if (item->ItemType == EQ::item::ItemTypeFishingPole && item->SubType == 0) { + expendable = 1; + } + + buffer.WriteUInt8(expendable); + + //u8 SpellDataSkillMask[78]; + for (int j = 0; j < 78; ++j) { + buffer.WriteUInt8(0); //unsure what this is exactly + } + + /* SpellData: + s32 SpellId; + u8 RequiredLevel; + u8 EffectType; + s32 EffectiveCasterLevel; + s32 MaxCharges; + s32 CastTime; + s32 RecastTime; + s32 RecastType; + s32 ProcRate; + char OverrideName[]; + s32 OverrideDesc; + */ + + //SpellData SpellDataClicky; + buffer.WriteInt32(item->Click.Effect); + buffer.WriteUInt8(item->Click.Level2); + buffer.WriteUInt8(item->Click.Type); + buffer.WriteInt32(item->Click.Level); + buffer.WriteInt32(item->MaxCharges); + buffer.WriteInt32(item->CastTime); + buffer.WriteInt32(item->RecastDelay); + buffer.WriteInt32(item->RecastType); + buffer.WriteInt32(0); //unsupported atm + if (strlen(item->ClickName) > 0) { + buffer.WriteString(item->ClickName); + } + else { + buffer.WriteString(""); + } + buffer.WriteInt32(0); //unsupported atm + + //SpellData SpellDataProc; + buffer.WriteInt32(item->Proc.Effect); + buffer.WriteUInt8(item->Proc.Level2); + buffer.WriteUInt8(item->Proc.Type); + buffer.WriteInt32(item->Proc.Level); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); //unsupported atm; even live sets this to 0 for procs + if (strlen(item->ProcName) > 0) { + buffer.WriteString(item->ProcName); + } + else { + buffer.WriteString(""); + } + buffer.WriteInt32(0); //unsupported atm + + //SpellData SpellDataWorn; + buffer.WriteInt32(item->Worn.Effect); + buffer.WriteUInt8(item->Worn.Level2); + buffer.WriteUInt8(item->Worn.Type); + buffer.WriteInt32(item->Worn.Level); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + if (strlen(item->WornName) > 0) { + buffer.WriteString(item->WornName); + } + else { + buffer.WriteString(""); + } + buffer.WriteInt32(0); //unsupported atm + + //SpellData SpellDataFocus; + buffer.WriteInt32(item->Focus.Effect); + buffer.WriteUInt8(item->Focus.Level2); + buffer.WriteUInt8(item->Focus.Type); + buffer.WriteInt32(item->Focus.Level); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + if (strlen(item->FocusName) > 0) { + buffer.WriteString(item->FocusName); + } + else { + buffer.WriteString(""); + } + buffer.WriteInt32(0); //unsupported atm + + //SpellData SpellDataScroll; + buffer.WriteInt32(item->Scroll.Effect); + buffer.WriteUInt8(item->Scroll.Level2); + buffer.WriteUInt8(item->Scroll.Type); + buffer.WriteInt32(item->Scroll.Level); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + if (strlen(item->ScrollName) > 0) { + buffer.WriteString(item->ScrollName); + } + else { + buffer.WriteString(""); + } + buffer.WriteInt32(0); //unsupported atm + + //SpellData SpellDataFocus2; //unsupported atm + buffer.WriteInt32(-1); + buffer.WriteUInt8(0); + buffer.WriteUInt8(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteString(""); + buffer.WriteInt32(0); + + //SpellData SpellDataBlessing; //unsupported atm + buffer.WriteInt32(-1); + buffer.WriteUInt8(0); + buffer.WriteUInt8(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteInt32(0); + buffer.WriteString(""); + buffer.WriteInt32(0); + + //s32 RightClickScriptID; + buffer.WriteInt32(0); //unsupported atm + + //bool QuestItem; + buffer.WriteInt8(item->QuestItemFlag); + + //s32 MaxPower; + buffer.WriteInt32(0); //unsupported atm + + //s32 Purity; + buffer.WriteInt32(item->Purity); + + //s32 BackstabDamage; + buffer.WriteInt32(item->BackstabDmg); + + //s32 HeroicSTR; + //s32 HeroicINT; + //s32 HeroicWIS; + //s32 HeroicAGI; + //s32 HeroicDEX; + //s32 HeroicSTA; + //s32 HeroicCHA; + buffer.WriteInt32(item->HeroicStr); + buffer.WriteInt32(item->HeroicInt); + buffer.WriteInt32(item->HeroicWis); + buffer.WriteInt32(item->HeroicAgi); + buffer.WriteInt32(item->HeroicDex); + buffer.WriteInt32(item->HeroicSta); + buffer.WriteInt32(item->HeroicCha); + + //s32 HealAmount; + //s32 SpellDamage; + //s32 Clairvoyance; + buffer.WriteInt32(item->HealAmt); + buffer.WriteInt32(item->SpellDmg); + buffer.WriteInt32(item->Clairvoyance); + + //s32 SubClass; + buffer.WriteInt32(item->SubType); + + //bool bLoginRegReqItem; + buffer.WriteUInt8(0); //unsupported atm + + //s32 ItemLaunchScriptID; + buffer.WriteInt32(0); //unsupported atm + + //bool Heirloom; + buffer.WriteUInt8(0); //unsupported atm + + //s32 Placeable; + buffer.WriteInt32(0); //unsupported atm + + //bool bPlaceableIgnoreCollisions; + buffer.WriteUInt8(0); + + //s32 PlacementType; + buffer.WriteInt32(0); //unsupported atm + + //s32 RealEstateDefID; + buffer.WriteInt32(0); //unsupported atm + + //float PlaceableScaleRangeMin; + //float PlaceableScaleRangeMax; + buffer.WriteFloat(0.0f); //unsupported atm + buffer.WriteFloat(0.0f); //unsupported atm + + //s32 RealEstateUpkeepID; + buffer.WriteInt32(0); //unsupported atm + + //s32 MaxPerRealEstate; + buffer.WriteInt32(-1); //unsupported atm + + //char HousepetFileName[]; + buffer.WriteString(""); //unsupported atm + + //bool bInteractiveObject; + buffer.WriteUInt8(0); //unsupported atm + + //s32 TrophyBenefitID; + buffer.WriteInt32(-1); //unsupported atm + + //bool bDisablePlacementRotation; + //bool bDisableFreePlacement; + buffer.WriteUInt8(0); //unsupported atm + buffer.WriteUInt8(0); //unsupported atm + + //s32 NpcRespawnInterval; + buffer.WriteInt32(0); //unsupported atm + + //float PlaceableDefScale; + //float PlaceableDefHeading; + //float PlaceableDefPitch; + //float PlaceableDefRoll; + buffer.WriteFloat(0.0f); + buffer.WriteFloat(0.0f); + buffer.WriteFloat(0.0f); + buffer.WriteFloat(0.0f); + + //u8 SocketSubClassCount; + //s32 SocketSubClass[SocketSubClassCount]; + buffer.WriteUInt8(0); //unsupported atm + + //bool Collectible; + buffer.WriteUInt8(0); //unsupported atm + + //bool NoDestroy; + buffer.WriteUInt8(0); //unsupported atm + + //bool bNoNPC; + buffer.WriteUInt8(0); //unsupported atm + + //bool NoZone; + buffer.WriteUInt8(0); //unsupported atm + + //s32 MakerId; + buffer.WriteInt32(0); //unsupported atm + + //bool NoGround; + buffer.WriteUInt8(0); //unsupported atm + + //bool bNoLoot; + buffer.WriteUInt8(0); //unsupported atm + + //bool MarketPlace; + buffer.WriteUInt8(0); //unsupported atm + + //bool bFreeSlot; + buffer.WriteUInt8(0); //unsupported atm + + //bool bAutoUse; + buffer.WriteUInt8(0); //unsupported atm + + //s32 Unknown0x0e4; + buffer.WriteInt32(-1); //unsupported atm + + //s32 MinLuck; + //s32 MaxLuck; + buffer.WriteUInt32(0); //unsupported atm + buffer.WriteUInt32(0); //unsupported atm + + //s32 LoreEquipped; + buffer.WriteUInt32(0); //unsupported atm + } + + void SerializeItem(SerializeBuffer& buffer, const EQ::ItemInstance* inst, int16 slot_id_in, uint8 depth, ItemPacketType packet_type) { + const EQ::ItemData* item = inst->GetUnscaledItem(); + + //char ItemGUID[]; + auto item_guid = fmt::format("{:016}", inst->GetSerialNumber()); + buffer.WriteString(item_guid); + + //u32 StackCount; + auto stacksize = + item->ID == PARCEL_MONEY_ITEM_ID ? inst->GetPrice() : (inst->IsStackable() ? ((inst->GetCharges() > 1000) + ? 0xFFFFFFFF : inst->GetCharges()) : 1); + buffer.WriteUInt32(stacksize); + + structs::InventorySlot_Struct slot_id{}; + switch (packet_type) { + case ItemPacketLoot: + slot_id = ServerToSteamLatestCorpseSlot(slot_id_in); + break; + default: + slot_id = ServerToSteamLatestSlot(slot_id_in); + break; + } + + //u32 slot_type; + buffer.WriteUInt32(inst->GetMerchantSlot() ? invtype::typeMerchant : slot_id.Type); + //s16 main_slot; + //s16 sub_slot; + //s16 aug_slot; + buffer.WriteInt16(inst->GetMerchantSlot() ? inst->GetMerchantSlot() : slot_id.Slot); + buffer.WriteInt16(inst->GetMerchantSlot() ? 0xffff : slot_id.SubIndex); + buffer.WriteInt16(inst->GetMerchantSlot() ? 0xffff : slot_id.AugIndex); + + //u64 price; + buffer.WriteUInt64(inst->GetPrice()); + + //u32 MerchantQuantity; + buffer.WriteUInt32(inst->GetMerchantSlot() ? inst->GetMerchantCount() : 1); + + //u32 ScriptIndex; + buffer.WriteUInt32(inst->IsScaling() ? (inst->GetExp() / 100) : 0); + + //u64 MerchantSlot; + buffer.WriteUInt64(inst->GetMerchantSlot() ? inst->GetMerchantSlot() : inst->GetSerialNumber()); + + //u32 LastCastTime; + buffer.WriteUInt32(inst->GetRecastTimestamp()); + + //s32 Charges; + auto charges = (inst->IsStackable() ? (item->MaxCharges ? 1 : 0) : ((inst->GetCharges() > 254) + ? -1 + : inst->GetCharges())); + + buffer.WriteInt32(charges); + + //s32 NoDropFlag; + buffer.WriteInt32(inst->IsAttuned() ? 1 : 0); + + //s32 Power; + buffer.WriteInt32(0); + + //s32 AugFlag; + buffer.WriteInt32(0); + + //bool bConvertable; + buffer.WriteInt8(0); + + //u32 ConvertItemNameLength; + buffer.WriteInt32(0); + + //char ConvertItemName[ConvertItemNameLength]; + + //u32 ConvertItemID; + buffer.WriteInt32(0); + + //u32 Open; + buffer.WriteInt32(0); + + //bool EvolvingItem; + buffer.WriteInt8(item->EvolvingItem); + + //EvoData evoData; + if (item->EvolvingItem > 0) { + //s32 GroupId; + buffer.WriteInt32(0); + + //s32 EvolvingCurrentLevel; + buffer.WriteInt32(item->EvolvingLevel); + + //double EvolvingExpPct; + buffer.WriteDouble(0.0); + + //s32 EvolvingMaxLevel; + buffer.WriteInt32(item->EvolvingMax); + + //s32 LastEquipped; + buffer.WriteInt32(0); + } + + uint32 ornamentation_icon = (inst->GetOrnamentationIcon() ? inst->GetOrnamentationIcon() : 0); + uint32 hero_model = 0; + + //s32 ActorTag1; + //s32 ActorTag2; + if (inst->GetOrnamentationIDFile()) { + hero_model = inst->GetOrnamentHeroModel(EQ::InventoryProfile::CalcMaterialFromSlot(slot_id_in)); + + buffer.WriteInt32(inst->GetOrnamentationIDFile()); + buffer.WriteInt32(inst->GetOrnamentationIDFile()); + } + else { + buffer.WriteInt32(0); + buffer.WriteInt32(0); + } + + //s32 OrnamentationIcon; + //s32 ArmorType; + //s32 NewArmorID; + //u32 Tint; + buffer.WriteInt32(ornamentation_icon); + buffer.WriteInt32(-1); + buffer.WriteInt32(hero_model); + buffer.WriteInt32(0); + + //bool bCopied; + buffer.WriteUInt8(0); + + //s32 RealEstateID; + buffer.WriteInt32(-1); + //s32 RespawnTime; + buffer.WriteInt32(0); + + //ItemDefinition Item; + SerializeItemDefinition(buffer, item); + + //u32 RealEstateArrayCount; + buffer.WriteInt32(0); + //s32 RealEstateArray[RealEstateArrayCount]; + + //bool bRealEstateItemPlaceable; + buffer.WriteInt8(0); + + //u32 SubContentSize; + uint32 subitem_count = 0; + + int16 SubSlotNumber = EQ::invbag::SLOT_INVALID; + + if (slot_id_in <= EQ::invslot::GENERAL_END && slot_id_in >= EQ::invslot::GENERAL_BEGIN) + SubSlotNumber = EQ::invbag::GENERAL_BAGS_BEGIN + ((slot_id_in - EQ::invslot::GENERAL_BEGIN) * EQ::invbag::SLOT_COUNT); + else if (slot_id_in == EQ::invslot::slotCursor) + SubSlotNumber = EQ::invbag::CURSOR_BAG_BEGIN; + else if (slot_id_in <= EQ::invslot::BANK_END && slot_id_in >= EQ::invslot::BANK_BEGIN) + SubSlotNumber = EQ::invbag::BANK_BAGS_BEGIN + ((slot_id_in - EQ::invslot::BANK_BEGIN) * EQ::invbag::SLOT_COUNT); + else if (slot_id_in <= EQ::invslot::SHARED_BANK_END && slot_id_in >= EQ::invslot::SHARED_BANK_BEGIN) + SubSlotNumber = EQ::invbag::SHARED_BANK_BAGS_BEGIN + ((slot_id_in - EQ::invslot::SHARED_BANK_BEGIN) * EQ::invbag::SLOT_COUNT); + else + SubSlotNumber = slot_id_in; // not sure if this is the best way to handle this..leaving for now + + if (SubSlotNumber != EQ::invbag::SLOT_INVALID) { + for (uint32 index = EQ::invbag::SLOT_BEGIN; index <= EQ::invbag::SLOT_END; ++index) { + EQ::ItemInstance* sub = inst->GetItem(index); + if (!sub) + continue; + + ++subitem_count; + } + + buffer.WriteUInt32(subitem_count); + + for (uint32 index = EQ::invbag::SLOT_BEGIN; index <= EQ::invbag::SLOT_END; ++index) { + EQ::ItemInstance* sub = inst->GetItem(index); + if (!sub) + continue; + + buffer.WriteUInt32(index); + + SerializeItem(buffer, sub, SubSlotNumber, (depth + 1), packet_type); + } + } + + //bool bCollected; + buffer.WriteInt8(0); //unsupported atm + //u64 DontKnow; + buffer.WriteUInt64(0); //unsupported atm + //s32 Luck; + buffer.WriteInt32(0); //unsupported atm + } + + static inline void ServerToSteamLatestConvertLinks(std::string& message_out, const std::string& message_in) + { + if (message_in.find('\x12') == std::string::npos) { + message_out = message_in; + return; + } + + auto segments = Strings::Split(message_in, '\x12'); + for (size_t segment_iter = 0; segment_iter < segments.size(); ++segment_iter) { + if (segment_iter & 1) { + auto etag = std::stoi(segments[segment_iter].substr(0, 1)); + + switch (etag) { + case 0: + { + size_t index = 1; + auto item_id = segments[segment_iter].substr(index, 5); + index += 5; + + auto aug1 = segments[segment_iter].substr(index, 5); + index += 5; + + auto aug2 = segments[segment_iter].substr(index, 5); + index += 5; + + auto aug3 = segments[segment_iter].substr(index, 5); + index += 5; + + auto aug4 = segments[segment_iter].substr(index, 5); + index += 5; + + auto aug5 = segments[segment_iter].substr(index, 5); + index += 5; + + auto aug6 = segments[segment_iter].substr(index, 5); + index += 5; + + auto is_evolving = segments[segment_iter].substr(index, 1); + index += 1; + + auto evolutionGroup = segments[segment_iter].substr(index, 4); + index += 4; + + auto evolutionLevel = segments[segment_iter].substr(index, 2); + index += 2; + + auto ornamentationIconID = segments[segment_iter].substr(index, 5); + index += 5; + + auto itemHash = segments[segment_iter].substr(index, 8); + index += 8; + + auto text = segments[segment_iter].substr(index); + + message_out.push_back('\x12'); + message_out.push_back('0'); //etag item + message_out.append(item_id); + message_out.append(aug1); + message_out.append("00000"); + message_out.append(aug2); + message_out.append("00000"); + message_out.append(aug3); + message_out.append("00000"); + message_out.append(aug4); + message_out.append("00000"); + message_out.append(aug5); + message_out.append("00000"); + message_out.append(aug6); + message_out.append("00000"); + message_out.append(is_evolving); + message_out.append(evolutionGroup); + message_out.append(evolutionLevel); + message_out.append(ornamentationIconID); + message_out.append("00000"); + message_out.append(itemHash); + message_out.append(text); + message_out.push_back('\x12'); + + break; + } + default: + //unsupported etag right now; just pass it as is + message_out.append(segments[segment_iter]); + break; + } + } + else { + message_out.append(segments[segment_iter]); + } + } + } + + static inline void SteamLatestToServerConvertLinks(std::string& message_out, const std::string& message_in) { + message_out = message_in; + } + + static inline uint32 ServerToSteamLatestSpawnAppearanceType(uint32 server_type) { + switch (server_type) + { + case AppearanceType::WhoLevel: + return structs::SteamLatestAppearance::WhoLevel; + case AppearanceType::MaxHealth: + return structs::SteamLatestAppearance::MaxHealth; + case AppearanceType::Invisibility: + return structs::SteamLatestAppearance::Invisibility; + case AppearanceType::PVP: + return structs::SteamLatestAppearance::PVP; + case AppearanceType::Light: + return structs::SteamLatestAppearance::Light; + case AppearanceType::Animation: + return structs::SteamLatestAppearance::Animation; + case AppearanceType::Sneak: + return structs::SteamLatestAppearance::Sneak; + case AppearanceType::SpawnID: + return structs::SteamLatestAppearance::SpawnID; + case AppearanceType::Health: + return structs::SteamLatestAppearance::Health; + case AppearanceType::Linkdead: + return structs::SteamLatestAppearance::Linkdead; + case AppearanceType::FlyMode: + return structs::SteamLatestAppearance::FlyMode; + case AppearanceType::GM: + return structs::SteamLatestAppearance::GM; + case AppearanceType::Anonymous: + return structs::SteamLatestAppearance::Anonymous; + case AppearanceType::GuildID: + return structs::SteamLatestAppearance::GuildID; + case AppearanceType::AFK: + return structs::SteamLatestAppearance::AFK; + case AppearanceType::Pet: + return structs::SteamLatestAppearance::Pet; + case AppearanceType::Summoned: + return structs::SteamLatestAppearance::Summoned; + case AppearanceType::SetType: + return structs::SteamLatestAppearance::NPCName; + case AppearanceType::CancelSneakHide: + return structs::SteamLatestAppearance::CancelSneakHide; + case AppearanceType::AreaHealthRegen: + return structs::SteamLatestAppearance::AreaHealthRegen; + case AppearanceType::AreaManaRegen: + return structs::SteamLatestAppearance::AreaManaRegen; + case AppearanceType::AreaEnduranceRegen: + return structs::SteamLatestAppearance::AreaEnduranceRegen; + case AppearanceType::FreezeBeneficialBuffs: + return structs::SteamLatestAppearance::FreezeBeneficialBuffs; + case AppearanceType::NPCTintIndex: + return structs::SteamLatestAppearance::NPCTintIndex; + case AppearanceType::ShowHelm: + return structs::SteamLatestAppearance::ShowHelm; + case AppearanceType::DamageState: + return structs::SteamLatestAppearance::DamageState; + case AppearanceType::TextureType: + return structs::SteamLatestAppearance::TextureType; + case AppearanceType::GuildShow: + return structs::SteamLatestAppearance::GuildShow; + case AppearanceType::OfflineMode: + return structs::SteamLatestAppearance::OfflineMode; + default: + return structs::SteamLatestAppearance::None; + } + } + + static inline uint32 SteamLatestToServerSpawnAppearanceType(uint32 steam_latest_type) { + switch (steam_latest_type) + { + case structs::SteamLatestAppearance::WhoLevel: + return AppearanceType::WhoLevel; + case structs::SteamLatestAppearance::MaxHealth: + return AppearanceType::MaxHealth; + case structs::SteamLatestAppearance::Invisibility: + return AppearanceType::Invisibility; + case structs::SteamLatestAppearance::PVP: + return AppearanceType::PVP; + case structs::SteamLatestAppearance::Light: + return AppearanceType::Light; + case structs::SteamLatestAppearance::Animation: + return AppearanceType::Animation; + case structs::SteamLatestAppearance::Sneak: + return AppearanceType::Sneak; + case structs::SteamLatestAppearance::SpawnID: + return AppearanceType::SpawnID; + case structs::SteamLatestAppearance::Health: + return AppearanceType::Health; + case structs::SteamLatestAppearance::Linkdead: + return AppearanceType::Linkdead; + case structs::SteamLatestAppearance::FlyMode: + return AppearanceType::FlyMode; + case structs::SteamLatestAppearance::GM: + return AppearanceType::GM; + case structs::SteamLatestAppearance::Anonymous: + return AppearanceType::Anonymous; + case structs::SteamLatestAppearance::GuildID: + return AppearanceType::GuildID; + case structs::SteamLatestAppearance::AFK: + return AppearanceType::AFK; + case structs::SteamLatestAppearance::Pet: + return AppearanceType::Pet; + case structs::SteamLatestAppearance::Summoned: + return AppearanceType::Summoned; + case structs::SteamLatestAppearance::SetType: + return AppearanceType::NPCName; + case structs::SteamLatestAppearance::CancelSneakHide: + return AppearanceType::CancelSneakHide; + case structs::SteamLatestAppearance::AreaHealthRegen: + return AppearanceType::AreaHealthRegen; + case structs::SteamLatestAppearance::AreaManaRegen: + return AppearanceType::AreaManaRegen; + case structs::SteamLatestAppearance::AreaEnduranceRegen: + return AppearanceType::AreaEnduranceRegen; + case structs::SteamLatestAppearance::FreezeBeneficialBuffs: + return AppearanceType::FreezeBeneficialBuffs; + case structs::SteamLatestAppearance::NPCTintIndex: + return AppearanceType::NPCTintIndex; + case structs::SteamLatestAppearance::ShowHelm: + return AppearanceType::ShowHelm; + case structs::SteamLatestAppearance::DamageState: + return AppearanceType::DamageState; + case structs::SteamLatestAppearance::TextureType: + return AppearanceType::TextureType; + case structs::SteamLatestAppearance::GuildShow: + return AppearanceType::GuildShow; + case structs::SteamLatestAppearance::OfflineMode: + return AppearanceType::OfflineMode; + default: + return AppearanceType::Die; + } + } + + static inline structs::InventorySlot_Struct ServerToSteamLatestSlot(uint32 server_slot) + { + structs::InventorySlot_Struct SteamLatestSlot; + SteamLatestSlot.Type = invtype::TYPE_INVALID; + SteamLatestSlot.Slot = invslot::SLOT_INVALID; + SteamLatestSlot.SubIndex = invbag::SLOT_INVALID; + SteamLatestSlot.AugIndex = invaug::SOCKET_INVALID; + + uint32 TempSlot = EQ::invslot::SLOT_INVALID; + + if (server_slot < EQ::invtype::POSSESSIONS_SIZE) { + SteamLatestSlot.Type = invtype::typePossessions; + + if (server_slot == EQ::invslot::slotCursor) { + SteamLatestSlot.Slot = invslot::slotCursor; + } + else + { + SteamLatestSlot.Slot = server_slot; + } + } + + else if (server_slot <= EQ::invbag::CURSOR_BAG_END && server_slot >= EQ::invbag::GENERAL_BAGS_BEGIN) { + TempSlot = server_slot - EQ::invbag::GENERAL_BAGS_BEGIN; + + SteamLatestSlot.Type = invtype::typePossessions; + SteamLatestSlot.Slot = invslot::GENERAL_BEGIN + (TempSlot / EQ::invbag::SLOT_COUNT); + SteamLatestSlot.SubIndex = TempSlot - ((SteamLatestSlot.Slot - invslot::GENERAL_BEGIN) * EQ::invbag::SLOT_COUNT); + } + + else if (server_slot <= EQ::invslot::TRIBUTE_END && server_slot >= EQ::invslot::TRIBUTE_BEGIN) { + SteamLatestSlot.Type = invtype::typeTribute; + SteamLatestSlot.Slot = server_slot - EQ::invslot::TRIBUTE_BEGIN; + } + + else if (server_slot <= EQ::invslot::GUILD_TRIBUTE_END && server_slot >= EQ::invslot::GUILD_TRIBUTE_BEGIN) { + SteamLatestSlot.Type = invtype::typeGuildTribute; + SteamLatestSlot.Slot = server_slot - EQ::invslot::GUILD_TRIBUTE_BEGIN; + } + + else if (server_slot == EQ::invslot::SLOT_TRADESKILL_EXPERIMENT_COMBINE) { + SteamLatestSlot.Type = invtype::typeWorld; + } + + else if (server_slot <= EQ::invslot::BANK_END && server_slot >= EQ::invslot::BANK_BEGIN) { + SteamLatestSlot.Type = invtype::typeBank; + SteamLatestSlot.Slot = server_slot - EQ::invslot::BANK_BEGIN; + } + + else if (server_slot <= EQ::invbag::BANK_BAGS_END && server_slot >= EQ::invbag::BANK_BAGS_BEGIN) { + TempSlot = server_slot - EQ::invbag::BANK_BAGS_BEGIN; + + SteamLatestSlot.Type = invtype::typeBank; + SteamLatestSlot.Slot = TempSlot / EQ::invbag::SLOT_COUNT; + SteamLatestSlot.SubIndex = TempSlot - (SteamLatestSlot.Slot * EQ::invbag::SLOT_COUNT); + } + + else if (server_slot <= EQ::invslot::SHARED_BANK_END && server_slot >= EQ::invslot::SHARED_BANK_BEGIN) { + SteamLatestSlot.Type = invtype::typeSharedBank; + SteamLatestSlot.Slot = server_slot - EQ::invslot::SHARED_BANK_BEGIN; + } + + else if (server_slot <= EQ::invbag::SHARED_BANK_BAGS_END && server_slot >= EQ::invbag::SHARED_BANK_BAGS_BEGIN) { + TempSlot = server_slot - EQ::invbag::SHARED_BANK_BAGS_BEGIN; + + SteamLatestSlot.Type = invtype::typeSharedBank; + SteamLatestSlot.Slot = TempSlot / EQ::invbag::SLOT_COUNT; + SteamLatestSlot.SubIndex = TempSlot - (SteamLatestSlot.Slot * EQ::invbag::SLOT_COUNT); + } + + else if (server_slot <= EQ::invslot::TRADE_END && server_slot >= EQ::invslot::TRADE_BEGIN) { + SteamLatestSlot.Type = invtype::typeTrade; + SteamLatestSlot.Slot = server_slot - EQ::invslot::TRADE_BEGIN; + } + + else if (server_slot <= EQ::invbag::TRADE_BAGS_END && server_slot >= EQ::invbag::TRADE_BAGS_BEGIN) { + TempSlot = server_slot - EQ::invbag::TRADE_BAGS_BEGIN; + + SteamLatestSlot.Type = invtype::typeTrade; + SteamLatestSlot.Slot = TempSlot / EQ::invbag::SLOT_COUNT; + SteamLatestSlot.SubIndex = TempSlot - (SteamLatestSlot.Slot * EQ::invbag::SLOT_COUNT); + } + + else if (server_slot <= EQ::invslot::WORLD_END && server_slot >= EQ::invslot::WORLD_BEGIN) { + SteamLatestSlot.Type = invtype::typeWorld; + SteamLatestSlot.Slot = server_slot - EQ::invslot::WORLD_BEGIN; + } + + Log(Logs::Detail, Logs::Netcode, "Convert Server Slot %i to SteamLatest Slot [%i, %i, %i, %i]", + server_slot, SteamLatestSlot.Type, SteamLatestSlot.Slot, SteamLatestSlot.SubIndex, SteamLatestSlot.AugIndex); + + return SteamLatestSlot; + } + + static inline structs::InventorySlot_Struct ServerToSteamLatestCorpseSlot(uint32 server_corpse_slot) + { + structs::InventorySlot_Struct SteamLatestSlot; + SteamLatestSlot.Type = invtype::TYPE_INVALID; + SteamLatestSlot.Slot = ServerToSteamLatestCorpseMainSlot(server_corpse_slot); + SteamLatestSlot.SubIndex = invbag::SLOT_INVALID; + SteamLatestSlot.AugIndex = invaug::SOCKET_INVALID; + + if (SteamLatestSlot.Slot != invslot::SLOT_INVALID) + SteamLatestSlot.Type = invtype::typeCorpse; + + Log(Logs::Detail, Logs::Netcode, "Convert Server Corpse Slot %i to SteamLatest Corpse Slot [%i, %i, %i, %i]", + server_corpse_slot, SteamLatestSlot.Type, SteamLatestSlot.Slot, SteamLatestSlot.SubIndex, SteamLatestSlot.AugIndex); + + return SteamLatestSlot; + } + + static inline uint32 ServerToSteamLatestCorpseMainSlot(uint32 server_corpse_slot) + { + uint32 SteamLatestSlot = invslot::SLOT_INVALID; + + if (server_corpse_slot <= EQ::invslot::CORPSE_END && server_corpse_slot >= EQ::invslot::CORPSE_BEGIN) { + SteamLatestSlot = server_corpse_slot; + } + + LogNetcode("Convert Server Corpse Slot [{}] to SteamLatest Corpse Main Slot [{}]", server_corpse_slot, SteamLatestSlot); + + return SteamLatestSlot; + } + + static inline structs::TypelessInventorySlot_Struct ServerToSteamLatestTypelessSlot(uint32 server_slot, int16 server_type) + { + structs::TypelessInventorySlot_Struct SteamLatestSlot; + SteamLatestSlot.Slot = invslot::SLOT_INVALID; + SteamLatestSlot.SubIndex = invbag::SLOT_INVALID; + SteamLatestSlot.AugIndex = invaug::SOCKET_INVALID; + + uint32 TempSlot = EQ::invslot::SLOT_INVALID; + + if (server_type == EQ::invtype::typePossessions) { + if (server_slot < EQ::invtype::POSSESSIONS_SIZE) { + SteamLatestSlot.Slot = server_slot; + } + + else if (server_slot <= EQ::invbag::CURSOR_BAG_END && server_slot >= EQ::invbag::GENERAL_BAGS_BEGIN) { + TempSlot = server_slot - EQ::invbag::GENERAL_BAGS_BEGIN; + + SteamLatestSlot.Slot = invslot::GENERAL_BEGIN + (TempSlot / EQ::invbag::SLOT_COUNT); + SteamLatestSlot.SubIndex = TempSlot - ((SteamLatestSlot.Slot - invslot::GENERAL_BEGIN) * EQ::invbag::SLOT_COUNT); + } + } + + Log(Logs::Detail, Logs::Netcode, "Convert Server Slot %i to SteamLatest Typeless Slot [%i, %i, %i] (implied type: %i)", + server_slot, SteamLatestSlot.Slot, SteamLatestSlot.SubIndex, SteamLatestSlot.AugIndex, server_type); + + return SteamLatestSlot; + } + + static inline uint32 SteamLatestToServerSlot(structs::InventorySlot_Struct steam_latest_slot) + { + if (steam_latest_slot.AugIndex < invaug::SOCKET_INVALID || steam_latest_slot.AugIndex >= invaug::SOCKET_COUNT) { + Log(Logs::Detail, Logs::Netcode, "Convert SteamLatest Slot [%i, %i, %i, %i] to Server Slot %i", + steam_latest_slot.Type, steam_latest_slot.Slot, steam_latest_slot.SubIndex, steam_latest_slot.AugIndex, EQ::invslot::SLOT_INVALID); + + return EQ::invslot::SLOT_INVALID; + } + + uint32 server_slot = EQ::invslot::SLOT_INVALID; + uint32 temp_slot = invslot::SLOT_INVALID; + + switch (steam_latest_slot.Type) { + case invtype::typePossessions: { + if (steam_latest_slot.Slot >= invslot::POSSESSIONS_BEGIN && steam_latest_slot.Slot <= invslot::POSSESSIONS_END) { + if (steam_latest_slot.SubIndex == invbag::SLOT_INVALID) { + if (steam_latest_slot.Slot == invslot::slotCursor) { + server_slot = EQ::invslot::slotCursor; + } + else if(steam_latest_slot.Slot == invslot::slotGeneral11 || steam_latest_slot.Slot == invslot::slotGeneral12) + { + return EQ::invslot::SLOT_INVALID; + } + else { + server_slot = steam_latest_slot.Slot; + } + } + + else if (steam_latest_slot.SubIndex >= invbag::SLOT_BEGIN && steam_latest_slot.SubIndex <= invbag::SLOT_END) { + if (steam_latest_slot.Slot < invslot::GENERAL_BEGIN) + return EQ::invslot::SLOT_INVALID; + + temp_slot = (steam_latest_slot.Slot - invslot::GENERAL_BEGIN) * invbag::SLOT_COUNT; + server_slot = EQ::invbag::GENERAL_BAGS_BEGIN + temp_slot + steam_latest_slot.SubIndex; + } + } + + break; + } + case invtype::typeBank: { + if (steam_latest_slot.Slot >= invslot::SLOT_BEGIN && steam_latest_slot.Slot < invtype::BANK_SIZE) { + if (steam_latest_slot.SubIndex == invbag::SLOT_INVALID) { + server_slot = EQ::invslot::BANK_BEGIN + steam_latest_slot.Slot; + } + + else if (steam_latest_slot.SubIndex >= invbag::SLOT_BEGIN && steam_latest_slot.SubIndex <= invbag::SLOT_END) { + temp_slot = steam_latest_slot.Slot * invbag::SLOT_COUNT; + server_slot = EQ::invbag::BANK_BAGS_BEGIN + temp_slot + steam_latest_slot.SubIndex; + } + } + + break; + } + case invtype::typeSharedBank: { + if (steam_latest_slot.Slot >= invslot::SLOT_BEGIN && steam_latest_slot.Slot < invtype::SHARED_BANK_SIZE) { + if (steam_latest_slot.SubIndex == invbag::SLOT_INVALID) { + server_slot = EQ::invslot::SHARED_BANK_BEGIN + steam_latest_slot.Slot; + } + + else if (steam_latest_slot.SubIndex >= invbag::SLOT_BEGIN && steam_latest_slot.SubIndex <= invbag::SLOT_END) { + temp_slot = steam_latest_slot.Slot * invbag::SLOT_COUNT; + server_slot = EQ::invbag::SHARED_BANK_BAGS_BEGIN + temp_slot + steam_latest_slot.SubIndex; + } + } + + break; + } + case invtype::typeTrade: { + if (steam_latest_slot.Slot >= invslot::SLOT_BEGIN && steam_latest_slot.Slot < invtype::TRADE_SIZE) { + if (steam_latest_slot.SubIndex == invbag::SLOT_INVALID) { + server_slot = EQ::invslot::TRADE_BEGIN + steam_latest_slot.Slot; + } + + else if (steam_latest_slot.SubIndex >= invbag::SLOT_BEGIN && steam_latest_slot.SubIndex <= invbag::SLOT_END) { + temp_slot = steam_latest_slot.Slot * invbag::SLOT_COUNT; + server_slot = EQ::invbag::TRADE_BAGS_BEGIN + temp_slot + steam_latest_slot.SubIndex; + } + } + + break; + } + case invtype::typeWorld: { + if (steam_latest_slot.Slot >= invslot::SLOT_BEGIN && steam_latest_slot.Slot < invtype::WORLD_SIZE) { + server_slot = EQ::invslot::WORLD_BEGIN + steam_latest_slot.Slot; + } + + else if (steam_latest_slot.Slot == invslot::SLOT_INVALID) { + server_slot = EQ::invslot::SLOT_TRADESKILL_EXPERIMENT_COMBINE; + } + + break; + } + case invtype::typeLimbo: { + if (steam_latest_slot.Slot >= invslot::SLOT_BEGIN && steam_latest_slot.Slot < invtype::LIMBO_SIZE) { + server_slot = EQ::invslot::slotCursor; + } + + break; + } + case invtype::typeTribute: { + if (steam_latest_slot.Slot >= invslot::SLOT_BEGIN && steam_latest_slot.Slot < invtype::TRIBUTE_SIZE) { + server_slot = EQ::invslot::TRIBUTE_BEGIN + steam_latest_slot.Slot; + } + + break; + } + case invtype::typeGuildTribute: { + if (steam_latest_slot.Slot >= invslot::SLOT_BEGIN && steam_latest_slot.Slot < invtype::GUILD_TRIBUTE_SIZE) { + server_slot = EQ::invslot::GUILD_TRIBUTE_BEGIN + steam_latest_slot.Slot; + } + + break; + } + case invtype::typeCorpse: { + if (steam_latest_slot.Slot >= invslot::CORPSE_BEGIN && steam_latest_slot.Slot <= invslot::CORPSE_END) { + server_slot = steam_latest_slot.Slot; + } + + break; + } + default: { + + break; + } + } + + Log(Logs::Detail, Logs::Netcode, "Convert SteamLatest Slot [%i, %i, %i, %i] to Server Slot %i", + steam_latest_slot.Type, steam_latest_slot.Slot, steam_latest_slot.SubIndex, steam_latest_slot.AugIndex, server_slot); + + return server_slot; + } + + static inline uint32 SteamLatestToServerCorpseSlot(structs::InventorySlot_Struct steam_latest_corpse_slot) + { + uint32 ServerSlot = EQ::invslot::SLOT_INVALID; + + if (steam_latest_corpse_slot.Type != invtype::typeCorpse || steam_latest_corpse_slot.SubIndex != invbag::SLOT_INVALID || steam_latest_corpse_slot.AugIndex != invaug::SOCKET_INVALID) { + ServerSlot = EQ::invslot::SLOT_INVALID; + } + + else { + ServerSlot = SteamLatestToServerCorpseMainSlot(steam_latest_corpse_slot.Slot); + } + + Log(Logs::Detail, Logs::Netcode, "Convert SteamLatest Slot [%i, %i, %i, %i] to Server Slot %i", + steam_latest_corpse_slot.Type, steam_latest_corpse_slot.Slot, steam_latest_corpse_slot.SubIndex, steam_latest_corpse_slot.AugIndex, ServerSlot); + + return ServerSlot; + } + + static inline uint32 SteamLatestToServerCorpseMainSlot(uint32 steam_latest_corpse_slot) + { + uint32 ServerSlot = EQ::invslot::SLOT_INVALID; + + if (steam_latest_corpse_slot <= invslot::CORPSE_END && steam_latest_corpse_slot >= invslot::CORPSE_BEGIN) { + ServerSlot = steam_latest_corpse_slot; + } + + LogNetcode("Convert SteamLatest Corpse Main Slot [{}] to Server Corpse Slot [{}]", steam_latest_corpse_slot, ServerSlot); + + return ServerSlot; + } + + static inline uint32 SteamLatestToServerTypelessSlot(structs::TypelessInventorySlot_Struct steam_latest_slot, int16 steam_latest_type) + { + if (steam_latest_slot.AugIndex < invaug::SOCKET_INVALID || steam_latest_slot.AugIndex >= invaug::SOCKET_COUNT) { + Log(Logs::Detail, Logs::Netcode, "Convert SteamLatest Typeless Slot [%i, %i, %i] (implied type: %i) to Server Slot %i", + steam_latest_slot.Slot, steam_latest_slot.SubIndex, steam_latest_slot.AugIndex, steam_latest_type, EQ::invslot::SLOT_INVALID); + + return EQ::invslot::SLOT_INVALID; + } + + uint32 ServerSlot = EQ::invslot::SLOT_INVALID; + uint32 TempSlot = invslot::SLOT_INVALID; + + switch (steam_latest_type) { + case invtype::typePossessions: { + if (steam_latest_slot.Slot >= invslot::POSSESSIONS_BEGIN && steam_latest_slot.Slot <= invslot::POSSESSIONS_END) { + if (steam_latest_slot.SubIndex == invbag::SLOT_INVALID) { + ServerSlot = steam_latest_slot.Slot; + } + + else if (steam_latest_slot.SubIndex >= invbag::SLOT_BEGIN && steam_latest_slot.SubIndex <= invbag::SLOT_END) { + if (steam_latest_slot.Slot < invslot::GENERAL_BEGIN) + return EQ::invslot::SLOT_INVALID; + + TempSlot = (steam_latest_slot.Slot - invslot::GENERAL_BEGIN) * invbag::SLOT_COUNT; + ServerSlot = EQ::invbag::GENERAL_BAGS_BEGIN + TempSlot + steam_latest_slot.SubIndex; + } + } + + break; + } + case invtype::typeBank: { + if (steam_latest_slot.Slot >= invslot::SLOT_BEGIN && steam_latest_slot.Slot < invtype::BANK_SIZE) { + if (steam_latest_slot.SubIndex == invbag::SLOT_INVALID) { + ServerSlot = EQ::invslot::BANK_BEGIN + steam_latest_slot.Slot; + } + + else if (steam_latest_slot.SubIndex >= invbag::SLOT_BEGIN && steam_latest_slot.SubIndex <= invbag::SLOT_END) { + TempSlot = steam_latest_slot.Slot * invbag::SLOT_COUNT; + ServerSlot = EQ::invbag::BANK_BAGS_BEGIN + TempSlot + steam_latest_slot.SubIndex; + } + } + + break; + } + case invtype::typeSharedBank: { + if (steam_latest_slot.Slot >= invslot::SLOT_BEGIN && steam_latest_slot.Slot < invtype::SHARED_BANK_SIZE) { + if (steam_latest_slot.SubIndex == invbag::SLOT_INVALID) { + ServerSlot = EQ::invslot::SHARED_BANK_BEGIN + steam_latest_slot.Slot; + } + + else if (steam_latest_slot.SubIndex >= invbag::SLOT_BEGIN && steam_latest_slot.SubIndex <= invbag::SLOT_END) { + TempSlot = steam_latest_slot.Slot * invbag::SLOT_COUNT; + ServerSlot = EQ::invbag::SHARED_BANK_BAGS_BEGIN + TempSlot + steam_latest_slot.SubIndex; + } + } + + break; + } + case invtype::typeTrade: { + if (steam_latest_slot.Slot >= invslot::SLOT_BEGIN && steam_latest_slot.Slot < invtype::TRADE_SIZE) { + if (steam_latest_slot.SubIndex == invbag::SLOT_INVALID) { + ServerSlot = EQ::invslot::TRADE_BEGIN + steam_latest_slot.Slot; + } + + else if (steam_latest_slot.SubIndex >= invbag::SLOT_BEGIN && steam_latest_slot.SubIndex <= invbag::SLOT_END) { + TempSlot = steam_latest_slot.Slot * invbag::SLOT_COUNT; + ServerSlot = EQ::invbag::TRADE_BAGS_BEGIN + TempSlot + steam_latest_slot.SubIndex; + } + } + + break; + } + case invtype::typeWorld: { + if (steam_latest_slot.Slot >= invslot::SLOT_BEGIN && steam_latest_slot.Slot < invtype::WORLD_SIZE) { + ServerSlot = EQ::invslot::WORLD_BEGIN + steam_latest_slot.Slot; + } + + else if (steam_latest_slot.Slot == invslot::SLOT_INVALID) { + ServerSlot = EQ::invslot::SLOT_TRADESKILL_EXPERIMENT_COMBINE; + } + + break; + } + case invtype::typeLimbo: { + if (steam_latest_slot.Slot >= invslot::SLOT_BEGIN && steam_latest_slot.Slot < invtype::LIMBO_SIZE) { + ServerSlot = EQ::invslot::slotCursor; + } + + break; + } + case invtype::typeTribute: { + if (steam_latest_slot.Slot >= invslot::SLOT_BEGIN && steam_latest_slot.Slot < invtype::TRIBUTE_SIZE) { + ServerSlot = EQ::invslot::TRIBUTE_BEGIN + steam_latest_slot.Slot; + } + + break; + } + case invtype::typeGuildTribute: { + if (steam_latest_slot.Slot >= invslot::SLOT_BEGIN && steam_latest_slot.Slot < invtype::GUILD_TRIBUTE_SIZE) { + ServerSlot = EQ::invslot::GUILD_TRIBUTE_BEGIN + steam_latest_slot.Slot; + } + + break; + } + case invtype::typeCorpse: { + if (steam_latest_slot.Slot >= invslot::CORPSE_BEGIN && steam_latest_slot.Slot <= invslot::CORPSE_END) { + ServerSlot = steam_latest_slot.Slot; + } + + break; + } + default: { + + break; + } + } + + Log(Logs::Detail, Logs::Netcode, "Convert SteamLatest Typeless Slot [%i, %i, %i] (implied type: %i) to Server Slot %i", + steam_latest_slot.Slot, steam_latest_slot.SubIndex, steam_latest_slot.AugIndex, steam_latest_type, ServerSlot); + + return ServerSlot; + } + + static inline structs::InventorySlot_Struct SteamLatestCastingInventorySlotToInventorySlot(structs::CastSpellInventorySlot_Struct steam_latest_slot) { + structs::InventorySlot_Struct ret; + ret.Type = steam_latest_slot.type; + ret.Slot = steam_latest_slot.slot; + ret.SubIndex = steam_latest_slot.sub_index; + ret.AugIndex = steam_latest_slot.aug_index; + return ret; + } + + static inline structs::CastSpellInventorySlot_Struct SteamLatestInventorySlotToCastingInventorySlot(structs::InventorySlot_Struct steam_latest_slot) { + structs::CastSpellInventorySlot_Struct ret; + ret.type = steam_latest_slot.Type; + ret.slot = steam_latest_slot.Slot; + ret.sub_index = steam_latest_slot.SubIndex; + ret.aug_index = steam_latest_slot.AugIndex; + return ret; + } + + static item::ItemPacketType ServerToSteamLatestItemPacketType(ItemPacketType server_type) { + switch (server_type) { + case ItemPacketType::ItemPacketMerchant: + return item::ItemPacketType::ItemPacketMerchant; + case ItemPacketType::ItemPacketTradeView: + return item::ItemPacketType::ItemPacketTradeView; + case ItemPacketType::ItemPacketLoot: + return item::ItemPacketType::ItemPacketLoot; + case ItemPacketType::ItemPacketTrade: + return item::ItemPacketType::ItemPacketTrade; + case ItemPacketType::ItemPacketCharInventory: + return item::ItemPacketType::ItemPacketCharInventory; + case ItemPacketType::ItemPacketLimbo: + return item::ItemPacketType::ItemPacketLimbo; + case ItemPacketType::ItemPacketWorldContainer: + return item::ItemPacketType::ItemPacketWorldContainer; + case ItemPacketType::ItemPacketTributeItem: + return item::ItemPacketType::ItemPacketTributeItem; + case ItemPacketType::ItemPacketGuildTribute: + return item::ItemPacketType::ItemPacketGuildTribute; + case ItemPacketType::ItemPacketCharmUpdate: + return item::ItemPacketType::ItemPacketCharmUpdate; + default: + return item::ItemPacketType::ItemPacketInvalid; + } + } + + //This stuff isn't right because they for one removed potion belt + //This will probably be enough to get casting working for now though + static inline spells::CastingSlot ServerToSteamLatestCastingSlot(EQ::spells::CastingSlot slot) { + switch (slot) { + case EQ::spells::CastingSlot::Gem1: + return spells::CastingSlot::Gem1; + case EQ::spells::CastingSlot::Gem2: + return spells::CastingSlot::Gem2; + case EQ::spells::CastingSlot::Gem3: + return spells::CastingSlot::Gem3; + case EQ::spells::CastingSlot::Gem4: + return spells::CastingSlot::Gem4; + case EQ::spells::CastingSlot::Gem5: + return spells::CastingSlot::Gem5; + case EQ::spells::CastingSlot::Gem6: + return spells::CastingSlot::Gem6; + case EQ::spells::CastingSlot::Gem7: + return spells::CastingSlot::Gem7; + case EQ::spells::CastingSlot::Gem8: + return spells::CastingSlot::Gem8; + case EQ::spells::CastingSlot::Gem9: + return spells::CastingSlot::Gem9; + case EQ::spells::CastingSlot::Gem10: + return spells::CastingSlot::Gem10; + case EQ::spells::CastingSlot::Gem11: + return spells::CastingSlot::Gem11; + case EQ::spells::CastingSlot::Gem12: + return spells::CastingSlot::Gem12; + case EQ::spells::CastingSlot::Item: + case EQ::spells::CastingSlot::PotionBelt: + return spells::CastingSlot::Item; + case EQ::spells::CastingSlot::Discipline: + return spells::CastingSlot::Discipline; + case EQ::spells::CastingSlot::AltAbility: + return spells::CastingSlot::AltAbility; + default: // we shouldn't have any issues with other slots ... just return something + return spells::CastingSlot::Discipline; + } + } + + static inline EQ::spells::CastingSlot SteamLatestToServerCastingSlot(spells::CastingSlot slot) { + switch (slot) { + case spells::CastingSlot::Gem1: + return EQ::spells::CastingSlot::Gem1; + case spells::CastingSlot::Gem2: + return EQ::spells::CastingSlot::Gem2; + case spells::CastingSlot::Gem3: + return EQ::spells::CastingSlot::Gem3; + case spells::CastingSlot::Gem4: + return EQ::spells::CastingSlot::Gem4; + case spells::CastingSlot::Gem5: + return EQ::spells::CastingSlot::Gem5; + case spells::CastingSlot::Gem6: + return EQ::spells::CastingSlot::Gem6; + case spells::CastingSlot::Gem7: + return EQ::spells::CastingSlot::Gem7; + case spells::CastingSlot::Gem8: + return EQ::spells::CastingSlot::Gem8; + case spells::CastingSlot::Gem9: + return EQ::spells::CastingSlot::Gem9; + case spells::CastingSlot::Gem10: + return EQ::spells::CastingSlot::Gem10; + case spells::CastingSlot::Gem11: + return EQ::spells::CastingSlot::Gem11; + case spells::CastingSlot::Gem12: + return EQ::spells::CastingSlot::Gem12; + case spells::CastingSlot::Discipline: + return EQ::spells::CastingSlot::Discipline; + case spells::CastingSlot::Item: + return EQ::spells::CastingSlot::Item; + case spells::CastingSlot::AltAbility: + return EQ::spells::CastingSlot::AltAbility; + default: // we shouldn't have any issues with other slots ... just return something + return EQ::spells::CastingSlot::Discipline; + } + } + + //SteamLatest has the same # of long buffs as rof2, but 10 more short buffs + static inline int ServerToSteamLatestBuffSlot(int index) + { + // we're a disc + if (index >= EQ::spells::LONG_BUFFS + EQ::spells::SHORT_BUFFS) + return index - EQ::spells::LONG_BUFFS - EQ::spells::SHORT_BUFFS + + spells::LONG_BUFFS + spells::SHORT_BUFFS; + // we're a song + if (index >= EQ::spells::LONG_BUFFS) + return index - EQ::spells::LONG_BUFFS + spells::LONG_BUFFS; + // we're a normal buff + return index; // as long as we guard against bad slots server side, we should be fine + } + + static inline int SteamLatestToServerBuffSlot(int index) + { + // we're a disc + if (index >= spells::LONG_BUFFS + spells::SHORT_BUFFS) + return index - spells::LONG_BUFFS - spells::SHORT_BUFFS + EQ::spells::LONG_BUFFS + + EQ::spells::SHORT_BUFFS; + // we're a song + if (index >= spells::LONG_BUFFS) + return index - spells::LONG_BUFFS + EQ::spells::LONG_BUFFS; + // we're a normal buff + return index; // as long as we guard against bad slots server side, we should be fine + } +} /*SteamLatest*/ +iff --git a/common/patches/steam_latest.h b/common/patches/steam_latest.h +ew file mode 100644 +ndex 000000000..f378e9e05 +-- /dev/null +++ b/common/patches/steam_latest.h +@ -0,0 +1,55 @@ +/* EQEMu: Everquest Server Emulator + + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef COMMON_LAURION_H +#define COMMON_LAURION_H + +#include "../struct_strategy.h" + +class EQStreamIdentifier; + +namespace SteamLatest +{ + + //these are the only public member of this namespace. + extern void Register(EQStreamIdentifier& into); + extern void Reload(); + + + + //you should not directly access anything below.. + //I just dont feel like making a seperate header for it. + + class Strategy : public StructStrategy { + public: + Strategy(); + + protected: + + virtual std::string Describe() const; + virtual const EQ::versions::ClientVersion ClientVersion() const; + + //magic macro to declare our opcode processors +#include "ss_declare.h" +#include "steam_latest_ops.h" + }; + +}; /*SteamLatest*/ + +#endif /*COMMON_LAURION_H*/ +iff --git a/common/patches/steam_latest_limits.cpp b/common/patches/steam_latest_limits.cpp +ew file mode 100644 +ndex 000000000..9716d083b +-- /dev/null +++ b/common/patches/steam_latest_limits.cpp +@ -0,0 +1,284 @@ +/* EQEMu: Everquest Server Emulator + + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "steam_latest_limits.h" + +#include "../strings.h" + + +int16 SteamLatest::invtype::GetInvTypeSize(int16 inv_type) +{ + switch (inv_type) { + case invtype::typePossessions: + return invtype::POSSESSIONS_SIZE; + case invtype::typeBank: + return invtype::BANK_SIZE; + case invtype::typeSharedBank: + return invtype::SHARED_BANK_SIZE; + case invtype::typeTrade: + return invtype::TRADE_SIZE; + case invtype::typeWorld: + return invtype::WORLD_SIZE; + case invtype::typeLimbo: + return invtype::LIMBO_SIZE; + case invtype::typeTribute: + return invtype::TRIBUTE_SIZE; + case invtype::typeTrophyTribute: + return invtype::TROPHY_TRIBUTE_SIZE; + case invtype::typeGuildTribute: + return invtype::GUILD_TRIBUTE_SIZE; + case invtype::typeMerchant: + return invtype::MERCHANT_SIZE; + case invtype::typeDeleted: + return invtype::DELETED_SIZE; + case invtype::typeCorpse: + return invtype::CORPSE_SIZE; + case invtype::typeBazaar: + return invtype::BAZAAR_SIZE; + case invtype::typeInspect: + return invtype::INSPECT_SIZE; + case invtype::typeRealEstate: + return invtype::REAL_ESTATE_SIZE; + case invtype::typeViewMODPC: + return invtype::VIEW_MOD_PC_SIZE; + case invtype::typeViewMODBank: + return invtype::VIEW_MOD_BANK_SIZE; + case invtype::typeViewMODSharedBank: + return invtype::VIEW_MOD_SHARED_BANK_SIZE; + case invtype::typeViewMODLimbo: + return invtype::VIEW_MOD_LIMBO_SIZE; + case invtype::typeAltStorage: + return invtype::ALT_STORAGE_SIZE; + case invtype::typeArchived: + return invtype::ARCHIVED_SIZE; + case invtype::typeMail: + return invtype::MAIL_SIZE; + case invtype::typeGuildTrophyTribute: + return invtype::GUILD_TROPHY_TRIBUTE_SIZE; + case invtype::typeKrono: + return invtype::KRONO_SIZE; + case invtype::typeOther: + return invtype::OTHER_SIZE; + default: + return INULL; + } +} + +const char* SteamLatest::invtype::GetInvTypeName(int16 inv_type) +{ + switch (inv_type) { + case invtype::TYPE_INVALID: + return "Invalid Type"; + case invtype::typePossessions: + return "Possessions"; + case invtype::typeBank: + return "Bank"; + case invtype::typeSharedBank: + return "Shared Bank"; + case invtype::typeTrade: + return "Trade"; + case invtype::typeWorld: + return "World"; + case invtype::typeLimbo: + return "Limbo"; + case invtype::typeTribute: + return "Tribute"; + case invtype::typeTrophyTribute: + return "Trophy Tribute"; + case invtype::typeGuildTribute: + return "Guild Tribute"; + case invtype::typeMerchant: + return "Merchant"; + case invtype::typeDeleted: + return "Deleted"; + case invtype::typeCorpse: + return "Corpse"; + case invtype::typeBazaar: + return "Bazaar"; + case invtype::typeInspect: + return "Inspect"; + case invtype::typeRealEstate: + return "Real Estate"; + case invtype::typeViewMODPC: + return "View MOD PC"; + case invtype::typeViewMODBank: + return "View MOD Bank"; + case invtype::typeViewMODSharedBank: + return "View MOD Shared Bank"; + case invtype::typeViewMODLimbo: + return "View MOD Limbo"; + case invtype::typeAltStorage: + return "Alt Storage"; + case invtype::typeArchived: + return "Archived"; + case invtype::typeMail: + return "Mail"; + case invtype::typeGuildTrophyTribute: + return "Guild Trophy Tribute"; + case invtype::typeKrono: + return "Krono"; + case invtype::typeOther: + return "Other"; + default: + return "Unknown Type"; + } +} + +bool SteamLatest::invtype::IsInvTypePersistent(int16 inv_type) +{ + switch (inv_type) { + case invtype::typePossessions: + case invtype::typeBank: + case invtype::typeSharedBank: + case invtype::typeTrade: + case invtype::typeWorld: + case invtype::typeLimbo: + case invtype::typeTribute: + case invtype::typeTrophyTribute: + case invtype::typeGuildTribute: + return true; + default: + return false; + } +} + +const char* SteamLatest::invslot::GetInvPossessionsSlotName(int16 inv_slot) +{ + switch (inv_slot) { + case invslot::SLOT_INVALID: + return "Invalid Slot"; + case invslot::slotCharm: + return "Charm"; + case invslot::slotEar1: + return "Ear 1"; + case invslot::slotHead: + return "Head"; + case invslot::slotFace: + return "Face"; + case invslot::slotEar2: + return "Ear 2"; + case invslot::slotNeck: + return "Neck"; + case invslot::slotShoulders: + return "Shoulders"; + case invslot::slotArms: + return "Arms"; + case invslot::slotBack: + return "Back"; + case invslot::slotWrist1: + return "Wrist 1"; + case invslot::slotWrist2: + return "Wrist 2"; + case invslot::slotRange: + return "Range"; + case invslot::slotHands: + return "Hands"; + case invslot::slotPrimary: + return "Primary"; + case invslot::slotSecondary: + return "Secondary"; + case invslot::slotFinger1: + return "Finger 1"; + case invslot::slotFinger2: + return "Finger 2"; + case invslot::slotChest: + return "Chest"; + case invslot::slotLegs: + return "Legs"; + case invslot::slotFeet: + return "Feet"; + case invslot::slotWaist: + return "Waist"; + case invslot::slotPowerSource: + return "Power Source"; + case invslot::slotAmmo: + return "Ammo"; + case invslot::slotGeneral1: + return "General 1"; + case invslot::slotGeneral2: + return "General 2"; + case invslot::slotGeneral3: + return "General 3"; + case invslot::slotGeneral4: + return "General 4"; + case invslot::slotGeneral5: + return "General 5"; + case invslot::slotGeneral6: + return "General 6"; + case invslot::slotGeneral7: + return "General 7"; + case invslot::slotGeneral8: + return "General 8"; + case invslot::slotGeneral9: + return "General 9"; + case invslot::slotGeneral10: + return "General 10"; + case invslot::slotCursor: + return "Cursor"; + default: + return "Unknown Slot"; + } +} + +const char* SteamLatest::invslot::GetInvSlotName(int16 inv_type, int16 inv_slot) +{ + if (inv_type == invtype::typePossessions) + return invslot::GetInvPossessionsSlotName(inv_slot); + + int16 type_size = invtype::GetInvTypeSize(inv_type); + + if (!type_size || inv_slot == invslot::SLOT_INVALID) + return "Invalid Slot"; + + if ((inv_slot + 1) >= type_size) + return "Unknown Slot"; + + static std::string ret_str; + ret_str = StringFormat("Slot %i", (inv_slot + 1)); + + return ret_str.c_str(); +} + +const char* SteamLatest::invbag::GetInvBagIndexName(int16 bag_index) +{ + if (bag_index == invbag::SLOT_INVALID) + return "Invalid Bag"; + + if (bag_index >= invbag::SLOT_COUNT) + return "Unknown Bag"; + + static std::string ret_str; + ret_str = StringFormat("Bag %i", (bag_index + 1)); + + return ret_str.c_str(); +} + +const char* SteamLatest::invaug::GetInvAugIndexName(int16 aug_index) +{ + if (aug_index == invaug::SOCKET_INVALID) + return "Invalid Augment"; + + if (aug_index >= invaug::SOCKET_COUNT) + return "Unknown Augment"; + + static std::string ret_str; + ret_str = StringFormat("Augment %i", (aug_index + 1)); + + return ret_str.c_str(); +} \ No newline at end of file diff --git a/common/patches/steam_latest.h b/common/patches/steam_latest.h new file mode 100644 index 000000000..e69de29bb diff --git a/common/patches/steam_latest_limits.cpp b/common/patches/steam_latest_limits.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/common/patches/steam_latest_limits.h b/common/patches/steam_latest_limits.h new file mode 100644 index 000000000..eb2a2dd62 --- /dev/null +++ b/common/patches/steam_latest_limits.h @@ -0,0 +1,336 @@ +#ifndef COMMON_LAURION_LIMITS_H +#define COMMON_LAURION_LIMITS_H + +#include "../types.h" +#include "../emu_versions.h" +#include "../skills.h" + +namespace SteamLatest +{ + const int16 IINVALID = -1; + const int16 INULL = 0; + + namespace inventory { + inline EQ::versions::ClientVersion GetInventoryRef() { return EQ::versions::ClientVersion::SteamLatest; } + + const bool ConcatenateInvTypeLimbo = false; + + const bool AllowOverLevelEquipment = true; + + const bool AllowEmptyBagInBag = true; + const bool AllowClickCastFromBag = true; + + } /*inventory*/ + + namespace invtype { + inline EQ::versions::ClientVersion GetInvTypeRef() { return EQ::versions::ClientVersion::SteamLatest; } + + namespace enum_ { + enum InventoryTypes : int16 { + typePossessions = INULL, + typeBank, + typeSharedBank, + typeTrade, + typeWorld, + typeLimbo, + typeTribute, + typeTrophyTribute, + typeGuildTribute, + typeMerchant, + typeDeleted, + typeCorpse, + typeBazaar, + typeInspect, + typeRealEstate, + typeViewMODPC, + typeViewMODBank, + typeViewMODSharedBank, + typeViewMODLimbo, + typeAltStorage, + typeArchived, + typeMail, + typeGuildTrophyTribute, + typeKrono, + typeOther, + typeMercenaryItems, + typeViewModMercenaryItems, + typeMountKeyRingItems, + typeViewModMountKeyRingItems, + typeIllusionKeyRingItems, + typeViewModIllusionKeyRingItems, + typeFamiliarKeyRingItems, + typeViewModFamiliarKeyRingItems, + typeHeroForgeKeyRingItems, + typeViewModHeroForgeKeyRingItems, + typeTeleportationKeyRingItems, + typeViewModTeleportationKeyRingItems, + typeOverflow, + typeDragonHoard, + typeTradeskillDepot, + typeGuildTradeskillDepot + }; + + } // namespace enum_ + using namespace enum_; + + const int16 POSSESSIONS_SIZE = 34; + const int16 BANK_SIZE = 24; + const int16 SHARED_BANK_SIZE = 2; + const int16 TRADE_SIZE = 8; + const int16 WORLD_SIZE = 10; + const int16 LIMBO_SIZE = 36; + const int16 TRIBUTE_SIZE = 5; + const int16 TROPHY_TRIBUTE_SIZE = 0;//unknown + const int16 GUILD_TRIBUTE_SIZE = 2;//unverified + const int16 MERCHANT_SIZE = 200; + const int16 DELETED_SIZE = 0;//unknown - "Recovery Tab" + const int16 CORPSE_SIZE = POSSESSIONS_SIZE; + const int16 BAZAAR_SIZE = 200; + const int16 INSPECT_SIZE = 23; + const int16 REAL_ESTATE_SIZE = 0;//unknown + const int16 VIEW_MOD_PC_SIZE = POSSESSIONS_SIZE; + const int16 VIEW_MOD_BANK_SIZE = BANK_SIZE; + const int16 VIEW_MOD_SHARED_BANK_SIZE = SHARED_BANK_SIZE; + const int16 VIEW_MOD_LIMBO_SIZE = LIMBO_SIZE; + const int16 ALT_STORAGE_SIZE = 0;//unknown - "Shroud Bank" + const int16 ARCHIVED_SIZE = 0;//unknown + const int16 MAIL_SIZE = 0;//unknown + const int16 GUILD_TROPHY_TRIBUTE_SIZE = 0;//unknown + const int16 KRONO_SIZE = 0;//unknown + const int16 OTHER_SIZE = 0;//unknown + + const int16 TRADE_NPC_SIZE = 4; // defined by implication + + const int16 TYPE_INVALID = IINVALID; + const int16 TYPE_BEGIN = typePossessions; + const int16 TYPE_END = typeOther; + const int16 TYPE_COUNT = (TYPE_END - TYPE_BEGIN) + 1; + + int16 GetInvTypeSize(int16 inv_type); + const char* GetInvTypeName(int16 inv_type); + + bool IsInvTypePersistent(int16 inv_type); + + } /*invtype*/ + + namespace invslot { + inline EQ::versions::ClientVersion GetInvSlotRef() { return EQ::versions::ClientVersion::SteamLatest; } + + namespace enum_ { + enum InventorySlots : int16 { + slotCharm = INULL, + slotEar1, + slotHead, + slotFace, + slotEar2, + slotNeck, + slotShoulders, + slotArms, + slotBack, + slotWrist1, + slotWrist2, + slotRange, + slotHands, + slotPrimary, + slotSecondary, + slotFinger1, + slotFinger2, + slotChest, + slotLegs, + slotFeet, + slotWaist, + slotPowerSource, + slotAmmo, + slotGeneral1, + slotGeneral2, + slotGeneral3, + slotGeneral4, + slotGeneral5, + slotGeneral6, + slotGeneral7, + slotGeneral8, + slotGeneral9, + slotGeneral10, + slotGeneral11, + slotGeneral12, + slotCursor + }; + + constexpr int16 format_as(InventorySlots slot) { return static_cast(slot); } + } // namespace enum_ + using namespace enum_; + + const int16 SLOT_INVALID = IINVALID; + const int16 SLOT_BEGIN = INULL; + + const int16 POSSESSIONS_BEGIN = slotCharm; + const int16 POSSESSIONS_END = slotCursor; + const int16 POSSESSIONS_COUNT = (POSSESSIONS_END - POSSESSIONS_BEGIN) + 1; + + const int16 EQUIPMENT_BEGIN = slotCharm; + const int16 EQUIPMENT_END = slotAmmo; + const int16 EQUIPMENT_COUNT = (EQUIPMENT_END - EQUIPMENT_BEGIN) + 1; + + //We support more if enabled but for now lets leave it at the 10 slots + const int16 GENERAL_BEGIN = slotGeneral1; + const int16 GENERAL_END = slotGeneral10; + const int16 GENERAL_COUNT = (GENERAL_END - GENERAL_BEGIN) + 1; + + const int16 BONUS_BEGIN = invslot::slotCharm; + const int16 BONUS_STAT_END = invslot::slotPowerSource; + const int16 BONUS_SKILL_END = invslot::slotAmmo; + + const int16 CORPSE_BEGIN = invslot::slotGeneral1; + const int16 CORPSE_END = invslot::slotGeneral1 + invslot::slotCursor; + + const uint64 EQUIPMENT_BITMASK = 0x00000000007FFFFF; + const uint64 GENERAL_BITMASK = 0x00000007FF800000; + const uint64 CURSOR_BITMASK = 0x0000000800000000; + const uint64 POSSESSIONS_BITMASK = (EQUIPMENT_BITMASK | GENERAL_BITMASK | CURSOR_BITMASK); // based on 36-slot count (SteamLatest+) + const uint64 CORPSE_BITMASK = (GENERAL_BITMASK | CURSOR_BITMASK | (EQUIPMENT_BITMASK << 36)); // based on 36-slot count (SteamLatest+) + + + const char* GetInvPossessionsSlotName(int16 inv_slot); + const char* GetInvSlotName(int16 inv_type, int16 inv_slot); + + } /*invslot*/ + + namespace invbag { + inline EQ::versions::ClientVersion GetInvBagRef() { return EQ::versions::ClientVersion::SteamLatest; } + + const int16 SLOT_INVALID = IINVALID; + const int16 SLOT_BEGIN = INULL; + const int16 SLOT_END = 9; //254; + const int16 SLOT_COUNT = 10; //255; // server Size will be 255..unsure what actual client is (test) + + const char* GetInvBagIndexName(int16 bag_index); + + } /*invbag*/ + + namespace invaug { + inline EQ::versions::ClientVersion GetInvAugRef() { return EQ::versions::ClientVersion::SteamLatest; } + + const int16 SOCKET_INVALID = IINVALID; + const int16 SOCKET_BEGIN = INULL; + const int16 SOCKET_END = 5; + const int16 SOCKET_COUNT = 6; + + const char* GetInvAugIndexName(int16 aug_index); + + } /*invaug*/ + + namespace item { + inline EQ::versions::ClientVersion GetItemRef() { return EQ::versions::ClientVersion::SteamLatest; } + + //enum Unknown : int { // looks like item class..but, RoF has it too - nothing in UF- + // Unknown1 = 0, + // Unknown2 = 1, + // Unknown3 = 2, + // Unknown4 = 5 // krono? + //}; + + enum ItemPacketType : int { + ItemPacketMerchant = 0x64, + ItemPacketTradeView = 0x65, + ItemPacketLoot = 0x66, + ItemPacketTrade = 0x67, + //looks like they added something at 0x68 that didn't exist before and shifted everything after it up by 1 + ItemPacketUnknown068 = 0x68, //Not sure but it seems to deal with the cursor somehow. + ItemPacketCharInventory = 0x6A, //Rof 0x69 -> Larion 0x6a (requires translation) + ItemPacketLimbo = 0x6B, //0x6A -> 0x6B + ItemPacketWorldContainer = 0x6C, + ItemPacketTributeItem = 0x6D, + ItemPacketGuildTribute = 0x6E, + ItemPacketCharmUpdate = 0x6f, + ItemPacketRecovery = 0x72, + ItemPacketParcel = 0x74, + ItemPacketUnknown075 = 0x75, //Not sure but uses a lot of the same logic as the trade and char inventory types + ItemPacketOverflow = 0x76, + ItemPacketDragonHoard = 0x77, + ItemPacketTradeskill = 0x78, + ItemPacketTradeskillDepot = 0x79, + ItemPacketInvalid = 0xFF + }; + + } /*item*/ + + namespace profile { + inline EQ::versions::ClientVersion GetProfileRef() { return EQ::versions::ClientVersion::SteamLatest; } + + const int16 BANDOLIERS_SIZE = 20; // number of bandolier instances + const int16 BANDOLIER_ITEM_COUNT = 4; // number of equipment slots in bandolier instance + + const int16 POTION_BELT_SIZE = 5; + + const int16 SKILL_ARRAY_SIZE = 100; + + } /*profile*/ + + namespace constants { + inline EQ::versions::ClientVersion GetConstantsRef() { return EQ::versions::ClientVersion::SteamLatest; } + + const EQ::expansions::Expansion EXPANSION = EQ::expansions::Expansion::LS; + const uint32 EXPANSION_BIT = EQ::expansions::bitLS; + const uint32 EXPANSIONS_MASK = EQ::expansions::maskLS; + + const size_t CHARACTER_CREATION_LIMIT = 12; + + const size_t SAY_LINK_BODY_SIZE = 56; + const uint32 MAX_GUILD_ID = 50000; + + } /*constants*/ + + namespace behavior { + inline EQ::versions::ClientVersion GetBehaviorRef() { return EQ::versions::ClientVersion::SteamLatest; } + + const bool CoinHasWeight = false; + + } /*behavior*/ + + namespace skills { + inline EQ::versions::ClientVersion GetSkillsRef() { return EQ::versions::ClientVersion::SteamLatest; } + + const size_t LastUsableSkill = EQ::skills::Skill2HPiercing; + + } /*skills*/ + + namespace spells { + inline EQ::versions::ClientVersion GetSkillsRef() { return EQ::versions::ClientVersion::SteamLatest; } + + enum class CastingSlot : uint32 { + Gem1 = 0, + Gem2 = 1, + Gem3 = 2, + Gem4 = 3, + Gem5 = 4, + Gem6 = 5, + Gem7 = 6, + Gem8 = 7, + Gem9 = 8, + Gem10 = 9, + Gem11 = 10, + Gem12 = 11, + MaxGems = 18, // fallacy..only 12 slot are useable... + Item = 12, + Discipline = 13, + AltAbility = 0xFF + }; + + const int SPELL_ID_MAX = 71999; + const int SPELLBOOK_SIZE = 1120; + const int SPELL_GEM_COUNT = static_cast(CastingSlot::MaxGems); + const int SPELL_GEM_RECAST_TIMER = 15; + + const int LONG_BUFFS = 42; + const int SHORT_BUFFS = 30; + const int DISC_BUFFS = 1; + const int TOTAL_BUFFS = LONG_BUFFS + SHORT_BUFFS + DISC_BUFFS; + const int NPC_BUFFS = 400; + const int PET_BUFFS = NPC_BUFFS; + const int MERC_BUFFS = LONG_BUFFS; + + } /*spells*/ + +}; /* SteamLatest */ + +#endif /*COMMON_LAURION_LIMITS_H*/ \ No newline at end of file diff --git a/common/patches/steam_latest_ops.h b/common/patches/steam_latest_ops.h new file mode 100644 index 000000000..fe29a1f1f --- /dev/null +++ b/common/patches/steam_latest_ops.h @@ -0,0 +1,95 @@ +//list of packets we need to encode on the way out: +E(OP_Action) +E(OP_Animation) +E(OP_ApplyPoison) +E(OP_AugmentInfo) +E(OP_BeginCast) +E(OP_BlockedBuffs) +E(OP_Buff) +E(OP_BuffCreate) +E(OP_CancelTrade) +E(OP_CastSpell) +E(OP_ChannelMessage) +E(OP_CharInventory) +E(OP_ClickObjectAction) +E(OP_ClientUpdate) +E(OP_Consider) +E(OP_Damage) +E(OP_Death) +E(OP_DeleteCharge) +E(OP_DeleteItem) +E(OP_DeleteSpawn) +E(OP_DisciplineUpdate) +E(OP_ExpansionInfo) +E(OP_ExpUpdate) +E(OP_FormattedMessage) +E(OP_GMTraining) +E(OP_GMTrainSkillConfirm) +E(OP_GroundSpawn) +E(OP_HPUpdate) +E(OP_Illusion) +E(OP_ItemPacket) +E(OP_LogServer) +E(OP_ManaChange) +E(OP_MobHealth) +E(OP_MoneyOnCorpse) +E(OP_MoveItem) +E(OP_NewSpawn) +E(OP_NewZone) +E(OP_OnLevelMessage) +E(OP_PlayerProfile) +E(OP_RemoveBlockedBuffs) +E(OP_RespondAA) +E(OP_RequestClientZoneChange) +E(OP_RecipeAutoCombine) +E(OP_SendAATable) +E(OP_SendCharInfo) +E(OP_SendMaxCharacters) +E(OP_SendMembership) +E(OP_SendMembershipDetails) +E(OP_SendZonepoints) +E(OP_ShopPlayerBuy) +E(OP_ShopPlayerSell) +E(OP_ShopRequest) +E(OP_SkillUpdate) +E(OP_SpecialMesg) +E(OP_SpawnAppearance) +E(OP_SpawnDoor) +E(OP_Stun) +E(OP_WearChange) +E(OP_ZoneChange) +E(OP_ZoneEntry) +E(OP_ZonePlayerToBind) +E(OP_ZoneSpawns) + +//list of packets we need to decode on the way in: +D(OP_Animation) +D(OP_ApplyPoison) +D(OP_AugmentInfo) +D(OP_AugmentItem) +D(OP_BlockedBuffs) +D(OP_CastSpell) +D(OP_ChannelMessage) +D(OP_ClientUpdate) +D(OP_ClickDoor) +D(OP_Consider) +D(OP_ConsiderCorpse) +D(OP_DeleteItem) +D(OP_EnterWorld) +D(OP_GMTraining) +D(OP_GroupDisband) +D(OP_GroupInvite) +D(OP_GroupInvite2) +D(OP_MoveItem) +D(OP_RemoveBlockedBuffs) +D(OP_SetServerFilter) +D(OP_ShopPlayerBuy) +D(OP_ShopPlayerSell) +D(OP_ShopRequest) +D(OP_SpawnAppearance) +D(OP_TradeSkillCombine) +D(OP_WearChange) +D(OP_ZoneEntry) +D(OP_ZoneChange) +#undef E +#undef D \ No newline at end of file diff --git a/common/patches/steam_latest_structs.h b/common/patches/steam_latest_structs.h new file mode 100644 index 000000000..5bca86dc8 --- /dev/null +++ b/common/patches/steam_latest_structs.h @@ -0,0 +1,1019 @@ +#ifndef STEAM_LATEST_STRUCTS_H_ +#define STEAM_LATEST_STRUCTS_H_ + +namespace SteamLatest { + namespace structs { + // constants + static const uint32 MAX_PP_AA_ARRAY = 300; + static const uint32 MAX_PP_SKILL = PACKET_SKILL_ARRAY_SIZE; + static const uint32 MAX_PP_INNATE_SKILL = 25; + static const uint32 MAX_PP_DISCIPLINES = 300; + static const uint32 MAX_PP_COMBAT_ABILITY_TIMERS = 25; + static const uint32 MAX_PP_UNKNOWN_ABILITIES = 25; + static const uint32 MAX_RECAST_TYPES = 25; + static const uint32 MAX_ITEM_RECAST_TYPES = 100; + static const uint32 BLOCKED_BUFF_COUNT = 40; + static const uint32 BUFF_COUNT = 62; + static const uint32 MAX_PP_LANGUAGE = 32; +#pragma pack(1) + + struct LoginInfo_Struct { + /*000*/ char login_info[64]; + /*064*/ uint8 unknown064[124]; + /*188*/ uint8 zoning; // 01 if zoning, 00 if not + /*189*/ uint8 unknown189[275]; + /*488*/ + }; + + struct ClientZoneEntry_Struct { + /*00*/ uint32 unknown00; // ***Placeholder + /*04*/ char char_name[64]; // Player firstname [32] + /*68*/ uint32 unknown68; + /*72*/ uint32 unknown72; + /*76*/ uint32 unknown76; + /*80*/ uint32 unknown80; + /*84*/ uint32 unknown84; + /*88*/ uint32 unknown88; + /*92*/ + }; + + struct Membership_Struct + { + /*000*/ uint8 membership; //0 not gold, 2 gold + /*001*/ uint32 races; // Seen ff ff 01 00 + /*005*/ uint32 classes; // Seen ff ff 01 00 + /*009*/ uint32 entrysize; // Seen 33 + /*013*/ int32 entries[33]; // Most -1, 1, and 0 for Gold Status + /*145*/ + }; + + struct Membership_Entry_Struct + { + /*000*/ uint32 purchase_id; // Seen 1, then increments 90287 to 90300 + /*004*/ uint32 bitwise_entry; // Seen 16 to 65536 - Skips 4096 + /*008*/ + }; + + struct Membership_Setting_Struct + { + /*000*/ int8 setting_index; // 0, 1, 2 or 3: f2p, silver, gold, platinum? + /*001*/ int32 setting_id; // 0 to 23 actually seen but the OP_Membership packet has up to 32 + /*005*/ int32 setting_value; + /*009*/ + }; + + struct Membership_Details_Struct + { + /*000*/ uint32 membership_setting_count; // Seen 96 + /*004*/ Membership_Setting_Struct settings[96]; // 864 Bytes + /*364*/ uint32 race_entry_count; // Seen 17 + /*368*/ Membership_Entry_Struct membership_races[17]; // 136 Bytes + /*3f0*/ uint32 class_entry_count; // Seen 15 + /*3f4*/ Membership_Entry_Struct membership_classes[17]; // 136 Bytes + /*47c*/ uint32 exit_url_length; // Length of the exit_url string (0 for none) + /*480*/ //char exit_url[42]; // Upgrade to Silver or Gold Membership URL + }; + + struct MaxCharacters_Struct { + /*000*/ uint32 max_chars; + /*004*/ uint32 marketplace_chars; + /*008*/ int32 unknown008; //some of these probably deal with heroic characters or something + /*00c*/ int32 unknown00c; + /*010*/ int32 unknown010; + /*014*/ int32 unknown014; + /*018*/ int32 unknown018; + /*01c*/ int32 unknown01c; + /*020*/ int32 unknown020; + /*024*/ int32 unknown024; + /*028*/ int32 unknown028; + /*02c*/ int32 unknown02c; + /*030*/ int32 unknown030; + /*034*/ int32 unknown034; + /*038*/ + }; + + struct ExpansionInfo_Struct { + /*000*/ char Unknown000[64]; + /*064*/ uint32 Expansions; + }; + + /* + * Visible equiptment. + * Size: 20 Octets + */ + struct Texture_Struct + { + uint32 Material; + uint32 Unknown1; + uint32 EliteMaterial; + uint32 HeroForgeModel; + uint32 Material2; // Same as material? + }; + + /* + ** Color_Struct + ** Size: 4 bytes + ** Used for convenience + ** Merth: Gave struct a name so gcc 2.96 would compile + ** + */ + struct Tint_Struct + { + union { + struct { + uint8 Blue; + uint8 Green; + uint8 Red; + uint8 UseTint; // if there's a tint this is FF + }; + uint32 Color; + }; + }; + + struct CharSelectEquip : Texture_Struct, Tint_Struct {}; + + struct CharacterSelectEntry_Struct + { + char Name[1]; + uint32 Class; + uint32 Race; + uint8 Level; + uint32 ShroudClass; + uint32 ShroudRace; + uint16 Zone; + uint16 Instance; + uint8 Gender; + uint8 Face; + CharSelectEquip Equip[9]; + uint8 Unknown1; //Seen 256 + uint8 Unknown2; //Seen 0 + uint32 DrakkinTattoo; + uint32 DrakkinDetails; + uint32 Deity; + uint32 PrimaryIDFile; + uint32 SecondaryIDFile; + uint8 HairColor; + uint8 BeardColor; + uint8 EyeColor1; + uint8 EyeColor2; + uint8 HairStyle; + uint8 Beard; + uint8 Enabled; + uint8 Tutorial; + uint32 DrakkinHeritage; + uint8 Unknown3; + uint8 GoHome; + uint32 LastLogin; + uint8 Unknown4; // Seen 0 + uint8 Unknown5; // Seen 0 + uint8 Unknown6; // Seen 0 + uint8 Unknown7; // Seen 0 + uint32 CharacterId; //A Guess, Character I made a little bit after has a number a few hundred after the first + uint32 Unknown8; // Seen 1 + }; + + /* + ** Character Selection Struct + ** + */ + struct CharacterSelect_Struct + { + /*000*/ uint32 CharCount; //number of chars in this packet + }; + + enum SteamLatestAppearance : uint32 + { + None, + WhoLevel, + MaxHealth, + Invisibility, + PVP, + Light, + Animation, + Sneak, + SpawnID, + Health, + Linkdead, + FlyMode, + GM, + Anonymous, + GuildID, + AFK, + Pet, + Summoned, + Unknown18, + Unknown19, + SetType, + NPCName, + CancelSneakHide, + AreaHealthRegen, + AreaManaRegen, + AreaEnduranceRegen, + FreezeBeneficialBuffs, + NPCTintIndex, + Unknown28, + Unknown29, + Unknown30, + ShowHelm, + DamageState, + Unknown33, //Some virtual function call; based on location might be EQPlayers (my guess personally) or FindBits + TextureType, //Texture ID + Unknown35, + Unknown36, + GuildShow, + OfflineMode, + Unknown39, + Unknown40, + Unknown41, + Unknown42, + Birthdate, + EncounterLock + }; + + struct SpawnAppearance_Struct + { + /*0000*/ uint32 spawn_id; // ID of the spawn + /*0004*/ uint32 type; // Values associated with the type + /*0008*/ uint64 parameter; // Type of data sent + /*0016*/ uint64 lock_id; //the only place client uses this as far as I can tell is when you send 0x2c as type in which case it sets LockID = this + /*0024*/ + }; + + struct Spawn_Struct_Bitfields + { + union { + struct { + // byte 1 + /*00*/ unsigned gender : 2; // Gender (0=male, 1=female, 2=monster) + /*02*/ unsigned ispet : 1; // Guessed based on observing live spawns + /*03*/ unsigned afk : 1; // 0=no, 1=afk + /*04*/ unsigned anon : 2; // 0=normal, 1=anon, 2=roleplay + /*06*/ unsigned gm : 1; + /*07*/ unsigned sneak : 1; + // byte 2 + /*08*/ unsigned lfg : 1; + /*09*/ unsigned unk9 : 1; + /*10*/ unsigned invis : 12; // there are 3000 different (non-GM) invis levels + /*22*/ unsigned linkdead : 1; // 1 Toggles LD on or off after name. Correct for RoF2 + /*23*/ unsigned showhelm : 1; + // byte 4 + /*24*/ unsigned betabuffed : 1; // Prefixes name with ! + /*25*/ unsigned trader : 1; + /*26*/ unsigned animationonpop : 1; + /*27*/ unsigned targetable : 1; + /*28*/ unsigned targetable_with_hotkey : 1; + /*29*/ unsigned showname : 1; + /*30*/ unsigned idleanimationsoff : 1; // what we called statue? + /*31*/ unsigned untargetable : 1; // bClickThrough + // byte 5 + /*32*/ unsigned buyer : 1; + /*33*/ unsigned offline : 1; + /*34*/ unsigned interactiveobject : 1; + /*35*/ unsigned missile : 1; + /*36*/ unsigned title : 1; + /*37*/ unsigned suffix : 1; + /*38*/ unsigned unk38 : 1; + /*39*/ unsigned unk39 : 1; + }; + uint8 raw[5]; + }; + }; + + struct Spawn_Struct_Position + { + union { + struct { + signed y : 19; + signed deltaX : 13; + + unsigned heading : 12; + signed z : 19; + unsigned pad1 : 1; + + unsigned pitch : 12; + signed animation : 10; //these might be swapped + signed deltaHeading : 10; //these might be swapped + + signed deltaY : 13; + signed deltaZ : 13; + unsigned pad3 : 6; + + signed x : 19; + unsigned pad4 : 13; + }; + uint32_t raw[5]; + }; + }; + + struct Client_Position + { + /*00*/ float delta_x; + /*04*/ float x; + /*08*/ float z; + /*12*/ signed animation : 10; + unsigned pitch : 12; + signed padding1 : 10; + /*16*/ float delta_y; + /*20*/ float y; + /*24*/ signed delta_heading : 10; + signed heading : 12; + signed padding2 : 10; + /*28*/ float delta_z; + /*32*/ + }; + + struct PlayerPositionUpdateServer_Struct + { + /*00*/ uint16 spawn_id; + /*02*/ uint16 vehicle_id; + /*04*/ Spawn_Struct_Position position; + /*24*/ + }; + + struct PlayerPositionUpdateClient_Struct { + /*00*/ uint16 sequence; + /*02*/ uint16 spawn_id; + /*04*/ uint16 vehicle_id; + /*06*/ Client_Position position; + /*38*/ + }; + + struct Door_Struct + { + /*000*/ char name[32]; + /*032*/ float DefaultY; + /*036*/ float DefaultX; + /*040*/ float DefaultZ; + /*044*/ float DefaultHeading; + /*048*/ uint32 DefaultDoorAngle; //rof2's incline + /*052*/ float Y; //most (all I've seen?) doors match the defaults here + /*056*/ float X; + /*060*/ float Z; + /*064*/ float Heading; + /*068*/ float DoorAngle; //not sure if this is actually a float; it might be a uint32 like DefaultDoorAngle + /*072*/ uint32 ScaleFactor; //rof2's size + /*076*/ uint32 Unknown76; //client doesn't seem to read this + /*080*/ uint8 Id; //doorid + /*081*/ uint8 Type; //opentype + /*082*/ uint8 State; //state_at_spawn + /*083*/ uint8 DefaultState; //invert_state + /*084*/ int32 Param; //door_param + /*088*/ uint32 AdventureDoorId; + /*092*/ uint32 DynDoorID; + /*096*/ uint32 RealEstateDoorID; + /*100*/ uint8 bHasScript; + /*101*/ uint8 bUsable; //1 if clickable + /*102*/ uint8 bRemainOpen; + /*103*/ uint8 bVisible; //1 is visible + /*104*/ uint8 bHeadingChanged; + /*105*/ uint8 padding1[3]; + /*108*/ float TopSpeed1; + /*112*/ float TopSpeed2; + /*116*/ uint8 bNeedsTimeStampSet; + /*117*/ uint8 padding2[3]; + /*120*/ float unknownFloat1; + /*124*/ float unknownFloat2; + /*128*/ uint8 unknownByte1; + /*129*/ uint8 padding3[3]; + /*132*/ + }; + + struct ZonePoint_Entry { + /*00*/ uint32 iterator; + /*04*/ float y; + /*08*/ float x; + /*12*/ float z; + /*16*/ float heading; + /*20*/ uint16 zoneid; + /*22*/ uint16 zoneinstance; + /*24*/ uint32 unknown024; + /*28*/ uint32 unknown028; + /*32*/ + }; + + struct ZonePoints { + /*00*/ uint32 count; + /*04*/ struct ZonePoint_Entry zpe[0]; // Always add one extra to the end after all zonepoints + }; + + struct EnterWorld_Struct { + /*000*/ char name[64]; + /*064*/ int32 unknown1; + /*068*/ int32 unknown2; //steam_latest handles these differently so for now im just going to ignore them till i figure it out + }; + + struct ZoneChange_Struct { + /*000*/ char char_name[64]; // Character Name + /*064*/ uint16 zoneID; + /*066*/ uint16 instanceID; + /*068*/ uint32 Unknown068; + /*072*/ uint32 Unknown072; + /*076*/ float y; + /*080*/ float x; + /*084*/ float z; + /*088*/ uint32 zone_reason; //0x0A == death, I think + /*092*/ int32 success; // =0 client->server, =1 server->client, -X=specific error + /*096*/ uint32 Unknown096; // Not sure the extra 4 bytes goes here or earlier in the struct. + /*100*/ + }; + + struct RequestClientZoneChange_Struct { + /*000*/ uint16 zone_id; + /*002*/ uint16 instance_id; + /*004*/ uint32 unknown004; + /*008*/ float y; + /*012*/ float x; + /*016*/ float z; + /*020*/ float heading; + /*024*/ uint32 type; //unknown... values + /*032*/ uint8 unknown032[144]; + /*172*/ uint32 unknown172; + /*176*/ + }; + + struct WearChange_Struct { + /*000*/ uint32 spawn_id; + /*004*/ uint32 wear_slot_id; + /*008*/ uint32 armor_id; + /*012*/ uint32 variation; + /*016*/ uint32 material; + /*020*/ uint32 new_armor_id; + /*024*/ uint32 new_armor_type; + /*028*/ uint32 color; + /*032*/ + }; + + struct ExpUpdate_Struct + { + /*000*/ uint64 exp; //This is exp % / 1000 now; eg 69250 = 69.25% + /*008*/ uint64 unknown; //unclear, I didn't see the client actually read this value but i might have missed it + }; + + struct DeleteSpawn_Struct + { + /*00*/ uint32 spawn_id; // Spawn ID to delete + /*04*/ uint8 unknown04; // Seen 1 + /*05*/ + }; + + //OP_SetServerFilter + struct SetServerFilter_Struct { + uint32 filters[68]; + }; + + // Was new to RoF2, doesn't look changed + // The padding is because these structs are padded to the default 4 bytes + struct InventorySlot_Struct + { + /*000*/ int16 Type; + /*002*/ int16 Padding1; + /*004*/ int16 Slot; + /*006*/ int16 SubIndex; + /*008*/ int16 AugIndex; + /*010*/ int16 Padding2; + /*012*/ + }; + + // Was new for RoF2 - Used for Merchant_Purchase_Struct, doesn't look changed + // Can't sellfrom other than main inventory so Slot Type is not needed. + // The padding is because these structs are padded to the default 4 bytes + struct TypelessInventorySlot_Struct + { + /*000*/ int16 Slot; + /*002*/ int16 SubIndex; + /*004*/ int16 AugIndex; + /*006*/ int16 Padding; + /*008*/ + }; + + struct Consider_Struct { + /*000*/ uint32 playerid; // PlayerID + /*004*/ uint32 targetid; // TargetID + /*008*/ uint32 faction; // Faction + /*012*/ uint32 level; // Level + /*016*/ uint32 report_mode; // 0 normally, 4 will do a more detailed report that only works if you have GM flag set + /*020*/ uint8 rare_creature; // Will do the rare creature string + /*021*/ uint8 loot_locked; // Will list the target as (loot locked) + /*022*/ uint8 unknown022; // Padding probably + /*023*/ uint8 unknown023; // Padding probably + /*024*/ + }; + + struct ChangeSize_Struct + { + /*00*/ uint32 EntityID; + /*04*/ float Size; + /*08*/ uint32 Unknown08; // Observed 0 + /*12*/ float Unknown12; // Observed 1.0f + /*16*/ + }; + + struct SpawnHPUpdate_Struct + { + /*00*/ int16 spawn_id; + /*02*/ int64 cur_hp; + /*10*/ int64 max_hp; + /*18*/ + }; + + struct ClickDoor_Struct { + /*00*/ uint16 player_id; + /*02*/ uint8 padding1[2]; + /*04*/ int32 unknown1; + /*08*/ int32 unknown2; + /*12*/ uint8 doorid; + /*13*/ uint8 padding2[3]; + }; + + /* + Flags for special: + WildRampage: 0x1 + Rampage: 0x2 + NoCastOnText: 0x4 + DoubleBowShot: 0x8 + UnknownSpellFlag: 0x10 + Flurry: 0x20 + Riposte: 0x40 + Critical: 0x80 + Lucky: 0x100 + FinishingBlow: 0x200 + CripplingBlow: 0x400 + Assassinate: 0x800 + DeadlyStrike: 0x1000 + SlayUndead: 0x2000 + Headshot: 0x4000 + Strikethrough: 0x8000 + LuckyRiposte: 0x10000 + Twincast: 0x20000 + Might be more flags beyond this but I'm not sure + */ + + struct CombatDamage_Struct + { + /*000*/ uint16 target; + /*002*/ uint16 source; + /*004*/ uint32 unknown1; //not read by the client + /*008*/ int64 damage; + /*016*/ uint32 special; //flags; will document above + /*020*/ int32 spellid; + /*024*/ uint32 spell_level; //spell caster level (unconfirmed; it is used for the spell link) + /*028*/ float force; //I haven't actually been able to confirm these three yet + /*032*/ float hit_heading; + /*036*/ int32 hit_pitch; + /*040*/ uint8 type; + /*041*/ uint8 padding[3]; + /*044*/ uint32 unknown2; //not read by the client + /*048*/ + }; + + struct Animation_Struct { + /*00*/ uint16 spawnid; + /*02*/ uint8 action; + /*03*/ uint8 speed; + /*04*/ + }; + + struct Death_Struct + { + /*000*/ uint32 spawn_id; + /*004*/ uint32 killer_id; + /*008*/ uint32 corpseid; //not read by client + /*012*/ uint32 unknown1; //not read by client + /*016*/ uint32 spell_id; + /*020*/ uint32 attack_skill; + /*024*/ uint64 damage; + /*032*/ uint32 unknown2; //not read by client + /*036*/ uint32 unknown3; //not read by client + /*040*/ + }; + + struct DeleteItem_Struct + { + /*0000*/ InventorySlot_Struct from_slot; + /*0012*/ InventorySlot_Struct to_slot; + /*0024*/ uint32 number_in_stack; + /*0028*/ + }; + + struct MoveItem_Struct + { + /*0000*/ InventorySlot_Struct from_slot; + /*0012*/ InventorySlot_Struct to_slot; + /*0024*/ uint32 number_in_stack; + /*0028*/ + }; + + struct MerchantClickRequest_Struct + { + /*000*/ uint32 npc_id; // Merchant NPC's entity id + /*004*/ + }; + + struct MerchantClickResponse_Struct + { + /*000*/ uint32 npc_id; // Merchant NPC's entity id + /*004*/ uint32 player_id; + /*008*/ float rate; + /*012*/ uint32 tab_display; // bitmask b000 none, b001 Purchase/Sell, b010 Recover, b100 Parcels + /*016*/ uint32 ldon_category; // ldon cat for ldon merchants + /*020*/ uint32 alt_currency1; //These two usually match but I imagine they could be different? + /*024*/ uint32 alt_currency2; + /*028*/ uint32 unknown028; // Observed 256 + /*032*/ + }; + + struct BeginCast_Struct + { + /*000*/ uint32 spell_id; + /*004*/ uint16 caster_id; + /*006*/ uint32 cast_time; // in miliseconds + /*010*/ uint32 unknown0a; // I think this is caster effective level but im not sure. live always sends 0 + /*014*/ uint8 unknown0e; // 0 will short circuit the cast, seen 1 from live usually, maybe related to interrupts or particles or something + /*015*/ + }; + + //I've observed 5 s16 that are all -1. + //Clicky items don't even trigger this as far as i can tell so not sure what this is for now. + //One of these could have changed to a s32 but im not sure. + struct CastSpellInventorySlot_Struct { + /*00*/ int16 type; + /*02*/ int16 slot; + /*04*/ int16 sub_index; + /*06*/ int16 aug_index; + /*08*/ int16 unknown1; + /*10*/ + }; + + struct CastSpell_Struct + { + /*00*/ uint32 slot; + /*04*/ uint32 spell_id; + /*08*/ CastSpellInventorySlot_Struct inventory_slot; + /*18*/ uint32 target_id; + /*22*/ uint32 spell_crc; + /*26*/ float y_pos; + /*30*/ float x_pos; + /*34*/ float z_pos; + /*38*/ uint8 unknown; //not sure, might also be before y_pos; only ever seen zero for both but should be easy to figure out later + /*39*/ + }; + + struct EQAffectSlot_Struct { + /*00*/ int32 slot; + /*04*/ int32 padding; + /*08*/ int64 value; + /*16*/ + }; + + struct EQAffect_Struct + { + /*000*/ EQAffectSlot_Struct slots[6]; + /*096*/ EqGuid caster_id; + /*104*/ uint32 flags; + /*108*/ uint32 spell_id; + /*112*/ uint32 duration; + /*116*/ uint32 initial_duration; + /*120*/ uint32 hit_count; + /*124*/ uint32 viral_timer; + /*128*/ float modifier; + /*132*/ float y; + /*136*/ float x; + /*140*/ float z; + /*144*/ uint8 level; + /*145*/ uint8 type; + /*146*/ uint8 charges; //no idea if these are right; eqlib doesn't seem to know either + /*147*/ uint8 activatable; + /*148*/ uint32 unknown1; //might be some timer, not sure though + /*152*/ + }; + + struct EQAffectPacket_Struct { + /*000*/ uint32 entity_id; + /*004*/ int32 unknown004; + /*008*/ EQAffect_Struct affect; + /*160*/ uint32 slot_id; + /*164*/ uint32 buff_fade; + /*168*/ + }; + + struct ManaChange_Struct + { + uint32 new_mana; + uint32 stamina; + uint32 spell_id; + uint32 keepcasting; + int32 slot; + }; + + //This is what we call OP_Action + //To the client though this is basically a missile hit though + //OP_Action is basically "instant missile hit" to the client + //@0x1401f0970 MissileHitInfo::Deserialize(CUnSerializeBuffer *buffer); + struct MissileHitInfo + { + uint16 target; + uint16 source; + uint32 spell_id; + //4 leaves a buff + uint32 effect_type; + uint32 effective_casting_level; + //Client does read this but only does something if it's negative + int64 unknown1; + //I don't see the client read this one outside basic serialization + int64 unknown2; + //I don't see the client read this one either but based on captures from live it seems to match spell damage + int64 damage; + float modifier; + float force; + float hit_heading; + float hit_pitch; + //same convention as damage + //231 for spell, otherwise it's skill in use + uint8 skill; + uint8 level; //the client doesn't actually deserialize anything past level + //live however has a lot more info here depending on packet type + }; + + struct MobHealth_Struct + { + /*01*/ int16 spawn_id; + /*00*/ uint32 hp; + }; + + struct GMTrainee_Struct + { + /*000*/ uint32 npcid; + /*004*/ uint32 playerid; + /*008*/ uint32 skills[PACKET_SKILL_ARRAY_SIZE]; + /*408*/ uint8 unknown408[36]; + /*444*/ + }; + + struct GMTrainSkillConfirm_Struct { + /*000*/ uint32 SkillID; + /*004*/ uint32 Cost; + /*008*/ uint8 NewSkill; // Set to 1 for 'You have learned the basics' message. + /*009*/ char TrainerName[64]; + /*073*/ uint8 Unknown073[3]; + /*076*/ + }; + + struct SkillUpdate_Struct { + /*00*/ uint32 skillId; + /*04*/ uint32 value; + /*08*/ uint8 active; + /*09*/ uint8 padding[3]; + /*12*/ + }; + + struct AA_Array + { + uint32 AA; + uint32 value; + uint32 charges; // expendable charges + bool bUnknown0x0c; // added test winter 2024; removed sometime in summer 2024 + }; + + struct AATable_Struct { + /*00*/ uint32 aa_spent; // Total AAs Spent + /*04*/ uint32 aapoints_assigned[6]; // none, general, arch, class, special, focus, merc + /*24*/ AA_Array aa_list[MAX_PP_AA_ARRAY]; + }; + + struct AltAdvStats_Struct { + /*000*/ uint32 experience; + /*004*/ uint32 unspent; + /*008*/ uint8 percentage; + /*009*/ uint8 unknown009[3]; + }; + + struct BlockedBuffs_Struct + { + /*000*/ int32 SpellID[BLOCKED_BUFF_COUNT]; + /*120*/ uint32 Count; + /*124*/ uint8 Pet; + /*125*/ uint8 Initialise; + /*126*/ uint16 Flags; + }; + + struct ZonePlayerToBind_Struct { + //Same structure as the binds in PlayerProfile_Struct + //Assembly calls the same function + /*000*/ uint16 bind_zone_id; + /*002*/ uint16 bind_instance_id; + /*004*/ float x; + /*008*/ float y; + /*012*/ float z; + /*016*/ float heading; + /*020*/ char zone_name[1]; // Or "Bind Location" + /*021*/ uint32 unknown1; + /*025*/ uint32 unknown2; + /*029*/ uint32 unknown3; + }; + + struct ArmorPropertyStruct + { + /*000*/ uint32 type; + /*004*/ uint32 variation; + /*008*/ uint32 material; + /*012*/ uint32 newArmorID; + /*016*/ uint32 newArmorType; + /*020*/ + }; + + struct Illusion_Struct { + /*000*/ uint32 spawnid; + /*004*/ char charname[64]; + /*068*/ uint16 race; //according to eqlib this is s32 + /*070*/ char unknown006[2]; + /*072*/ uint8 gender; + /*073*/ uint8 texture; + /*074*/ uint8 armorVariation; + /*075*/ uint8 armorMaterial; + /*076*/ uint8 helmtexture; + /*077*/ uint8 unknown077; //padding from this being a pack(4) struct actually + /*078*/ uint8 unknown078; + /*079*/ uint8 unknown079; + /*080*/ uint32 face; + /*084*/ uint8 hairstyle; + /*085*/ uint8 haircolor; + /*086*/ uint8 beard; + /*087*/ uint8 beardcolor; + /*088*/ float size; + /*092*/ uint32_t npc_tint; + /*096*/ bool keep_armor_properties; + /*097*/ uint8 unknown097[3]; //padding from this being a pack(4) struct actually + /*100*/ ArmorPropertyStruct armorProperties[9]; + /*280*/ uint32_t armorTints[9]; + /*316*/ int32 class_; + /*320*/ uint32 drakkin_heritage; + /*324*/ uint32 drakkin_tattoo; + /*328*/ uint32 drakkin_details; + /*332*/ + }; + + struct moneyOnCorpseStruct { + /*000*/ uint8 type; + /*001*/ uint8 padding1[3]; + /*004*/ uint32 flags; + /*008*/ uint32 platinum; + /*012*/ uint32 gold; + /*016*/ uint32 silver; + /*020*/ uint32 copper; + /*024*/ + }; + + struct GroupGeneric_Struct { + /*0000*/ char name1[64]; + /*0064*/ char name2[64]; + /*0128*/ uint32 unknown0128; + /*0132*/ uint32 unknown0132; + /*0136*/ uint32 unknown0136; + /*0140*/ uint32 unknown0140; + /*0144*/ uint32 unknown0144; + /*0148*/ uint32 unknown0148; + /*0152*/ uint16 unknown0152; + /*0154*/ + }; + + struct AugmentInfo_Struct + { + /*000*/ uint32 itemid; // id of the solvent needed + /*004*/ uint32 window; // window to display the information in + /*008*/ char augment_info[64]; // total packet length 80, all the rest were always 00 + /*072*/ uint32 unknown072; // seen 0, 56 + /*076*/ uint32 unknown076; // seen 8, 3, 11, always matches what client sends + /*080*/ + }; + + //seems to be unchanged from rof2? + //it's the same size at least + struct AugmentItem_Struct { + /*00*/ uint32 dest_inst_id; // The unique serial number for the item instance that is being augmented + /*04*/ uint32 container_index; // Seen 0 + /*08*/ InventorySlot_Struct container_slot; // Slot of the item being augmented + /*20*/ uint32 augment_index; // Seen 0 + /*24*/ InventorySlot_Struct augment_slot; // Slot of the distiller to use (if one applies) + /*36*/ int32 augment_action; // Guessed - 0 = augment, 1 = remove with distiller, 3 = delete aug + /*36*/ //int32 augment_slot; + /*40*/ + }; + + struct ApplyPoison_Struct + { + TypelessInventorySlot_Struct inventorySlot; + uint32 success; + }; + + /* + ** Click Object Acknowledgement Struct + ** Response to client clicking on a World Container (ie, forge) + ** Seems to have not changed from RoF2 + */ + struct ClickObjectAction_Struct { + /*00*/ //uint32 player_id; // Appears to have been removed + /*00*/ uint32 drop_id; // Appears to use the object_count field now + /*04*/ int32 unknown04; // Seen -1 + /*08*/ int32 unknown08; // Seen -1 + /*08*/ //uint32 open; // 1=opening, 0=closing - Removed? + /*12*/ uint32 type; // See object.h, "Object Types" + /*16*/ uint32 unknown16; // + /*20*/ uint32 icon; // Icon to display for tradeskill containers + /*24*/ uint32 unknown24; // + /*28*/ char object_name[64]; // Object name to display + /*92*/ + }; + + //received and sent back as an ACK with different reply_code + struct RecipeAutoCombine_Struct { + /*00*/ uint32 object_type; + /*04*/ uint32 some_id; + /*08*/ InventorySlot_Struct container_slot; //echoed in reply - Was uint32 unknown1 + /*20*/ InventorySlot_Struct unknown_slot; //echoed in reply + /*32*/ uint32 recipe_id; + /*36*/ uint32 reply_code; + /*40*/ + }; + + /* + ** New Combine Struct + ** Client requesting to perform a tradeskill combine + ** Size: 24 bytes + ** Used In: OP_TradeSkillCombine + ** Last Updated: 01-05-2013 + */ + struct NewCombine_Struct + { + /*00*/ InventorySlot_Struct container_slot; + /*12*/ InventorySlot_Struct guildtribute_slot; // Slot type is 8? (MapGuildTribute = 8) + /*24*/ + }; + + struct Disciplines_Struct { + uint32 values[MAX_PP_DISCIPLINES]; + }; + + struct Merchant_Sell_Request_Struct { + /*000*/ uint32 npcid; // Merchant NPC's entity id + /*004*/ uint32 playerid; // Player's entity id + /*008*/ uint32 itemslot; // Merchant Slot / Item Instance ID + /*012*/ uint32 unknown12; + /*016*/ uint32 quantity; // Already sold + /*020*/ + }; + + struct Merchant_Sell_Response_Struct { + /*000*/ uint32 npcid; // Merchant NPC's entity id + /*004*/ uint32 playerid; // Player's entity id + /*008*/ uint32 itemslot; // Merchant Slot / Item Instance ID + /*012*/ uint32 unknown12; + /*016*/ uint32 quantity; // Already sold + /*020*/ uint32 unknown20; + /*024*/ uint32 price; + /*028*/ uint32 unknown28; // Normally 0, but seen 84 c5 63 00 as well + /*032*/ + }; + + struct Merchant_Purchase_Request_Struct { + /*000*/ uint32 npcid; // Merchant NPC's entity id + /*004*/ TypelessInventorySlot_Struct inventory_slot; + /*012*/ uint32 quantity; + /*016*/ + }; + + struct Merchant_Purchase_Response_Struct { + /*000*/ uint32 npcid; // Merchant NPC's entity id + /*004*/ TypelessInventorySlot_Struct inventory_slot; + /*012*/ uint32 quantity; + /*016*/ uint32 price; + /*020*/ uint32 unknown020; + /*024*/ + }; + + /* + ** Cancel Trade struct + ** Sent when a player cancels a trade + ** Size: 8 bytes + ** Used In: OP_CancelTrade + ** + */ + struct CancelTrade_Struct { + /*00*/ uint32 fromid; + /*04*/ uint32 action; + /*08*/ + }; + + struct Stun_Struct { // 8 bytes total + /*000*/ uint32 duration; // Duration of stun + /*004*/ uint8 unknown004; // seen 0 + /*005*/ uint8 unknown005; // seen 163 + /*006*/ uint8 unknown006; // seen 67 + /*007*/ uint8 unknown007; // seen 0 + /*008*/ + }; +#pragma pack() + + }; //end namespace structs +}; //end namespace steam_latest + +#endif /*LAURION_STRUCTS_H_*/ \ No newline at end of file diff --git a/common/ruletypes.h b/common/ruletypes.h index d9df94afd..fad7e2a96 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -354,6 +354,7 @@ RULE_STRING(World, CustomFilesKey, "", "Enable if the server requires custom fil RULE_STRING(World, CustomFilesUrl, "github.com/knervous/eqnexus/releases", "URL to display at character select if client is missing custom files") RULE_INT(World, CustomFilesAdminLevel, 20, "Admin level at which custom file key is not required when CustomFilesKey is specified") RULE_BOOL(World, RealTimeCalculateGuilds, false, "(Temp feature flag) If true, guilds will be calculated in real time instead of at zone boot. This is a performance hit but allows for more dynamic guilds.") +RULE_INT(World, Id, 100, "Used by later clients to create GUIDs, expected to be Unique to the world but ultimately not that important") RULE_CATEGORY_END() RULE_CATEGORY(Zone) diff --git a/utils/patches/patch_SteamLatest.conf b/utils/patches/patch_SteamLatest.conf new file mode 100644 index 000000000..c51d912fb --- /dev/null +++ b/utils/patches/patch_SteamLatest.conf @@ -0,0 +1,699 @@ +# ShowEQ Import Notes: +# ZERO THE FILE first +# perl -pi -e 's/0x[0-9a-fA-F]{4}/0x0000/g' opcodes.conf +# Unknown Mapping: +# OP_Action2 -> OP_Damage +# OP_EnvDamage -> OP_Damage ---> might have been a one time mistake +# Name Differences: +# OP_CancelInvite -> OP_GroupCancelInvite +# OP_GMFind -> OP_FindPersonRequest +# OP_CommonMessage -> OP_ChannelMessage + +OP_Unknown=0x0000 +OP_ExploreUnknown=0x0000 # used for unknown explorer + +# world packets +# Required to reach Char Select: +OP_SendLoginInfo=0x2fca +OP_ApproveWorld=0x0000 +OP_LogServer=0x6d4d +OP_SendCharInfo=0x832 +OP_ExpansionInfo=0x066d +OP_EnterWorld=0x6691 +OP_PostEnterWorld=0x2062 +OP_World_Client_CRC1=0x74c8 +OP_World_Client_CRC2=0x3984 +OP_World_Client_CRC3=0x6516 +OP_SendSpellChecksum=0x0000 +OP_SendSkillCapsChecksum=0x0000 + +# Character Select Related: +OP_SendMaxCharacters=0x13af +OP_SendMembership=0x2aca +OP_SendMembershipDetails=0x2608 +OP_CharacterCreateRequest=0x2df4 +OP_CharacterCreate=0x6a3c +OP_DeleteCharacter=x67d7 +OP_RandomNameGenerator=0x49d9 +OP_ApproveName=0x11e5 +OP_MOTD=0x0be4 +OP_SetChatServer=0x0000 +OP_SetChatServer2=0x2726 +OP_ZoneServerInfo=0x2273 +OP_WorldComplete=0x195c +OP_WorldUnknown001=0x2049 +OP_FloatListThing=0x66fd + +# Reasons for Disconnect: +OP_ZoneUnavail=0x582d +OP_WorldClientReady=0x7ed8 +OP_CharacterStillInZone=0x0000 +OP_WorldChecksumFailure=0x0000 +OP_WorldLoginFailed=0x0000 +OP_WorldLogout=0x0000 +OP_WorldLevelTooHigh=0x0000 +OP_CharInacessable=0x0000 +OP_UserCompInfo=0x0000 +OP_SendExeChecksum=0x0000 +OP_SendBaseDataChecksum=0x0000 + +# Zone in opcodes +OP_AckPacket=0x77c9 +OP_ZoneEntry=0x784a +OP_ReqNewZone=0x3895 +OP_NewZone=0x4341 +OP_ZoneSpawns=0x17d9 +OP_PlayerProfile=0x1c76 +OP_TimeOfDay=0x3736 +OP_LevelUpdate=0x0eb2 +OP_Stamina=0x1563 +OP_RequestClientZoneChange=0x0191 +OP_ZoneChange=0x17a3 +OP_LockoutTimerInfo=0x0000 +OP_ZoneServerReady=0x0000 +OP_ZoneInUnknown=0x0000 +OP_LogoutReply=0x0000 +OP_PreLogoutReply=0x0000 + +# Required to fully log in +OP_SpawnAppearance=0x4eb0 +OP_ChangeSize=0x2fdc +OP_Weather=0x6fe6 +OP_ReqClientSpawn=0x6732 +OP_SpawnDoor=0x4273 +OP_GroundSpawn=0x49c5 +OP_SendZonepoints=0x279f +OP_BlockedBuffs=0x4fdb +OP_RemoveBlockedBuffs=0x53cd +OP_ClearBlockedBuffs=0x5752 +OP_WorldObjectsSent=0x2879 +OP_SendExpZonein=0x02b4 +OP_SendAATable=0x5f30 +OP_ClearAA=0x3498 +OP_ClearLeadershipAbilities=0x0000 #removed; leadership abilities are baked in and always on +OP_RespondAA=0x4c67 +OP_UpdateAA=0x3b30 +OP_SendAAStats=0x7d65 #i'll be honest i think this was removed at some point but this is the op at the spot in the list +OP_AAExpUpdate=0x642f #need to look into whether this has changed; exp did +OP_ExpUpdate=0x611d +OP_HPUpdate=0x775c +OP_ManaChange=0x700f +OP_TGB=0x0000 #removed; tgb is baked in and always on +OP_SpecialMesg=0x7d93 +OP_CharInventory=0x21d6 +OP_WearChange=0x44c0 +OP_ClientUpdate=0x3a4b +OP_ClientReady=0x0831 +OP_SetServerFilter=0x6b7f + +# Guild Opcodes +OP_GuildsList=0x0000 +OP_GuildMemberList=0x0000 +OP_GuildMOTD=0x0000 +OP_GetGuildMOTD=0x0000 +OP_GetGuildMOTDReply=0x0000 +OP_GuildMemberUpdate=0x0000 +OP_GuildInvite=0x0000 +OP_GuildRemove=0x0000 +OP_GuildPeace=0x0000 +OP_SetGuildMOTD=0x0000 +OP_GuildWar=0x0000 +OP_GuildLeader=0x0000 +OP_GuildDelete=0x0000 +OP_GuildInviteAccept=0x0000 +OP_GuildDemote=0x0000 +OP_GuildPromote=0x0000 +OP_GuildPublicNote=0x0000 +OP_GuildManageBanker=0x0000 +OP_GuildBank=0x0000 +OP_GuildBankItemList=0x0000 +OP_SetGuildRank=0x0000 +OP_GuildUpdate=0x0000 +OP_GuildStatus=0x0000 +OP_GuildCreate=0x0000 +OP_GuildOpenGuildWindow=0x0000 +OP_GuildMemberLevel=0x0000 +OP_GuildMemberRankAltBanker=0x0000 +OP_GuildMemberPublicNote=0x0000 +OP_GuildMemberAdd=0x0000 +OP_GuildMemberRename=0x0000 +OP_GuildMemberDelete=0x0000 +OP_GuildMemberDetails=0x0000 +OP_GuildRenameGuild=0x0000 +OP_LFGuild=0x0000 +OP_GuildDeleteGuild=0x0000 + +# GM/Guide Opcodes +OP_GMServers=0x0000 +OP_GMBecomeNPC=0x0000 +OP_GMZoneRequest=0x0000 +OP_GMZoneRequest2=0x0000 +OP_GMGoto=0x0000 +OP_GMSearchCorpse=0x0000 +OP_GMHideMe=0x0000 +OP_GMDelCorpse=0x0000 +OP_GMApproval=0x0000 +OP_GMToggle=0x0000 +OP_GMSummon=0x0000 +OP_GMEmoteZone=0x0000 +OP_GMEmoteWorld=0x0000 +OP_GMFind=0x0000 +OP_GMKick=0x0000 +OP_GMKill=0x0000 +OP_GMNameChange=0x0000 +OP_GMLastName=0x0000 + +# Misc Opcodes +OP_QueryUCSServerStatus=0x2570 +OP_InspectRequest=0x0000 +OP_InspectAnswer=0x0000 +OP_InspectMessageUpdate=0x0000 +OP_BeginCast=0x31f9 +OP_ColoredText=0x0f3c +OP_ConsentResponse=0x3229 +OP_MemorizeSpell=0x1d31 +OP_LinkedReuse=0x7a8e +OP_SwapSpell=0x63c7 +OP_CastSpell=0x325b +OP_Consider=0x53e3 +OP_FormattedMessage=0x7f7f +OP_SimpleMessage=0x1943 +OP_Buff=0x6ce5 +OP_Illusion=0x5a3f +OP_MoneyOnCorpse=0x39d3 +OP_RandomReply=0x6603 +OP_DenyResponse=0x3f2c +OP_SkillUpdate=0x6735 +OP_GMTrainSkillConfirm=0x6fbc +OP_RandomReq=0x528a +OP_Death=0x429a +OP_GMTraining=0x7c7a +OP_GMEndTraining=0x3ec6 +OP_GMTrainSkill=0x54e1 +OP_Animation=0x79c7 +OP_Begging=0x7ded +OP_Consent=0x44fc +OP_ConsentDeny=0x3df9 +OP_AutoFire=0x5280 +OP_PetCommands=0x0000 +OP_PetCommandState=0x0000 +OP_PetHoTT=0x0000 +OP_DeleteSpell=0x4281 +OP_Surname=0x0000 +OP_ClearSurname=0x0000 +OP_FaceChange=0x0000 +OP_SetFace=0x0000 +OP_SenseHeading=0x6fcf +OP_Action=0x4c13 +OP_ConsiderCorpse=0x6092 +OP_HideCorpse=0x3f5c +OP_CorpseDrag=0x234d +OP_CorpseDrop=0x4342 +OP_Bug=0x770b +OP_Feedback=0x0000 +OP_Report=0x5bcf +OP_Damage=0x7d07 +OP_ChannelMessage=0x6adc +OP_Assist=0x51f1 +OP_AssistGroup=0x3f23 +OP_MoveCoin=0x4e6a +OP_ZonePlayerToBind=0x5643 +OP_KeyRing=0x0000 +OP_WhoAllRequest=0x2a09 +OP_WhoAllResponse=0x6404 +OP_FriendsWho=0x75a2 +OP_ConfirmDelete=0x4dd0 +OP_Logout=0x771d +OP_Rewind=0x2b19 +OP_TargetCommand=0x3b18 +OP_Hide=0x1cdf +OP_Jump=0x6fa0 +OP_Camp=0x326f +OP_Emote=0x0000 +OP_SetRunMode=0x1449 +OP_BankerChange=0x2a33 +OP_TargetMouse=0x5741 +OP_MobHealth=0x5b77 +OP_InitialMobHealth=0x0000 # Unused? +OP_TargetHoTT=0x0000 +OP_TargetBuffs=0x0000 +OP_XTargetResponse=0x0000 +OP_XTargetRequest=0x0000 +OP_XTargetAutoAddHaters=0x0000 +OP_XTargetOpen=0x0000 +OP_XTargetOpenResponse=0x0000 +OP_BuffCreate=0x27a1 +OP_BuffRemoveRequest=0x4507 +OP_DeleteSpawn=0x7712 +OP_AutoAttack=0x3f03 +OP_AutoAttack2=0x1c31 +OP_Consume=0x5ef7 +OP_MoveItem=0x11e3 +OP_MoveMultipleItems=0x5205 +OP_DeleteItem=0x0150 +OP_DeleteCharge=0x1b7e +OP_ItemPacket=0x7d43 +OP_ItemLinkResponse=0x0000 +OP_ItemLinkClick=0x0000 +OP_ItemPreview=0x0000 +OP_NewSpawn=0x3ea8 +OP_Track=0x5351 +OP_TrackTarget=0x611a +OP_TrackUnknown=0x2c7a +OP_ClickDoor=0x733c +OP_MoveDoor=0x567c +OP_RemoveAllDoors=0x73e8 +OP_EnvDamage=0x1ffd +OP_BoardBoat=0x7015 +OP_LeaveBoat=0x2486 +OP_ControlBoat=0x166f +OP_Forage=0x4c52 +OP_SafeFallSuccess=0x6690 +OP_RezzComplete=0x0000 +OP_RezzRequest=0x0000 +OP_RezzAnswer=0x0000 +OP_Shielding=0x0000 +OP_RequestDuel=0x0000 +OP_MobRename=0x0000 +OP_AugmentItem=0x3a1b +OP_WeaponEquip1=0x0000 +OP_PlayerStateAdd=0x2178 +OP_PlayerStateRemove=0x178e +OP_ApplyPoison=0x55b9 +OP_Save=0x6da2 +OP_TestBuff=0x0000 +OP_CustomTitles=0x0000 +OP_Split=0x7f6e +OP_YellForHelp=0x5fc9 +OP_LoadSpellSet=0x0000 +OP_Bandolier=0x0000 +OP_PotionBelt=0x0000 +OP_DuelDecline=0x0000 +OP_DuelAccept=0x0000 +OP_SaveOnZoneReq=0x3bfe +OP_ReadBook=0x51af +OP_Dye=0x0000 +OP_InterruptCast=0x1d71 +OP_AAAction=0x71BB +OP_LeadershipExpToggle=0x0000 #removed, these act as if all purchased now +OP_LeadershipExpUpdate=0x0000 #removed, these act as if all purchased now +OP_PurchaseLeadershipAA=0x0000 #removed, these act as if all purchased now +OP_UpdateLeadershipAA=0x0000 #removed, these act as if all purchased now +OP_MarkNPC=0x0000 +OP_ClearNPCMarks=0x0000 +OP_DelegateAbility=0x0000 +OP_SetGroupTarget=0x0000 +OP_Charm=0x66bb +OP_Stun=0x34be +OP_SendFindableNPCs=0x0000 +OP_FindPersonRequest=0x0000 +OP_FindPersonReply=0x0000 +OP_Sound=0x2fa8 +OP_CashReward=0x5e23 +OP_PetBuffWindow=0x0000 +OP_LevelAppearance=0x5d24 +OP_Translocate=0x2772 +OP_Sacrifice=0x2cbf +OP_PopupResponse=0x6be9 +OP_OnLevelMessage=0x2a41 +OP_AugmentInfo=0x2e11 +OP_Petition=0x0000 +OP_SomeItemPacketMaybe=0x0000 +OP_PVPStats=0x0000 +OP_PVPLeaderBoardRequest=0x0000 +OP_PVPLeaderBoardReply=0x0000 +OP_PVPLeaderBoardDetailsRequest=0x0000 +OP_PVPLeaderBoardDetailsReply=0x0000 +OP_RestState=0x0a92 +OP_RespawnWindow=0x55ed +OP_LDoNButton=0x0000 +OP_SetStartCity=0x0000 +OP_VoiceMacroIn=0x703f +OP_VoiceMacroOut=0x72d1 +OP_ItemViewUnknown=0x0000 +OP_VetRewardsAvaliable=0x0000 +OP_VetClaimRequest=0x0000 +OP_VetClaimReply=0x0000 +OP_DisciplineUpdate=0x6ce4 +OP_DisciplineTimer=0x7436 +OP_BecomeCorpse=0x0000 # Unused? +OP_Action2=0x0000 # Unused? +OP_MobUpdate=0x0000 +OP_NPCMoveUpdate=0x0000 +OP_CameraEffect=0x2f01 +OP_SpellEffect=0x7378 +OP_AddNimbusEffect=0x069f +OP_RemoveNimbusEffect=0x19ee +OP_AltCurrency=0x0000 +OP_AltCurrencyMerchantRequest=0x0000 +OP_AltCurrencyMerchantReply=0x0000 +OP_AltCurrencyPurchase=0x0000 +OP_AltCurrencySell=0x0000 +OP_AltCurrencySellSelection=0x0000 +OP_AltCurrencyReclaim=0x0000 +OP_CrystalCountUpdate=0x0000 +OP_CrystalCreate=0x0000 +OP_CrystalReclaim=0x0000 +OP_Untargetable=0x026f +OP_IncreaseStats=0x1005 +OP_Weblink=0x16a3 +OP_OpenContainer=0x6758 +OP_Marquee=0x6bca +OP_ItemRecastDelay=0x547a +#OP_OpenInventory=0x0000 # Likely does not exist in RoF -U +OP_ResetAA=0x53c0 +OP_Fling=0x3731 +OP_CancelSneakHide=0x7452 +OP_AggroMeterLockTarget=0x0000 +OP_AggroMeterTargetInfo=0x0000 +OP_AggroMeterUpdate=0x0000 +OP_UnderWorld=0x4ca9 # clients sends up when they detect an underworld issue, might be useful for cheat detection +OP_KickPlayers=0x7154 +OP_BookButton=0x014d + +# Expeditions +OP_DzQuit=0x0000 +OP_DzListTimers=0x0000 +OP_DzAddPlayer=0x0000 +OP_DzRemovePlayer=0x0000 +OP_DzSwapPlayer=0x0000 +OP_DzMakeLeader=0x0000 +OP_DzPlayerList=0x0000 +OP_DzExpeditionInvite=0x0000 +OP_DzExpeditionInviteResponse=0x0000 +OP_DzExpeditionInfo=0x0000 +OP_DzExpeditionLockoutTimers=0x0000 +OP_DzMemberList=0x0000 +OP_DzMemberListName=0x0000 +OP_DzMemberListStatus=0x0000 +OP_DzSetLeaderName=0x0000 +OP_DzExpeditionEndsWarning=0x0000 +OP_DzCompass=0x0000 +OP_DzChooseZone=0x0000 +OP_DzChooseZoneReply=0x0000 + +# New Opcodes +OP_SpawnPositionUpdate=0x0000 # Actually OP_MobUpdate ? +OP_ManaUpdate=0x0000 +OP_EnduranceUpdate=0x0000 +OP_MobManaUpdate=0x0000 +OP_MobEnduranceUpdate=0x0000 + +# Mercenary Opcodes +OP_MercenaryDataUpdateRequest=0x0000 +OP_MercenaryDataUpdate=0x0000 +OP_MercenaryDataRequest=0x0000 +OP_MercenaryDataResponse=0x0000 +OP_MercenaryHire=0x0000 +OP_MercenaryDismiss=0x0000 +OP_MercenaryTimerRequest=0x0000 +OP_MercenaryTimer=0x0000 +OP_MercenaryUnknown1=0x0000 +OP_MercenaryCommand=0x0000 +OP_MercenarySuspendRequest=0x0000 +OP_MercenarySuspendResponse=0x0000 +OP_MercenaryUnsuspendResponse=0x0000 + +# Looting +OP_LootRequest=0x60e5 +OP_EndLootRequest=0x35f6 +OP_LootItem=0x0856 +OP_LootComplete=0x1f5e + +# bazaar trader stuff: +OP_BazaarSearch=0x0000 +OP_TraderDelItem=0x0000 +OP_BecomeTrader=0x0000 +OP_TraderShop=0x0000 +OP_TraderBulkSend=0x0000 +OP_Trader=0x0000 +OP_Barter=0x0000 +OP_BuyerItems=0x0000 +OP_TraderBuy=0x0000 +OP_ShopItem=0x0000 +OP_BazaarInspect=0x0000 +OP_Bazaar=0x0000 +OP_TraderItemUpdate=0x0000 + +# pc/npc trading +OP_TradeRequest=0x7066 +OP_TradeAcceptClick=0x34ad +OP_TradeRequestAck=0x1c6b +OP_TradeCoins=0x44fe +OP_FinishTrade=0x0ec6 +OP_CancelTrade=0x5839 +OP_TradeMoneyUpdate=0x5fb3 +OP_MoneyUpdate=0x70bb +OP_TradeBusy=0x109f + +# Sent after canceling trade or after closing tradeskill object +OP_FinishWindow=0x50d4 +OP_FinishWindow2=0x6b03 + +# Sent on Live for what seems to be item existance verification +# Ex. Before Right Click Effect happens from items +OP_ItemVerifyRequest=0x2003 +OP_ItemVerifyReply=0x43d0 + +OP_ItemAdvancedLoreText=0x0000 + +# merchant stuff +OP_ShopPlayerSell=0x6489 +OP_ShopRequest=0x840 +OP_ShopEnd=0x74bb +OP_ShopEndConfirm=0x2ed1 +OP_ShopPlayerBuy=0x625e +OP_ShopDelItem=0x4ce4 +OP_ShopSendParcel=0x0f16 +OP_ShopDeleteParcel=0x4e2a +OP_ShopRetrieveParcel=0x27d1 +OP_ShopParcelIcon=0x4f27 + +# tradeskill stuff: +OP_ClickObject=0x687e +OP_ClickObjectAction=0x110f +OP_ClearObject=0x6155 +OP_RecipeDetails=0x01e7 +OP_RecipesFavorite=0x0495 +OP_RecipesSearch=0x2f4e +OP_RecipeReply=0x2cd2 +OP_RecipeAutoCombine=0x5dba +OP_TradeSkillCombine=0x4ed8 + +# Tribute Packets: +OP_TributeUpdate=0x0000 +OP_TributeTimer=0x0000 +OP_SendTributes=0x0000 +OP_RequestGuildTributes=0x0000 +OP_TributeInfo=0x0000 +OP_OpenTributeMaster=0x0000 +OP_SelectTribute=0x0000 +OP_TributeItem=0x0000 +OP_TributeMoney=0x0000 +OP_TributeToggle=0x0000 +OP_TributePointUpdate=0x0000 +OP_TributeNPC=0x0000 +OP_GuildTributeInfo=0x0000 +OP_OpenTributeReply=0x0000 +OP_GuildTributeStatus=0x0000 +OP_GuildSaveActiveTributes=0x0000 +OP_GuildSendActiveTributes=0x0000 +OP_GuildTributeToggleReq=0x0000 +OP_GuildTributeToggleReply=0x0000 +OP_GuildTributeFavorAndTimer=0x0000 +OP_GuildTributeDonateItem=0x0000 +OP_GuildTributeDonatePlat=0x0000 +OP_GuildSelectTribute=0x0000 +OP_GuildModifyBenefits=0x0000 +OP_GuildOptInOut=0x0000 +OP_SendGuildTributes=0x0000 +OP_OpenGuildTributeMaster=0x0000 + +# Adventure packets: +OP_LeaveAdventure=0x0000 +OP_AdventureFinish=0x0000 +OP_AdventureInfoRequest=0x0000 +OP_AdventureInfo=0x0000 +OP_AdventureRequest=0x0000 +OP_AdventureDetails=0x0000 +OP_AdventureData=0x0000 +OP_AdventureUpdate=0x0000 +OP_AdventureMerchantRequest=0x0000 +OP_AdventureMerchantResponse=0x0000 +OP_AdventureMerchantPurchase=0x0000 +OP_AdventureMerchantSell=0x0000 +OP_AdventurePointsUpdate=0x0000 +OP_AdventureStatsRequest=0x0000 +OP_AdventureStatsReply=0x0000 +OP_AdventureLeaderboardRequest=0x0000 +OP_AdventureLeaderboardReply=0x0000 + +# Group Opcodes +OP_GroupDisband=0x78ef +OP_GroupInvite=0x1d90 +OP_GroupFollow=0x0000 +OP_GroupUpdate=0x0000 +OP_GroupUpdateB=0x0000 +OP_GroupCancelInvite=0x0000 +OP_GroupAcknowledge=0x0000 +OP_GroupDelete=0x0000 +OP_CancelInvite=0x0000 +OP_GroupFollow2=0x0000 +OP_GroupInvite2=0x1e7e +OP_GroupDisbandYou=0x0000 +OP_GroupDisbandOther=0x0000 +OP_GroupLeaderChange=0x0000 +OP_GroupRoles=0x0000 +OP_GroupMakeLeader=0x0000 +OP_DoGroupLeadershipAbility=0x0000 +OP_GroupLeadershipAAUpdate=0x0000 # removed these act as if you have always purchased them +OP_GroupMentor=0x0000 +OP_InspectBuffs=0x0000 + +# LFG/LFP Opcodes +OP_LFGCommand=0x0000 +OP_LFGGetMatchesRequest=0x0000 +OP_LFGGetMatchesResponse=0x0000 +OP_LFPGetMatchesRequest=0x0000 +OP_LFPGetMatchesResponse=0x0000 +OP_LFPCommand=0x0000 +OP_LFGAppearance=0x0000 +OP_LFGResponse=0x0000 + +# Raid Opcodes +OP_RaidInvite=0x0000 +OP_RaidUpdate=0x0000 +OP_RaidJoin=0x0000 +OP_RaidDelegateAbility=0x0000 +OP_MarkRaidNPC=0x0000 +OP_RaidClearNPCMarks=0x0000 + +# Button-push commands +OP_Taunt=0x5064 +OP_CombatAbility=0xbf +OP_SenseTraps=0x579c +OP_PickPocket=0x53d1 +OP_DisarmTraps=0x21bf +OP_Disarm=0x31e9 +OP_Sneak=0x78a7 +OP_Fishing=0x57cc +OP_InstillDoubt=0x57cc +OP_FeignDeath=0x14b8 +OP_Mend=0x6b8 +OP_Bind_Wound=0x650e +OP_LDoNOpen=0x448 +OP_LDoNPickLock=0x61c8 +OP_LDoNInspect=0xc1c + +# Task packets +OP_TaskDescription=0x0000 +OP_TaskActivity=0x0000 +OP_CompletedTasks=0x0000 +OP_TaskActivityComplete=0x0000 +OP_AcceptNewTask=0x0000 +OP_CancelTask=0x0000 +OP_AvaliableTask=0x0000 +OP_TaskHistoryRequest=0x0000 +OP_TaskHistoryReply=0x0000 +OP_DeclineAllTasks=0x0000 +OP_TaskRequestTimer=0x0000 +OP_TaskSelectWindow=0x0000 + +# Shared Tasks +OP_SharedTaskMemberList=0x0000 # +OP_SharedTaskRemovePlayer=0x0000 # /taskremoveplayer +OP_SharedTaskAddPlayer=0x0000 # /taskaddplayer +OP_SharedTaskMakeLeader=0x0000 # /taskmakeleader +OP_SharedTaskInvite=0x0000 # Dialog window +OP_SharedTaskInviteResponse=0x0000 # Dialog window response +OP_SharedTaskAcceptNew=0x0000 # +OP_SharedTaskMemberChange=0x0000 # +OP_TaskTimers=0x0000 # /tasktimers +OP_SharedTaskQuit=0x0000 # /taskquit +OP_SharedTaskSelectWindow=0x0000 +OP_SharedTaskPlayerList=0x0000 # /taskplayerlist + +# Title opcodes +OP_NewTitlesAvailable=0x0000 +OP_RequestTitles=0x0000 +OP_SendTitleList=0x0000 +OP_SetTitle=0x0000 +OP_SetTitleReply=0x0000 + +# mail opcodes +OP_Command=0x0000 +OP_MailboxHeader=0x0000 +OP_MailHeader=0x0000 +OP_MailBody=0x0000 +OP_NewMail=0x0000 +OP_SentConfirm=0x0000 + +########### Below this point should not be needed ########### + +# This section are all unknown in Titanium +OP_ForceFindPerson=0x0000 +OP_LocInfo=0x0000 +OP_ReloadUI=0x0000 +OP_ItemName=0x0000 +OP_ItemLinkText=0x0000 +OP_MultiLineMsg=0x0000 +OP_MendHPUpdate=0x0000 +OP_TargetReject=0x0000 +OP_SafePoint=0x0000 +OP_ApproveZone=0x0000 +OP_ZoneComplete=0x0000 +OP_ClientError=0x0000 +OP_DumpName=0x0000 +OP_Heartbeat=0x0000 +OP_CrashDump=0x0000 +OP_LoginComplete=0x0000 + +# discovered opcodes not yet used: +OP_PickLockSuccess=0x0000 +OP_PlayMP3=0x6451 +OP_ReclaimCrystals=0x0000 +OP_DynamicWall=0x0000 +OP_OpenDiscordMerchant=0x0000 +OP_DiscordMerchantInventory=0x0000 +OP_GiveMoney=0x0000 +OP_RequestKnowledgeBase=0x0000 +OP_KnowledgeBase=0x0000 +OP_SlashAdventure=0x0000 # /adventure +OP_BecomePVPPrompt=0x0000 +OP_MoveLogRequest=0x0000 # gone I think +OP_MoveLogDisregard=0x0000 # gone I think + +# named unknowns, to make looking for real unknown easier +OP_AnnoyingZoneUnknown=0x0000 +OP_Some6ByteHPUpdate=0x0000 #seems to happen when you target group members +OP_QueryResponseThing=0x0000 + + +# realityincarnate: these are just here to stop annoying several thousand byte packet dumps +#OP_LoginUnknown1=0x0000 # OP_SendSpellChecksum +#OP_LoginUnknown2=0x0000 # OP_SendSkillCapsChecksum + +# Petition Opcodes +OP_PetitionSearch=0x0000 #search term for petition +OP_PetitionSearchResults=0x0000 #(list of?) matches from search +OP_PetitionSearchText=0x0000 #text results of search + +OP_PetitionUpdate=0x0000 +OP_PetitionCheckout=0x0000 +OP_PetitionCheckIn=0x0000 +OP_PetitionQue=0x0000 +OP_PetitionUnCheckout=0x0000 +OP_PetitionDelete=0x0000 +OP_DeletePetition=0x0000 +OP_PetitionResolve=0x0000 +OP_PDeletePetition=0x0000 +OP_PetitionBug=0x0000 +OP_PetitionRefresh=0x0000 +OP_PetitionCheckout2=0x0000 +OP_PetitionViewPetition=0x0000 + +#aura related +OP_UpdateAura=0x0000 +OP_RemoveTrap=0x0000 + +OP_Fingerprint=0x7a5b \ No newline at end of file diff --git a/world/client.cpp b/world/client.cpp index 46d505294..4a5b20b1f 100644 --- a/world/client.cpp +++ b/world/client.cpp @@ -1223,8 +1223,10 @@ bool Client::Process() { } if(connect.Check()){ - SendGuildList();// Send OPCode: OP_GuildsList - SendApproveWorld(); + if (!(m_ClientVersionBit & EQ::versions::maskLaurionAndLater)) { + SendGuildList();// Send OPCode: OP_GuildsList + SendApproveWorld(); + } connect.Disable(); } diff --git a/zone/client.cpp b/zone/client.cpp index 67462169f..84c15aabc 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -1205,7 +1205,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s LogDebug("Client::ChannelMessageReceived() Channel:[{}] message:[{}]", chan_num, message); if (RuleB(Chat, AlwaysCaptureCommandText)) { - if (message[0] == COMMAND_CHAR) { + if (message[0] == COMMAND_CHAR || message[0] == COMMAND_CHAR_NON_HASH) { if (command_dispatch(this, message, false) == -2) { if (parse->PlayerHasQuestSub(EVENT_COMMAND)) { int i = parse->EventPlayer(EVENT_COMMAND, this, message, 0); @@ -1538,7 +1538,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s } } - if (message[0] == COMMAND_CHAR) { + if (message[0] == COMMAND_CHAR || message[0] == COMMAND_CHAR_NON_HASH) { if (command_dispatch(this, message, false) == -2) { if (parse->PlayerHasQuestSub(EVENT_COMMAND)) { int i = parse->EventPlayer(EVENT_COMMAND, this, message, 0); @@ -8083,7 +8083,7 @@ void Client::GarbleMessage(char *message, uint8 variance) int delimiter_count = 0; // Don't garble # commands - if (message[0] == COMMAND_CHAR || message[0] == BOT_COMMAND_CHAR) { + if (message[0] == COMMAND_CHAR || message[0] == COMMAND_CHAR_NON_HASH || message[0] == BOT_COMMAND_CHAR) { return; } @@ -9158,6 +9158,44 @@ void Client::SendHPUpdateMarquee(){ SendMarqueeMessage(Chat::Yellow, 510, 0, 3000, 3000, health_update_notification); } +void Client::SendMembership() { + if (m_ClientVersion >= EQ::versions::ClientVersion::Laurion) { + auto outapp = new EQApplicationPacket(OP_SendMembership, sizeof(Membership_Struct)); + Membership_Struct* mc = (Membership_Struct*)outapp->pBuffer; + + mc->membership = 2; //Hardcode to gold for now. We don't use anything else. + mc->races = 0x1ffff; // Available Races (4110 for silver) + mc->classes = 0x1ffff; // Available Classes (4614 for silver) - Was 0x101ffff + mc->entrysize = 21; // Number of membership setting entries below + mc->entries[0] = 0xffffffff; // Max AA Restriction + mc->entries[1] = 0xffffffff; // Max Level Restriction + mc->entries[2] = 0xffffffff; // Max Char Slots per Account (not used by client?) + mc->entries[3] = 0xffffffff; // 1 for Silver + mc->entries[4] = 0xffffffff; // Main Inventory Size (0xffffffff on Live for Gold, but limiting to 8 until 10 is supported) + mc->entries[5] = 0xffffffff; // Max Platinum per level + mc->entries[6] = 1; // 0 for Silver + mc->entries[7] = 1; // 0 for Silver + mc->entries[8] = 1; // 1 for Silver + mc->entries[9] = 0xffffffff; // Unknown - Maybe Loyalty Points every 12 hours? 60 per week for Silver + mc->entries[10] = 1; // 1 for Silver + mc->entries[11] = 0xffffffff; // Shared Bank Slots + mc->entries[12] = 0xffffffff; // Unknown - Maybe Max Active Tasks? + mc->entries[13] = 1; // 1 for Silver + mc->entries[14] = 1; // 0 for Silver + mc->entries[15] = 1; // 0 for Silver + mc->entries[16] = 1; // 1 for Silver + mc->entries[17] = 1; // 0 for Silver + mc->entries[18] = 1; // 0 for Silver + mc->entries[19] = 0xffffffff; // 0 for Silver + mc->entries[20] = 0xffffffff; // 0 for Silver + mc->exit_url_length = 0; + //mc->exit_url = 0; // Used on Live: "http://www.everquest.com/free-to-play/exit-silver" + + QueuePacket(outapp); + safe_delete(outapp); + } +} + uint32 Client::GetMoney(uint8 type, uint8 subtype) { uint32 value = 0; diff --git a/zone/client.h b/zone/client.h index 9dc5960e7..18487e9e3 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1856,6 +1856,7 @@ public: void ResetHPUpdateTimer() { hpupdate_timer.Start(); } void SendHPUpdateMarquee(); + void SendMembership(); void CheckRegionTypeChanges(); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 87a4a4009..59fe844f0 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -105,7 +105,6 @@ void MapOpcodes() ConnectingOpcodes[OP_ZoneEntry] = &Client::Handle_Connect_OP_ZoneEntry; // connected opcode handler assignments: - ConnectedOpcodes[OP_0x0193] = &Client::Handle_0x0193; ConnectedOpcodes[OP_AAAction] = &Client::Handle_OP_AAAction; ConnectedOpcodes[OP_AcceptNewTask] = &Client::Handle_OP_AcceptNewTask; ConnectedOpcodes[OP_AdventureInfoRequest] = &Client::Handle_OP_AdventureInfoRequest; @@ -1704,10 +1703,8 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) if ((m_pp.RestTimer > RuleI(Character, RestRegenTimeToActivate)) && (m_pp.RestTimer > RuleI(Character, RestRegenRaidTimeToActivate))) m_pp.RestTimer = 0; - /* This checksum should disappear once dynamic structs are in... each struct strategy will do it */ // looks to be in place now - //CRC32::SetEQChecksum((unsigned char*)&m_pp, sizeof(PlayerProfile_Struct) - sizeof(m_pp.m_player_profile_version) - 4); - // m_pp.checksum = 0; // All server out-bound player profile packets are now translated - no need to waste cycles calculating this... - + SendMembership(); + outapp = new EQApplicationPacket(OP_PlayerProfile, sizeof(PlayerProfile_Struct)); /* The entityid field in the Player Profile is used by the Client in relation to Group Leadership AA */ @@ -1881,16 +1878,6 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) return; } -// connected opcode handlers -void Client::Handle_0x0193(const EQApplicationPacket *app) -{ - // Not sure what this opcode does. It started being sent when OP_ClientUpdate was - // changed to pump OP_ClientUpdate back out instead of OP_MobUpdate - // 2 bytes: 00 00 - - return; -} - void Client::Handle_OP_AAAction(const EQApplicationPacket *app) { LogAA("Received OP_AAAction"); @@ -4276,21 +4263,30 @@ void Client::Handle_OP_Camp(const EQApplicationPacket *app) if (IsLFP()) worldserver.StopLFP(CharacterID()); - - if (GetGM()) - { - if (RuleB(Character, EnableHackedFastCampForGM)) - { - camp_timer.Start(100, true); - } - else { - OnDisconnect(true); + + if (ClientVersion() >= EQ::versions::ClientVersion::SteamLatest) { + if (!GetGM()) { + camp_timer.Start(29000, true); } - return; + auto outapp = new EQApplicationPacket(OP_Camp, 1); + FastQueuePacket(&outapp); } + else { + if (GetGM()) + { + if (RuleB(Character, EnableHackedFastCampForGM)) + { + camp_timer.Start(100, true); + } + else { + OnDisconnect(true); + } + return; + } - camp_timer.Start(29000, true); + camp_timer.Start(29000, true); + } if (RuleB(Bots, Enabled)) { bot_camp_timer.Start((RuleI(Bots, CampTimer) * 1000), true); @@ -14568,7 +14564,10 @@ void Client::Handle_OP_ShopRequest(const EQApplicationPacket *app) mco->rate = 1 / buy_cost_mod; } - outapp->priority = 6; + if (m_ClientVersion >= EQ::versions::ClientVersion::SteamLatest) { + mco->player_id = GetID(); + } + QueuePacket(outapp); safe_delete(outapp); diff --git a/zone/client_packet.h b/zone/client_packet.h index 009dd75d6..0adb29fd1 100644 --- a/zone/client_packet.h +++ b/zone/client_packet.h @@ -38,8 +38,6 @@ void Handle_Connect_OP_ZoneComplete(const EQApplicationPacket *app); void Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app); /* Connected opcode handlers*/ - void Handle_0x0193(const EQApplicationPacket *app); - void Handle_0x01e7(const EQApplicationPacket *app); void Handle_OP_AAAction(const EQApplicationPacket *app); void Handle_OP_AcceptNewTask(const EQApplicationPacket *app); void Handle_OP_AdventureInfoRequest(const EQApplicationPacket *app); diff --git a/zone/command.h b/zone/command.h index 8fa473ba6..995efc5eb 100644 --- a/zone/command.h +++ b/zone/command.h @@ -25,6 +25,7 @@ class Client; class Seperator; #define COMMAND_CHAR '#' +#define COMMAND_CHAR_NON_HASH '$' typedef void (*CmdFuncPtr)(Client *, const Seperator *); diff --git a/zone/lua_packet.cpp b/zone/lua_packet.cpp index 8f231363e..3f0fc93dd 100644 --- a/zone/lua_packet.cpp +++ b/zone/lua_packet.cpp @@ -693,8 +693,6 @@ luabind::scope lua_register_packet_opcodes() { luabind::value("ShopEndConfirm", static_cast(OP_ShopEndConfirm)), luabind::value("AdventureMerchantRequest", static_cast(OP_AdventureMerchantRequest)), luabind::value("Sound", static_cast(OP_Sound)), - luabind::value("0x0193", static_cast(OP_0x0193)), - luabind::value("0x0347", static_cast(OP_0x0347)), luabind::value("WorldComplete", static_cast(OP_WorldComplete)), luabind::value("MobRename", static_cast(OP_MobRename)), luabind::value("TaskDescription", static_cast(OP_TaskDescription)), diff --git a/zone/mob.cpp b/zone/mob.cpp index d55fa4ab7..0b70e75a1 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -1284,6 +1284,14 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) strcpy(ns->spawn.name, name); if(IsClient()) { strn0cpy(ns->spawn.lastName, lastname, sizeof(ns->spawn.lastName)); + ns->spawn.CharacterGuid.Id = CastToClient()->CharacterID(); + ns->spawn.CharacterGuid.WorldId = RuleI(World, Id); + ns->spawn.CharacterGuid.Reserved = 0; + } + else { + ns->spawn.CharacterGuid.Id = 0; + ns->spawn.CharacterGuid.WorldId = 0; + ns->spawn.CharacterGuid.Reserved = 0; } ns->spawn.heading = FloatToEQ12(m_Position.w);