Implement 40250 classic client port

This commit is contained in:
shenlei
2026-09-01 18:49:28 +09:00
parent 2277b1dd60
commit 8f61990a83
481 changed files with 169826 additions and 293 deletions
+169
View File
@@ -0,0 +1,169 @@
#pragma once
// ClassicSession — MT_PROTOCOL=classic: drives ClassicStream through
// HANDSHAKE -> LOGIN -> SELECT -> LOADING -> GAME and parses GC packets into a
// shared EntityStore. See docs/CLIENT-40250-PORT.md §4 / §8.
//
// The stock 40250 server uses _IMPROVED_PACKET_ENCRYPTION_; ClassicStream
// negotiates that DH2 + CTR session before the first login packet.
#include "classic_parser.h"
#include "classic_stream.h"
#include "wire_classic.h"
#include "../entity_store.h"
#include "../i_net_session.h"
#include <cstddef>
#include <cstdint>
#include <functional>
#include <string>
namespace mtnet::classic {
class ClassicSession : public mtnet::INetSession {
public:
ClassicSession();
// --- INetSession ---
bool connect(const std::string &game_host, uint16_t game_port, const std::string &id,
const std::string &pw) override;
void disconnect() override;
bool select_char(int slot) override;
bool enter_game() override;
bool create_character(int slot, const std::string &name, int job, int shape,
int con, int intel, int str, int dex);
bool delete_character(int slot, const std::string &private_code);
bool change_name(int slot, const std::string &name);
bool send_empire(uint8_t empire);
void pump() override;
Stage stage() const override { return m_stage; }
const std::string &last_error() const override { return m_last_error; }
EntityStore &world() override { return m_world; }
bool send_move(uint8_t func, uint8_t arg, float rot_deg, int32_t x, int32_t y,
uint32_t time_ms = 0) override;
bool send_attack(uint8_t type, uint32_t victim_vid) override;
bool send_chat(uint8_t type, const std::string &text) override;
bool send_target(uint32_t vid) override;
bool send_character_position(uint8_t position) override;
bool send_click_npc(uint32_t vid) override;
bool send_script_answer(uint8_t answer) override;
bool send_item_use(uint8_t window, uint16_t cell) override;
bool send_item_move(uint8_t window, uint16_t cell, uint8_t to_window, uint16_t to_cell,
uint8_t count) override;
bool send_item_drop(uint8_t window, uint16_t cell, uint32_t gold, uint8_t count) override;
bool send_item_pickup(uint32_t ground_vid) override;
bool send_use_skill(uint32_t skill_vnum, uint32_t target_vid) override;
bool send_item_use_to_item(uint8_t window, uint16_t cell, uint8_t target_window,
uint16_t target_cell);
bool send_item_give(uint32_t target_vid, uint8_t window, uint16_t cell, uint8_t count);
bool send_request_warp();
bool send_fishing(uint8_t rot);
bool send_dungeon();
bool send_sync_positions(const std::vector<SyncPosElement> &positions);
bool send_whisper(const std::string &to, const std::string &text);
bool send_shoot(uint8_t type);
bool send_add_fly_targeting(uint32_t target_vid, int32_t x, int32_t y);
bool send_quickslot_add(uint8_t pos, uint8_t type, uint8_t ref);
bool send_quickslot_del(uint8_t pos);
bool send_quickslot_swap(uint8_t pos, uint8_t pos_to);
bool send_script_button(uint32_t index);
bool send_script_select_item(uint32_t selection);
bool send_quest_input(const std::string &text);
bool send_quest_confirm(bool yes, uint32_t request_pid);
bool send_quest_cancel();
bool send_party_invite(uint32_t vid);
bool send_party_answer(uint32_t leader_vid, bool accept);
bool send_party_remove(uint32_t pid);
bool send_party_set_state(uint32_t pid, uint8_t role, bool on);
bool send_party_use_skill(uint8_t skill_index, uint32_t vid);
bool send_party_parameter(uint8_t mode);
bool send_shop_buy(uint8_t pos, uint8_t count);
bool send_shop_sell(uint8_t pos, uint8_t count);
bool send_shop_close();
bool send_private_shop(const std::string &sign, const std::vector<CGMyShopItem> &items);
bool send_cube_make(int result_index);
bool send_cube_result_list(int npc_vnum);
bool send_cube_materials(int start_index, int count);
bool send_cube_open();
bool send_cube_close();
bool send_cube_list();
bool send_cube_add_item(int cube_index, int inventory_index);
bool send_cube_delete_item(int cube_index);
bool send_guild_sub(uint8_t subheader);
bool send_guild_u32(uint8_t subheader, uint32_t value);
bool send_guild_i32(uint8_t subheader, int32_t value);
bool send_guild_grade_name(uint8_t grade, const std::string &name);
bool send_guild_grade_authority(uint8_t grade, uint8_t authority);
bool send_guild_member_grade(uint32_t pid, uint8_t grade);
bool send_guild_member_general(uint32_t pid, bool enabled);
bool send_guild_comment(const std::string &text);
bool send_guild_invite_answer(uint32_t guild_id, bool accept);
bool send_guild_answer_make(const std::string &name);
bool send_guild_skill(uint32_t skill_vnum, uint32_t target_pid);
bool send_refine(uint8_t pos, uint8_t type);
bool send_dragon_soul_refine(uint8_t subheader, const ItemPos *grid, size_t count);
bool send_friend_add(const std::string &name) override;
bool send_friend_remove(const std::string &name) override;
bool send_exchange_start(uint32_t partner_vid) override;
bool send_exchange_item_add(uint8_t inv_window, uint16_t inv_cell,
uint8_t display_pos) override;
bool send_exchange_gold(uint32_t gold) override;
bool send_exchange_accept() override;
bool send_exchange_cancel() override;
bool send_safebox_checkin(uint8_t safe_pos, uint8_t inv_window,
uint16_t inv_cell) override;
bool send_safebox_checkout(uint8_t safe_pos, uint8_t inv_window,
uint16_t inv_cell) override;
bool send_safebox_move(uint16_t from_cell, uint16_t to_cell, uint8_t count) override;
bool send_mall_checkout(uint8_t mall_pos, uint8_t inv_window,
uint16_t inv_cell) override;
// --- callbacks ---
std::function<void(const std::vector<CharSlot> &)> on_char_list;
std::function<void()> on_entered_game;
std::function<void(Stage)> on_stage_change;
std::function<void(const std::string &)> on_error;
// Set up creds + state WITHOUT opening a socket — feed the stream by hand via
// stream().feed()/take_outgoing(). Used by the offline flow test and any
// non-TCP transport. connect() == start_offline() + m_stream.connect().
void start_offline(const std::string &id, const std::string &pw);
// --- accessors ---
const std::vector<CharSlot> &char_slots() const { return m_parser.char_slots(); }
ClassicStream &stream() { return m_stream; } // for tests / trace
ClassicParser &parser() { return m_parser; }
uint32_t now_ms() const { return m_now; }
void set_now(uint32_t ms) {
m_now = ms;
m_world.set_now(ms);
}
// auto-send CG_ENTERGAME this many ms after entering PHASE_LOADING (0 = never,
// caller drives enter_game()). Default mirrors the m2dev client's ~1.5s.
void set_auto_entergame_delay(uint32_t ms) { m_entergame_delay = ms; }
void set_wire_trace(bool on) { m_stream.set_wire_trace(on); }
private:
void set_stage(Stage s);
void on_phase(uint8_t phase);
bool on_packet(uint8_t header, const uint8_t *body, uint32_t len);
void send_login();
bool send_client_version();
EntityStore m_world;
ClassicStream m_stream;
ClassicParser m_parser;
Stage m_stage = Stage::Offline;
std::string m_last_error;
std::string m_id, m_pw;
uint32_t m_now = 0;
uint32_t m_loading_since = 0;
uint32_t m_entergame_delay = 1500;
bool m_entergame_sent = false;
bool m_login_sent = false;
bool m_version_sent = false;
};
} // namespace mtnet::classic