- 装备校验:can_equip 按 CanEquipNow→FindEquipCell→EquipItem 精确重排, 新增 find_equip_cell() 1:1 复刻(RING/UNIQUE 占用翻转、COSTUME/BELT/DS), 帝国 antiflag 降级为 tooltip 提示,LIMIT_PCBANG/REAL_TIME* 语义与倒计时文案 - 快捷栏:死亡全槽禁用+激活守卫、变身(affect 220)技能置灰、 物品槽失配置灰(服务器回收/换武器后刷新) - 公会:GUILD_AUTH_* 权限位门控踢人/公告/技能升级,/gskillup、 被宣战应答弹窗(/war·/nowar)、资金存取行(40250 服务端已禁用仍保留 1:1) - 好友/情侣:messenger 三固定组(好友/公会/情侣),lover_login/logout/ near/far/divorce 命令事件→在线/在身边状态与离婚隐藏 - 交易/仓库:ExchangeState.last_notice(ALREADY/LESS_GOLD)结束浮层提示; 仓库改密 /safebox_change_password 三段输入;关窗补 /safebox_close·/mall_close; 仓库金钱经核实 40250 无 wire 协议,UI 定界只读 - app_flow:重连时 guild_marks_ready 重复连接加 is_connected 守卫 - 相机 look_at 零向量守卫(首帧/传送落点) - 测试:equip/love/guild 断言更新,79 项中 78 绿(game_camera_test 为 HEAD 既有失败);真服冒烟 LIVE_SMOKE PASS(192.168.21.203 全链路) - docs/CLIENT-GAP.md 同步增量 129 Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018kUFvQGYusuY3dkfGitmAe
1511 lines
56 KiB
C++
1511 lines
56 KiB
C++
#pragma once
|
|
// 40250 "classic" Metin2 wire format — the switchable `MT_PROTOCOL=classic`
|
|
// backend (see docs/CLIENT-40250-PORT.md). This is NOT the m2dev fork protocol
|
|
// (that stays in ../wire.h).
|
|
//
|
|
// Framing (docs §2.2):
|
|
// recv: read 1 byte header -> look up static size (packet_size) -> read body.
|
|
// dynamic-size packets carry a `uint16 size` right after the header.
|
|
// send: most CG game packets append 1 trailing `uint8 sequence` byte once
|
|
// sequence mode is on (is_sequence()).
|
|
//
|
|
// Values + struct layouts verified against the 40250 sources:
|
|
// Server/metin2/src/server/game/src/packet.h (header enum, EPhase, wire structs)
|
|
// Server/metin2/src/server/game/src/packet_info.cpp (CG size table + bSeq flags)
|
|
// Server/metin2/src/server/common/{tables.h,length.h}
|
|
// ClientVS22/source/UserInterface/{Packet.h,PythonNetworkStream.cpp} (GC size/dynamic map)
|
|
// ClientVS22/source/EterBase/cipher.{h,cpp}
|
|
//
|
|
// The 40250 client Packet.h header enum is #if defined(GAIDEN)-gated and the
|
|
// shipped client build does NOT define GAIDEN; where client/server names differ
|
|
// for the same value we follow the SERVER packet.h name (it is the wire value).
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
|
|
// §1.10: every fixed-length `char name[...]` / sign / comment field below is
|
|
// filled at the call site via mtnet::to_wire(src, cap) (byte cap, no split
|
|
// multibyte char) and read back via mtnet::from_wire_str().
|
|
#include "../text_codec.h"
|
|
|
|
namespace mtnet::classic {
|
|
|
|
// ------------------------------------------------------------------ constants
|
|
inline constexpr int CHARACTER_NAME_MAX_LEN = 24; // common/length.h -> name[25]
|
|
inline constexpr int GUILD_NAME_MAX_LEN = 12; // -> guild_name[13]
|
|
inline constexpr int GUILD_GRADE_NAME_MAX_LEN = 8;
|
|
inline constexpr int GUILD_COMMENT_MAX_LEN = 50;
|
|
inline constexpr int GUILD_SKILL_MAX_NUM = 12;
|
|
inline constexpr int PLAYER_PER_ACCOUNT = 4; // common/length.h
|
|
inline constexpr int PLAYER_PER_ACCOUNT3 = 3; // legacy GC_LOGIN_SUCCESS (hdr 6)
|
|
inline constexpr int ID_MAX_NUM = 30; // Packet.h -> login[31]
|
|
inline constexpr int PASS_MAX_NUM = 16; // -> passwd[17]
|
|
inline constexpr int PRIVATE_CODE_LENGTH = 8;
|
|
inline constexpr int POINT_MAX_NUM = 255; // common/length.h:58
|
|
inline constexpr int CHR_EQUIPPART_NUM = 4; // ARMOR/WEAPON/HEAD/HAIR
|
|
inline constexpr int LOGIN_STATUS_MAX_LEN = 8; // -> szStatus[9]
|
|
inline constexpr int KEY_AGREEMENT_MAX_DATA_LEN = 256;
|
|
inline constexpr int ITEM_SOCKET_MAX_NUM = 3; // common/item_length.h
|
|
inline constexpr int ITEM_ATTRIBUTE_MAX_NUM = 7;
|
|
inline constexpr int SKILL_MAX_NUM = 255; // common/length.h
|
|
inline constexpr int QUICKSLOT_MAX_NUM = 36;
|
|
inline constexpr int SHOP_HOST_ITEM_MAX_NUM = 40;
|
|
inline constexpr int SHOP_TAB_NAME_MAX = 32;
|
|
inline constexpr int SHOP_SIGN_MAX_LEN = 32;
|
|
inline constexpr int SHOP_TAB_COUNT_MAX = 3;
|
|
inline constexpr int GUILD_MARK_WIDTH = 16;
|
|
inline constexpr int GUILD_MARK_HEIGHT = 12;
|
|
inline constexpr int MARK_BLOCK_TOTAL_COUNT = 80;
|
|
|
|
// CHRTYPE_* — the domain of Entity::ch_type and packet_add_char.bType
|
|
// (client GameType.h / server char.h). Values line up with the Entity comment.
|
|
enum : uint8_t {
|
|
CHRTYPE_PC = 0,
|
|
CHRTYPE_NPC = 1,
|
|
CHRTYPE_MONSTER = 2,
|
|
CHRTYPE_STONE = 3,
|
|
CHRTYPE_WARP = 4,
|
|
CHRTYPE_DOOR = 5,
|
|
CHRTYPE_BUILDING = 6,
|
|
CHRTYPE_PET = 7,
|
|
CHRTYPE_HORSE = 8,
|
|
CHRTYPE_GOTO = 9,
|
|
};
|
|
|
|
// Races the reference client never spawns — REF/UserInterface/
|
|
// PythonNetworkStreamPhaseGameActor.cpp:66 IsInvisibleRace(). GC_CHARACTER_ADD
|
|
// for one of these is dropped before any pending/stash step.
|
|
inline constexpr bool is_invisible_race(uint16_t race) {
|
|
return race == 20025 || race == 20038 || race == 20039;
|
|
}
|
|
|
|
// EPacketShopSubHeaders (client Packet.h L1864)
|
|
enum : uint8_t {
|
|
SHOP_SUB_START = 0,
|
|
SHOP_SUB_END = 1,
|
|
SHOP_SUB_UPDATE_ITEM = 2,
|
|
SHOP_SUB_UPDATE_PRICE = 3,
|
|
SHOP_SUB_OK = 4,
|
|
SHOP_SUB_NOT_ENOUGH_MONEY = 5,
|
|
SHOP_SUB_SOLDOUT = 6,
|
|
SHOP_SUB_INVENTORY_FULL = 7,
|
|
SHOP_SUB_INVALID_POS = 8,
|
|
SHOP_SUB_SOLD_OUT = 9,
|
|
SHOP_SUB_START_EX = 10,
|
|
SHOP_SUB_NOT_ENOUGH_MONEY_EX = 11,
|
|
};
|
|
|
|
// ------------------------------------------------------------------ EPhase
|
|
// Server enum EPhase (packet.h:850). Only 0..6 reach the client via GC_PHASE.
|
|
enum Phase : uint8_t {
|
|
PHASE_CLOSE = 0,
|
|
PHASE_HANDSHAKE = 1,
|
|
PHASE_LOGIN = 2,
|
|
PHASE_SELECT = 3,
|
|
PHASE_LOADING = 4,
|
|
PHASE_GAME = 5,
|
|
PHASE_DEAD = 6,
|
|
PHASE_CLIENT_CONNECTING = 7,
|
|
PHASE_DBCLIENT = 8,
|
|
PHASE_P2P = 9,
|
|
PHASE_AUTH = 10,
|
|
PHASE_TEEN = 11,
|
|
PHASE_PASSPOD = 12,
|
|
};
|
|
|
|
// ------------------------------------------------------------------ headers
|
|
// Control plane (two-directional). Decimal values shown for the 0xF- block.
|
|
enum : uint8_t {
|
|
HDR_HANDSHAKE = 0xFF, // GC + CG
|
|
HDR_GC_BINDUDP = 0xFE,
|
|
HDR_CG_PONG = 0xFE,
|
|
HDR_GC_PHASE = 0xFD,
|
|
HDR_CG_CLIENT_VERSION = 0xFD,
|
|
HDR_GC_TIME_SYNC = 0xFC, // client name: HEADER_GC_HANDSHAKE_OK
|
|
HDR_CG_TIME_SYNC = 0xFC,
|
|
HDR_KEY_AGREEMENT = 0xFB, // GC + CG
|
|
HDR_GC_KEY_AGREEMENT_COMPLETED = 0xFA,
|
|
HDR_CG_CLIENT_VERSION2 = 0xF1,
|
|
};
|
|
|
|
// CG (client -> server). Server packet.h.
|
|
enum : uint8_t {
|
|
HDR_CG_LOGIN = 1,
|
|
HDR_CG_ATTACK = 2,
|
|
HDR_CG_CHAT = 3,
|
|
HDR_CG_CHARACTER_CREATE = 4,
|
|
HDR_CG_CHARACTER_DELETE = 5,
|
|
HDR_CG_CHARACTER_SELECT = 6,
|
|
HDR_CG_MOVE = 7,
|
|
HDR_CG_SYNC_POSITION = 8,
|
|
HDR_CG_ENTERGAME = 10,
|
|
HDR_CG_ITEM_USE = 11,
|
|
HDR_CG_ITEM_DROP = 12,
|
|
HDR_CG_ITEM_MOVE = 13,
|
|
HDR_CG_ITEM_PICKUP = 15,
|
|
HDR_CG_QUICKSLOT_ADD = 16,
|
|
HDR_CG_QUICKSLOT_DEL = 17,
|
|
HDR_CG_QUICKSLOT_SWAP = 18,
|
|
HDR_CG_WHISPER = 19,
|
|
HDR_CG_ITEM_DROP2 = 20,
|
|
HDR_CG_ON_CLICK = 26,
|
|
HDR_CG_EXCHANGE = 27,
|
|
HDR_CG_CHARACTER_POSITION = 28,
|
|
HDR_CG_SCRIPT_ANSWER = 29,
|
|
HDR_CG_QUEST_INPUT_STRING = 30,
|
|
HDR_CG_QUEST_CONFIRM = 31,
|
|
HDR_CG_SHOP = 50,
|
|
HDR_CG_FLY_TARGETING = 51,
|
|
HDR_CG_USE_SKILL = 52,
|
|
HDR_CG_ADD_FLY_TARGETING = 53,
|
|
HDR_CG_SHOOT = 54,
|
|
HDR_CG_MYSHOP = 55,
|
|
HDR_CG_ITEM_USE_TO_ITEM = 60,
|
|
HDR_CG_TARGET = 61,
|
|
HDR_CG_TEXT = 64,
|
|
HDR_CG_WARP = 65,
|
|
HDR_CG_SCRIPT_BUTTON = 66,
|
|
HDR_CG_MESSENGER = 67,
|
|
HDR_CG_MALL_CHECKOUT = 69,
|
|
HDR_CG_SAFEBOX_CHECKIN = 70,
|
|
HDR_CG_SAFEBOX_CHECKOUT = 71,
|
|
HDR_CG_PARTY_INVITE = 72,
|
|
HDR_CG_PARTY_INVITE_ANSWER = 73,
|
|
HDR_CG_PARTY_REMOVE = 74,
|
|
HDR_CG_PARTY_SET_STATE = 75,
|
|
HDR_CG_PARTY_USE_SKILL = 76,
|
|
HDR_CG_SAFEBOX_ITEM_MOVE = 77,
|
|
HDR_CG_PARTY_PARAMETER = 78,
|
|
HDR_CG_GUILD = 80,
|
|
HDR_CG_ANSWER_MAKE_GUILD = 81,
|
|
HDR_CG_FISHING = 82,
|
|
HDR_CG_ITEM_GIVE = 83,
|
|
HDR_CG_EMPIRE = 90,
|
|
HDR_CG_REFINE = 96,
|
|
HDR_CG_MARK_LOGIN = 100,
|
|
HDR_CG_MARK_CRCLIST = 101,
|
|
HDR_CG_MARK_UPLOAD = 102,
|
|
HDR_CG_MARK_IDXLIST = 104,
|
|
HDR_CG_HACK = 105,
|
|
HDR_CG_CHANGE_NAME = 106,
|
|
HDR_CG_LOGIN2 = 109,
|
|
HDR_CG_DUNGEON = 110,
|
|
HDR_CG_LOGIN3 = 111,
|
|
HDR_CG_GUILD_SYMBOL_UPLOAD = 112,
|
|
HDR_CG_SYMBOL_CRC = 113,
|
|
HDR_CG_SCRIPT_SELECT_ITEM = 114,
|
|
HDR_CG_LOGIN5_OPENID = 116,
|
|
HDR_CG_PASSPOD_ANSWER = 202,
|
|
HDR_CG_HS_ACK = 203,
|
|
HDR_CG_XTRAP_ACK = 204,
|
|
HDR_CG_DRAGON_SOUL_REFINE = 205,
|
|
HDR_CG_STATE_CHECKER = 206,
|
|
};
|
|
|
|
// GC (server -> client). Server packet.h (wire authority).
|
|
enum : uint8_t {
|
|
HDR_GC_CHARACTER_ADD = 1,
|
|
HDR_GC_CHARACTER_DEL = 2,
|
|
HDR_GC_MOVE = 3,
|
|
HDR_GC_CHAT = 4,
|
|
HDR_GC_SYNC_POSITION = 5,
|
|
HDR_GC_LOGIN_SUCCESS = 6, // client: LOGIN_SUCCESS3 (players[3]); legacy
|
|
HDR_GC_LOGIN_FAILURE = 7,
|
|
HDR_GC_CHARACTER_CREATE_SUCCESS = 8,
|
|
HDR_GC_CREATE_FAILURE = 9,
|
|
HDR_GC_DELETE_SUCCESS = 10,
|
|
HDR_GC_DELETE_WRONG_SOCIAL_ID = 11,
|
|
HDR_GC_ATTACK = 12,
|
|
HDR_GC_STUN = 13,
|
|
HDR_GC_DEAD = 14,
|
|
HDR_GC_MAIN_CHARACTER_OLD = 15,
|
|
HDR_GC_CHARACTER_POINTS = 16,
|
|
HDR_GC_CHARACTER_POINT_CHANGE = 17,
|
|
HDR_GC_CHANGE_SPEED = 18,
|
|
HDR_GC_CHARACTER_UPDATE = 19,
|
|
HDR_GC_ITEM_DEL = 20,
|
|
HDR_GC_ITEM_SET = 21,
|
|
HDR_GC_ITEM_USE = 22,
|
|
HDR_GC_ITEM_DROP = 23,
|
|
HDR_GC_CHARACTER_UPDATE_NEW = 24,
|
|
HDR_GC_ITEM_UPDATE = 25,
|
|
HDR_GC_ITEM_GROUND_ADD = 26,
|
|
HDR_GC_ITEM_GROUND_DEL = 27,
|
|
HDR_GC_QUICKSLOT_ADD = 28,
|
|
HDR_GC_QUICKSLOT_DEL = 29,
|
|
HDR_GC_QUICKSLOT_SWAP = 30,
|
|
HDR_GC_ITEM_OWNERSHIP = 31,
|
|
HDR_GC_LOGIN_SUCCESS_NEWSLOT = 32, // client: LOGIN_SUCCESS4 (players[4]) — what 40250 sends
|
|
HDR_GC_WHISPER = 34,
|
|
HDR_GC_MOTION = 36,
|
|
HDR_GC_PARTS = 37,
|
|
HDR_GC_SHOP = 38, // dynamic
|
|
HDR_GC_SHOP_SIGN = 39,
|
|
HDR_GC_DUEL_START = 40, // dynamic
|
|
HDR_GC_PVP = 41,
|
|
HDR_GC_EXCHANGE = 42,
|
|
HDR_GC_CHARACTER_POSITION = 43,
|
|
HDR_GC_PING = 44,
|
|
HDR_GC_SCRIPT = 45, // dynamic
|
|
HDR_GC_QUEST_CONFIRM = 46,
|
|
HDR_GC_MOUNT = 61,
|
|
HDR_GC_OWNERSHIP = 62,
|
|
HDR_GC_TARGET = 63,
|
|
HDR_GC_WARP = 65,
|
|
HDR_GC_ADD_FLY_TARGETING = 69,
|
|
HDR_GC_CREATE_FLY = 70,
|
|
HDR_GC_FLY_TARGETING = 71,
|
|
HDR_GC_SKILL_LEVEL_OLD = 72,
|
|
HDR_GC_SKILL_COOLTIME_END = 73,
|
|
HDR_GC_MESSENGER = 74, // dynamic
|
|
HDR_GC_GUILD = 75, // dynamic
|
|
HDR_GC_SKILL_LEVEL = 76,
|
|
HDR_GC_PARTY_INVITE = 77,
|
|
HDR_GC_PARTY_ADD = 78,
|
|
HDR_GC_PARTY_UPDATE = 79,
|
|
HDR_GC_PARTY_REMOVE = 80,
|
|
HDR_GC_QUEST_INFO = 81, // dynamic
|
|
HDR_GC_REQUEST_MAKE_GUILD = 82,
|
|
HDR_GC_PARTY_PARAMETER = 83,
|
|
HDR_GC_SAFEBOX_MONEY_CHANGE = 84,
|
|
HDR_GC_SAFEBOX_SET = 85,
|
|
HDR_GC_SAFEBOX_DEL = 86,
|
|
HDR_GC_SAFEBOX_WRONG_PASSWORD = 87,
|
|
HDR_GC_SAFEBOX_SIZE = 88,
|
|
HDR_GC_FISHING = 89,
|
|
HDR_GC_EMPIRE = 90,
|
|
HDR_GC_PARTY_LINK = 91,
|
|
HDR_GC_PARTY_UNLINK = 92,
|
|
HDR_GC_REFINE_INFORMATION_OLD = 95,
|
|
HDR_GC_OBSERVER_ADD = 96,
|
|
HDR_GC_OBSERVER_REMOVE = 97,
|
|
HDR_GC_OBSERVER_MOVE = 98,
|
|
HDR_GC_VIEW_EQUIP = 99,
|
|
HDR_GC_MARK_BLOCK = 100,
|
|
HDR_GC_MARK_IDXLIST = 102,
|
|
HDR_GC_TIME = 106,
|
|
HDR_GC_CHANGE_NAME = 107,
|
|
HDR_GC_DUNGEON = 110, // dynamic
|
|
HDR_GC_WALK_MODE = 111,
|
|
HDR_GC_SKILL_GROUP = 112,
|
|
HDR_GC_MAIN_CHARACTER = 113, // client non-GAIDEN: MAIN_CHARACTER2_EMPIRE
|
|
HDR_GC_SPECIAL_EFFECT = 114, // sic "SEPCIAL" in source
|
|
HDR_GC_NPC_POSITION = 115, // dynamic
|
|
HDR_GC_MATRIX_CARD = 116,
|
|
HDR_GC_CHARACTER_UPDATE2 = 117,
|
|
HDR_GC_LOGIN_KEY = 118,
|
|
HDR_GC_REFINE_INFORMATION = 119,
|
|
HDR_GC_CHARACTER_ADD2 = 120,
|
|
HDR_GC_CHANNEL = 121,
|
|
HDR_GC_MALL_OPEN = 122,
|
|
HDR_GC_TARGET_UPDATE = 123,
|
|
HDR_GC_TARGET_DELETE = 124,
|
|
HDR_GC_TARGET_CREATE = 125,
|
|
HDR_GC_AFFECT_ADD = 126,
|
|
HDR_GC_AFFECT_REMOVE = 127,
|
|
HDR_GC_MALL_SET = 128,
|
|
HDR_GC_MALL_DEL = 129,
|
|
HDR_GC_LAND_LIST = 130, // dynamic
|
|
HDR_GC_LOVER_INFO = 131,
|
|
HDR_GC_LOVE_POINT_UPDATE = 132,
|
|
HDR_GC_SYMBOL_DATA = 133,
|
|
HDR_GC_DIG_MOTION = 134,
|
|
HDR_GC_DAMAGE_INFO = 135,
|
|
HDR_GC_CHAR_ADDITIONAL_INFO = 136,
|
|
HDR_GC_MAIN_CHARACTER3_BGM = 137,
|
|
HDR_GC_MAIN_CHARACTER4_BGM_VOL = 138,
|
|
HDR_GC_AUTH_SUCCESS = 150,
|
|
HDR_GC_PANAMA_PACK = 151,
|
|
HDR_GC_HYBRIDCRYPT_KEYS = 152, // dynamic — EterPack key delivery, ignore
|
|
HDR_GC_HYBRIDCRYPT_SDB = 153, // dynamic
|
|
HDR_GC_AUTH_SUCCESS_OPENID = 154,
|
|
HDR_GC_RUNUP_MATRIX_QUIZ = 201,
|
|
HDR_GC_REQUEST_PASSPOD = 202,
|
|
HDR_GC_REQUEST_PASSPOD_FAILED = 203,
|
|
HDR_GC_HS_REQUEST = 204,
|
|
HDR_GC_XTRAP_CS1_REQUEST = 205,
|
|
HDR_GC_SPECIFIC_EFFECT = 208,
|
|
HDR_GC_DRAGON_SOUL_REFINE = 209,
|
|
HDR_GC_RESPOND_CHANNELSTATUS = 210,
|
|
};
|
|
|
|
// ------------------------------------------------------------------ structs
|
|
#pragma pack(push, 1)
|
|
|
|
struct ItemPos { // TItemPos (GameType.h, pack(1))
|
|
uint8_t window_type;
|
|
uint16_t cell;
|
|
};
|
|
static_assert(sizeof(ItemPos) == 3);
|
|
|
|
struct SimplePlayer { // TSimplePlayer (common/tables.h) == client TSimplePlayerInformation
|
|
uint32_t id;
|
|
char name[CHARACTER_NAME_MAX_LEN + 1];
|
|
uint8_t job; // BYTE (create packet's `job` is WORD; this summary field is BYTE)
|
|
uint8_t level;
|
|
uint32_t play_minutes;
|
|
uint8_t st, ht, dx, iq;
|
|
uint16_t main_part;
|
|
uint8_t change_name;
|
|
uint16_t hair_part;
|
|
uint8_t dummy[4];
|
|
int32_t x, y;
|
|
int32_t addr;
|
|
uint16_t port;
|
|
uint8_t skill_group;
|
|
};
|
|
// 4 + 25 + 1 + 1 + 4 + 4 + 2 + 1 + 2 + 4 + 8 + 4 + 2 + 1
|
|
static_assert(sizeof(SimplePlayer) == 63);
|
|
|
|
// --- control / handshake ---
|
|
struct Blank { uint8_t header; };
|
|
static_assert(sizeof(Blank) == 1);
|
|
|
|
struct DynHead { uint8_t header; uint16_t size; }; // TDynamicSizePacketHeader
|
|
static_assert(sizeof(DynHead) == 3);
|
|
|
|
struct Handshake { // packet_handshake (GC == CG)
|
|
uint8_t header;
|
|
uint32_t handshake;
|
|
uint32_t time;
|
|
int32_t delta;
|
|
};
|
|
static_assert(sizeof(Handshake) == 13);
|
|
|
|
struct Phase_ { uint8_t header; uint8_t phase; }; // packet_phase
|
|
static_assert(sizeof(Phase_) == 2);
|
|
|
|
struct BindUDP { uint8_t header; uint32_t addr; uint16_t port; };
|
|
static_assert(sizeof(BindUDP) == 7);
|
|
|
|
// TPacketCGText is the handshake/admin command base. The server's
|
|
// CInputHandshake::Analyze() consumes bytes after this one-byte header up to
|
|
// and including the first newline, so the size table describes only this
|
|
// fixed prefix.
|
|
struct CGText { uint8_t header; };
|
|
static_assert(sizeof(CGText) == 1);
|
|
|
|
struct KeyAgreement { // TPacketKeyAgreement — fixed 261B, registered STATIC
|
|
uint8_t header;
|
|
uint16_t agreed_length;
|
|
uint16_t data_length;
|
|
uint8_t data[KEY_AGREEMENT_MAX_DATA_LEN];
|
|
};
|
|
static_assert(sizeof(KeyAgreement) == 261);
|
|
|
|
struct KeyAgreementCompleted { uint8_t header; uint8_t data[3]; };
|
|
static_assert(sizeof(KeyAgreementCompleted) == 4);
|
|
|
|
// --- login ---
|
|
struct CGLogin { uint8_t header; char login[ID_MAX_NUM + 1]; char passwd[PASS_MAX_NUM + 1]; };
|
|
static_assert(sizeof(CGLogin) == 1 + 31 + 17);
|
|
|
|
struct CGLogin2 { uint8_t header; char login[ID_MAX_NUM + 1]; uint32_t login_key; uint32_t client_key[4]; };
|
|
static_assert(sizeof(CGLogin2) == 1 + 31 + 4 + 16);
|
|
|
|
struct CGLogin3 { uint8_t header; char login[ID_MAX_NUM + 1]; char passwd[PASS_MAX_NUM + 1]; uint32_t client_key[4]; };
|
|
static_assert(sizeof(CGLogin3) == 1 + 31 + 17 + 16);
|
|
|
|
struct GCLoginKey { uint8_t header; uint32_t login_key; };
|
|
static_assert(sizeof(GCLoginKey) == 5);
|
|
|
|
template <int N>
|
|
struct GCLoginSuccessT { // packet_login_success — 40250 sends the NEWSLOT (N=4) form
|
|
uint8_t header;
|
|
SimplePlayer players[N];
|
|
uint32_t guild_id[N];
|
|
char guild_name[N][GUILD_NAME_MAX_LEN + 1];
|
|
uint32_t handle;
|
|
uint32_t random_key;
|
|
};
|
|
using GCLoginSuccess = GCLoginSuccessT<PLAYER_PER_ACCOUNT>; // header 32
|
|
using GCLoginSuccess3 = GCLoginSuccessT<PLAYER_PER_ACCOUNT3>; // header 6 (legacy)
|
|
// 1 + 63*N + 4*N + 13*N + 4 + 4
|
|
static_assert(sizeof(GCLoginSuccess) == 1 + 63 * 4 + 4 * 4 + 13 * 4 + 4 + 4); // 329
|
|
static_assert(sizeof(GCLoginSuccess3) == 1 + 63 * 3 + 4 * 3 + 13 * 3 + 4 + 4); // 249
|
|
|
|
struct GCLoginFailure { uint8_t header; char status[LOGIN_STATUS_MAX_LEN + 1]; };
|
|
static_assert(sizeof(GCLoginFailure) == 10);
|
|
struct GCEmpire { uint8_t header; uint8_t empire; };
|
|
|
|
// Account/security packets are not part of the Godot login flow, but the
|
|
// stock 40250 header map treats them as static. Keep their exact sizes so a
|
|
// deployment which enables one can still drain the stream safely.
|
|
struct GCAuthSuccess { uint8_t header; uint32_t login_key; uint8_t result; };
|
|
struct GCAuthSuccessOpenID {
|
|
uint8_t header;
|
|
uint32_t login_key;
|
|
uint8_t result;
|
|
char login[ID_MAX_NUM + 1];
|
|
};
|
|
struct GCMatrixCard { uint8_t header; uint32_t rows; uint32_t cols; };
|
|
struct GCRunupMatrixQuiz { uint8_t header; char quiz[8 + 1]; };
|
|
struct GCRequestPasspod { uint8_t header; };
|
|
struct GCPasspodFailed { uint8_t header; char message[128 + 1]; };
|
|
struct GCXTrapRequest { uint8_t header; uint8_t packet_data[128]; };
|
|
// Optional Panama package IV delivery. It is not needed by the Godot asset
|
|
// loader, but it is sent by a stock server when panama.lst is configured.
|
|
struct GCPanamaPack { uint8_t header; char pack_name[256]; uint8_t iv[32]; };
|
|
// FreeBSD's game build wraps AHNHS_TRANS_BUFFER in #pragma pack(1):
|
|
// byte[400] + uint16 length, followed by the one-byte packet header.
|
|
struct GCHSRequest { uint8_t header; uint8_t buffer[400]; uint16_t length; };
|
|
static_assert(sizeof(GCAuthSuccess) == 6 && sizeof(GCAuthSuccessOpenID) == 37 &&
|
|
sizeof(GCMatrixCard) == 9 && sizeof(GCRunupMatrixQuiz) == 10 &&
|
|
sizeof(GCRequestPasspod) == 1 && sizeof(GCPasspodFailed) == 130 &&
|
|
sizeof(GCXTrapRequest) == 129 && sizeof(GCPanamaPack) == 289 &&
|
|
sizeof(GCHSRequest) == 403);
|
|
static_assert(sizeof(GCEmpire) == 2);
|
|
|
|
// --- select / create / delete ---
|
|
struct CGPlayerSelect { uint8_t header; uint8_t player_index; };
|
|
static_assert(sizeof(CGPlayerSelect) == 2);
|
|
|
|
struct CGPlayerCreate { // command_player_create — note `job` is WORD here
|
|
uint8_t header;
|
|
uint8_t index;
|
|
char name[CHARACTER_NAME_MAX_LEN + 1];
|
|
uint16_t job;
|
|
uint8_t shape;
|
|
uint8_t con, intel, str, dex;
|
|
};
|
|
static_assert(sizeof(CGPlayerCreate) == 1 + 1 + 25 + 2 + 1 + 4);
|
|
|
|
struct CGPlayerDelete { uint8_t header; uint8_t index; char private_code[PRIVATE_CODE_LENGTH]; };
|
|
static_assert(sizeof(CGPlayerDelete) == 10);
|
|
|
|
struct CGChangeName {
|
|
uint8_t header;
|
|
uint8_t index;
|
|
char name[CHARACTER_NAME_MAX_LEN + 1];
|
|
};
|
|
static_assert(sizeof(CGChangeName) == 27);
|
|
|
|
struct CGEmpire { uint8_t header; uint8_t empire; };
|
|
static_assert(sizeof(CGEmpire) == 2);
|
|
|
|
// CG_CLIENT_VERSION / CG_CLIENT_VERSION2 — TPacketCGClientVersion. The
|
|
// 40250 server records this before accepting game-phase movement; omitting it
|
|
// causes the server to close the connection on the first CG_MOVE.
|
|
struct CGClientVersion {
|
|
uint8_t header;
|
|
char filename[33];
|
|
char timestamp[33];
|
|
};
|
|
static_assert(sizeof(CGClientVersion) == 67);
|
|
|
|
// CG_CLIENT_VERSION2 (header 0xf1) — TPacketCGClientVersion2. Same layout as
|
|
// CGClientVersion; the EUROPE / non-YMIR locale sends THIS one with a fixed
|
|
// timestamp "1215955205" (REF/.../PythonNetworkStreamPhaseGame.cpp:4155,
|
|
// Locale.cpp:186). The 0xfd variant carries the C __TIMESTAMP__ instead.
|
|
struct CGClientVersion2 {
|
|
uint8_t header;
|
|
char filename[33];
|
|
char timestamp[33];
|
|
};
|
|
static_assert(sizeof(CGClientVersion2) == 67);
|
|
|
|
struct GCPlayerCreateSuccess { uint8_t header; uint8_t account_slot; SimplePlayer player; };
|
|
static_assert(sizeof(GCPlayerCreateSuccess) == 2 + 63);
|
|
struct GCPlayerCreateFailure { uint8_t header; uint8_t type; };
|
|
struct GCChangeNameNotice { uint8_t header; uint32_t pid; char name[CHARACTER_NAME_MAX_LEN + 1]; };
|
|
static_assert(sizeof(GCPlayerCreateFailure) == 2 && sizeof(GCChangeNameNotice) == 30);
|
|
|
|
struct GCPlayerDeleteSuccess { uint8_t header; uint8_t account_index; };
|
|
static_assert(sizeof(GCPlayerDeleteSuccess) == 2);
|
|
|
|
struct CGEnterGame { uint8_t header; };
|
|
static_assert(sizeof(CGEnterGame) == 1);
|
|
|
|
// --- independent guild-mark connection ---
|
|
// The mark server uses the same classic DH2/CTR control handshake, but its
|
|
// mark packets are not in the normal game length map. These fixed packet
|
|
// layouts are copied from ClientVS22/UserInterface/Packet.h.
|
|
struct CGMarkLogin { uint8_t header; uint32_t handle; uint32_t random_key; };
|
|
struct CGMarkIDXList { uint8_t header; };
|
|
struct CGMarkCRCList { uint8_t header; uint8_t img_idx; uint32_t crclist[MARK_BLOCK_TOTAL_COUNT]; };
|
|
struct CGMarkUpload {
|
|
uint8_t header;
|
|
uint32_t gid;
|
|
uint8_t image[GUILD_MARK_WIDTH * GUILD_MARK_HEIGHT * 4];
|
|
};
|
|
struct CGGuildSymbolUpload { uint8_t header; uint16_t size; uint32_t guild_id; };
|
|
struct CGSymbolCRC { uint8_t header; uint32_t guild_id; uint32_t crc; uint32_t size; };
|
|
struct GCMarkIDXList { uint8_t header; uint32_t buf_size; uint16_t count; };
|
|
struct GCMarkBlock { uint8_t header; uint32_t buf_size; uint8_t img_idx; uint32_t count; };
|
|
struct GCSymbolData { uint8_t header; uint16_t size; uint32_t guild_id; };
|
|
static_assert(sizeof(CGMarkLogin) == 9 && sizeof(CGMarkIDXList) == 1 &&
|
|
sizeof(CGMarkCRCList) == 322 && sizeof(CGMarkUpload) == 773 &&
|
|
sizeof(CGGuildSymbolUpload) == 7 && sizeof(CGSymbolCRC) == 13 &&
|
|
sizeof(GCMarkIDXList) == 7 && sizeof(GCMarkBlock) == 10 &&
|
|
sizeof(GCSymbolData) == 7);
|
|
|
|
// --- main character (header 113) ---
|
|
struct GCMainCharacter { // packet.h:1043 (== client TPacketGCMainCharacter2_EMPIRE)
|
|
uint8_t header;
|
|
uint32_t vid;
|
|
uint16_t race;
|
|
char name[CHARACTER_NAME_MAX_LEN + 1];
|
|
int32_t x, y, z;
|
|
uint8_t empire;
|
|
uint8_t skill_group;
|
|
};
|
|
static_assert(sizeof(GCMainCharacter) == 1 + 4 + 2 + 25 + 12 + 1 + 1);
|
|
|
|
// SUPPORT_BGM variants emitted by CHARACTER::MainCharacterPacket when the
|
|
// map has a configured background track. The BGM field is not needed by the
|
|
// Godot world model, but it must be part of the frame so the next GC packet is
|
|
// not mistaken for the tail of this one.
|
|
struct GCMainCharacter3BGM {
|
|
uint8_t header;
|
|
uint32_t vid;
|
|
uint16_t race;
|
|
char name[CHARACTER_NAME_MAX_LEN + 1];
|
|
char bgm_name[24 + 1];
|
|
int32_t x, y, z;
|
|
uint8_t empire;
|
|
uint8_t skill_group;
|
|
};
|
|
struct GCMainCharacter4BGM {
|
|
uint8_t header;
|
|
uint32_t vid;
|
|
uint16_t race;
|
|
char name[CHARACTER_NAME_MAX_LEN + 1];
|
|
char bgm_name[24 + 1];
|
|
float bgm_volume;
|
|
int32_t x, y, z;
|
|
uint8_t empire;
|
|
uint8_t skill_group;
|
|
};
|
|
static_assert(sizeof(GCMainCharacter3BGM) == 71 && sizeof(GCMainCharacter4BGM) == 75);
|
|
|
|
// --- spawn / despawn / move ---
|
|
struct GCCharacterAdd { // packet_add_char (server) — no name/parts
|
|
uint8_t header;
|
|
uint32_t vid;
|
|
float angle;
|
|
int32_t x, y, z;
|
|
uint8_t type;
|
|
uint16_t race;
|
|
uint8_t moving_speed;
|
|
uint8_t attack_speed;
|
|
uint8_t state_flag;
|
|
uint32_t affect_flag[2];
|
|
};
|
|
static_assert(sizeof(GCCharacterAdd) == 1 + 4 + 4 + 12 + 1 + 2 + 1 + 1 + 1 + 8);
|
|
|
|
struct GCCharacterAdd2 { // packet_add_char2 — name + parts + guild
|
|
uint8_t header;
|
|
uint32_t vid;
|
|
char name[CHARACTER_NAME_MAX_LEN + 1];
|
|
float angle;
|
|
int32_t x, y, z;
|
|
uint8_t type;
|
|
uint16_t race;
|
|
uint16_t parts[CHR_EQUIPPART_NUM];
|
|
uint8_t moving_speed;
|
|
uint8_t attack_speed;
|
|
uint8_t state_flag;
|
|
uint32_t affect_flag[2];
|
|
uint8_t empire;
|
|
uint32_t guild;
|
|
int16_t alignment;
|
|
uint8_t pk_mode;
|
|
uint32_t mount_vnum;
|
|
};
|
|
static_assert(sizeof(GCCharacterAdd2) ==
|
|
1 + 4 + 25 + 4 + 12 + 1 + 2 + 8 + 1 + 1 + 1 + 8 + 1 + 4 + 2 + 1 + 4);
|
|
|
|
struct GCCharacterDel { uint8_t header; uint32_t vid; };
|
|
static_assert(sizeof(GCCharacterDel) == 5);
|
|
|
|
struct CGMove { // command_move
|
|
uint8_t header;
|
|
uint8_t func;
|
|
uint8_t arg;
|
|
uint8_t rot;
|
|
int32_t x, y;
|
|
uint32_t time;
|
|
};
|
|
static_assert(sizeof(CGMove) == 4 + 8 + 4);
|
|
|
|
struct GCMove { // packet_move (server) — adds vid + duration
|
|
uint8_t header;
|
|
uint8_t func;
|
|
uint8_t arg;
|
|
uint8_t rot;
|
|
uint32_t vid;
|
|
int32_t x, y;
|
|
uint32_t time;
|
|
uint32_t duration;
|
|
};
|
|
static_assert(sizeof(GCMove) == 4 + 4 + 8 + 4 + 4);
|
|
|
|
// --- points ---
|
|
struct GCPoints { uint8_t header; int32_t points[POINT_MAX_NUM]; };
|
|
static_assert(sizeof(GCPoints) == 1 + 255 * 4);
|
|
|
|
struct GCPointChange { // packet_point_change — NOTE: `int header` (4 bytes) in the source
|
|
int32_t header; // wire: byte 0 = real header, bytes 1..3 = junk from the int
|
|
uint32_t vid;
|
|
uint8_t type;
|
|
int32_t amount;
|
|
int32_t value;
|
|
};
|
|
static_assert(sizeof(GCPointChange) == 4 + 4 + 1 + 4 + 4);
|
|
|
|
// --- CG in-game intents ---
|
|
struct CGAttack { // command_attack
|
|
uint8_t header;
|
|
uint8_t type;
|
|
uint32_t victim_vid;
|
|
uint8_t crc_proc;
|
|
uint8_t crc_file;
|
|
};
|
|
static_assert(sizeof(CGAttack) == 8);
|
|
|
|
struct CGTarget { uint8_t header; uint32_t vid; }; // command_target
|
|
static_assert(sizeof(CGTarget) == 5);
|
|
struct CGOnClick { uint8_t header; uint32_t vid; }; // command_on_click
|
|
static_assert(sizeof(CGOnClick) == 5);
|
|
struct CGPosition { uint8_t header; uint8_t position; }; // command_position
|
|
static_assert(sizeof(CGPosition) == 2);
|
|
struct CGScriptAnswer { uint8_t header; uint8_t answer; };
|
|
static_assert(sizeof(CGScriptAnswer) == 2);
|
|
|
|
struct CGItemUse { uint8_t header; ItemPos pos; }; // command_item_use
|
|
static_assert(sizeof(CGItemUse) == 4);
|
|
struct CGItemMove { uint8_t header; ItemPos pos; ItemPos change_pos; uint8_t num; };
|
|
static_assert(sizeof(CGItemMove) == 8);
|
|
struct CGItemDrop { uint8_t header; ItemPos pos; uint32_t gold; }; // command_item_drop
|
|
static_assert(sizeof(CGItemDrop) == 8);
|
|
struct CGItemDrop2 { uint8_t header; ItemPos pos; uint32_t gold; uint8_t count; };
|
|
static_assert(sizeof(CGItemDrop2) == 9);
|
|
struct CGItemPickup { uint8_t header; uint32_t vid; }; // command_item_pickup
|
|
static_assert(sizeof(CGItemPickup) == 5);
|
|
struct CGUseSkill { uint8_t header; uint32_t vnum; uint32_t target_vid; };
|
|
static_assert(sizeof(CGUseSkill) == 9);
|
|
struct CGFlyTargeting { uint8_t header; uint32_t target_vid; int32_t x, y; };
|
|
static_assert(sizeof(CGFlyTargeting) == 13);
|
|
struct CGShoot { uint8_t header; uint8_t type; };
|
|
static_assert(sizeof(CGShoot) == 2);
|
|
struct CGWarp { uint8_t header; };
|
|
static_assert(sizeof(CGWarp) == 1);
|
|
struct CGFishing { uint8_t header; uint8_t dir; };
|
|
static_assert(sizeof(CGFishing) == 2);
|
|
struct CGItemUseToItem { uint8_t header; ItemPos pos; ItemPos target_pos; };
|
|
static_assert(sizeof(CGItemUseToItem) == 7);
|
|
struct CGGiveItem { uint8_t header; uint32_t target_vid; ItemPos pos; uint8_t count; };
|
|
static_assert(sizeof(CGGiveItem) == 9);
|
|
struct CGQuickSlotAdd { uint8_t header; uint8_t pos; uint8_t type; uint8_t position; };
|
|
struct CGQuickSlotDel { uint8_t header; uint8_t pos; };
|
|
struct CGQuickSlotSwap { uint8_t header; uint8_t pos; uint8_t pos_to; };
|
|
static_assert(sizeof(CGQuickSlotAdd) == 4 && sizeof(CGQuickSlotDel) == 2 &&
|
|
sizeof(CGQuickSlotSwap) == 3);
|
|
|
|
// --- P8 social / exchange / storage intents -------------------------------
|
|
enum : uint8_t {
|
|
EXCHANGE_CG_START = 0,
|
|
EXCHANGE_CG_ITEM_ADD = 1,
|
|
EXCHANGE_CG_ITEM_DEL = 2,
|
|
EXCHANGE_CG_GOLD_ADD = 3,
|
|
EXCHANGE_CG_ACCEPT = 4,
|
|
EXCHANGE_CG_CANCEL = 5,
|
|
|
|
MESSENGER_CG_ADD_BY_VID = 0,
|
|
MESSENGER_CG_ADD_BY_NAME = 1,
|
|
MESSENGER_CG_REMOVE = 2,
|
|
};
|
|
|
|
struct CGExchange {
|
|
uint8_t header;
|
|
uint8_t subheader;
|
|
uint32_t arg1;
|
|
uint8_t arg2;
|
|
ItemPos pos;
|
|
};
|
|
static_assert(sizeof(CGExchange) == 10);
|
|
|
|
struct CGMessenger { uint8_t header; uint8_t subheader; };
|
|
static_assert(sizeof(CGMessenger) == 2);
|
|
|
|
struct CGSafeboxCheckout {
|
|
uint8_t header;
|
|
uint8_t safe_pos;
|
|
ItemPos item_pos;
|
|
};
|
|
struct CGSafeboxCheckin {
|
|
uint8_t header;
|
|
uint8_t safe_pos;
|
|
ItemPos item_pos;
|
|
};
|
|
struct CGMallCheckout {
|
|
uint8_t header;
|
|
uint8_t mall_pos;
|
|
ItemPos item_pos;
|
|
};
|
|
static_assert(sizeof(CGSafeboxCheckout) == 5 && sizeof(CGSafeboxCheckin) == 5 &&
|
|
sizeof(CGMallCheckout) == 5);
|
|
|
|
enum : uint8_t {
|
|
SHOP_CG_END = 0,
|
|
SHOP_CG_BUY = 1,
|
|
SHOP_CG_SELL = 2,
|
|
SHOP_CG_SELL2 = 3,
|
|
};
|
|
struct CGShop { uint8_t header; uint8_t subheader; };
|
|
struct CGShopBuy { uint8_t count; uint8_t pos; };
|
|
struct CGShopSell { uint8_t pos; };
|
|
struct CGShopSell2 { uint8_t pos; uint8_t count; };
|
|
static_assert(sizeof(CGShop) == 2 && sizeof(CGShopBuy) == 2 && sizeof(CGShopSell) == 1 &&
|
|
sizeof(CGShopSell2) == 2);
|
|
|
|
struct CGScriptButton { uint8_t header; uint32_t index; };
|
|
struct CGQuestInput { uint8_t header; char msg[64 + 1]; };
|
|
struct CGQuestConfirm { uint8_t header; uint8_t answer; uint32_t request_pid; };
|
|
struct CGScriptSelectItem { uint8_t header; uint32_t selection; };
|
|
static_assert(sizeof(CGScriptButton) == 5 && sizeof(CGQuestInput) == 66 &&
|
|
sizeof(CGQuestConfirm) == 6 && sizeof(CGScriptSelectItem) == 5);
|
|
|
|
struct CGPartyInvite { uint8_t header; uint32_t vid; };
|
|
struct CGPartyInviteAnswer { uint8_t header; uint32_t leader_vid; uint8_t accept; };
|
|
struct CGPartyRemove { uint8_t header; uint32_t pid; };
|
|
struct CGPartySetState { uint8_t header; uint32_t pid; uint8_t role; uint8_t flag; };
|
|
struct CGPartyUseSkill { uint8_t header; uint8_t skill_index; uint32_t vid; };
|
|
struct CGPartyParameter { uint8_t header; uint8_t distribute_mode; };
|
|
static_assert(sizeof(CGPartyInvite) == 5 && sizeof(CGPartyInviteAnswer) == 6 &&
|
|
sizeof(CGPartyRemove) == 5 && sizeof(CGPartySetState) == 7 &&
|
|
sizeof(CGPartyUseSkill) == 6 && sizeof(CGPartyParameter) == 2);
|
|
|
|
enum : uint8_t {
|
|
GUILD_CG_ADD_MEMBER = 0,
|
|
GUILD_CG_REMOVE_MEMBER = 1,
|
|
GUILD_CG_CHANGE_GRADE_NAME = 2,
|
|
GUILD_CG_CHANGE_GRADE_AUTHORITY = 3,
|
|
GUILD_CG_OFFER = 4,
|
|
GUILD_CG_POST_COMMENT = 5,
|
|
GUILD_CG_DELETE_COMMENT = 6,
|
|
GUILD_CG_REFRESH_COMMENT = 7,
|
|
GUILD_CG_CHANGE_MEMBER_GRADE = 8,
|
|
GUILD_CG_USE_SKILL = 9,
|
|
GUILD_CG_CHANGE_MEMBER_GENERAL = 10,
|
|
GUILD_CG_GUILD_INVITE_ANSWER = 11,
|
|
GUILD_CG_CHARGE_GSP = 12,
|
|
GUILD_CG_DEPOSIT_MONEY = 13,
|
|
GUILD_CG_WITHDRAW_MONEY = 14,
|
|
};
|
|
struct CGGuild { uint8_t header; uint8_t subheader; };
|
|
struct CGAnswerMakeGuild { uint8_t header; char guild_name[GUILD_NAME_MAX_LEN + 1]; };
|
|
struct CGGuildUseSkill { uint32_t vnum; uint32_t target_pid; };
|
|
static_assert(sizeof(CGGuild) == 2 && sizeof(CGAnswerMakeGuild) == 14 &&
|
|
sizeof(CGGuildUseSkill) == 8);
|
|
|
|
struct CGMyShopHead {
|
|
uint8_t header;
|
|
char sign[SHOP_TAB_NAME_MAX + 1];
|
|
uint8_t count;
|
|
};
|
|
// TShopItemTable is defined by the common table header. The fields used by
|
|
// the classic server are kept here in wire order; socket/attribute payloads
|
|
// are copied by ClassicSession without depending on the fork's wire.h types.
|
|
struct CGMyShopItem {
|
|
uint32_t vnum;
|
|
uint8_t count;
|
|
ItemPos pos;
|
|
uint32_t price;
|
|
uint8_t display_pos;
|
|
};
|
|
static_assert(sizeof(CGMyShopItem) == 13);
|
|
|
|
struct CGRefine { uint8_t header; uint8_t pos; uint8_t type; };
|
|
static_assert(sizeof(CGRefine) == 3);
|
|
|
|
inline constexpr int DS_REFINE_GRID_MAX_NUM = 15;
|
|
struct CGDragonSoulRefine {
|
|
uint8_t header;
|
|
uint8_t subheader;
|
|
ItemPos grid[DS_REFINE_GRID_MAX_NUM];
|
|
};
|
|
static_assert(sizeof(CGDragonSoulRefine) == 47);
|
|
|
|
// --- GC entity state ---
|
|
struct GCCharacterUpdate { // packet_update_char (header 19)
|
|
uint8_t header;
|
|
uint32_t vid;
|
|
uint16_t parts[CHR_EQUIPPART_NUM];
|
|
uint8_t moving_speed;
|
|
uint8_t attack_speed;
|
|
uint8_t state_flag;
|
|
uint32_t affect_flag[2];
|
|
uint32_t guild_id;
|
|
int16_t alignment;
|
|
uint8_t pk_mode;
|
|
uint32_t mount_vnum;
|
|
};
|
|
static_assert(sizeof(GCCharacterUpdate) == 1 + 4 + 8 + 1 + 1 + 1 + 8 + 4 + 2 + 1 + 4); // 35
|
|
|
|
struct GCStun { uint8_t header; uint32_t vid; }; // header 13
|
|
struct GCDead { uint8_t header; uint32_t vid; }; // header 14
|
|
static_assert(sizeof(GCStun) == 5 && sizeof(GCDead) == 5);
|
|
|
|
struct GCMotion { uint8_t header; uint32_t vid; uint32_t victim_vid; uint16_t motion; }; // 36
|
|
static_assert(sizeof(GCMotion) == 11);
|
|
|
|
struct GCCharacterPosition { uint8_t header; uint32_t vid; uint8_t position; }; // header 43
|
|
static_assert(sizeof(GCCharacterPosition) == 6);
|
|
|
|
struct GCChangeSpeed { uint8_t header; uint32_t vid; uint16_t moving_speed; }; // header 18
|
|
static_assert(sizeof(GCChangeSpeed) == 7);
|
|
|
|
struct GCWalkMode { uint8_t header; uint32_t vid; uint8_t mode; }; // header 111
|
|
static_assert(sizeof(GCWalkMode) == 6);
|
|
|
|
struct GCTarget { uint8_t header; uint32_t vid; uint8_t hp_percent; }; // header 63
|
|
static_assert(sizeof(GCTarget) == 6);
|
|
|
|
struct GCDamageInfo { uint8_t header; uint32_t vid; uint8_t flag; int32_t damage; }; // header 135
|
|
static_assert(sizeof(GCDamageInfo) == 10);
|
|
|
|
struct ItemAttr3 { uint8_t type; int16_t value; }; // TPlayerItemAttribute (pack(1) = 3B)
|
|
static_assert(sizeof(ItemAttr3) == 3);
|
|
|
|
struct GCAttack { uint8_t header; uint32_t vid; uint32_t victim_vid; uint8_t type; };
|
|
struct GCItemUse { uint8_t header; ItemPos cell; uint32_t ch_vid; uint32_t victim_vid; uint32_t vnum; };
|
|
struct GCItemOwnership { uint8_t header; uint32_t vid; char owner[CHARACTER_NAME_MAX_LEN + 1]; };
|
|
struct GCOwnership { uint8_t header; uint32_t owner_vid; uint32_t victim_vid; };
|
|
struct GCMount {
|
|
uint8_t header;
|
|
uint32_t vid;
|
|
uint32_t mount_vnum;
|
|
uint8_t pos;
|
|
int32_t x, y;
|
|
};
|
|
struct GCCreateFly { uint8_t header; uint8_t type; uint32_t start_vid; uint32_t end_vid; };
|
|
struct GCFlyTargeting {
|
|
uint8_t header;
|
|
uint32_t shooter_vid;
|
|
uint32_t target_vid;
|
|
int32_t x, y;
|
|
};
|
|
struct GCPvp { uint8_t header; uint32_t src_vid; uint32_t dst_vid; uint8_t mode; };
|
|
struct GCDuelStart { uint8_t header; uint16_t size; };
|
|
struct GCShopSign { uint8_t header; uint32_t vid; char sign[SHOP_SIGN_MAX_LEN + 1]; };
|
|
struct GCSkillGroup { uint8_t header; uint8_t skill_group; };
|
|
struct GCSpecialEffect { uint8_t header; uint8_t type; uint32_t vid; };
|
|
struct GCSpecificEffect { uint8_t header; uint32_t vid; char effect_file[128]; };
|
|
struct GCSkillLevelOld { uint8_t header; uint8_t levels[SKILL_MAX_NUM]; };
|
|
struct GCSkillCooltimeEnd { uint8_t header; uint8_t skill; };
|
|
struct GCFishing { uint8_t header; uint8_t subheader; uint32_t info; uint8_t dir; };
|
|
struct GCObserverAdd { uint8_t header; uint32_t vid; uint16_t x, y; };
|
|
struct GCObserverMove { uint8_t header; uint32_t vid; uint16_t x, y; };
|
|
struct GCObserverRemove { uint8_t header; uint32_t vid; };
|
|
// TPacketGCWarp in the 40250 server/client packet.h is a packed static packet
|
|
// with no dynamic length field (BYTE + LONG x 3 + WORD).
|
|
struct GCWarpClassic { uint8_t header; int32_t x, y, addr; uint16_t port; };
|
|
static_assert(sizeof(GCWarpClassic) == 15);
|
|
struct GCTime { uint8_t header; int32_t time; };
|
|
struct GCChannel { uint8_t header; uint8_t channel; };
|
|
struct GCDungeonHead { uint8_t subheader; };
|
|
struct GCDungeonDestination { int32_t x, y; };
|
|
struct GCNPCPositionHead { uint16_t count; };
|
|
struct NPCPosition34 {
|
|
uint8_t type;
|
|
char name[CHARACTER_NAME_MAX_LEN + 1];
|
|
int32_t x, y;
|
|
};
|
|
struct LandPacketElement24 {
|
|
uint32_t id;
|
|
int32_t x, y;
|
|
int32_t width, height;
|
|
uint32_t guild_id;
|
|
};
|
|
struct GCTargetCreate43 {
|
|
uint8_t header;
|
|
int32_t id;
|
|
char name[32 + 1];
|
|
uint32_t vid;
|
|
uint8_t type;
|
|
};
|
|
struct GCTargetUpdate13 { uint8_t header; int32_t id; int32_t x, y; };
|
|
struct GCTargetDelete5 { uint8_t header; int32_t id; };
|
|
struct GCLoverInfo { uint8_t header; char name[CHARACTER_NAME_MAX_LEN + 1]; uint8_t love_point; };
|
|
struct GCLovePointUpdate { uint8_t header; uint8_t love_point; };
|
|
struct GCDigMotion { uint8_t header; uint32_t vid; uint32_t target_vid; uint8_t count; };
|
|
struct EquipmentItem38 {
|
|
uint32_t vnum;
|
|
uint8_t count;
|
|
int32_t sockets[ITEM_SOCKET_MAX_NUM];
|
|
ItemAttr3 attrs[ITEM_ATTRIBUTE_MAX_NUM];
|
|
};
|
|
inline constexpr int CLASSIC_WEAR_MAX_NUM = 32; // server common/length.h
|
|
struct GCViewEquip {
|
|
uint8_t header;
|
|
uint32_t vid;
|
|
EquipmentItem38 equips[CLASSIC_WEAR_MAX_NUM];
|
|
};
|
|
struct GCChangeName { uint8_t header; uint32_t pid; char name[CHARACTER_NAME_MAX_LEN + 1]; };
|
|
struct GCRefineMaterial { uint32_t vnum; int32_t count; };
|
|
struct GCRefineInfo {
|
|
uint8_t header;
|
|
uint8_t type;
|
|
uint8_t pos;
|
|
uint32_t src_vnum;
|
|
uint32_t result_vnum;
|
|
uint8_t material_count;
|
|
int32_t cost;
|
|
int32_t prob;
|
|
GCRefineMaterial materials[5];
|
|
};
|
|
struct GCDragonSoulRefine {
|
|
uint8_t header;
|
|
uint8_t sub_type;
|
|
ItemPos pos;
|
|
};
|
|
static_assert(sizeof(GCAttack) == 10 && sizeof(GCItemUse) == 16 &&
|
|
sizeof(GCItemOwnership) == 30 && sizeof(GCOwnership) == 9 && sizeof(GCMount) == 18 &&
|
|
sizeof(GCCreateFly) == 10 && sizeof(GCFlyTargeting) == 17 &&
|
|
sizeof(GCPvp) == 10 && sizeof(GCDuelStart) == 3 && sizeof(GCShopSign) == 38 &&
|
|
sizeof(GCSkillGroup) == 2 && sizeof(GCSpecialEffect) == 6 && sizeof(GCSpecificEffect) == 133 &&
|
|
sizeof(GCSkillLevelOld) == 256 && sizeof(GCSkillCooltimeEnd) == 2 &&
|
|
sizeof(GCFishing) == 7 &&
|
|
sizeof(GCObserverAdd) == 9 && sizeof(GCObserverMove) == 9 &&
|
|
sizeof(GCObserverRemove) == 5 && sizeof(GCTime) == 5 && sizeof(GCChannel) == 2 &&
|
|
sizeof(GCDungeonHead) == 1 && sizeof(GCNPCPositionHead) == 2 &&
|
|
sizeof(NPCPosition34) == 34 && sizeof(LandPacketElement24) == 24 &&
|
|
sizeof(GCTargetCreate43) == 43 && sizeof(GCTargetUpdate13) == 13 &&
|
|
sizeof(GCTargetDelete5) == 5 && sizeof(GCLoverInfo) == 27 &&
|
|
sizeof(GCLovePointUpdate) == 2 && sizeof(GCDigMotion) == 10 &&
|
|
sizeof(EquipmentItem38) == 38 && sizeof(GCViewEquip) == 1221 &&
|
|
sizeof(GCChangeName) == 30 && sizeof(GCRefineMaterial) == 8 &&
|
|
sizeof(GCRefineInfo) == 60 && sizeof(GCDragonSoulRefine) == 5);
|
|
|
|
// packet_char_additional_info (header 136) — fills a spawned entity's name/parts/...
|
|
struct GCCharAddInfo {
|
|
uint8_t header;
|
|
uint32_t vid;
|
|
char name[CHARACTER_NAME_MAX_LEN + 1];
|
|
uint16_t parts[CHR_EQUIPPART_NUM];
|
|
uint8_t empire;
|
|
uint32_t guild_id;
|
|
uint32_t level;
|
|
int16_t alignment;
|
|
uint8_t pk_mode;
|
|
uint32_t mount_vnum;
|
|
};
|
|
static_assert(sizeof(GCCharAddInfo) == 1 + 4 + 25 + 8 + 1 + 4 + 4 + 2 + 1 + 4); // 54
|
|
|
|
// --- GC skills / quickslots ---
|
|
struct PlayerSkill3 { // TPlayerSkill (common/tables.h) — time_t is 32-bit on the -m32 server
|
|
uint8_t master_type;
|
|
uint8_t level;
|
|
uint32_t next_read;
|
|
};
|
|
static_assert(sizeof(PlayerSkill3) == 6);
|
|
|
|
struct GCSkillLevel { uint8_t header; PlayerSkill3 skills[SKILL_MAX_NUM]; }; // header 76
|
|
static_assert(sizeof(GCSkillLevel) == 1 + 255 * 6); // 1531
|
|
|
|
struct QuickSlot2 { uint8_t type; uint8_t position; }; // TQuickSlot / TQuickslot
|
|
struct GCQuickSlotAdd { uint8_t header; uint8_t pos; QuickSlot2 slot; }; // header 28
|
|
struct GCQuickSlotDel { uint8_t header; uint8_t pos; }; // header 29
|
|
struct GCQuickSlotSwap { uint8_t header; uint8_t pos; uint8_t pos_to; }; // header 30
|
|
static_assert(sizeof(GCQuickSlotAdd) == 4 && sizeof(GCQuickSlotDel) == 2 &&
|
|
sizeof(GCQuickSlotSwap) == 3);
|
|
|
|
// --- GC affects ---
|
|
struct AffectElement { // TPacketAffectElement (client Packet.h L2555)
|
|
uint32_t type;
|
|
uint8_t point_idx;
|
|
int32_t apply_value;
|
|
uint32_t flag;
|
|
int32_t duration;
|
|
int32_t sp_cost;
|
|
};
|
|
static_assert(sizeof(AffectElement) == 21);
|
|
struct GCAffectAdd { uint8_t header; AffectElement elem; }; // header 126
|
|
struct GCAffectRemove { uint8_t header; uint32_t type; uint8_t apply_on; }; // header 127
|
|
static_assert(sizeof(GCAffectAdd) == 22 && sizeof(GCAffectRemove) == 6);
|
|
|
|
// --- GC dynamic: whisper / sync-position ---
|
|
struct GCWhisperHead { // packet_whisper (header 34) — + text
|
|
uint8_t header;
|
|
uint16_t size;
|
|
uint8_t type;
|
|
char name_from[CHARACTER_NAME_MAX_LEN + 1];
|
|
};
|
|
static_assert(sizeof(GCWhisperHead) == 1 + 2 + 1 + 25); // 29
|
|
|
|
struct SyncPosElement { uint32_t vid; int32_t x, y; };
|
|
static_assert(sizeof(SyncPosElement) == 12);
|
|
|
|
// --- GC quest / script ---
|
|
struct GCScriptHead { uint8_t header; uint16_t size; uint8_t skin; uint16_t src_size; }; // 45 dyn
|
|
static_assert(sizeof(GCScriptHead) == 6);
|
|
struct GCQuestConfirm { // packet_quest_confirm (header 46, STATIC)
|
|
uint8_t header;
|
|
char msg[64 + 1];
|
|
int32_t timeout;
|
|
uint32_t request_pid;
|
|
};
|
|
static_assert(sizeof(GCQuestConfirm) == 1 + 65 + 4 + 4); // 74
|
|
// GC_QUEST_INFO (81, dynamic): after [header][u16 size] -> [u16 index][u8 flag][flag-driven...]
|
|
enum : uint8_t {
|
|
QUEST_SEND_IS_BEGIN = 1 << 0,
|
|
QUEST_SEND_TITLE = 1 << 1,
|
|
QUEST_SEND_CLOCK_NAME = 1 << 2,
|
|
QUEST_SEND_CLOCK_VALUE = 1 << 3,
|
|
QUEST_SEND_COUNTER_NAME = 1 << 4,
|
|
QUEST_SEND_COUNTER_VALUE = 1 << 5,
|
|
QUEST_SEND_ICON_FILE = 1 << 6,
|
|
};
|
|
// Field widths written by PC::SendQuestInfoPakcet() (questpc.cpp) and read back
|
|
// by the original client's field-by-field Recv() in RecvQuestInfoPacket().
|
|
inline constexpr int QUEST_INFO_HEAD_SIZE = 6; // header + u16 size + u16 index + u8 flag
|
|
inline constexpr int QUEST_INFO_IS_BEGIN_SIZE = 1;
|
|
inline constexpr int QUEST_INFO_TITLE_SIZE = 30 + 1;
|
|
inline constexpr int QUEST_INFO_CLOCK_NAME_SIZE = 16 + 1;
|
|
inline constexpr int QUEST_INFO_CLOCK_VALUE_SIZE = 4;
|
|
inline constexpr int QUEST_INFO_COUNTER_NAME_SIZE = 16 + 1;
|
|
inline constexpr int QUEST_INFO_COUNTER_VALUE_SIZE = 4;
|
|
inline constexpr int QUEST_INFO_ICON_FILE_SIZE = 24 + 1;
|
|
|
|
// The 40250 server fills the fixed head with `size = sizeof(packet_quest_info)`
|
|
// (6) and then appends the optional fields while only bumping its own local
|
|
// copy of `size` — the count on the wire never covers the tail. Framing this
|
|
// packet off its size field therefore leaves the tail in the stream and
|
|
// desyncs every packet after it, so derive the real length from `flag` the way
|
|
// the original client does.
|
|
constexpr int quest_info_packet_size(uint8_t flag) {
|
|
int n = QUEST_INFO_HEAD_SIZE;
|
|
if (flag & QUEST_SEND_IS_BEGIN) n += QUEST_INFO_IS_BEGIN_SIZE;
|
|
if (flag & QUEST_SEND_TITLE) n += QUEST_INFO_TITLE_SIZE;
|
|
if (flag & QUEST_SEND_CLOCK_NAME) n += QUEST_INFO_CLOCK_NAME_SIZE;
|
|
if (flag & QUEST_SEND_CLOCK_VALUE) n += QUEST_INFO_CLOCK_VALUE_SIZE;
|
|
if (flag & QUEST_SEND_COUNTER_NAME) n += QUEST_INFO_COUNTER_NAME_SIZE;
|
|
if (flag & QUEST_SEND_COUNTER_VALUE) n += QUEST_INFO_COUNTER_VALUE_SIZE;
|
|
if (flag & QUEST_SEND_ICON_FILE) n += QUEST_INFO_ICON_FILE_SIZE;
|
|
return n;
|
|
}
|
|
|
|
// --- GC party ---
|
|
inline constexpr int PARTY_AFFECT_SLOT_MAX_NUM = 7;
|
|
struct GCPartyInvite { uint8_t header; uint32_t leader_vid; }; // header 77
|
|
struct GCPartyAdd { uint8_t header; uint32_t pid; char name[CHARACTER_NAME_MAX_LEN + 1]; }; // 78
|
|
struct GCPartyUpdate { // header 79
|
|
uint8_t header;
|
|
uint32_t pid;
|
|
uint8_t role;
|
|
uint8_t percent_hp;
|
|
int16_t affects[PARTY_AFFECT_SLOT_MAX_NUM];
|
|
};
|
|
struct GCPartyRemove { uint8_t header; uint32_t pid; }; // header 80
|
|
struct GCPartyLink { uint8_t header; uint32_t pid; uint32_t vid; }; // header 91
|
|
struct GCPartyUnlink { uint8_t header; uint32_t pid; uint32_t vid; }; // header 92
|
|
struct GCPartyParameter { uint8_t header; uint8_t distribute_mode; }; // header 83
|
|
static_assert(sizeof(GCPartyInvite) == 5 && sizeof(GCPartyAdd) == 30 &&
|
|
sizeof(GCPartyUpdate) == 21 && sizeof(GCPartyRemove) == 5 && sizeof(GCPartyLink) == 9 &&
|
|
sizeof(GCPartyParameter) == 2);
|
|
|
|
// --- GC items ---
|
|
enum : uint8_t {
|
|
EXCHANGE_GC_START = 0,
|
|
EXCHANGE_GC_ITEM_ADD = 1,
|
|
EXCHANGE_GC_ITEM_DEL = 2,
|
|
EXCHANGE_GC_GOLD_ADD = 3,
|
|
EXCHANGE_GC_ACCEPT = 4,
|
|
EXCHANGE_GC_END = 5,
|
|
EXCHANGE_GC_ALREADY = 6,
|
|
EXCHANGE_GC_LESS_GOLD = 7,
|
|
|
|
MESSENGER_GC_LIST = 0,
|
|
MESSENGER_GC_LOGIN = 1,
|
|
MESSENGER_GC_LOGOUT = 2,
|
|
MESSENGER_GC_INVITE = 3,
|
|
MESSENGER_GC_MOBILE = 4,
|
|
};
|
|
|
|
struct GCExchange {
|
|
uint8_t header;
|
|
uint8_t subheader;
|
|
uint8_t is_me;
|
|
uint32_t arg1;
|
|
ItemPos arg2;
|
|
uint32_t arg3;
|
|
int32_t sockets[ITEM_SOCKET_MAX_NUM];
|
|
ItemAttr3 attrs[ITEM_ATTRIBUTE_MAX_NUM];
|
|
};
|
|
static_assert(sizeof(GCExchange) == 47);
|
|
|
|
struct GCItemSet { // packet_item_set (server packet.h:1134, header 21)
|
|
uint8_t header;
|
|
ItemPos cell;
|
|
uint32_t vnum;
|
|
uint8_t count;
|
|
uint32_t flags;
|
|
uint32_t anti_flags;
|
|
uint8_t highlight; // bool on the wire
|
|
int32_t sockets[ITEM_SOCKET_MAX_NUM];
|
|
ItemAttr3 attrs[ITEM_ATTRIBUTE_MAX_NUM];
|
|
};
|
|
static_assert(sizeof(GCItemSet) == 1 + 3 + 4 + 1 + 4 + 4 + 1 + 12 + 21); // 51
|
|
|
|
struct GCItemDel { uint8_t header; uint8_t pos; }; // header 20 — inventory pos only
|
|
static_assert(sizeof(GCItemDel) == 2);
|
|
|
|
// GC_MESSENGER is dynamic: after [header][u16 size], body starts with the
|
|
// subheader and then the subheader-specific records below.
|
|
struct GCMessengerHead { uint8_t header; uint16_t size; uint8_t subheader; };
|
|
struct MessengerListEntry { uint8_t connected; uint8_t length; };
|
|
struct MessengerNameLength { uint8_t length; };
|
|
static_assert(sizeof(GCMessengerHead) == 4 && sizeof(MessengerListEntry) == 2 &&
|
|
sizeof(MessengerNameLength) == 1);
|
|
|
|
struct GCSafeboxSize { uint8_t header; uint8_t size; };
|
|
struct GCSafeboxMoneyChange { uint8_t header; int32_t money; };
|
|
struct GCMallOpen { uint8_t header; uint8_t size; };
|
|
static_assert(sizeof(GCSafeboxSize) == 2 && sizeof(GCSafeboxMoneyChange) == 5 &&
|
|
sizeof(GCMallOpen) == 2);
|
|
|
|
struct GCItemUpdate { // packet_item_update (header 25)
|
|
uint8_t header;
|
|
ItemPos cell;
|
|
uint8_t count;
|
|
int32_t sockets[ITEM_SOCKET_MAX_NUM];
|
|
ItemAttr3 attrs[ITEM_ATTRIBUTE_MAX_NUM];
|
|
};
|
|
static_assert(sizeof(GCItemUpdate) == 1 + 3 + 1 + 12 + 21); // 38
|
|
|
|
struct GCItemGroundAdd { // packet_item_ground_add (header 26)
|
|
uint8_t header;
|
|
int32_t x, y, z;
|
|
uint32_t vid;
|
|
uint32_t vnum;
|
|
};
|
|
static_assert(sizeof(GCItemGroundAdd) == 1 + 12 + 4 + 4); // 21
|
|
|
|
struct GCItemGroundDel { uint8_t header; uint32_t vid; }; // header 27
|
|
static_assert(sizeof(GCItemGroundDel) == 5);
|
|
|
|
// --- GC shop (dynamic, sub-header after [header][u16 size]) ---
|
|
struct ShopItem43 { // packet_shop_item
|
|
uint32_t vnum;
|
|
uint32_t price;
|
|
uint8_t count;
|
|
uint8_t display_pos;
|
|
int32_t sockets[ITEM_SOCKET_MAX_NUM];
|
|
ItemAttr3 attrs[ITEM_ATTRIBUTE_MAX_NUM];
|
|
};
|
|
static_assert(sizeof(ShopItem43) == 4 + 4 + 1 + 1 + 12 + 21); // 43
|
|
struct ShopTabHead32 { char name[SHOP_TAB_NAME_MAX]; uint8_t coin_type; };
|
|
static_assert(sizeof(ShopTabHead32) == 33);
|
|
struct ShopUpdateItem { uint8_t pos; ShopItem43 item; };
|
|
struct ShopUpdatePrice { int32_t price; };
|
|
static_assert(sizeof(ShopUpdateItem) == 44 && sizeof(ShopUpdatePrice) == 4);
|
|
|
|
// --- GC guild (dynamic, [header][u16 size][subheader]...) ---
|
|
struct GuildMember38 {
|
|
uint32_t pid;
|
|
uint8_t grade;
|
|
uint8_t is_general;
|
|
uint8_t job;
|
|
uint8_t level;
|
|
uint32_t offer;
|
|
uint8_t name_flag;
|
|
char name[CHARACTER_NAME_MAX_LEN + 1];
|
|
};
|
|
struct GuildInfo35 {
|
|
uint16_t member_count;
|
|
uint16_t max_member_count;
|
|
uint32_t guild_id;
|
|
uint32_t master_pid;
|
|
uint32_t exp;
|
|
uint8_t level;
|
|
char name[GUILD_NAME_MAX_LEN + 1];
|
|
uint32_t gold;
|
|
uint8_t has_land;
|
|
};
|
|
struct GuildGrade10 { char name[GUILD_GRADE_NAME_MAX_LEN + 1]; uint8_t auth; };
|
|
struct GuildWar10 { uint32_t self_id; uint32_t opponent_id; uint8_t type; uint8_t state; };
|
|
struct GuildWarPoint12 { uint32_t gain_guild_id; uint32_t opponent_guild_id; int32_t point; };
|
|
struct GuildWarPair8 { uint32_t src; uint32_t dst; };
|
|
struct GuildName16 { uint32_t id; char name[GUILD_NAME_MAX_LEN]; };
|
|
struct GuildSkill17 {
|
|
uint8_t skill_point;
|
|
uint8_t levels[GUILD_SKILL_MAX_NUM];
|
|
uint16_t guild_point;
|
|
uint16_t max_guild_point;
|
|
};
|
|
struct GuildComment80 {
|
|
uint32_t id;
|
|
char name[CHARACTER_NAME_MAX_LEN + 1];
|
|
char content[GUILD_COMMENT_MAX_LEN + 1];
|
|
};
|
|
struct GuildInvite17 {
|
|
uint32_t guild_id;
|
|
char guild_name[GUILD_NAME_MAX_LEN + 1];
|
|
};
|
|
static_assert(sizeof(GuildMember38) == 38 && sizeof(GuildInfo35) == 35 &&
|
|
sizeof(GuildGrade10) == 10 && sizeof(GuildWar10) == 10 &&
|
|
sizeof(GuildWarPoint12) == 12 && sizeof(GuildWarPair8) == 8 &&
|
|
sizeof(GuildName16) == 16 && sizeof(GuildSkill17) == 17 &&
|
|
sizeof(GuildComment80) == 80 && sizeof(GuildInvite17) == 17);
|
|
|
|
// --- chat (dynamic) ---
|
|
struct CGChatHead { uint8_t header; uint16_t length; uint8_t type; }; // + char szChat[]
|
|
static_assert(sizeof(CGChatHead) == 4);
|
|
struct CGWhisper { uint8_t header; uint16_t size; char name_to[CHARACTER_NAME_MAX_LEN + 1]; };
|
|
static_assert(sizeof(CGWhisper) == 28);
|
|
|
|
struct GCChatHead { // packet_chatting — + text
|
|
uint8_t header;
|
|
uint16_t size;
|
|
uint8_t type;
|
|
uint32_t vid;
|
|
uint8_t empire;
|
|
};
|
|
static_assert(sizeof(GCChatHead) == 9);
|
|
|
|
#pragma pack(pop)
|
|
|
|
// ------------------------------------------------------------------ size table
|
|
// FULL on-wire size of a static-size packet — the 1-byte header INCLUDED, any
|
|
// trailing sequence byte EXCLUDED (add 1 when is_sequence_cg()). Matches
|
|
// packet_info.cpp's `CPacketInfo::Set(header, sizeof(TPacketXxx), ...)` (CG) and
|
|
// CMainPacketHeaderMap's `sizeof(...)` (GC). Returns 0 for "unknown / dynamic" —
|
|
// callers must check is_dynamic_*() first. Grows as classic_parser lands packets.
|
|
constexpr int packet_size_cg(uint8_t h) {
|
|
switch (h) {
|
|
case HDR_HANDSHAKE: return sizeof(Handshake);
|
|
case HDR_CG_TIME_SYNC: return sizeof(Handshake);
|
|
case HDR_CG_PONG: return 1;
|
|
case HDR_CG_TEXT: return sizeof(CGText);
|
|
case HDR_KEY_AGREEMENT: return sizeof(KeyAgreement);
|
|
case HDR_CG_LOGIN: return sizeof(CGLogin);
|
|
case HDR_CG_LOGIN2: return sizeof(CGLogin2);
|
|
case HDR_CG_LOGIN3: return sizeof(CGLogin3);
|
|
case HDR_CG_CHARACTER_SELECT: return sizeof(CGPlayerSelect);
|
|
case HDR_CG_CHARACTER_CREATE: return sizeof(CGPlayerCreate);
|
|
case HDR_CG_CHARACTER_DELETE: return sizeof(CGPlayerDelete);
|
|
case HDR_CG_CHANGE_NAME: return sizeof(CGChangeName);
|
|
case HDR_CG_EMPIRE: return sizeof(CGEmpire);
|
|
case HDR_CG_CLIENT_VERSION: return sizeof(CGClientVersion);
|
|
case HDR_CG_CLIENT_VERSION2: return sizeof(CGClientVersion2);
|
|
case HDR_CG_ENTERGAME: return sizeof(CGEnterGame);
|
|
case HDR_CG_MOVE: return sizeof(CGMove);
|
|
case HDR_CG_ATTACK: return sizeof(CGAttack);
|
|
case HDR_CG_TARGET: return sizeof(CGTarget);
|
|
case HDR_CG_ON_CLICK: return sizeof(CGOnClick);
|
|
case HDR_CG_CHARACTER_POSITION: return sizeof(CGPosition);
|
|
case HDR_CG_SCRIPT_ANSWER: return sizeof(CGScriptAnswer);
|
|
case HDR_CG_SCRIPT_BUTTON: return sizeof(CGScriptButton);
|
|
case HDR_CG_SCRIPT_SELECT_ITEM: return sizeof(CGScriptSelectItem);
|
|
case HDR_CG_QUEST_INPUT_STRING: return sizeof(CGQuestInput);
|
|
case HDR_CG_QUEST_CONFIRM: return sizeof(CGQuestConfirm);
|
|
case HDR_CG_ITEM_USE: return sizeof(CGItemUse);
|
|
case HDR_CG_ITEM_MOVE: return sizeof(CGItemMove);
|
|
case HDR_CG_ITEM_DROP: return sizeof(CGItemDrop);
|
|
case HDR_CG_ITEM_DROP2: return sizeof(CGItemDrop2);
|
|
case HDR_CG_ITEM_PICKUP: return sizeof(CGItemPickup);
|
|
case HDR_CG_ITEM_USE_TO_ITEM: return sizeof(CGItemUseToItem);
|
|
case HDR_CG_ITEM_GIVE: return sizeof(CGGiveItem);
|
|
case HDR_CG_QUICKSLOT_ADD: return sizeof(CGQuickSlotAdd);
|
|
case HDR_CG_QUICKSLOT_DEL: return sizeof(CGQuickSlotDel);
|
|
case HDR_CG_QUICKSLOT_SWAP: return sizeof(CGQuickSlotSwap);
|
|
case HDR_CG_USE_SKILL: return sizeof(CGUseSkill);
|
|
case HDR_CG_FLY_TARGETING: return sizeof(CGFlyTargeting);
|
|
case HDR_CG_ADD_FLY_TARGETING: return sizeof(CGFlyTargeting);
|
|
case HDR_CG_SHOOT: return sizeof(CGShoot);
|
|
case HDR_CG_WARP: return sizeof(CGWarp);
|
|
case HDR_CG_FISHING: return sizeof(CGFishing);
|
|
case HDR_CG_SYNC_POSITION: return sizeof(DynHead);
|
|
case HDR_CG_WHISPER: return sizeof(CGWhisper);
|
|
case HDR_CG_PARTY_INVITE: return sizeof(CGPartyInvite);
|
|
case HDR_CG_PARTY_INVITE_ANSWER: return sizeof(CGPartyInviteAnswer);
|
|
case HDR_CG_PARTY_REMOVE: return sizeof(CGPartyRemove);
|
|
case HDR_CG_PARTY_SET_STATE: return sizeof(CGPartySetState);
|
|
case HDR_CG_PARTY_USE_SKILL: return sizeof(CGPartyUseSkill);
|
|
case HDR_CG_PARTY_PARAMETER: return sizeof(CGPartyParameter);
|
|
case HDR_CG_GUILD: return sizeof(CGGuild);
|
|
case HDR_CG_ANSWER_MAKE_GUILD: return sizeof(CGAnswerMakeGuild);
|
|
case HDR_CG_MYSHOP: return sizeof(CGMyShopHead);
|
|
case HDR_CG_REFINE: return sizeof(CGRefine);
|
|
case HDR_CG_DRAGON_SOUL_REFINE: return sizeof(CGDragonSoulRefine);
|
|
case HDR_CG_SHOP: return sizeof(CGShop);
|
|
case HDR_CG_EXCHANGE: return sizeof(CGExchange);
|
|
case HDR_CG_MESSENGER: return sizeof(CGMessenger);
|
|
case HDR_CG_MALL_CHECKOUT: return sizeof(CGMallCheckout);
|
|
case HDR_CG_SAFEBOX_CHECKIN: return sizeof(CGSafeboxCheckin);
|
|
case HDR_CG_SAFEBOX_CHECKOUT: return sizeof(CGSafeboxCheckout);
|
|
case HDR_CG_SAFEBOX_ITEM_MOVE: return sizeof(CGItemMove);
|
|
case HDR_CG_STATE_CHECKER: return 1;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
constexpr int packet_size_gc(uint8_t h) {
|
|
switch (h) {
|
|
case HDR_HANDSHAKE: return sizeof(Handshake);
|
|
case HDR_GC_TIME_SYNC: return sizeof(Blank);
|
|
case HDR_GC_PHASE: return sizeof(Phase_);
|
|
case HDR_GC_BINDUDP: return sizeof(BindUDP);
|
|
case HDR_GC_PING: return sizeof(Blank);
|
|
case HDR_KEY_AGREEMENT: return sizeof(KeyAgreement);
|
|
case HDR_GC_KEY_AGREEMENT_COMPLETED: return sizeof(KeyAgreementCompleted);
|
|
case HDR_GC_LOGIN_SUCCESS: return sizeof(GCLoginSuccess3);
|
|
case HDR_GC_LOGIN_SUCCESS_NEWSLOT: return sizeof(GCLoginSuccess);
|
|
case HDR_GC_LOGIN_FAILURE: return sizeof(GCLoginFailure);
|
|
case HDR_GC_AUTH_SUCCESS: return sizeof(GCAuthSuccess);
|
|
case HDR_GC_AUTH_SUCCESS_OPENID: return sizeof(GCAuthSuccessOpenID);
|
|
case HDR_GC_MATRIX_CARD: return sizeof(GCMatrixCard);
|
|
case HDR_GC_RUNUP_MATRIX_QUIZ: return sizeof(GCRunupMatrixQuiz);
|
|
case HDR_GC_REQUEST_PASSPOD: return sizeof(GCRequestPasspod);
|
|
case HDR_GC_REQUEST_PASSPOD_FAILED: return sizeof(GCPasspodFailed);
|
|
case HDR_GC_HS_REQUEST: return sizeof(GCHSRequest);
|
|
case HDR_GC_XTRAP_CS1_REQUEST: return sizeof(GCXTrapRequest);
|
|
case HDR_GC_PANAMA_PACK: return sizeof(GCPanamaPack);
|
|
case HDR_GC_EMPIRE: return sizeof(GCEmpire);
|
|
case HDR_GC_LOGIN_KEY: return sizeof(GCLoginKey);
|
|
case HDR_GC_CHARACTER_CREATE_SUCCESS: return sizeof(GCPlayerCreateSuccess);
|
|
case HDR_GC_CREATE_FAILURE: return sizeof(GCPlayerCreateFailure);
|
|
case HDR_GC_DELETE_SUCCESS: return sizeof(GCPlayerDeleteSuccess);
|
|
case HDR_GC_DELETE_WRONG_SOCIAL_ID: return sizeof(Blank);
|
|
case HDR_GC_MAIN_CHARACTER: return sizeof(GCMainCharacter);
|
|
case HDR_GC_MAIN_CHARACTER3_BGM: return sizeof(GCMainCharacter3BGM);
|
|
case HDR_GC_MAIN_CHARACTER4_BGM_VOL: return sizeof(GCMainCharacter4BGM);
|
|
case HDR_GC_CHARACTER_ADD: return sizeof(GCCharacterAdd);
|
|
case HDR_GC_CHARACTER_ADD2: return sizeof(GCCharacterAdd2);
|
|
case HDR_GC_CHARACTER_UPDATE_NEW: return sizeof(GCCharacterUpdate);
|
|
case HDR_GC_CHARACTER_UPDATE2: return sizeof(GCCharacterUpdate);
|
|
case HDR_GC_CHARACTER_DEL: return sizeof(GCCharacterDel);
|
|
case HDR_GC_CHANGE_NAME: return sizeof(GCChangeNameNotice);
|
|
case HDR_GC_ATTACK: return sizeof(GCAttack);
|
|
case HDR_GC_MOVE: return sizeof(GCMove);
|
|
case HDR_GC_CHARACTER_POINTS: return sizeof(GCPoints);
|
|
case HDR_GC_CHARACTER_POINT_CHANGE: return sizeof(GCPointChange);
|
|
case HDR_GC_ITEM_SET: return sizeof(GCItemSet);
|
|
case HDR_GC_ITEM_DEL: return sizeof(GCItemDel);
|
|
case HDR_GC_ITEM_USE: return sizeof(GCItemUse);
|
|
case HDR_GC_ITEM_UPDATE: return sizeof(GCItemUpdate);
|
|
case HDR_GC_ITEM_GROUND_ADD: return sizeof(GCItemGroundAdd);
|
|
case HDR_GC_ITEM_GROUND_DEL: return sizeof(GCItemGroundDel);
|
|
case HDR_GC_ITEM_OWNERSHIP: return sizeof(GCItemOwnership);
|
|
case HDR_GC_OWNERSHIP: return sizeof(GCOwnership);
|
|
case HDR_GC_CHARACTER_UPDATE: return sizeof(GCCharacterUpdate);
|
|
case HDR_GC_STUN: return sizeof(GCStun);
|
|
case HDR_GC_DEAD: return sizeof(GCDead);
|
|
case HDR_GC_MOTION: return sizeof(GCMotion);
|
|
case HDR_GC_CHARACTER_POSITION: return sizeof(GCCharacterPosition);
|
|
case HDR_GC_CHANGE_SPEED: return sizeof(GCChangeSpeed);
|
|
case HDR_GC_WALK_MODE: return sizeof(GCWalkMode);
|
|
case HDR_GC_TARGET: return sizeof(GCTarget);
|
|
case HDR_GC_DAMAGE_INFO: return sizeof(GCDamageInfo);
|
|
case HDR_GC_CHAR_ADDITIONAL_INFO: return sizeof(GCCharAddInfo);
|
|
case HDR_GC_SKILL_LEVEL: return sizeof(GCSkillLevel);
|
|
case HDR_GC_SKILL_COOLTIME_END: return sizeof(GCSkillCooltimeEnd);
|
|
case HDR_GC_QUICKSLOT_ADD: return sizeof(GCQuickSlotAdd);
|
|
case HDR_GC_QUICKSLOT_DEL: return sizeof(GCQuickSlotDel);
|
|
case HDR_GC_QUICKSLOT_SWAP: return sizeof(GCQuickSlotSwap);
|
|
case HDR_GC_AFFECT_ADD: return sizeof(GCAffectAdd);
|
|
case HDR_GC_AFFECT_REMOVE: return sizeof(GCAffectRemove);
|
|
case HDR_GC_EXCHANGE: return sizeof(GCExchange);
|
|
case HDR_GC_PVP: return sizeof(GCPvp);
|
|
case HDR_GC_MOUNT: return sizeof(GCMount);
|
|
case HDR_GC_CREATE_FLY: return sizeof(GCCreateFly);
|
|
case HDR_GC_FLY_TARGETING: return sizeof(GCFlyTargeting);
|
|
case HDR_GC_ADD_FLY_TARGETING: return sizeof(GCFlyTargeting);
|
|
case HDR_GC_SHOP_SIGN: return sizeof(GCShopSign);
|
|
case HDR_GC_SKILL_LEVEL_OLD: return sizeof(GCSkillLevelOld);
|
|
case HDR_GC_SKILL_GROUP: return sizeof(GCSkillGroup);
|
|
case HDR_GC_SPECIAL_EFFECT: return sizeof(GCSpecialEffect);
|
|
case HDR_GC_SPECIFIC_EFFECT: return sizeof(GCSpecificEffect);
|
|
case HDR_GC_OBSERVER_ADD: return sizeof(GCObserverAdd);
|
|
case HDR_GC_OBSERVER_REMOVE: return sizeof(GCObserverRemove);
|
|
case HDR_GC_OBSERVER_MOVE: return sizeof(GCObserverMove);
|
|
case HDR_GC_PARTY_INVITE: return sizeof(GCPartyInvite);
|
|
case HDR_GC_PARTY_ADD: return sizeof(GCPartyAdd);
|
|
case HDR_GC_PARTY_UPDATE: return sizeof(GCPartyUpdate);
|
|
case HDR_GC_PARTY_REMOVE: return sizeof(GCPartyRemove);
|
|
case HDR_GC_PARTY_LINK: return sizeof(GCPartyLink);
|
|
case HDR_GC_PARTY_UNLINK: return sizeof(GCPartyUnlink);
|
|
case HDR_GC_PARTY_PARAMETER: return sizeof(GCPartyParameter);
|
|
case HDR_GC_SAFEBOX_MONEY_CHANGE: return sizeof(GCSafeboxMoneyChange);
|
|
case HDR_GC_SAFEBOX_SET: return sizeof(GCItemSet);
|
|
case HDR_GC_SAFEBOX_DEL: return sizeof(GCItemDel);
|
|
case HDR_GC_SAFEBOX_WRONG_PASSWORD: return sizeof(Blank);
|
|
case HDR_GC_SAFEBOX_SIZE: return sizeof(GCSafeboxSize);
|
|
case HDR_GC_FISHING: return sizeof(GCFishing);
|
|
case HDR_GC_REQUEST_MAKE_GUILD: return sizeof(Blank);
|
|
case HDR_GC_MALL_OPEN: return sizeof(GCMallOpen);
|
|
case HDR_GC_MALL_SET: return sizeof(GCItemSet);
|
|
case HDR_GC_MALL_DEL: return sizeof(GCItemDel);
|
|
case HDR_GC_TIME: return sizeof(GCTime);
|
|
case HDR_GC_CHANNEL: return sizeof(GCChannel);
|
|
case HDR_GC_WARP: return sizeof(GCWarpClassic);
|
|
case HDR_GC_TARGET_UPDATE: return sizeof(GCTargetUpdate13);
|
|
case HDR_GC_TARGET_DELETE: return sizeof(GCTargetDelete5);
|
|
case HDR_GC_TARGET_CREATE: return sizeof(GCTargetCreate43);
|
|
case HDR_GC_LOVER_INFO: return sizeof(GCLoverInfo);
|
|
case HDR_GC_LOVE_POINT_UPDATE: return sizeof(GCLovePointUpdate);
|
|
case HDR_GC_DIG_MOTION: return sizeof(GCDigMotion);
|
|
case HDR_GC_VIEW_EQUIP: return sizeof(GCViewEquip);
|
|
case HDR_GC_REFINE_INFORMATION_OLD:
|
|
case HDR_GC_REFINE_INFORMATION: return sizeof(GCRefineInfo);
|
|
case HDR_GC_DRAGON_SOUL_REFINE: return sizeof(GCDragonSoulRefine);
|
|
case HDR_GC_QUEST_CONFIRM: return sizeof(GCQuestConfirm);
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
// docs §2.2: CG packets flagged bSeq=true carry a trailing sequence byte once
|
|
// sequence mode is enabled. bSeq=false set: TEXT, HANDSHAKE, MARK_*, KEY_AGREEMENT,
|
|
// GUILD_SYMBOL_UPLOAD, SYMBOL_CRC, DRAGON_SOUL_REFINE, STATE_CHECKER.
|
|
constexpr bool is_sequence_cg(uint8_t h) {
|
|
switch (h) {
|
|
case HDR_CG_TEXT:
|
|
case HDR_HANDSHAKE:
|
|
case HDR_CG_MARK_LOGIN:
|
|
case HDR_CG_MARK_IDXLIST:
|
|
case HDR_CG_MARK_CRCLIST:
|
|
case HDR_CG_MARK_UPLOAD:
|
|
case HDR_KEY_AGREEMENT:
|
|
case HDR_CG_GUILD_SYMBOL_UPLOAD:
|
|
case HDR_CG_SYMBOL_CRC:
|
|
case HDR_CG_DRAGON_SOUL_REFINE:
|
|
case HDR_CG_STATE_CHECKER:
|
|
return false;
|
|
default:
|
|
return true; // every other registered CG packet
|
|
}
|
|
}
|
|
|
|
// docs §2.2: client CMainPacketHeaderMap DYNAMIC_SIZE_PACKET set (GC) + the two
|
|
// CG packets whose struct carries its own length word.
|
|
constexpr bool is_dynamic_gc(uint8_t h) {
|
|
switch (h) {
|
|
case HDR_GC_CHAT:
|
|
case HDR_GC_SCRIPT:
|
|
case HDR_GC_SHOP:
|
|
case HDR_GC_GUILD:
|
|
case HDR_GC_MESSENGER:
|
|
case HDR_GC_QUEST_INFO:
|
|
case HDR_GC_DUEL_START:
|
|
case HDR_GC_SYNC_POSITION:
|
|
// client CMainPacketHeaderMap registers GC_WHISPER STATIC, but its
|
|
// `packet_whisper.wSize` sits exactly where TDynamicSizePacketHeader.size
|
|
// does, so the dynamic path frames it correctly (the handler reads the
|
|
// text tail itself anyway).
|
|
case HDR_GC_WHISPER:
|
|
case HDR_GC_DUNGEON:
|
|
case HDR_GC_NPC_POSITION:
|
|
case HDR_GC_LAND_LIST:
|
|
case HDR_GC_SYMBOL_DATA:
|
|
case HDR_GC_HYBRIDCRYPT_KEYS:
|
|
case HDR_GC_HYBRIDCRYPT_SDB:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
constexpr bool is_dynamic_cg(uint8_t h) { return h == HDR_CG_CHAT || h == HDR_CG_WHISPER; }
|
|
|
|
} // namespace mtnet::classic
|