From 485ae4809d010c416567890ae9c3a1f3d4b95977 Mon Sep 17 00:00:00 2001 From: KimLS Date: Mon, 30 Mar 2026 18:36:13 -0700 Subject: [PATCH] some changes, working on login --- loginserver/client.cpp | 106 +++-- loginserver/login_types.h | 15 + obr/packets/login/LoginAcceptedDecrypted.txt | 28 +- obr/packets/zone/OP_PlayerProfile.txt | 434 ++++++++++++++++++ obr/scripts/opcode.patch.py | 28 ++ obr/scripts/opcode.template | 0 .../OpCodes.txt => scripts/opcodes.csv} | 0 7 files changed, 564 insertions(+), 47 deletions(-) create mode 100644 obr/packets/zone/OP_PlayerProfile.txt create mode 100644 obr/scripts/opcode.patch.py create mode 100644 obr/scripts/opcode.template rename obr/{packets/OpCodes.txt => scripts/opcodes.csv} (100%) diff --git a/loginserver/client.cpp b/loginserver/client.cpp index 97a885239..3afa3211d 100644 --- a/loginserver/client.cpp +++ b/loginserver/client.cpp @@ -452,46 +452,86 @@ void Client::DoSuccessfulLogin(LoginAccountsRepository::LoginAccounts &a) m_account_name = a.account_name; m_loginserver_name = a.source_loginserver; - // unencrypted - LoginBaseMessage h{}; - h.sequence = m_login_base_message.sequence; - h.compressed = false; - h.encrypt_type = m_login_base_message.encrypt_type; - h.unk3 = m_login_base_message.unk3; + if (m_client_version == cv_steam_latest) { + // unencrypted + LoginBaseMessage h{}; + h.sequence = m_login_base_message.sequence; + h.compressed = false; + h.encrypt_type = m_login_base_message.encrypt_type; + h.unk3 = m_login_base_message.unk3; - // not serializing any of the variable length strings so just use struct directly - PlayerLoginReply r{}; - r.base_reply.success = true; - r.base_reply.error_str_id = 101; // No Error - r.unk1 = 0; - r.unk2 = 0; - r.lsid = a.id; - r.failed_attempts = 0; - r.show_player_count = server.options.IsShowPlayerCountEnabled(); - r.offer_min_days = 99; - r.offer_min_views = -1; - r.offer_cooldown_minutes = 0; - r.web_offer_number = 0; - r.web_offer_min_days = 99; - r.web_offer_min_views = -1; - r.web_offer_cooldown_minutes = 0; - memcpy(r.key, m_key.c_str(), m_key.size()); + // not serializing any of the variable length strings so just use struct directly + PlayerLoginReplySteamLatest r{}; + r.base_reply.success = true; + r.base_reply.error_str_id = 101; // No Error + r.unk1 = 0; + r.unk2 = 0; + r.lsid = a.id; + r.failed_attempts = 0; + r.show_player_count = server.options.IsShowPlayerCountEnabled(); + r.unk3 = 0; + r.unk4 = 0; + memcpy(r.key, m_key.c_str(), m_key.size()); - SendExpansionPacketData(r); + //todo: needs to be fixed + //SendExpansionPacketData(r); - char encrypted_buffer[80] = {0}; + char encrypted_buffer[80] = { 0 }; - auto rc = eqcrypt_block((const char *) &r, sizeof(r), encrypted_buffer, 1); - if (rc == nullptr) { - LogDebug("Failed to encrypt eqcrypt block"); + auto rc = eqcrypt_block((const char*)&r, sizeof(r), encrypted_buffer, 1); + if (rc == nullptr) { + LogDebug("Failed to encrypt eqcrypt block"); + } + + constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer); + auto outapp = std::make_unique(OP_LoginAccepted, outsize); + outapp->WriteData(&h, sizeof(h)); + outapp->WriteData(&encrypted_buffer, sizeof(encrypted_buffer)); + + m_connection->QueuePacket(outapp.get()); } + else { + // unencrypted + LoginBaseMessage h{}; + h.sequence = m_login_base_message.sequence; + h.compressed = false; + h.encrypt_type = m_login_base_message.encrypt_type; + h.unk3 = m_login_base_message.unk3; - constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer); - auto outapp = std::make_unique(OP_LoginAccepted, outsize); - outapp->WriteData(&h, sizeof(h)); - outapp->WriteData(&encrypted_buffer, sizeof(encrypted_buffer)); + // not serializing any of the variable length strings so just use struct directly + PlayerLoginReply r{}; + r.base_reply.success = true; + r.base_reply.error_str_id = 101; // No Error + r.unk1 = 0; + r.unk2 = 0; + r.lsid = a.id; + r.failed_attempts = 0; + r.show_player_count = server.options.IsShowPlayerCountEnabled(); + r.offer_min_days = 99; + r.offer_min_views = -1; + r.offer_cooldown_minutes = 0; + r.web_offer_number = 0; + r.web_offer_min_days = 99; + r.web_offer_min_views = -1; + r.web_offer_cooldown_minutes = 0; + memcpy(r.key, m_key.c_str(), m_key.size()); - m_connection->QueuePacket(outapp.get()); + SendExpansionPacketData(r); + + char encrypted_buffer[80] = { 0 }; + + auto rc = eqcrypt_block((const char*)&r, sizeof(r), encrypted_buffer, 1); + if (rc == nullptr) { + LogDebug("Failed to encrypt eqcrypt block"); + } + + constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer); + auto outapp = std::make_unique(OP_LoginAccepted, outsize); + outapp->WriteData(&h, sizeof(h)); + outapp->WriteData(&encrypted_buffer, sizeof(encrypted_buffer)); + + m_connection->QueuePacket(outapp.get()); + } m_client_status = cs_logged_in; } diff --git a/loginserver/login_types.h b/loginserver/login_types.h index a623ca42b..0a91ade60 100644 --- a/loginserver/login_types.h +++ b/loginserver/login_types.h @@ -50,6 +50,21 @@ struct PlayerLoginReply { char unknown[1]; // variable length, password unlikely? client doesn't send this on re-login from char select }; +struct PlayerLoginReplySteamLatest +{ + LoginBaseReplyMessage base_reply; + int8_t unk1; // (default: 0) + int8_t unk2; // (default: 0) + int32_t lsid; // (default: -1) + char key[11]; // client reads until null (variable length) + int32_t failed_attempts; + bool show_player_count; // admin flag, enables admin button and shows server player counts (default: false) + int32_t unk3; // guess, needs more investigation (default: 0) + int32_t unk4; // guess, needs more investigation (default: 0) + char username[1]; // variable length, if not empty client attempts to re-login to server select when quitting from char select and sends this in a struct + char unknown[1]; // variable length, password unlikely? client doesn't send this on re-login from char select +}; + // variable length, for reference struct LoginClientServerData { char ip[1]; diff --git a/obr/packets/login/LoginAcceptedDecrypted.txt b/obr/packets/login/LoginAcceptedDecrypted.txt index 8a5809c75..b834df326 100644 --- a/obr/packets/login/LoginAcceptedDecrypted.txt +++ b/obr/packets/login/LoginAcceptedDecrypted.txt @@ -1,23 +1,23 @@ -struct LoginBaseReply +struct BaseResponse { u8 success; - s32 error_string_id; - char error_string[]; + u32 error_str_id; + char error_str[]; }; -struct Packet -{ - LoginBaseReply reply; - u8 unknown1; - u8 unknown2; - u32 loginserver_id; - char key[11]; - u32 failed_attempts; +struct Packet { + BaseResponse base; + s8 unk1; //I think this is just padding + s8 unk2; //I think this is just padding + u32 lsid; + char key[]; + s32 failed_attempts; u8 show_player_count; - u32 offer_min_days; - s32 offer_min_views; + s32 unk3; // 0 + s32 unk4; // 0 char username[]; - char unknown[]; + char password[]; //I'm not sure this is correct, it feels like this might be some internal refresh token + char paddingEnd[2]; }; Packet p @ 0x00; \ No newline at end of file diff --git a/obr/packets/zone/OP_PlayerProfile.txt b/obr/packets/zone/OP_PlayerProfile.txt new file mode 100644 index 000000000..70bf426cc --- /dev/null +++ b/obr/packets/zone/OP_PlayerProfile.txt @@ -0,0 +1,434 @@ +struct Bind { + u32 zoneid; + float x; + float y; + float z; + float heading; +}; + +struct ArmorProperty +{ + s32 type; + s32 variation; + s32 material; + s32 newArmorId; + s32 newArmorType; +}; + +struct AA +{ + s32 index; + s32 points_spent; + s32 charges_spent; +}; + +struct EQGuid +{ + u32 entity_id; + u32 realm_id; +}; + +struct SlotData +{ + s32 slot_id; + s64 value; +}; + +struct EQAffect +{ + float modifier; + EQGuid caster_id; + u32 duration; + u32 max_duration; + u8 level; + s32 spell_id; + s32 hitcount; + u32 flags; + u32 viral_timer; + u8 type; + SlotData slots[6]; +}; + +struct Coin +{ + u32 platinum; + u32 gold; + u32 silver; + u32 copper; +}; + +struct BandolierItemInfo { + char name[]; + s32 item_id; + s32 icon; +}; + +struct BandolierSet +{ + char name[]; + BandolierItemInfo items[4]; +}; + +struct ItemIndex +{ + s16 slot1; + s16 slot2; + s16 slot3; +}; + +struct Claim +{ + s32 feature_id; + s32 count; +}; + +struct 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; +}; + +struct TributeBenefit +{ + s32 benefit_id; + s32 benefit_tier; +}; + +struct RaidData +{ + 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; +}; + +struct LdonData +{ + u32 count; + u32 ldon_categories[count]; + u32 ldon_points_available; +}; + +struct PvPData +{ + u32 kills; + u32 deaths; + u32 current_points; + u32 career_points; + u32 best_kill_streak; + u32 worst_death_streak; + u32 current_kill_streak; +}; + +struct PvPKill +{ + char name[]; + u32 level; + u32 unknown1; //not sure + u32 unknown2; //not sure + u32 race; + u32 class; + u32 zone; + u32 time; + u32 points; +}; + +struct PvPDeath +{ + char name[]; + u32 level; + u32 race; + u32 class; + u32 zone; + u32 time; + u32 points; +}; + +struct AltCurrency +{ + u32 alt_currency_str_length; + u32 unknown1; + char alt_currency_string[alt_currency_str_length]; +}; + +struct AchivementSubComponentData +{ + s32 achievement_id; + s32 component_id; + s32 requirement_id; + s32 requirement_type; + s32 count; +}; + +struct AlchemyBonusSkillData +{ + s32 skill_id; + s32 bonus; +}; + +struct PersonaItemSlot +{ + u32 item_id; + u32 slot_id; +}; + +struct PersonaEquipment +{ + PersonaItemSlot item; + u32 augment_count; + PersonaItemSlot augments[augment_count]; +}; + +struct PersonaEquipmentSet +{ + u32 class_id; + u32 equipment_count; + PersonaEquipment equipment[equipment_count]; +}; + +struct PcProfile +{ + u32 profile_type; + u32 profile_id; + u32 shroud_template_id; + u8 gender; + u32 race; + u32 class; + u8 level; + u8 level1; + u32 bind_count; + Bind binds[bind_count]; + u32 deity; + u32 intoxication; + u32 property_count; + u32 properties[property_count]; + u32 armor_prop_count; + ArmorProperty armor_props[armor_prop_count]; + u32 base_armor_prop_count; + ArmorProperty base_armor_props[base_armor_prop_count]; + u32 body_tint_count; + u32 body_tints[body_tint_count]; + u32 equip_tint_count; + u32 equip_tints[equip_tint_count]; + 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; + u8 texture_type; + u8 material; + u8 variation; + float height; + float width; + float length; + float view_height; + u32 primary; + u32 secondary; + 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; + u32 aa_count; + AA aas[aa_count]; + u32 skill_count; + s32 skills[skill_count]; + u32 innate_skill_count; + s32 innate_skills[innate_skill_count]; + u32 combat_ability_count; + s32 combat_abilities[combat_ability_count]; + u32 combat_ability_timer_count; + s32 combat_ability_timers[combat_ability_timer_count]; + u32 unk_ability_count; + u32 linked_spell_timer_count; + s32 linked_spell_timers[linked_spell_timer_count]; + u32 item_recast_timer_count; + s32 item_recast_timers[item_recast_timer_count]; + u32 spell_book_slot_count; + s32 spell_book_slots[spell_book_slot_count]; + u32 spell_gem_count; + s32 spell_gems[spell_gem_count]; + u32 spell_recast_timer_count; + s32 spell_recast_timers[spell_recast_timer_count]; + u8 max_allowed_spell_slots; + u32 buff_count; + EQAffect buffs[buff_count]; + Coin coin; + Coin cursor_coin; + u32 disc_timer; + u32 mend_timer; + u32 forage_timer; + u32 thirst; + u32 hunger; + u32 aa_spent; + u32 aa_window_count; + u32 aa_window_stats[aa_window_count]; + u32 aa_points_unspent; + u8 sneak; + u8 hide; + u32 bandolier_count; + BandolierSet bandolier_sets[bandolier_count]; + u32 invslot_bitmask; + 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; + float basedata_walkspeed; + float basedata_runspeed; + 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; + ItemIndex keyring_item_index[5]; + u64 exp; + u64 aa_exp; //this is a guess, used to be 32 upped to 64 + u16 unknown1; + EQGuid character_id; + u32 name_length; + char name[name_length]; + u32 last_name_length; + char last_name[last_name_length]; + u32 creation_time; + u32 account_creation_time; + u32 last_played_time; + u32 played_minutes; + u32 entitled_days; + u32 expansion_flags; + u32 unknown2; //new field from laurion to obrood + u32 language_count; + u8 languages[language_count]; + u32 current_zone; + float current_x; + float current_y; + float current_z; + float current_heading; + u8 animation; + u8 pvp; + u8 anon; + u8 gm; + u64 guild_id; + u8 guild_show_sprite; + u8 status; + Coin coin2; + Coin bank2; + u32 bank_shared_plat; + u32 claim_count; + Claim claims[claim_count]; + Tribute tribute; + u32 tribute_benefit_count; + TributeBenefit tribute_benefits[tribute_benefit_count]; + u32 trophy_tribute_benefit_count; + TributeBenefit trophy_tribute_benefit[trophy_tribute_benefit_count]; + u8 tasks[137]; //honestly not sure what this is, was just a guess + u32 good_points_available; + u32 good_points_earned; + u32 bad_points_available; + u32 bad_points_earned; + u32 momentum_balance; + u32 loyalty_reward_balance; + u32 parcel_status; + u32 vehicle_name_length; + char vehicle_name[vehicle_name_length]; + u8 super_pkill; + u8 unclone; + u8 dead; + u32 ld_timer; + u32 spell_interrupt_count; + u8 autosplit; + u8 tells_off; + u8 gm_invis; + u32 kill_me; + u8 cheater_ld_flag; + u8 norent; + u8 corpse; + u8 client_gm_flag_set; + u32 mentor_pct; + RaidData raid; + u32 unique_player_id; + LdonData ldon_data; + u32 air_supply; + PvPData pvp_data; + PvPKill last_kill; + PvPDeath last_death; + u32 kills_in_past_24_hours; + u32 kill_list_count; + PvPKill kill_list[kill_list_count]; + u32 pvp_infamy_level; + u32 pvp_vitality; + u32 cursor_krono; + u32 krono; + u8 autoconsent_group; + u8 autoconsent_raid; + u8 autoconsent_guild; + u8 autoconsent_fellowship; + u8 private_for_eq_players; + u32 main_level; + u8 show_helm; + u32 downtime; + AltCurrency alt_currency; + u32 completed_event_subcomponent_count; + AchivementSubComponentData completed_event_subcomponents[completed_event_subcomponent_count]; + u32 inprogress_event_subcomponent_count; + AchivementSubComponentData inprogress_event_subcomponents[inprogress_event_subcomponent_count]; + u64 merc_aa_exp; + u32 merc_aa_points; + u32 merc_aa_spent; + u32 starting_city_zone_id; + u8 use_advanced_looting; + u8 is_master_loot_candidate; + u32 alchemy_bonus_list_count; + AlchemyBonusSkillData alchemy_bonus_list[alchemy_bonus_list_count]; + u32 persona_count; + PersonaEquipmentSet persona_equipment_set[persona_count]; + u8 term; +}; + +struct Packet +{ + u32 crc; + u32 length; + PcProfile profile; +}; + +Packet p @ 0x00; diff --git a/obr/scripts/opcode.patch.py b/obr/scripts/opcode.patch.py new file mode 100644 index 000000000..21867556e --- /dev/null +++ b/obr/scripts/opcode.patch.py @@ -0,0 +1,28 @@ +def patch_template(template_path, opcodes_path, output_path): + try: + with open(opcodes_path, 'r') as f: + opcodes = [line.strip() for line in f if line.strip()] + + with open(template_path, 'r') as f: + content = f.read() + + for index, value in enumerate(opcodes): + placeholder = f"{{{{{index}}}}}" + content = content.replace(placeholder, value) + + with open(output_path, 'w') as f: + f.write(content) + + print(f"Successfully transformed: {output_path}") + + except FileNotFoundError as e: + print(f"Error: File Not Found - {e}") + except Exception as e: + print(f"Error: Exception - {e}") + +if __name__ == "__main__": + patch_template( + template_path='opcode.template', + opcodes_path='opcodes.csv', + output_path='patch_SteamLatest.conf' + ) \ No newline at end of file diff --git a/obr/scripts/opcode.template b/obr/scripts/opcode.template new file mode 100644 index 000000000..e69de29bb diff --git a/obr/packets/OpCodes.txt b/obr/scripts/opcodes.csv similarity index 100% rename from obr/packets/OpCodes.txt rename to obr/scripts/opcodes.csv