Files
mtgodot-poc/extension/src/net/wire.h
T

2040 lines
60 KiB
C++

#pragma once
// Metin2 net wire format (this m2dev fork). Every packet on the wire is
// [uint16 header LE][uint16 length LE][payload...]
// where `length` is the total packet size including the 4-byte prefix
// (>= PACKET_HEADER_SIZE). Runs of zero bytes between packets are padding and
// are skipped. Headers are the namespaced CG::/GC:: constants from the client's
// UserInterface/Packet.h + EterLib/ControlPackets.h.
//
// Struct layouts and header values verified against:
// EterLib/ControlPackets.h (KX / ping / phase)
// UserInterface/Packet.h (CG/GC enums, PHASE_*, login3, auth_success)
// UserInterface/AccountConnector.cpp (auth-server flow: PHASE_AUTH -> LOGIN3 -> AUTH_SUCCESS)
#include <cstdint>
namespace mtnet {
inline constexpr uint16_t PACKET_HEADER_SIZE = 4;
inline constexpr int ID_MAX_NUM = 30;
inline constexpr int PASS_MAX_NUM = 16;
// EPhases (UserInterface/Packet.h) — value = enum index.
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,
};
// Control plane (EterLib/ControlPackets.h)
inline constexpr uint16_t CG_PONG = 0x0006;
inline constexpr uint16_t GC_PING = 0x0007;
inline constexpr uint16_t GC_PHASE = 0x0008;
inline constexpr uint16_t CG_KEY_RESPONSE = 0x000A;
inline constexpr uint16_t GC_KEY_CHALLENGE = 0x000B;
inline constexpr uint16_t GC_KEY_COMPLETE = 0x000C;
inline constexpr uint16_t CG_CLIENT_VERSION = 0x000D; // sent after GC_MAIN_CHARACTER
// Auth (UserInterface/Packet.h namespace CG/GC)
inline constexpr uint16_t CG_LOGIN2 = 0x0101; // game server: name + login_key
inline constexpr uint16_t CG_LOGIN3 = 0x0102; // auth server: name + pwd
inline constexpr uint16_t CG_LOGIN_SECURE = 0x0103; // name + pwd + session_token
inline constexpr uint16_t CG_EMPIRE = 0x010A;
inline constexpr uint16_t CG_CHANGE_NAME = 0x010B;
inline constexpr uint16_t GC_CHANGE_NAME = 0x010C;
inline constexpr uint16_t CG_CHARACTER_CREATE = 0x0201;
inline constexpr uint16_t CG_CHARACTER_DELETE = 0x0202;
inline constexpr uint16_t CG_CHARACTER_SELECT = 0x0203;
inline constexpr uint16_t CG_ENTERGAME = 0x0204;
inline constexpr uint16_t GC_LOGIN_SUCCESS3 = 0x0104; // char list (PLAYER_PER_ACCOUNT3)
inline constexpr uint16_t GC_LOGIN_SUCCESS4 = 0x0105;
inline constexpr uint16_t GC_LOGIN_FAILURE = 0x0106;
inline constexpr uint16_t GC_EMPIRE = 0x0109;
inline constexpr uint16_t GC_AUTH_SUCCESS = 0x0108;
inline constexpr uint16_t GC_PLAYER_CREATE_SUCCESS = 0x020C;
inline constexpr uint16_t GC_PLAYER_CREATE_FAILURE = 0x020D;
inline constexpr uint16_t GC_PLAYER_DELETE_SUCCESS = 0x020E;
inline constexpr uint16_t GC_PLAYER_DELETE_WRONG_SOCIAL_ID = 0x020F;
inline constexpr int PRIVATE_CODE_LENGTH = 8; // CG_CHARACTER_DELETE social-id code
inline constexpr int CHARACTER_NAME_MAX_LEN = 64;
inline constexpr int GUILD_NAME_MAX_LEN = 12;
inline constexpr int PLAYER_PER_ACCOUNT3 = 3;
inline constexpr int PLAYER_PER_ACCOUNT4 = 4;
inline constexpr int CHR_EQUIPPART_NUM = 4; // ARMOR, WEAPON, HEAD, HAIR
inline constexpr int MUSIC_NAME_MAX_LEN = 24;
// Game phase — server -> client
inline constexpr uint16_t GC_CHARACTER_ADD = 0x0205;
inline constexpr uint16_t GC_CHARACTER_ADD2 = 0x0206;
inline constexpr uint16_t GC_CHAR_ADD_INFO = 0x0207; // name/level/guild/mount for a vid
inline constexpr uint16_t GC_CHARACTER_DEL = 0x0208;
inline constexpr uint16_t GC_CHARACTER_UPDATE = 0x0209;
inline constexpr uint16_t GC_MAIN_CHARACTER = 0x0210;
inline constexpr uint16_t GC_CHARACTER_POSITION = 0x020B;
inline constexpr uint16_t GC_SKILL_LEVEL = 0x021A; // flat u8[255] skill->level
inline constexpr uint16_t GC_SKILL_LEVEL_NEW = 0x021B; // {master,level,next_read}[255]
inline constexpr uint16_t GC_SKILL_COOLTIME_END = 0x021C; // one skill's cooldown ended
inline constexpr uint16_t GC_CHANGE_SKILL_GROUP = 0x021D;
inline constexpr uint16_t GC_VIEW_EQUIP = 0x021E; // inspect another character's equipment
inline constexpr uint16_t GC_QUICKSLOT_ADD = 0x0519;
inline constexpr uint16_t GC_QUICKSLOT_DEL = 0x051A;
inline constexpr uint16_t GC_QUICKSLOT_SWAP = 0x051B;
inline constexpr int QUICKSLOT_MAX_NUM = 36;
inline constexpr uint16_t GC_PLAYER_POINTS = 0x0214; // full stat block (main char)
inline constexpr uint16_t GC_PLAYER_POINT_CHANGE = 0x0215; // one point delta (any vid)
inline constexpr uint16_t GC_STUN = 0x0216;
inline constexpr uint16_t GC_DEAD = 0x0217;
inline constexpr uint16_t GC_CHANGE_SPEED = 0x0218;
inline constexpr uint16_t GC_WALK_MODE = 0x0219;
inline constexpr uint16_t GC_MOVE = 0x0302;
inline constexpr uint16_t GC_SYNC_POSITION = 0x0304;
inline constexpr uint16_t GC_WARP = 0x0306;
inline constexpr uint16_t GC_MOTION = 0x0307; // vid does <motion> at victim
inline constexpr uint16_t GC_DIG_MOTION = 0x0308; // mining motion broadcast
inline constexpr uint16_t GC_DAMAGE_INFO = 0x0410; // floating damage number
inline constexpr uint16_t GC_FLY_TARGETING = 0x0411;
inline constexpr uint16_t GC_ADD_FLY_TARGETING = 0x0412;
inline constexpr uint16_t GC_CREATE_FLY = 0x0413; // projectile start_vid -> end_vid
inline constexpr uint16_t GC_PVP = 0x0414;
inline constexpr uint16_t GC_DUEL_START = 0x0415;
inline constexpr uint16_t GC_ITEM_DEL = 0x0510;
inline constexpr uint16_t GC_ITEM_SET = 0x0511;
inline constexpr uint16_t GC_ITEM_USE = 0x0512;
inline constexpr uint16_t GC_ITEM_UPDATE = 0x0514;
inline constexpr uint16_t GC_ITEM_GROUND_ADD = 0x0515;
inline constexpr uint16_t GC_ITEM_GROUND_DEL = 0x0516;
inline constexpr uint16_t GC_ITEM_OWNERSHIP = 0x0517;
inline constexpr uint16_t GC_ITEM_GET = 0x0518;
inline constexpr uint16_t GC_SCRIPT = 0x0910; // NPC dialog EventManager script
inline constexpr uint16_t GC_QUEST_CONFIRM = 0x0911; // yes/no confirm request
inline constexpr uint16_t GC_QUEST_INFO = 0x0912; // quest-log entry (flag-driven)
inline constexpr uint16_t GC_CHAT = 0x0603;
inline constexpr uint16_t GC_WHISPER = 0x0604;
// P8 — social / shop / storage (server -> client)
inline constexpr uint16_t GC_EXCHANGE = 0x051C; // sub-header packet
inline constexpr uint16_t GC_REFINE_INFO = 0x051D; // refine dialog data
inline constexpr uint16_t GC_REFINE_INFO_NEW = 0x051E; // same layout as 0x051D
inline constexpr uint16_t GC_DRAGON_SOUL_REFINE = 0x051F; // sub-type + item pos
inline constexpr uint16_t GC_GUILD = 0x0730; // sub-header packet
inline constexpr uint16_t GC_REQUEST_MAKE_GUILD = 0x0731; // server asks client to create guild
inline constexpr uint16_t GC_SYMBOL_DATA = 0x0732; // guild symbol file bytes
inline constexpr uint16_t GC_PARTY_INVITE = 0x0710;
inline constexpr uint16_t GC_PARTY_ADD = 0x0711;
inline constexpr uint16_t GC_PARTY_UPDATE = 0x0712;
inline constexpr uint16_t GC_PARTY_REMOVE = 0x0713;
inline constexpr uint16_t GC_PARTY_LINK = 0x0714; // pid <-> vid binding
inline constexpr uint16_t GC_PARTY_UNLINK = 0x0715;
inline constexpr uint16_t GC_PARTY_PARAMETER = 0x0716; // exp distribution mode
inline constexpr uint16_t GC_MESSENGER = 0x0741; // sub-header + var name list
inline constexpr uint16_t GC_LOVER_INFO = 0x0750;
inline constexpr uint16_t GC_LOVE_POINT_UPDATE = 0x0751;
inline constexpr uint16_t GC_SHOP = 0x0810; // sub-header packet
inline constexpr uint16_t GC_SHOP_SIGN = 0x0811;
inline constexpr uint16_t GC_SAFEBOX_SET = 0x0830; // body = GCItemSet
inline constexpr uint16_t GC_SAFEBOX_DEL = 0x0831; // body = GCItemDel
inline constexpr uint16_t GC_SAFEBOX_WRONG_PASSWORD = 0x0832;
inline constexpr uint16_t GC_SAFEBOX_SIZE = 0x0833; // open: page count
inline constexpr uint16_t GC_SAFEBOX_MONEY_CHANGE = 0x0834;
inline constexpr uint16_t GC_MALL_OPEN = 0x0841; // {u8 size}
inline constexpr uint16_t GC_MALL_SET = 0x0842; // body = GCItemSet (pos.cell)
inline constexpr uint16_t GC_MALL_DEL = 0x0843; // body = GCItemDel (pos.cell)
// TPacketGC*Sub subheaders (UserInterface/Packet.h namespaces).
enum : uint8_t { // MessengerSub::GC
MESSENGER_GC_LIST = 0,
MESSENGER_GC_LOGIN = 1,
MESSENGER_GC_LOGOUT = 2,
MESSENGER_GC_INVITE = 3,
MESSENGER_GC_REMOVE_FRIEND = 4,
};
enum : uint8_t { // MessengerSub::CG
MESSENGER_CG_ADD_BY_VID = 0,
MESSENGER_CG_ADD_BY_NAME = 1,
MESSENGER_CG_REMOVE = 2,
MESSENGER_CG_INVITE_ANSWER = 3,
};
enum : uint8_t { // ShopSub::GC
SHOP_GC_START = 0,
SHOP_GC_END = 1,
SHOP_GC_UPDATE_ITEM = 2,
SHOP_GC_UPDATE_PRICE = 3,
SHOP_GC_OK = 4,
SHOP_GC_NOT_ENOUGH_MONEY = 5,
SHOP_GC_SOLDOUT = 6,
SHOP_GC_INVENTORY_FULL = 7,
SHOP_GC_INVALID_POS = 8,
SHOP_GC_START_EX = 10,
};
enum : uint8_t { // ShopSub::CG
SHOP_CG_END = 0,
SHOP_CG_BUY = 1,
SHOP_CG_SELL = 2,
SHOP_CG_SELL2 = 3,
};
enum : uint8_t { // ExchangeSub (CG and GC share these low values)
EXCHANGE_SUB_START = 0,
EXCHANGE_SUB_ITEM_ADD = 1,
EXCHANGE_SUB_ITEM_DEL = 2,
EXCHANGE_SUB_ELK_ADD = 3,
EXCHANGE_SUB_ACCEPT = 4,
EXCHANGE_SUB_CANCEL = 5, // CG only
EXCHANGE_GC_END = 5, // GC only (overlaps CANCEL slot)
EXCHANGE_GC_ALREADY = 6,
EXCHANGE_GC_LESS_ELK = 7,
};
// TPacketGCPartyUpdate::state bits (PARTY_ROLE_*: 1=normal, then leader flag etc.)
inline constexpr int PARTY_AFFECT_SLOT_MAX_NUM = 7;
inline constexpr int SHOP_HOST_ITEM_MAX_NUM = 40;
inline constexpr int SHOP_SIGN_MAX_LEN = 32;
inline constexpr int SHOP_TAB_NAME_MAX = 32; // Packet.h SHOP_TAB_NAME_MAX
inline constexpr int SHOP_TAB_COUNT_MAX = 3; // Packet.h SHOP_TAB_COUNT_MAX
inline constexpr int SAFEBOX_MAX_NUM = 135; // up to 3 pages x 45
inline constexpr int MALL_MAX_NUM = 135; // item-mall storage, same page geometry
inline constexpr int PRIVATE_SHOP_ITEM_MAX_NUM = 39; // TPacketCGMyShop::bCount cap
inline constexpr int DRAGON_SOUL_MAX_NUM = 180; // 6 groups x 30
// Guild-mark image geometry (UserInterface/MarkImage.h). One 512x512 RGBA image
// holds 8x10 blocks of 64x48 px; each block = 4x4 guild marks of 16x12 px.
inline constexpr int GUILD_MARK_WIDTH = 16;
inline constexpr int GUILD_MARK_HEIGHT = 12;
inline constexpr int MARK_BLOCK_WIDTH = 64;
inline constexpr int MARK_BLOCK_HEIGHT = 48;
inline constexpr int MARK_BLOCK_PIXELS = MARK_BLOCK_WIDTH * MARK_BLOCK_HEIGHT; // 3072
inline constexpr int MARK_IMAGE_WIDTH = 512;
inline constexpr int MARK_IMAGE_HEIGHT = 512;
inline constexpr int MARK_IMAGE_PIXELS = MARK_IMAGE_WIDTH * MARK_IMAGE_HEIGHT;
inline constexpr int MARK_BLOCK_COL_COUNT = 8; // 512 / 64
inline constexpr int MARK_BLOCK_ROW_COUNT = 10; // 512 / 48 (rounded down)
inline constexpr int MARK_BLOCK_TOTAL_COUNT = MARK_BLOCK_COL_COUNT * MARK_BLOCK_ROW_COUNT; // 80
inline constexpr int MARK_COL_COUNT = 32; // guild marks per image row (8 * 4)
inline constexpr int MARK_ROW_COUNT = 40; // guild marks per image col (10 * 4)
inline constexpr int MARK_PER_IMAGE = MARK_COL_COUNT * MARK_ROW_COUNT; // 1280
inline constexpr int MARK_IMAGE_MAX_COUNT = 5;
// TPacketGCQuestInfo::flag bits (Packet.h).
enum : uint8_t {
QUEST_SEND_IS_BEGIN = 1 << 0,
QUEST_SEND_TITLE = 1 << 1, // <= 28 chars
QUEST_SEND_CLOCK_NAME = 1 << 2, // <= 16
QUEST_SEND_CLOCK_VALUE = 1 << 3, // int32
QUEST_SEND_COUNTER_NAME = 1 << 4, // <= 16
QUEST_SEND_COUNTER_VALUE = 1 << 5,// int32
QUEST_SEND_ICON_FILE = 1 << 6, // <= 24
};
inline constexpr uint16_t GC_TARGET_INFO = 0x0A10; // selected target's HP %
inline constexpr uint16_t GC_TARGET_UPDATE = 0x0A11; // move a world marker
inline constexpr uint16_t GC_TARGET_DELETE = 0x0A12; // remove a world marker
inline constexpr uint16_t GC_TARGET_CREATE = 0x0A13; // quest/world target marker (New form)
inline constexpr uint16_t GC_AFFECT_ADD = 0x0A20;
inline constexpr uint16_t GC_AFFECT_REMOVE = 0x0A21;
inline constexpr uint16_t GC_SPECIAL_EFFECT = 0x0A30; // vid plays effect <type id>
inline constexpr uint16_t GC_SPECIFIC_EFFECT = 0x0A31; // vid plays effect <.mse path>
inline constexpr uint16_t GC_NPC_POSITION = 0x0A50; // atlas/minimap NPC list
inline constexpr uint16_t GC_MOUNT = 0x0A40; // vid mounts/dismounts mount_vnum
// P9 — world systems (server -> client)
inline constexpr uint16_t CG_FISHING = 0x0B01;
inline constexpr uint16_t CG_DUNGEON = 0x0B02;
inline constexpr uint16_t GC_FISHING = 0x0B10;
inline constexpr uint16_t GC_DUNGEON = 0x0B11;
inline constexpr uint16_t GC_LAND_LIST = 0x0B12; // guild territory list
inline constexpr uint16_t GC_TIME = 0x0B13; // server epoch for the day/night clock
inline constexpr uint16_t GC_CHANNEL = 0x0B14; // which channel we're on
inline constexpr uint16_t GC_MARK_UPDATE = 0x0B15; // main conn: a guild mark changed -> re-download
inline constexpr uint16_t GC_OBSERVER_ADD = 0x0B20; // minimap observer add
inline constexpr uint16_t GC_OBSERVER_REMOVE = 0x0B21; // minimap observer remove
inline constexpr uint16_t GC_OBSERVER_MOVE = 0x0B22; // minimap observer move
// Guild-mark side connection (0x0C00-0x0CFF). A separate stream with its own KX;
// GC_MARK_* here use a u32 buf_size, NOT the u16 frame length, for the body.
inline constexpr uint16_t GC_MARK_IDXLIST = 0x0C11; // guild_id -> mark_id table
inline constexpr uint16_t GC_MARK_BLOCK = 0x0C10; // LZO-compressed 64x48 image blocks
inline constexpr uint16_t GC_MARK_DIFF_DATA = 0x0C12; // (ignored)
inline constexpr uint16_t CG_MARK_LOGIN = 0x0C01; // {handle, random_key} from GC_LOGIN_SUCCESS*
inline constexpr uint16_t CG_MARK_CRCLIST = 0x0C02; // {img_idx, crc[80]} - request an image (0 = full)
inline constexpr uint16_t CG_MARK_UPLOAD = 0x0C03; // {gid, u8 image[16*12*4]} raw 16x12 mark
inline constexpr uint16_t CG_MARK_IDXLIST = 0x0C04; // request the guild_id -> mark_id table
inline constexpr uint16_t CG_GUILD_SYMBOL_UPLOAD = 0x0722; // {handle=gid} + raw symbol file bytes
// TPacketGCTargetCreateNew::byType (CREATE_TARGET_TYPE_*).
enum : uint8_t {
CREATE_TARGET_TYPE_NONE = 0,
CREATE_TARGET_TYPE_LOCATION = 1,
CREATE_TARGET_TYPE_CHARACTER = 2,
};
// EChatType (Packet.h). BIG_NOTICE etc. beyond SHOUT are rendered as NOTICE.
enum : uint8_t {
CHAT_TYPE_TALKING = 0,
CHAT_TYPE_INFO = 1, // "you picked up X", exp/gold changes
CHAT_TYPE_NOTICE = 2,
CHAT_TYPE_PARTY = 3,
CHAT_TYPE_GUILD = 4,
CHAT_TYPE_COMMAND = 5,
CHAT_TYPE_SHOUT = 6,
CHAT_TYPE_WHISPER = 7, // client-only marker
CHAT_TYPE_BIG_NOTICE = 8,
};
// TPacketGCWhisper::bType (PythonChat.h)
enum : uint8_t {
WHISPER_TYPE_CHAT = 0,
WHISPER_TYPE_SYSTEM = 1,
WHISPER_TYPE_GM = 2,
};
// EWindows (GameType.h) — TItemPos.window_type
enum : uint8_t {
WINDOW_INVENTORY = 1,
WINDOW_EQUIPMENT = 2,
WINDOW_SAFEBOX = 3,
WINDOW_MALL = 4,
WINDOW_DRAGON_SOUL = 5,
WINDOW_GROUND = 6,
WINDOW_BELT = 7,
};
inline constexpr int ITEM_SOCKET_SLOT_MAX_NUM = 3; // GameType.h
inline constexpr int ITEM_ATTRIBUTE_SLOT_MAX_NUM = 7;
inline constexpr int INVENTORY_MAX_NUM = 90; // 45 x 2 pages
// Packet.h retains the 11 legacy wear entries, but the enabled costume / new
// equipment runtime uses positions 0..23 (body through belt). On the wire the
// equipment cells are normally INVENTORY_MAX_NUM + wear.
inline constexpr int WEAR_MAX_NUM = 24;
inline constexpr int EQUIPMENT_SLOT_START = INVENTORY_MAX_NUM;
inline constexpr int EQUIPMENT_SLOT_RESERVED_MAX_NUM = 32;
// GameType.h: 90 + 32 reserved wear + 12 active DragonSoul + 18 reserved DS.
inline constexpr int BELT_INVENTORY_SLOT_START = EQUIPMENT_SLOT_START +
EQUIPMENT_SLOT_RESERVED_MAX_NUM + 6 * 2 + 6 * 3; // 152
inline constexpr int BELT_INVENTORY_MAX_NUM = 16; // 4 x 4
// EPointTypes (UserInterface/Packet.h) — the ones we surface.
enum : uint8_t {
POINT_LEVEL = 1,
POINT_EXP = 3,
POINT_NEXT_EXP = 4,
POINT_HP = 5,
POINT_MAX_HP = 6,
POINT_SP = 7,
POINT_MAX_SP = 8,
POINT_STAMINA = 9,
POINT_MAX_STAMINA = 10,
POINT_GOLD = 11,
POINT_ATT_SPEED = 17,
POINT_SUB_SKILL = 27, // 辅助/被动技能点 SKILL_SUB — uicharacter.py skillPageStatDict["SUPPORT"]
POINT_SKILL = 28, // 主动技能点 SKILL — skillPageStatDict["ACTIVE"]
POINT_BOW_DISTANCE = 34, // 활 사정거리 증가치 — __GetBowRange() bonus
POINT_HORSE_SKILL = 113, // 马术技能点 — skillPageStatDict["HORSE"] (Packet.h:1641)
POINT_ENERGY = 128,
POINT_ENERGY_END_TIME = 129,
};
inline constexpr int POINT_MAX_NUM = 255; // StdAfx.h
inline constexpr int SKILL_MAX_NUM = 255; // Packet.h
inline constexpr int QUEST_INPUT_STRING_MAX_NUM = 64; // Packet.h
enum : uint8_t {
FISHING_START = 0,
FISHING_STOP = 1,
FISHING_REACT = 2,
FISHING_SUCCESS = 3,
FISHING_FAIL = 4,
FISHING_FISH = 5,
};
enum : uint8_t {
DUNGEON_TIME_ATTACK_START = 0,
DUNGEON_DESTINATION_POSITION = 1,
};
// (FUNC_SKILL = 0x80 lives in entity_store.h's FUNC_* enum; CG_MOVE.func =
// FUNC_SKILL | skill_motion_idx to cast.)
// TPacketGCDamageInfo::flag bits (InstanceBase.h EDamageFlag).
enum : uint8_t {
DAMAGE_NORMAL = 1 << 0,
DAMAGE_POISON = 1 << 1,
DAMAGE_DODGE = 1 << 2,
DAMAGE_BLOCK = 1 << 3,
DAMAGE_PENETRATE = 1 << 4,
DAMAGE_CRITICAL = 1 << 5,
};
// Game phase — client -> server
inline constexpr uint16_t CG_SYNC_POSITION = 0x0303;
inline constexpr uint16_t CG_MOVE = 0x0301;
inline constexpr uint16_t CG_WARP = 0x0305;
inline constexpr uint16_t CG_ATTACK = 0x0401;
inline constexpr uint16_t CG_USE_SKILL = 0x0402;
inline constexpr uint16_t CG_SHOOT = 0x0403;
inline constexpr uint16_t CG_FLY_TARGETING = 0x0404;
inline constexpr uint16_t CG_ADD_FLY_TARGETING = 0x0405;
inline constexpr uint16_t CG_ITEM_USE = 0x0501;
inline constexpr uint16_t CG_ITEM_DROP = 0x0502;
inline constexpr uint16_t CG_ITEM_DROP2 = 0x0503;
inline constexpr uint16_t CG_ITEM_MOVE = 0x0504;
inline constexpr uint16_t CG_ITEM_PICKUP = 0x0505;
inline constexpr uint16_t CG_ITEM_USE_TO_ITEM = 0x0506;
inline constexpr uint16_t CG_ITEM_GIVE = 0x0507;
inline constexpr uint16_t CG_QUICKSLOT_ADD = 0x0509;
inline constexpr uint16_t CG_QUICKSLOT_DEL = 0x050A;
inline constexpr uint16_t CG_QUICKSLOT_SWAP = 0x050B;
inline constexpr uint16_t CG_CHAT = 0x0601;
inline constexpr uint16_t CG_WHISPER = 0x0602;
inline constexpr uint16_t CG_SCRIPT_ANSWER = 0x0901; // dialog choice (or 255 = continue)
inline constexpr uint16_t CG_SCRIPT_BUTTON = 0x0902; // quest-log button
inline constexpr uint16_t CG_SCRIPT_SELECT_ITEM = 0x0903;
inline constexpr uint16_t CG_QUEST_INPUT_STRING = 0x0904;
inline constexpr uint16_t CG_QUEST_CONFIRM = 0x0905;
inline constexpr uint16_t CG_QUEST_CANCEL = 0x0906;
inline constexpr uint16_t CG_ON_CLICK = 0x0A02; // click an NPC / interactable
inline constexpr uint16_t CG_TARGET = 0x0A01;
inline constexpr uint16_t CG_CHARACTER_POSITION = 0x0A60;
// P8 — social / shop / storage (client -> server)
inline constexpr uint16_t CG_EXCHANGE = 0x0508;
inline constexpr uint16_t CG_REFINE = 0x050C; // {pos, type}
inline constexpr uint16_t CG_DRAGON_SOUL_REFINE = 0x050D; // {sub_type, ItemPos grid[15]}
// DragonSoulSub (UserInterface/Packet.h) — GC sub-type + the CG "do" mode.
enum : uint8_t {
DS_SUB_OPEN = 0,
DS_SUB_CLOSE = 1,
DS_SUB_DO_UPGRADE = 2,
DS_SUB_DO_IMPROVEMENT = 3,
DS_SUB_DO_REFINE = 4,
DS_SUB_REFINE_FAIL = 5,
DS_SUB_REFINE_FAIL_MAX_REFINE = 6,
DS_SUB_REFINE_FAIL_INVALID_MATERIAL = 7,
DS_SUB_REFINE_FAIL_NOT_ENOUGH_MONEY = 8,
DS_SUB_REFINE_FAIL_NOT_ENOUGH_MATERIAL = 9,
DS_SUB_REFINE_FAIL_TOO_MUCH_MATERIAL = 10,
DS_SUB_REFINE_SUCCEED = 11,
};
inline constexpr int DS_REFINE_WINDOW_MAX_NUM = 15;
inline constexpr uint16_t CG_GUILD = 0x0720; // sub-header packet
inline constexpr uint16_t CG_ANSWER_MAKE_GUILD = 0x0721; // {guild_name[13]}
inline constexpr uint16_t CG_SYMBOL_CRC = 0x0723; // guild symbol CRC/size query
// GuildSub::GC subheaders (UserInterface/Packet.h).
enum : uint8_t {
GUILD_GC_LOGIN = 0,
GUILD_GC_LOGOUT = 1,
GUILD_GC_LIST = 2,
GUILD_GC_GRADE = 3,
GUILD_GC_ADD = 4,
GUILD_GC_REMOVE = 5,
GUILD_GC_GRADE_NAME = 6,
GUILD_GC_GRADE_AUTH = 7,
GUILD_GC_INFO = 8,
GUILD_GC_COMMENTS = 9,
GUILD_GC_CHANGE_EXP = 10,
GUILD_GC_CHANGE_MEMBER_GRADE = 11,
GUILD_GC_SKILL_INFO = 12,
GUILD_GC_CHANGE_MEMBER_GENERAL = 13,
GUILD_GC_GUILD_INVITE = 14,
GUILD_GC_WAR = 15,
GUILD_GC_NAME = 16,
GUILD_GC_GUILD_WAR_LIST = 17, // active GvG pairs {u32 src, u32 dst}[]
GUILD_GC_GUILD_WAR_END_LIST = 18, // pairs to drop
GUILD_GC_WAR_POINT = 19, // GuildWarPoint
GUILD_GC_MONEY_CHANGE = 20,
};
// EGuildWarState (UserInterface/Packet.h).
enum : uint8_t {
GUILD_WAR_NONE = 0,
GUILD_WAR_SEND_DECLARE = 1,
GUILD_WAR_REFUSE = 2,
GUILD_WAR_RECV_DECLARE = 3,
GUILD_WAR_WAIT_START = 4,
GUILD_WAR_CANCEL = 5,
GUILD_WAR_ON_WAR = 6,
GUILD_WAR_END = 7,
};
inline constexpr int GUILD_SKILL_MAX_NUM = 12; // PythonGuild.h
enum : uint8_t { // GuildSub::CG
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_INVITE_ANSWER = 11,
GUILD_CG_CHARGE_GSP = 12,
GUILD_CG_DEPOSIT_MONEY = 13,
GUILD_CG_WITHDRAW_MONEY = 14,
};
inline constexpr int GUILD_GRADE_NAME_MAX_LEN = 8;
inline constexpr int REFINE_MATERIAL_MAX_NUM = 5;
inline constexpr uint16_t CG_PARTY_INVITE = 0x0701;
inline constexpr uint16_t CG_PARTY_INVITE_ANSWER = 0x0702;
inline constexpr uint16_t CG_PARTY_REMOVE = 0x0703; // expel / leave
inline constexpr uint16_t CG_PARTY_SET_STATE = 0x0704;
inline constexpr uint16_t CG_PARTY_USE_SKILL = 0x0705;
inline constexpr uint16_t CG_PARTY_PARAMETER = 0x0706;
inline constexpr uint16_t CG_MESSENGER = 0x0740;
inline constexpr uint16_t CG_SHOP = 0x0801;
inline constexpr uint16_t CG_MYSHOP = 0x0802; // open a private (PC) shop
inline constexpr uint16_t CG_SAFEBOX_CHECKIN = 0x0820; // inv -> safebox
inline constexpr uint16_t CG_SAFEBOX_CHECKOUT = 0x0821; // safebox -> inv
inline constexpr uint16_t CG_SAFEBOX_ITEM_MOVE = 0x0822; // within safebox
inline constexpr uint16_t CG_MALL_CHECKOUT = 0x0840; // item-mall -> inventory
#pragma pack(push, 1)
struct PacketHeaderPeek {
uint16_t header;
};
struct DynHeader {
uint16_t header;
uint16_t length;
};
struct GCPhase {
uint16_t header;
uint16_t length;
uint8_t phase;
};
struct GCPing {
uint16_t header;
uint16_t length;
uint32_t server_time;
};
struct CGPong {
uint16_t header;
uint16_t length;
};
struct GCKeyChallenge {
uint16_t header;
uint16_t length;
uint8_t server_pk[32];
uint8_t challenge[32];
uint32_t server_time;
};
struct CGKeyResponse {
uint16_t header;
uint16_t length;
uint8_t client_pk[32];
uint8_t challenge_response[32];
};
struct GCKeyComplete {
uint16_t header;
uint16_t length;
uint8_t encrypted_token[32 + 16]; // AEAD ciphertext + tag
uint8_t nonce[24];
};
// auth server: sent when server enters PHASE_AUTH
struct CGLogin3 {
uint16_t header;
uint16_t length;
char name[ID_MAX_NUM + 1];
char pwd[PASS_MAX_NUM + 1];
};
// auth server result (GC_AUTH_SUCCESS on ok, GC_LOGIN_FAILURE otherwise)
struct GCAuthSuccess {
uint16_t header;
uint16_t length;
uint32_t login_key;
uint8_t result;
};
// game server: name + the login_key handed back by the auth server
struct CGLogin2 {
uint16_t header;
uint16_t length;
char name[ID_MAX_NUM + 1];
uint32_t login_key;
};
// UserInterface/Packet.h TSimplePlayerInformation (pack(1)) — one per char slot.
struct SimplePlayerInfo {
uint32_t id;
char name[CHARACTER_NAME_MAX_LEN + 1];
uint8_t job;
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;
uint32_t addr;
uint16_t port;
uint8_t skill_group;
};
// GC_LOGIN_SUCCESS3 — the character list for a 3-slot account.
struct GCLoginSuccess3 {
uint16_t header;
uint16_t length;
SimplePlayerInfo players[PLAYER_PER_ACCOUNT3];
uint32_t guild_id[PLAYER_PER_ACCOUNT3];
char guild_name[PLAYER_PER_ACCOUNT3][GUILD_NAME_MAX_LEN + 1];
uint32_t handle;
uint32_t random_key;
};
// GC_LOGIN_SUCCESS4 — the character list for a 4-slot account (some servers).
struct GCLoginSuccess4 {
uint16_t header;
uint16_t length;
SimplePlayerInfo players[PLAYER_PER_ACCOUNT4];
uint32_t guild_id[PLAYER_PER_ACCOUNT4];
char guild_name[PLAYER_PER_ACCOUNT4][GUILD_NAME_MAX_LEN + 1];
uint32_t handle;
uint32_t random_key;
};
struct CGCharacterSelect {
uint16_t header;
uint16_t length;
uint8_t player_index;
};
// CG_CHARACTER_CREATE — TPacketCGCreateCharacter. NOTE job is uint16 in the fork.
struct CGCreateCharacter {
uint16_t header;
uint16_t length;
uint8_t index;
char name[CHARACTER_NAME_MAX_LEN + 1];
uint16_t job;
uint8_t shape;
uint8_t con;
uint8_t intel;
uint8_t str;
uint8_t dex;
};
// CG_CHARACTER_DELETE — TPacketCGDestroyCharacter.
struct CGDeleteCharacter {
uint16_t header;
uint16_t length;
uint8_t index;
char private_code[PRIVATE_CODE_LENGTH];
};
// CG_CHANGE_NAME — rename a character slot (the server validates the card).
struct CGChangeName {
uint16_t header;
uint16_t length;
uint8_t index;
char name[CHARACTER_NAME_MAX_LEN + 1];
};
// GC_CHANGE_NAME — broadcast the resulting name by player id.
struct GCChangeName {
uint16_t header;
uint16_t length;
uint32_t pid;
char name[CHARACTER_NAME_MAX_LEN + 1];
};
// GC_PLAYER_CREATE_SUCCESS — TPacketGCPlayerCreateSuccess (slot + SimplePlayerInfo).
struct GCPlayerCreateSuccess {
uint16_t header;
uint16_t length;
uint8_t slot;
SimplePlayerInfo player;
};
// GC_PLAYER_CREATE_FAILURE — TPacketGCCreateFailure ({u8 type}).
struct GCCreateFailure {
uint16_t header;
uint16_t length;
uint8_t type;
};
// GC_PLAYER_DELETE_SUCCESS — TPacketGCDestroyCharacterSuccess ({u8 account_index}).
struct GCDeleteSuccess {
uint16_t header;
uint16_t length;
uint8_t account_index;
};
// CG_CLIENT_VERSION — TPacketCGClientVersion. The server validates this after
// GC_MAIN_CHARACTER; without it the connection is dropped before PHASE_GAME.
struct CGClientVersion {
uint16_t header;
uint16_t length;
char filename[33];
char timestamp[33];
};
// ---- game phase ---------------------------------------------------------------
// GC_MAIN_CHARACTER — spawn info for the local player (name, position, BGM).
struct GCMainCharacter {
uint16_t header;
uint16_t length;
uint32_t vid;
uint16_t race;
char name[CHARACTER_NAME_MAX_LEN + 1];
char bgm[MUSIC_NAME_MAX_LEN + 1];
float bgm_vol;
int32_t x, y, z;
uint8_t empire;
uint8_t skill_group;
};
// GC_CHARACTER_ADD — a nearby entity appears. Name/level/guild/mount arrive in
// a following GC_CHAR_ADD_INFO; `type` is 0 for NPCs/mobs on this fork (classify
// by race via mob_proto). Matches TPacketGCCharacterAdd (//comment fields are
// NOT on the wire).
struct GCCharacterAdd {
uint16_t header;
uint16_t length;
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];
};
// GC_CHAR_ADD_INFO — TPacketGCCharacterAdditionalInfo. Sent right after
// GC_CHARACTER_ADD to fill in name / equipment / level / guild / mount.
struct GCCharAddInfo {
uint16_t header;
uint16_t length;
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;
};
// GC_CHARACTER_UPDATE — TPacketGCCharacterUpdate. Equipment / speed / guild /
// alignment / mount changed for an entity already in view.
struct GCCharacterUpdate {
uint16_t header;
uint16_t length;
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;
};
// GC_CHARACTER_ADD2 — the fat variant with name + equipment.
struct GCCharacterAdd2 {
uint16_t header;
uint16_t length;
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;
};
struct GCCharacterDel {
uint16_t header;
uint16_t length;
uint32_t vid;
};
// GC_MOVE — an entity's movement command (server-authoritative).
struct GCMove {
uint16_t header;
uint16_t length;
uint8_t func;
uint8_t arg;
uint8_t rot;
uint32_t vid;
int32_t x, y;
uint32_t time;
uint32_t duration;
};
// Server-authoritative movement metadata and batched position correction.
struct GCCharacterPosition {
uint16_t header;
uint16_t length;
uint32_t vid;
uint8_t position;
};
struct GCChangeSpeed {
uint16_t header;
uint16_t length;
uint32_t vid;
uint16_t moving_speed;
};
struct GCWalkMode {
uint16_t header;
uint16_t length;
uint32_t vid;
uint8_t mode;
};
struct GCSyncPosition {
uint16_t header;
uint16_t length;
};
struct GCSyncPositionElement {
uint32_t vid;
int32_t x;
int32_t y;
};
// GC_CHAT — fixed head; szChat text follows (length - sizeof(GCChat) bytes).
struct GCChat {
uint16_t header;
uint16_t length;
uint8_t type;
uint32_t vid;
uint8_t empire;
};
// GC_WHISPER — fixed head; message text follows (length - sizeof(GCWhisper)).
struct GCWhisper {
uint16_t header;
uint16_t length;
uint8_t type; // WHISPER_TYPE_*
char from_name[CHARACTER_NAME_MAX_LEN + 1];
};
// GC_SCRIPT — NPC dialog. EventManager script text follows (src_size bytes).
struct GCScript {
uint16_t header;
uint16_t length;
uint8_t skin;
uint16_t src_size;
};
// GC_QUEST_CONFIRM — a yes/no prompt with a timeout.
struct GCQuestConfirm {
uint16_t header;
uint16_t length;
char msg[65];
int32_t timeout;
uint32_t request_pid;
};
// GC_QUEST_INFO — fixed head; then flag-driven fields (see QUEST_SEND_*).
struct GCQuestInfo {
uint16_t header;
uint16_t length;
uint16_t index;
uint8_t flag;
};
// --- CG quest/script intents ---
struct CGOnClick {
uint16_t header;
uint16_t length;
uint32_t vid;
};
struct CGScriptAnswer {
uint16_t header;
uint16_t length;
uint8_t answer;
};
struct CGScriptButton {
uint16_t header;
uint16_t length;
uint32_t idx;
};
struct CGScriptSelectItem {
uint16_t header;
uint16_t length;
uint32_t selection;
};
struct CGQuestInputString {
uint16_t header;
uint16_t length;
char str[QUEST_INPUT_STRING_MAX_NUM + 1];
};
struct CGQuestConfirm {
uint16_t header;
uint16_t length;
uint8_t answer;
uint32_t request_pid;
};
struct CGQuestCancel {
uint16_t header;
uint16_t length;
};
// TPacketAffectElement — one buff/debuff on the local player.
struct AffectElement {
uint32_t type; // affect id (often a skill vnum)
uint8_t point_idx; // EPointTypes it modifies
int32_t apply_value;
uint32_t flag;
int32_t duration; // seconds
int32_t sp_cost;
};
struct GCAffectAdd {
uint16_t header;
uint16_t length;
AffectElement elem;
};
struct GCAffectRemove {
uint16_t header;
uint16_t length;
uint32_t type;
uint8_t apply_on;
};
// GC_SPECIAL_EFFECT — vid plays a built-in effect identified by a type id.
struct GCSpecialEffect {
uint16_t header;
uint16_t length;
uint8_t type;
uint32_t vid;
};
// GC_SPECIFIC_EFFECT — vid plays the effect at <effect_file> (a .mse path).
struct GCSpecificEffect {
uint16_t header;
uint16_t length;
uint32_t vid;
char effect_file[128];
};
// GC_SKILL_LEVEL — the local player's whole skill->level table (0 = don't have).
struct GCSkillLevel {
uint16_t header;
uint16_t length;
uint8_t levels[SKILL_MAX_NUM];
};
// GC_SKILL_LEVEL_NEW — {master_type, level, next_read}[255]. The fork's on-wire
// TPlayerSkill uses a 32-bit time_t, so the record is 6 bytes (not 10).
struct PlayerSkill {
uint8_t master_type; // 0 normal / 1 master / 2 grand master / 3 perfect master
uint8_t level;
uint32_t next_read; // skill-book read cooldown expiry (server epoch)
};
struct GCSkillLevelNew {
uint16_t header;
uint16_t length;
PlayerSkill skills[SKILL_MAX_NUM];
};
// TQuickSlot (GameType.h). Type: 0 none / 1 item / 2 skill / 3 command / 4 emotion.
struct QuickSlot {
uint8_t type;
uint8_t position;
};
struct GCQuickSlotAdd {
uint16_t header;
uint16_t length;
uint8_t pos;
QuickSlot slot;
};
struct GCQuickSlotDel {
uint16_t header;
uint16_t length;
uint8_t pos;
};
struct GCQuickSlotSwap {
uint16_t header;
uint16_t length;
uint8_t pos;
uint8_t change_pos;
};
// GC_SKILL_COOLTIME_END — skill `skill`'s cooldown has finished.
struct GCSkillCooltimeEnd {
uint16_t header;
uint16_t length;
uint8_t skill;
};
struct GCChangeSkillGroup {
uint16_t header;
uint16_t length;
uint8_t skill_group;
};
// GC_CREATE_FLY — a projectile of `type` from start_vid to end_vid.
struct GCCreateFly {
uint16_t header;
uint16_t length;
uint8_t type;
uint32_t start_vid;
uint32_t end_vid;
};
// GC_FLY_TARGETING / GC_ADD_FLY_TARGETING — a shooter locks (or appends) a
// target. Coordinates are server-space centimetres when target VID is zero.
struct GCFlyTargeting {
uint16_t header;
uint16_t length;
uint32_t shooter_vid;
uint32_t target_vid;
int32_t x;
int32_t y;
};
// GC_PLAYER_POINTS — the local player's full stat array (TPacketGCPoints).
// Index with EPointTypes (POINT_HP, POINT_MAX_HP, ...).
struct GCPlayerPoints {
uint16_t header;
uint16_t length;
int32_t points[POINT_MAX_NUM];
};
// GC_PLAYER_POINT_CHANGE — one point of one entity changed (TPacketGCPointChange).
struct GCPointChange {
uint16_t header;
uint16_t length;
uint32_t vid;
uint8_t type; // EPointTypes
int32_t amount; // delta
int32_t value; // new absolute value
};
// GC_STUN / GC_DEAD — an entity is stunned / died (TPacketGCStun / TPacketGCDead).
struct GCStun {
uint16_t header;
uint16_t length;
uint32_t vid;
};
struct GCDead {
uint16_t header;
uint16_t length;
uint32_t vid;
};
// GC_MOTION — vid plays <motion> (optionally at victim_vid) (TPacketGCMotion).
struct GCMotion {
uint16_t header;
uint16_t length;
uint32_t vid;
uint32_t victim_vid;
uint16_t motion;
};
// GC_DIG_MOTION — one mining action may repeat `count` times toward target_vid.
struct GCDigMotion {
uint16_t header;
uint16_t length;
uint32_t vid;
uint32_t target_vid;
uint8_t count;
};
// GC_DAMAGE_INFO — a floating damage number over an entity (TPacketGCDamageInfo).
struct GCDamageInfo {
uint16_t header;
uint16_t length;
uint32_t vid;
uint8_t flag; // EDamageFlag bits
int32_t damage;
};
// GC_TARGET_INFO — the currently-selected target's HP percentage (TPacketGCTarget).
struct GCTargetInfo {
uint16_t header;
uint16_t length;
uint32_t vid;
uint8_t hp_percent;
};
// --- items (UserInterface/Packet.h + GameType.h) ------------------------------
// SItemPos: which window + which cell. 3 bytes packed.
struct ItemPos {
uint8_t window_type;
uint16_t cell;
};
// TPlayerItemAttribute: bonus type + value. 3 bytes.
struct ItemAttr {
uint8_t type;
int16_t value;
};
// The inspect-equipment packet keeps the legacy 11 visible wear slots. This
// is intentionally distinct from this client's 24 local equipment slots.
inline constexpr int VIEW_EQUIP_WEAR_MAX_NUM = 11;
struct EquipmentItemSet {
uint32_t vnum;
uint8_t count;
int32_t sockets[ITEM_SOCKET_SLOT_MAX_NUM];
ItemAttr attrs[ITEM_ATTRIBUTE_SLOT_MAX_NUM];
};
struct GCViewEquip {
uint16_t header;
uint16_t length;
uint32_t vid;
EquipmentItemSet equips[VIEW_EQUIP_WEAR_MAX_NUM];
};
// GC_ITEM_SET — a slot's full contents (vnum 0 = the slot was cleared).
struct GCItemSet {
uint16_t header;
uint16_t length;
ItemPos pos;
uint32_t vnum;
uint8_t count;
uint32_t flags;
uint32_t anti_flags;
uint8_t highlight;
int32_t sockets[ITEM_SOCKET_SLOT_MAX_NUM];
ItemAttr attrs[ITEM_ATTRIBUTE_SLOT_MAX_NUM];
};
struct GCItemDel {
uint16_t header;
uint16_t length;
ItemPos pos;
};
// GC_ITEM_UPDATE — count / sockets / attrs of an existing slot changed.
struct GCItemUpdate {
uint16_t header;
uint16_t length;
ItemPos cell;
uint8_t count;
int32_t sockets[ITEM_SOCKET_SLOT_MAX_NUM];
ItemAttr attrs[ITEM_ATTRIBUTE_SLOT_MAX_NUM];
};
// GC_ITEM_USE — someone used an item (cosmetic / effect trigger).
struct GCItemUse {
uint16_t header;
uint16_t length;
ItemPos cell;
uint32_t ch_vid;
uint32_t victim_vid;
uint32_t vnum;
};
// GC_ITEM_GROUND_ADD / DEL — an item lying in the world.
struct GCItemGroundAdd {
uint16_t header;
uint16_t length;
int32_t x, y, z;
uint32_t vid;
uint32_t vnum;
};
struct GCItemGroundDel {
uint16_t header;
uint16_t length;
uint32_t vid;
};
struct GCItemOwnership {
uint16_t header;
uint16_t length;
uint32_t vid;
char owner[CHARACTER_NAME_MAX_LEN + 1];
};
// GC_ITEM_GET — pickup notification ("You picked up N x <vnum>").
struct GCItemGet {
uint16_t header;
uint16_t length;
uint32_t vnum;
uint8_t count;
uint8_t arg; // 0 normal, 1 from party member
char from_name[CHARACTER_NAME_MAX_LEN + 1];
};
// GC_PVP / DUEL_START — relation changes used by target / name-color rules.
struct GCPvp {
uint16_t header;
uint16_t length;
uint32_t src_vid;
uint32_t dst_vid;
uint8_t mode; // 0 none, 1 agree, 2 fight, 3 revenge
};
struct GCDuelStart {
uint16_t header;
uint16_t length;
};
struct GCLoverInfo {
uint16_t header;
uint16_t length;
char name[CHARACTER_NAME_MAX_LEN + 1];
uint8_t love_point;
};
struct GCLovePointUpdate {
uint16_t header;
uint16_t length;
uint8_t love_point;
};
// --- CG item intents ---
struct CGItemUse {
uint16_t header;
uint16_t length;
ItemPos pos;
};
struct CGItemMove {
uint16_t header;
uint16_t length;
ItemPos pos;
ItemPos change_pos;
uint8_t num;
};
struct CGItemDrop {
uint16_t header;
uint16_t length;
ItemPos pos;
uint32_t gold;
};
struct CGItemDrop2 {
uint16_t header;
uint16_t length;
ItemPos pos;
uint32_t gold;
uint8_t count;
};
struct CGItemUseToItem {
uint16_t header;
uint16_t length;
ItemPos source_pos;
ItemPos target_pos;
};
struct CGGiveItem {
uint16_t header;
uint16_t length;
uint32_t target_vid;
ItemPos pos;
uint8_t count;
};
struct CGItemPickUp {
uint16_t header;
uint16_t length;
uint32_t vid;
};
// CG_MOVE — the local player's movement intent.
struct CGMove {
uint16_t header;
uint16_t length;
uint8_t func;
uint8_t arg;
uint8_t rot;
int32_t x, y;
uint32_t time;
};
// CG_CHARACTER_POSITION — local posture/character-position update.
struct CGCharacterPosition {
uint16_t header;
uint16_t length;
uint8_t position;
};
// CG_SYNC_POSITION — batched position corrections for nearby victims/entities.
struct CGSyncPosition {
uint16_t header;
uint16_t length;
};
// CG_WARP — request a server-side teleport (destination is supplied by server).
struct CGWarp {
uint16_t header;
uint16_t length;
};
struct CGSyncPositionElement {
uint32_t vid;
int32_t x;
int32_t y;
};
struct CGAttack {
uint16_t header;
uint16_t length;
uint8_t type;
uint32_t victim_vid;
uint8_t crc_proc;
uint8_t crc_file;
};
// Skill activation keeps the original game's separate intent packets: a
// target's world position is supplied first, then the skill vnum and VID.
struct CGUseSkill {
uint16_t header;
uint16_t length;
uint32_t vnum;
uint32_t target_vid;
};
struct CGShoot {
uint16_t header;
uint16_t length;
uint8_t type;
};
struct CGFlyTargeting {
uint16_t header;
uint16_t length;
uint32_t target_vid;
int32_t x;
int32_t y;
};
struct CGQuickSlotAdd {
uint16_t header;
uint16_t length;
uint8_t pos;
QuickSlot slot;
};
struct CGQuickSlotDel {
uint16_t header;
uint16_t length;
uint8_t pos;
};
struct CGQuickSlotSwap {
uint16_t header;
uint16_t length;
uint8_t pos;
uint8_t change_pos;
};
struct CGTarget {
uint16_t header;
uint16_t length;
uint32_t vid;
};
// CG_CHAT — fixed head; text follows.
struct CGChat {
uint16_t header;
uint16_t length;
uint8_t type;
};
// CG_WHISPER — fixed head; message text follows.
struct CGWhisper {
uint16_t header;
uint16_t length;
char to_name[CHARACTER_NAME_MAX_LEN + 1];
};
// --- P8 party (UserInterface/Packet.h) ---------------------------------------
struct GCPartyInvite { // someone asks you to join their party
uint16_t header;
uint16_t length;
uint32_t leader_pid;
};
struct GCPartyAdd {
uint16_t header;
uint16_t length;
uint32_t pid;
char name[CHARACTER_NAME_MAX_LEN + 1];
};
struct GCPartyUpdate {
uint16_t header;
uint16_t length;
uint32_t pid;
uint8_t state; // role bits (bit0 leader in this fork's server)
uint8_t percent_hp; // 0..100
int16_t affects[PARTY_AFFECT_SLOT_MAX_NUM];
};
struct GCPartyRemove {
uint16_t header;
uint16_t length;
uint32_t pid;
};
struct GCPartyLink {
uint16_t header;
uint16_t length;
uint32_t pid;
uint32_t vid;
};
struct GCPartyUnlink {
uint16_t header;
uint16_t length;
uint32_t pid;
uint32_t vid;
};
struct GCPartyParameter {
uint16_t header;
uint16_t length;
uint8_t distribute_mode;
};
struct CGPartyInvite {
uint16_t header;
uint16_t length;
uint32_t vid;
};
struct CGPartyInviteAnswer {
uint16_t header;
uint16_t length;
uint32_t leader_pid;
uint8_t accept;
};
struct CGPartyRemove {
uint16_t header;
uint16_t length;
uint32_t pid;
};
struct CGPartyUseSkill {
uint16_t header;
uint16_t length;
uint8_t skill_index;
uint32_t target_vid;
};
struct CGPartyParameter {
uint16_t header;
uint16_t length;
uint8_t mode;
};
// TPacketCGPartySetState (Packet.h): assign/clear a party role for a member.
// dwVID field actually carries the member PID (matches uiparty.py). role = PARTY_ROLE_*.
struct CGPartySetState {
uint16_t header;
uint16_t length;
uint32_t pid;
uint8_t role;
uint8_t on; // byFlag: 1 = set this role, 0 = clear
};
// PythonPlayer.h EPartyRole
enum {
PARTY_ROLE_NORMAL = 0,
PARTY_ROLE_LEADER = 1,
PARTY_ROLE_ATTACKER = 2,
PARTY_ROLE_TANKER = 3,
PARTY_ROLE_BUFFER = 4,
PARTY_ROLE_SKILL_MASTER = 5,
PARTY_ROLE_BERSERKER = 6,
PARTY_ROLE_DEFENDER = 7,
PARTY_ROLE_MAX_NUM = 8,
};
// --- P8 messenger / shop / exchange (sub-header packets) --------------------
struct GCSubHeader { // GC_MESSENGER / GC_SHOP fixed head; body follows
uint16_t header;
uint16_t length;
uint8_t subheader;
};
struct CGSubHeader { // CG_MESSENGER / CG_SHOP fixed head; body follows
uint16_t header;
uint16_t length;
uint8_t subheader;
};
// packet_shop_item (GameType.h TShopItemData) — one row of an NPC shop.
struct ShopItem {
uint32_t vnum;
uint32_t price;
uint8_t count;
uint8_t display_pos;
int32_t sockets[ITEM_SOCKET_SLOT_MAX_NUM];
ItemAttr attrs[ITEM_ATTRIBUTE_SLOT_MAX_NUM];
};
// SHOP_GC_START body: u32 owner_vid, then ShopItem[SHOP_HOST_ITEM_MAX_NUM].
//
// SHOP_GC_START_EX body (Packet.h TPacketGCShopStartEx): u32 owner_vid, u8 tab_count,
// then tab_count x { ShopTabHead, ShopItem[SHOP_HOST_ITEM_MAX_NUM] }.
struct ShopTabHead {
char name[SHOP_TAB_NAME_MAX];
uint8_t coin_type;
};
// TPacketGCExchange — one exchange-window mutation (both directions carry it).
struct GCExchange {
uint16_t header;
uint16_t length;
uint8_t subheader;
uint8_t is_me;
uint32_t arg1; // START: partner vid; ITEM_ADD: vnum; ITEM_DEL/ACCEPT/ELK: value
ItemPos arg2; // ITEM_ADD: exchange slot index in .cell
uint32_t arg3; // ITEM_ADD: count
int32_t sockets[ITEM_SOCKET_SLOT_MAX_NUM];
ItemAttr attrs[ITEM_ATTRIBUTE_SLOT_MAX_NUM];
};
// TPacketCGExchange
struct CGExchange {
uint16_t header;
uint16_t length;
uint8_t subheader;
uint32_t arg1; // START: target vid; ELK_ADD: gold; ITEM_DEL: slot
uint8_t arg2; // ITEM_ADD: display pos
ItemPos pos; // ITEM_ADD: inventory position
};
// --- P8 safebox -----------------------------------------------------------
struct GCSafeboxSize {
uint16_t header;
uint16_t length;
uint8_t size; // page count
};
struct GCSafeboxMoneyChange {
uint16_t header;
uint16_t length;
int32_t money;
};
struct CGSafeboxCheckin { // inventory -> safebox
uint16_t header;
uint16_t length;
uint8_t safe_pos;
ItemPos inv_pos;
};
struct CGSafeboxCheckout { // safebox -> inventory
uint16_t header;
uint16_t length;
uint8_t safe_pos;
ItemPos inv_pos;
};
// CG_MALL_CHECKOUT — TPacketCGMallCheckout: pull one item-mall slot to inventory.
struct CGMallCheckout {
uint16_t header;
uint16_t length;
uint8_t mall_pos;
ItemPos inv_pos;
};
// GC_MALL_OPEN — TPacketGCMallOpen ({u8 size}). GC_MALL_SET/DEL reuse GCItemSet/GCItemDel.
struct GCMallOpen {
uint16_t header;
uint16_t length;
uint8_t size;
};
// CG_MYSHOP — TPacketCGMyShop fixed head; PRIVATE_SHOP_ITEM_MAX_NUM MyShopItem follow.
struct CGMyShopHead {
uint16_t header;
uint16_t length;
char sign[SHOP_SIGN_MAX_LEN + 1];
uint8_t count;
};
// TShopItemTable (PC-shop layout).
struct MyShopItem {
uint32_t vnum;
uint8_t count;
ItemPos pos; // source cell in the seller's inventory
uint32_t price;
uint8_t display_pos; // slot in the shop grid
};
// --- guild (GC_GUILD sub-header packets) ---------------------------------
// GUILD_GC_INFO body (TPacketGCGuildInfo).
struct GuildInfoBody {
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;
};
// GUILD_GC_LIST body = repeated GuildSubMember; when name_flag != 0 a char
// name[65] follows the fixed part.
struct GuildSubMember {
uint32_t pid;
uint8_t grade;
uint8_t is_general;
uint8_t job;
uint8_t level;
uint32_t offer;
uint8_t name_flag;
};
// GUILD_GC_GRADE body = [u8 count] then count x [u8 index][GuildSubGrade].
struct GuildSubGrade {
char grade_name[GUILD_GRADE_NAME_MAX_LEN + 1];
uint8_t auth_flag;
};
// GUILD_GC_SKILL_INFO body (CPythonGuild::TGuildSkillData): skill point, the 12
// guild-skill levels, then current / max guild point.
struct GuildSkillInfoBody {
uint8_t skill_point;
uint8_t skill_level[GUILD_SKILL_MAX_NUM];
uint16_t guild_point;
uint16_t max_guild_point;
};
// GUILD_GC_WAR body (TPacketGCGuildWar).
struct GCGuildWar {
uint32_t guild_self;
uint32_t guild_opp;
uint8_t type;
uint8_t war_state; // GUILD_WAR_*
};
// GUILD_GC_WAR_POINT body (TPacketGuildWarPoint).
struct GuildWarPoint {
uint32_t gain_guild_id;
uint32_t opp_guild_id;
int32_t point;
};
// GUILD_GC_GUILD_WAR_LIST / _END_LIST body element.
struct GuildWarPair {
uint32_t src;
uint32_t dst;
};
// GUILD_GC_NAME body element: {u32 id, char name[GUILD_NAME_MAX_LEN]} (no NUL).
struct GuildNameEntry {
uint32_t id;
char name[GUILD_NAME_MAX_LEN];
};
struct CGGuild { // sub-header + variable body
uint16_t header;
uint16_t length;
uint8_t subheader;
};
struct CGAnswerMakeGuild {
uint16_t header;
uint16_t length;
char guild_name[GUILD_NAME_MAX_LEN + 1];
};
// --- refine (GC_REFINE_INFORMATION[_NEW] / CG_REFINE) -------------------
struct RefineMaterial {
uint32_t vnum;
int32_t count;
};
struct RefineTable {
uint32_t src_vnum;
uint32_t result_vnum;
uint8_t material_count;
int32_t cost;
int32_t prob; // success %
RefineMaterial materials[REFINE_MATERIAL_MAX_NUM];
};
struct GCRefineInfo {
uint16_t header;
uint16_t length;
uint8_t type;
uint8_t pos; // inventory cell of the item being refined
RefineTable table;
};
struct CGRefine {
uint16_t header;
uint16_t length;
uint8_t pos;
uint8_t type;
};
// GC_DRAGON_SOUL_REFINE — sub_type + one item position (the affected DS on
// success/fail; unused for OPEN).
struct GCDragonSoulRefine {
uint16_t header;
uint16_t length;
uint8_t sub_type;
ItemPos pos;
};
// CG_DRAGON_SOUL_REFINE — sub_type = the mode; grid[0] = the DS, grid[1..] mats.
struct CGDragonSoulRefine {
uint16_t header;
uint16_t length;
uint8_t sub_type;
ItemPos grid[DS_REFINE_WINDOW_MAX_NUM];
};
// --- guild marks (side connection) --------------------------------------
struct CGMarkLogin {
uint16_t header;
uint16_t length;
uint32_t handle;
uint32_t random_key;
};
struct CGMarkIDXList {
uint16_t header;
uint16_t length;
};
struct CGMarkCRCList {
uint16_t header;
uint16_t length;
uint8_t img_idx;
uint32_t crclist[MARK_BLOCK_TOTAL_COUNT]; // all-zero = "send the whole image"
};
// CG_MARK_UPLOAD — a single raw 16x12 RGBA guild mark (native byte order).
struct CGMarkUpload {
uint16_t header;
uint16_t length;
uint32_t gid;
uint8_t image[GUILD_MARK_WIDTH * GUILD_MARK_HEIGHT * 4]; // 768
};
struct CGSymbolCRC {
uint16_t header;
uint16_t length;
uint32_t guild_id;
uint32_t crc;
uint32_t size;
};
struct GCSymbolData {
uint16_t header;
uint16_t length;
uint32_t guild_id;
};
// CG_GUILD_SYMBOL_UPLOAD — head only; the raw symbol file bytes follow.
// `handle` actually carries the guild id (reference misnames the field).
struct CGSymbolUpload {
uint16_t header;
uint16_t length;
uint32_t handle;
};
// Fixed head of GC_MARK_IDXLIST; buf_size counts the whole packet, then
// `count` x { uint16_t guild_id; uint16_t mark_id }.
struct GCMarkIDXList {
uint16_t header;
uint16_t length;
uint32_t buf_size;
uint16_t count;
};
// Fixed head of GC_MARK_BLOCK; buf_size counts the whole packet, then
// `count` x { uint8_t block_pos; uint32_t comp_size; uint8_t comp[comp_size] }.
struct GCMarkBlock {
uint16_t header;
uint16_t length;
uint32_t buf_size;
uint8_t img_idx;
uint32_t count;
};
// Main-connection push: a guild's mark image changed; re-download.
struct GCMarkUpdate {
uint16_t header;
uint16_t length;
uint32_t guild_id;
uint16_t img_idx;
};
// --- P9 world systems -----------------------------------------------------
struct CGFishing {
uint16_t header;
uint16_t length;
uint8_t dir;
};
struct GCFishing {
uint16_t header;
uint16_t length;
uint8_t subheader;
uint32_t info;
uint8_t dir;
};
struct CGDungeon {
uint16_t header;
uint16_t length;
};
struct GCDungeon {
uint16_t header;
uint16_t length;
uint8_t subheader;
};
// GC_LAND_LIST — fixed head followed by TLandPacketElement entries.
struct GCLandList {
uint16_t header;
uint16_t length;
};
struct LandPacketElement {
uint32_t id;
int32_t x, y;
int32_t width, height;
uint32_t guild_id;
};
// Observer markers are used by the legacy minimap. Coordinates are map units;
// the original client multiplies them by 100 before rendering.
struct GCObserverAdd {
uint16_t header;
uint16_t length;
uint32_t vid;
uint16_t x, y;
};
struct GCObserverMove {
uint16_t header;
uint16_t length;
uint32_t vid;
uint16_t x, y;
};
struct GCObserverRemove {
uint16_t header;
uint16_t length;
uint32_t vid;
};
// GC_WARP — teleport. addr/port 0 = same server (just move); nonzero = reconnect.
struct GCWarp {
uint16_t header;
uint16_t length;
int32_t x, y;
int32_t addr;
uint16_t port;
};
// GC_TIME — server wall-clock (unix epoch, seconds). Drives the day/night cycle.
struct GCTime {
uint16_t header;
uint16_t length;
int64_t time;
};
// GC_CHANNEL — the channel number we're playing on.
struct GCChannel {
uint16_t header;
uint16_t length;
uint8_t channel;
};
// GC_NPC_POSITION — fixed head; then `count` x TNPCPosition for the atlas/minimap.
struct GCNPCPosition {
uint16_t header;
uint16_t length;
uint16_t count;
};
struct NPCPositionEntry {
uint8_t type;
uint32_t vnum;
char name[CHARACTER_NAME_MAX_LEN + 1];
int32_t x, y;
};
// GC_TARGET_CREATE (New) — a quest/world marker (id + name + optional vid + type).
struct GCTargetCreate {
uint16_t header;
uint16_t length;
int32_t id;
char name[33];
uint32_t vid;
uint8_t type; // CREATE_TARGET_TYPE_*
};
// GC_TARGET_UPDATE — move marker `id` to (x, y).
struct GCTargetUpdate {
uint16_t header;
uint16_t length;
int32_t id;
int32_t x, y;
};
// GC_TARGET_DELETE — remove marker `id`.
struct GCTargetDelete {
uint16_t header;
uint16_t length;
int32_t id;
};
// GC_MOUNT — vid mounts (mount_vid != 0) or dismounts (TPacketGCMount).
struct GCMount {
uint16_t header;
uint16_t length;
uint32_t vid;
uint32_t mount_vid;
uint8_t pos;
uint32_t x, y;
};
#pragma pack(pop)
// Wire-layout guards. If any of these fire, the struct drifted from the client's
// #pragma pack(1) definition in UserInterface/Packet.h and the parser will read
// garbage. Verified 2026-08-31 against m2dev-client-src-main.
static_assert(sizeof(DynHeader) == 4);
static_assert(sizeof(GCKeyChallenge) == 72);
static_assert(sizeof(CGKeyResponse) == 68);
static_assert(sizeof(GCKeyComplete) == 76);
static_assert(sizeof(CGLogin3) == 52);
static_assert(sizeof(GCAuthSuccess) == 9);
static_assert(sizeof(CGLogin2) == 39);
static_assert(sizeof(SimplePlayerInfo) == 103);
static_assert(sizeof(GCLoginSuccess3) == 4 + 3 * 103 + 3 * 4 + 3 * 13 + 4 + 4); // 372
static_assert(sizeof(GCLoginSuccess4) == 4 + 4 * 103 + 4 * 4 + 4 * 13 + 4 + 4); // 492
static_assert(sizeof(CGCharacterSelect) == 5);
static_assert(sizeof(CGCreateCharacter) == 4 + 1 + 65 + 2 + 1 + 4); // 77
static_assert(sizeof(CGDeleteCharacter) == 4 + 1 + 8); // 13
static_assert(sizeof(CGChangeName) == 4 + 1 + CHARACTER_NAME_MAX_LEN + 1); // 70
static_assert(sizeof(GCChangeName) == 4 + 4 + CHARACTER_NAME_MAX_LEN + 1); // 73
static_assert(sizeof(GCPlayerCreateSuccess) == 5 + 103); // 108
static_assert(sizeof(GCCreateFailure) == 5);
static_assert(sizeof(GCDeleteSuccess) == 5);
static_assert(sizeof(CGClientVersion) == 4 + 33 + 33); // 70
static_assert(sizeof(GCMainCharacter) == 118);
static_assert(sizeof(GCCharacterAdd) == 38);
static_assert(sizeof(GCCharAddInfo) == 4 + 4 + 65 + 8 + 1 + 4 + 4 + 2 + 1 + 4); // 97
static_assert(sizeof(GCCharacterUpdate) == 4 + 4 + 8 + 1 + 1 + 1 + 8 + 4 + 2 + 1 + 4); // 38
static_assert(sizeof(GCCharacterAdd2) == 123);
static_assert(sizeof(GCCharacterDel) == 8);
static_assert(sizeof(GCMove) == 27);
static_assert(sizeof(GCCharacterPosition) == 9);
static_assert(sizeof(GCChangeSpeed) == 10);
static_assert(sizeof(GCWalkMode) == 9);
static_assert(sizeof(GCSyncPosition) == 4);
static_assert(sizeof(GCSyncPositionElement) == 12);
static_assert(sizeof(GCChat) == 10);
static_assert(sizeof(CGMove) == 19);
static_assert(sizeof(CGCharacterPosition) == 5);
static_assert(sizeof(CGSyncPosition) == 4);
static_assert(sizeof(CGWarp) == 4);
static_assert(sizeof(CGSyncPositionElement) == 12);
static_assert(sizeof(CGAttack) == 11);
static_assert(sizeof(CGUseSkill) == 12);
static_assert(sizeof(CGShoot) == 5);
static_assert(sizeof(CGFlyTargeting) == 16);
static_assert(sizeof(CGQuickSlotAdd) == 7);
static_assert(sizeof(CGQuickSlotDel) == 5);
static_assert(sizeof(CGQuickSlotSwap) == 6);
static_assert(sizeof(CGTarget) == 8);
static_assert(sizeof(CGChat) == 5);
static_assert(sizeof(GCWhisper) == 5 + CHARACTER_NAME_MAX_LEN + 1); // 70
static_assert(sizeof(CGWhisper) == 4 + CHARACTER_NAME_MAX_LEN + 1); // 69
static_assert(sizeof(AffectElement) == 21);
static_assert(sizeof(GCAffectAdd) == 25);
static_assert(sizeof(GCAffectRemove) == 9);
static_assert(sizeof(GCSpecialEffect) == 9);
static_assert(sizeof(GCSpecificEffect) == 8 + 128); // 136
static_assert(sizeof(GCSkillLevel) == 4 + 255); // 259
static_assert(sizeof(PlayerSkill) == 6);
static_assert(sizeof(GCSkillLevelNew) == 4 + 6 * 255); // 1534
static_assert(sizeof(GCQuickSlotAdd) == 7);
static_assert(sizeof(GCQuickSlotDel) == 5);
static_assert(sizeof(GCQuickSlotSwap) == 6);
static_assert(sizeof(GCSkillCooltimeEnd) == 5);
static_assert(sizeof(GCChangeSkillGroup) == 5);
static_assert(sizeof(GCCreateFly) == 13);
static_assert(sizeof(GCFlyTargeting) == 20);
static_assert(sizeof(GCScript) == 7);
static_assert(sizeof(GCQuestConfirm) == 4 + 65 + 4 + 4); // 77
static_assert(sizeof(GCQuestInfo) == 7);
static_assert(sizeof(CGOnClick) == 8);
static_assert(sizeof(CGScriptAnswer) == 5);
static_assert(sizeof(CGScriptButton) == 8);
static_assert(sizeof(CGScriptSelectItem) == 8);
static_assert(sizeof(CGQuestInputString) == 4 + 65); // 69
static_assert(sizeof(CGQuestConfirm) == 9);
static_assert(sizeof(CGQuestCancel) == 4);
static_assert(sizeof(GCPlayerPoints) == 4 + 255 * 4); // 1024
static_assert(sizeof(GCPointChange) == 17);
static_assert(sizeof(GCStun) == 8);
static_assert(sizeof(GCDead) == 8);
static_assert(sizeof(GCMotion) == 14);
static_assert(sizeof(GCDigMotion) == 13);
static_assert(sizeof(GCDamageInfo) == 13);
static_assert(sizeof(GCTargetInfo) == 9);
static_assert(sizeof(ItemPos) == 3);
static_assert(sizeof(ItemAttr) == 3);
static_assert(sizeof(EquipmentItemSet) == 4 + 1 + 12 + 21); // 38
static_assert(sizeof(GCViewEquip) == 2 + 2 + 4 + 11 * 38); // 426
static_assert(sizeof(GCItemSet) == 2 + 2 + 3 + 4 + 1 + 4 + 4 + 1 + 12 + 21); // 54
static_assert(sizeof(GCItemDel) == 7);
static_assert(sizeof(GCItemUpdate) == 2 + 2 + 3 + 1 + 12 + 21); // 41
static_assert(sizeof(GCItemUse) == 19);
static_assert(sizeof(GCItemGroundAdd) == 24);
static_assert(sizeof(GCItemGroundDel) == 8);
static_assert(sizeof(GCItemOwnership) == 4 + 4 + CHARACTER_NAME_MAX_LEN + 1); // 73
static_assert(sizeof(GCItemGet) == 2 + 2 + 4 + 1 + 1 + 65); // 75
static_assert(sizeof(GCPvp) == 13);
static_assert(sizeof(GCDuelStart) == 4);
static_assert(sizeof(GCLoverInfo) == 4 + CHARACTER_NAME_MAX_LEN + 1 + 1); // 70
static_assert(sizeof(GCLovePointUpdate) == 5);
static_assert(sizeof(CGItemUse) == 7);
static_assert(sizeof(CGItemMove) == 11);
static_assert(sizeof(CGItemDrop) == 11);
static_assert(sizeof(CGItemDrop2) == 12);
static_assert(sizeof(CGItemUseToItem) == 10);
static_assert(sizeof(CGGiveItem) == 12);
static_assert(sizeof(CGItemPickUp) == 8);
// P8 social / shop / storage
static_assert(sizeof(GCPartyInvite) == 8);
static_assert(sizeof(GCPartyAdd) == 4 + 4 + CHARACTER_NAME_MAX_LEN + 1); // 73
static_assert(sizeof(GCPartyUpdate) == 4 + 4 + 1 + 1 + 2 * PARTY_AFFECT_SLOT_MAX_NUM); // 26
static_assert(sizeof(GCPartyRemove) == 8);
static_assert(sizeof(GCPartyLink) == 12);
static_assert(sizeof(GCPartyUnlink) == 12);
static_assert(sizeof(GCPartyParameter) == 5);
static_assert(sizeof(CGPartyInvite) == 8);
static_assert(sizeof(CGPartyInviteAnswer) == 9);
static_assert(sizeof(CGPartyRemove) == 8);
static_assert(sizeof(CGPartyUseSkill) == 9);
static_assert(sizeof(CGPartyParameter) == 5);
static_assert(sizeof(CGPartySetState) == 2 + 2 + 4 + 1 + 1); // 10
static_assert(sizeof(GCSubHeader) == 5);
static_assert(sizeof(CGSubHeader) == 5);
static_assert(sizeof(ShopItem) == 4 + 4 + 1 + 1 + 12 + 21); // 43
static_assert(sizeof(ShopTabHead) == SHOP_TAB_NAME_MAX + 1); // 33
static_assert(sizeof(GCExchange) == 4 + 1 + 1 + 4 + 3 + 4 + 12 + 21); // 50
static_assert(sizeof(CGExchange) == 4 + 1 + 4 + 1 + 3); // 13
static_assert(sizeof(GCSafeboxSize) == 5);
static_assert(sizeof(GCSafeboxMoneyChange) == 8);
static_assert(sizeof(CGSafeboxCheckin) == 8);
static_assert(sizeof(CGMallCheckout) == 4 + 1 + 3); // 8
static_assert(sizeof(GCMallOpen) == 5);
static_assert(sizeof(CGMyShopHead) == 4 + (SHOP_SIGN_MAX_LEN + 1) + 1); // 38
static_assert(sizeof(MyShopItem) == 4 + 1 + 3 + 4 + 1); // 13
static_assert(sizeof(CGSafeboxCheckout) == 8);
static_assert(sizeof(GuildInfoBody) == 2 + 2 + 4 + 4 + 4 + 1 + 13 + 4 + 1); // 35
static_assert(sizeof(GuildSubMember) == 13);
static_assert(sizeof(GuildSubGrade) == 10);
static_assert(sizeof(GuildSkillInfoBody) == 1 + 12 + 2 + 2); // 17
static_assert(sizeof(GCGuildWar) == 10);
static_assert(sizeof(GuildWarPoint) == 12);
static_assert(sizeof(GuildWarPair) == 8);
static_assert(sizeof(GuildNameEntry) == 4 + 12); // 16
static_assert(sizeof(CGGuild) == 5);
static_assert(sizeof(CGAnswerMakeGuild) == 4 + 13); // 17
static_assert(sizeof(CGSymbolCRC) == 16);
static_assert(sizeof(GCSymbolData) == 8);
static_assert(sizeof(RefineMaterial) == 8);
static_assert(sizeof(RefineTable) == 4 + 4 + 1 + 4 + 4 + 8 * 5); // 57
static_assert(sizeof(GCRefineInfo) == 4 + 1 + 1 + 57); // 63
static_assert(sizeof(CGRefine) == 6);
static_assert(sizeof(GCDragonSoulRefine) == 4 + 1 + 3); // 8
static_assert(sizeof(CGDragonSoulRefine) == 4 + 1 + 3 * 15); // 50
static_assert(sizeof(CGMarkLogin) == 12);
static_assert(sizeof(CGMarkIDXList) == 4);
static_assert(sizeof(CGMarkCRCList) == 4 + 1 + 4 * 80); // 325
static_assert(sizeof(CGMarkUpload) == 4 + 4 + 768); // 776
static_assert(sizeof(CGSymbolUpload) == 8);
static_assert(sizeof(GCMarkIDXList) == 10);
static_assert(sizeof(GCMarkBlock) == 13);
static_assert(sizeof(GCMarkUpdate) == 10);
// P9 world systems
static_assert(sizeof(GCWarp) == 18);
static_assert(sizeof(CGFishing) == 5);
static_assert(sizeof(GCFishing) == 10);
static_assert(sizeof(CGDungeon) == 4);
static_assert(sizeof(GCDungeon) == 5);
static_assert(sizeof(GCLandList) == 4);
static_assert(sizeof(LandPacketElement) == 24);
static_assert(sizeof(GCObserverAdd) == 12);
static_assert(sizeof(GCObserverMove) == 12);
static_assert(sizeof(GCObserverRemove) == 8);
static_assert(sizeof(GCTime) == 12);
static_assert(sizeof(GCChannel) == 5);
static_assert(sizeof(GCNPCPosition) == 6);
static_assert(sizeof(NPCPositionEntry) == 1 + 4 + CHARACTER_NAME_MAX_LEN + 1 + 4 + 4); // 78
static_assert(sizeof(GCTargetCreate) == 4 + 4 + 33 + 4 + 1); // 46
static_assert(sizeof(GCTargetUpdate) == 16);
static_assert(sizeof(GCTargetDelete) == 8);
static_assert(sizeof(GCMount) == 21);
} // namespace mtnet