mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 15:20:15 +00:00
refactor: derive turtle opcodes from classic
This commit is contained in:
parent
0b6265bc55
commit
6ede9a2968
12 changed files with 428 additions and 394 deletions
|
|
@ -273,7 +273,7 @@
|
|||
"SMSG_INVENTORY_CHANGE_FAILURE": "0x112",
|
||||
"SMSG_OPEN_CONTAINER": "0x113",
|
||||
"CMSG_INSPECT": "0x114",
|
||||
"SMSG_INSPECT": "0x115",
|
||||
"SMSG_INSPECT_RESULTS_UPDATE": "0x115",
|
||||
"CMSG_INITIATE_TRADE": "0x116",
|
||||
"CMSG_BEGIN_TRADE": "0x117",
|
||||
"CMSG_BUSY_TRADE": "0x118",
|
||||
|
|
@ -300,7 +300,7 @@
|
|||
"CMSG_NEW_SPELL_SLOT": "0x12D",
|
||||
"CMSG_CAST_SPELL": "0x12E",
|
||||
"CMSG_CANCEL_CAST": "0x12F",
|
||||
"SMSG_CAST_RESULT": "0x130",
|
||||
"SMSG_CAST_FAILED": "0x130",
|
||||
"SMSG_SPELL_START": "0x131",
|
||||
"SMSG_SPELL_GO": "0x132",
|
||||
"SMSG_SPELL_FAILURE": "0x133",
|
||||
|
|
@ -504,8 +504,7 @@
|
|||
"CMSG_GM_SET_SECURITY_GROUP": "0x1F9",
|
||||
"CMSG_GM_NUKE": "0x1FA",
|
||||
"MSG_RANDOM_ROLL": "0x1FB",
|
||||
"SMSG_ENVIRONMENTALDAMAGELOG": "0x1FC",
|
||||
"CMSG_RWHOIS_OBSOLETE": "0x1FD",
|
||||
"SMSG_ENVIRONMENTAL_DAMAGE_LOG": "0x1FC",
|
||||
"SMSG_RWHOIS": "0x1FE",
|
||||
"MSG_LOOKING_FOR_GROUP": "0x1FF",
|
||||
"CMSG_SET_LOOKING_FOR_GROUP": "0x200",
|
||||
|
|
@ -528,7 +527,6 @@
|
|||
"CMSG_GMTICKET_GETTICKET": "0x211",
|
||||
"SMSG_GMTICKET_GETTICKET": "0x212",
|
||||
"CMSG_UNLEARN_TALENTS": "0x213",
|
||||
"SMSG_GAMEOBJECT_SPAWN_ANIM_OBSOLETE": "0x214",
|
||||
"SMSG_GAMEOBJECT_DESPAWN_ANIM": "0x215",
|
||||
"MSG_CORPSE_QUERY": "0x216",
|
||||
"CMSG_GMTICKET_DELETETICKET": "0x217",
|
||||
|
|
@ -538,7 +536,7 @@
|
|||
"SMSG_GMTICKET_SYSTEMSTATUS": "0x21B",
|
||||
"CMSG_SPIRIT_HEALER_ACTIVATE": "0x21C",
|
||||
"CMSG_SET_STAT_CHEAT": "0x21D",
|
||||
"SMSG_SET_REST_START": "0x21E",
|
||||
"SMSG_QUEST_FORCE_REMOVE": "0x21E",
|
||||
"CMSG_SKILL_BUY_STEP": "0x21F",
|
||||
"CMSG_SKILL_BUY_RANK": "0x220",
|
||||
"CMSG_XP_CHEAT": "0x221",
|
||||
|
|
@ -571,8 +569,6 @@
|
|||
"CMSG_BATTLEFIELD_LIST": "0x23C",
|
||||
"SMSG_BATTLEFIELD_LIST": "0x23D",
|
||||
"CMSG_BATTLEFIELD_JOIN": "0x23E",
|
||||
"SMSG_BATTLEFIELD_WIN_OBSOLETE": "0x23F",
|
||||
"SMSG_BATTLEFIELD_LOSE_OBSOLETE": "0x240",
|
||||
"CMSG_TAXICLEARNODE": "0x241",
|
||||
"CMSG_TAXIENABLENODE": "0x242",
|
||||
"CMSG_ITEM_TEXT_QUERY": "0x243",
|
||||
|
|
@ -605,7 +601,6 @@
|
|||
"SMSG_AUCTION_BIDDER_NOTIFICATION": "0x25E",
|
||||
"SMSG_AUCTION_OWNER_NOTIFICATION": "0x25F",
|
||||
"SMSG_PROCRESIST": "0x260",
|
||||
"SMSG_STANDSTATE_CHANGE_FAILURE_OBSOLETE": "0x261",
|
||||
"SMSG_DISPEL_FAILED": "0x262",
|
||||
"SMSG_SPELLORDAMAGE_IMMUNE": "0x263",
|
||||
"CMSG_AUCTION_LIST_BIDDER_ITEMS": "0x264",
|
||||
|
|
@ -693,8 +688,8 @@
|
|||
"SMSG_SCRIPT_MESSAGE": "0x2B6",
|
||||
"SMSG_DUEL_COUNTDOWN": "0x2B7",
|
||||
"SMSG_AREA_TRIGGER_MESSAGE": "0x2B8",
|
||||
"CMSG_TOGGLE_HELM": "0x2B9",
|
||||
"CMSG_TOGGLE_CLOAK": "0x2BA",
|
||||
"CMSG_SHOWING_HELM": "0x2B9",
|
||||
"CMSG_SHOWING_CLOAK": "0x2BA",
|
||||
"SMSG_MEETINGSTONE_JOINFAILED": "0x2BB",
|
||||
"SMSG_PLAYER_SKINNED": "0x2BC",
|
||||
"SMSG_DURABILITY_DAMAGE_DEATH": "0x2BD",
|
||||
|
|
@ -821,6 +816,5 @@
|
|||
"SMSG_LOTTERY_RESULT_OBSOLETE": "0x337",
|
||||
"SMSG_CHARACTER_PROFILE": "0x338",
|
||||
"SMSG_CHARACTER_PROFILE_REALM_CONNECTED": "0x339",
|
||||
"SMSG_UNK": "0x33A",
|
||||
"SMSG_DEFENSE_MESSAGE": "0x33B"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,302 +1,6 @@
|
|||
{
|
||||
"CMSG_PING": "0x1DC",
|
||||
"CMSG_AUTH_SESSION": "0x1ED",
|
||||
"CMSG_CHAR_CREATE": "0x036",
|
||||
"CMSG_CHAR_ENUM": "0x037",
|
||||
"CMSG_CHAR_DELETE": "0x038",
|
||||
"CMSG_PLAYER_LOGIN": "0x03D",
|
||||
"MSG_MOVE_START_FORWARD": "0x0B5",
|
||||
"MSG_MOVE_START_BACKWARD": "0x0B6",
|
||||
"MSG_MOVE_STOP": "0x0B7",
|
||||
"MSG_MOVE_START_STRAFE_LEFT": "0x0B8",
|
||||
"MSG_MOVE_START_STRAFE_RIGHT": "0x0B9",
|
||||
"MSG_MOVE_STOP_STRAFE": "0x0BA",
|
||||
"MSG_MOVE_JUMP": "0x0BB",
|
||||
"MSG_MOVE_START_TURN_LEFT": "0x0BC",
|
||||
"MSG_MOVE_START_TURN_RIGHT": "0x0BD",
|
||||
"MSG_MOVE_STOP_TURN": "0x0BE",
|
||||
"MSG_MOVE_SET_FACING": "0x0DA",
|
||||
"MSG_MOVE_FALL_LAND": "0x0C9",
|
||||
"MSG_MOVE_START_SWIM": "0x0CA",
|
||||
"MSG_MOVE_STOP_SWIM": "0x0CB",
|
||||
"MSG_MOVE_HEARTBEAT": "0x0EE",
|
||||
"SMSG_AUTH_CHALLENGE": "0x1EC",
|
||||
"SMSG_AUTH_RESPONSE": "0x1EE",
|
||||
"SMSG_CHAR_CREATE": "0x03A",
|
||||
"SMSG_CHAR_ENUM": "0x03B",
|
||||
"SMSG_CHAR_DELETE": "0x03C",
|
||||
"SMSG_CHARACTER_LOGIN_FAILED": "0x041",
|
||||
"SMSG_PONG": "0x1DD",
|
||||
"SMSG_LOGIN_VERIFY_WORLD": "0x236",
|
||||
"SMSG_INIT_WORLD_STATES": "0x2C2",
|
||||
"SMSG_LOGIN_SETTIMESPEED": "0x042",
|
||||
"SMSG_TUTORIAL_FLAGS": "0x0FD",
|
||||
"SMSG_INITIALIZE_FACTIONS": "0x122",
|
||||
"SMSG_WARDEN_DATA": "0x2E6",
|
||||
"CMSG_WARDEN_DATA": "0x2E7",
|
||||
"SMSG_NOTIFICATION": "0x1CB",
|
||||
"SMSG_ACCOUNT_DATA_TIMES": "0x209",
|
||||
"SMSG_UPDATE_OBJECT": "0x0A9",
|
||||
"SMSG_COMPRESSED_UPDATE_OBJECT": "0x1F6",
|
||||
"SMSG_PARTYKILLLOG": "0x1F5",
|
||||
"SMSG_MONSTER_MOVE_TRANSPORT": "0x2AE",
|
||||
"SMSG_SPLINE_MOVE_SET_WALK_MODE": "0x30E",
|
||||
"SMSG_SPLINE_MOVE_SET_RUN_MODE": "0x30D",
|
||||
"SMSG_SPLINE_SET_RUN_SPEED": "0x2FE",
|
||||
"SMSG_SPLINE_SET_RUN_BACK_SPEED": "0x2FF",
|
||||
"SMSG_SPLINE_SET_SWIM_SPEED": "0x300",
|
||||
"SMSG_DESTROY_OBJECT": "0x0AA",
|
||||
"CMSG_MESSAGECHAT": "0x095",
|
||||
"SMSG_MESSAGECHAT": "0x096",
|
||||
"CMSG_WHO": "0x062",
|
||||
"SMSG_WHO": "0x063",
|
||||
"CMSG_PLAYED_TIME": "0x1CC",
|
||||
"SMSG_PLAYED_TIME": "0x1CD",
|
||||
"CMSG_QUERY_TIME": "0x1CE",
|
||||
"SMSG_QUERY_TIME_RESPONSE": "0x1CF",
|
||||
"SMSG_FRIEND_STATUS": "0x068",
|
||||
"SMSG_CONTACT_LIST": "0x067",
|
||||
"CMSG_ADD_FRIEND": "0x069",
|
||||
"CMSG_DEL_FRIEND": "0x06A",
|
||||
"CMSG_ADD_IGNORE": "0x06C",
|
||||
"CMSG_DEL_IGNORE": "0x06D",
|
||||
"CMSG_PLAYER_LOGOUT": "0x04A",
|
||||
"CMSG_LOGOUT_REQUEST": "0x04B",
|
||||
"CMSG_LOGOUT_CANCEL": "0x04E",
|
||||
"SMSG_LOGOUT_RESPONSE": "0x04C",
|
||||
"SMSG_LOGOUT_COMPLETE": "0x04D",
|
||||
"CMSG_STANDSTATECHANGE": "0x101",
|
||||
"CMSG_SHOWING_HELM": "0x2B9",
|
||||
"CMSG_SHOWING_CLOAK": "0x2BA",
|
||||
"CMSG_TOGGLE_PVP": "0x253",
|
||||
"CMSG_GUILD_INVITE": "0x082",
|
||||
"CMSG_GUILD_ACCEPT": "0x084",
|
||||
"CMSG_GUILD_DECLINE": "0x085",
|
||||
"CMSG_GUILD_INFO": "0x087",
|
||||
"CMSG_GUILD_ROSTER": "0x089",
|
||||
"CMSG_GUILD_PROMOTE": "0x08B",
|
||||
"CMSG_GUILD_DEMOTE": "0x08C",
|
||||
"CMSG_GUILD_LEAVE": "0x08D",
|
||||
"CMSG_GUILD_MOTD": "0x091",
|
||||
"SMSG_GUILD_INFO": "0x088",
|
||||
"SMSG_GUILD_ROSTER": "0x08A",
|
||||
"CMSG_GUILD_QUERY": "0x054",
|
||||
"SMSG_GUILD_QUERY_RESPONSE": "0x055",
|
||||
"SMSG_GUILD_INVITE": "0x083",
|
||||
"CMSG_GUILD_REMOVE": "0x08E",
|
||||
"SMSG_GUILD_EVENT": "0x092",
|
||||
"SMSG_GUILD_COMMAND_RESULT": "0x093",
|
||||
"MSG_RAID_READY_CHECK": "0x322",
|
||||
"SMSG_ITEM_PUSH_RESULT": "0x166",
|
||||
"CMSG_DUEL_ACCEPTED": "0x16C",
|
||||
"CMSG_DUEL_CANCELLED": "0x16D",
|
||||
"SMSG_DUEL_REQUESTED": "0x167",
|
||||
"CMSG_INITIATE_TRADE": "0x116",
|
||||
"MSG_RANDOM_ROLL": "0x1FB",
|
||||
"CMSG_SET_SELECTION": "0x13D",
|
||||
"CMSG_NAME_QUERY": "0x050",
|
||||
"SMSG_NAME_QUERY_RESPONSE": "0x051",
|
||||
"CMSG_CREATURE_QUERY": "0x060",
|
||||
"SMSG_CREATURE_QUERY_RESPONSE": "0x061",
|
||||
"CMSG_GAMEOBJECT_QUERY": "0x05E",
|
||||
"SMSG_GAMEOBJECT_QUERY_RESPONSE": "0x05F",
|
||||
"CMSG_SET_ACTIVE_MOVER": "0x26A",
|
||||
"CMSG_BINDER_ACTIVATE": "0x1B5",
|
||||
"SMSG_LOG_XPGAIN": "0x1D0",
|
||||
"_NOTE_MONSTER_MOVE": "These look swapped vs vanilla (0x0DD/0x2FB) but may be intentional Turtle WoW changes. Check if NPC movement breaks.",
|
||||
"SMSG_MONSTER_MOVE": "0x2FB",
|
||||
"SMSG_COMPRESSED_MOVES": "0x06B",
|
||||
"CMSG_ATTACKSWING": "0x141",
|
||||
"CMSG_ATTACKSTOP": "0x142",
|
||||
"SMSG_ATTACKSTART": "0x143",
|
||||
"SMSG_ATTACKSTOP": "0x144",
|
||||
"SMSG_ATTACKERSTATEUPDATE": "0x14A",
|
||||
"SMSG_AI_REACTION": "0x13C",
|
||||
"SMSG_SPELLNONMELEEDAMAGELOG": "0x250",
|
||||
"SMSG_PLAY_SPELL_VISUAL": "0x1F3",
|
||||
"SMSG_SPELLHEALLOG": "0x150",
|
||||
"SMSG_SPELLENERGIZELOG": "0x151",
|
||||
"SMSG_PERIODICAURALOG": "0x24E",
|
||||
"SMSG_ENVIRONMENTAL_DAMAGE_LOG": "0x1FC",
|
||||
"CMSG_CAST_SPELL": "0x12E",
|
||||
"CMSG_CANCEL_CAST": "0x12F",
|
||||
"CMSG_CANCEL_AURA": "0x136",
|
||||
"SMSG_CAST_FAILED": "0x130",
|
||||
"SMSG_SPELL_START": "0x131",
|
||||
"SMSG_SPELL_GO": "0x132",
|
||||
"SMSG_SPELL_FAILURE": "0x133",
|
||||
"SMSG_SPELL_COOLDOWN": "0x134",
|
||||
"SMSG_COOLDOWN_EVENT": "0x135",
|
||||
"SMSG_UPDATE_AURA_DURATION": "0x137",
|
||||
"SMSG_INITIAL_SPELLS": "0x12A",
|
||||
"SMSG_LEARNED_SPELL": "0x12B",
|
||||
"SMSG_SUPERCEDED_SPELL": "0x12C",
|
||||
"SMSG_REMOVED_SPELL": "0x203",
|
||||
"SMSG_SPELL_DELAYED": "0x1E2",
|
||||
"SMSG_SET_FLAT_SPELL_MODIFIER": "0x266",
|
||||
"SMSG_SET_PCT_SPELL_MODIFIER": "0x267",
|
||||
"CMSG_LEARN_TALENT": "0x251",
|
||||
"MSG_TALENT_WIPE_CONFIRM": "0x2AA",
|
||||
"CMSG_GROUP_INVITE": "0x06E",
|
||||
"SMSG_GROUP_INVITE": "0x06F",
|
||||
"CMSG_GROUP_ACCEPT": "0x072",
|
||||
"CMSG_GROUP_DECLINE": "0x073",
|
||||
"SMSG_GROUP_DECLINE": "0x074",
|
||||
"CMSG_GROUP_UNINVITE_GUID": "0x076",
|
||||
"SMSG_GROUP_UNINVITE": "0x077",
|
||||
"CMSG_GROUP_SET_LEADER": "0x078",
|
||||
"SMSG_GROUP_SET_LEADER": "0x079",
|
||||
"CMSG_GROUP_DISBAND": "0x07B",
|
||||
"SMSG_GROUP_LIST": "0x07D",
|
||||
"SMSG_PARTY_COMMAND_RESULT": "0x07F",
|
||||
"MSG_RAID_TARGET_UPDATE": "0x321",
|
||||
"CMSG_REQUEST_RAID_INFO": "0x2CD",
|
||||
"SMSG_RAID_INSTANCE_INFO": "0x2CC",
|
||||
"CMSG_AUTOSTORE_LOOT_ITEM": "0x108",
|
||||
"CMSG_LOOT": "0x15D",
|
||||
"CMSG_LOOT_MONEY": "0x15E",
|
||||
"CMSG_LOOT_RELEASE": "0x15F",
|
||||
"SMSG_LOOT_RESPONSE": "0x160",
|
||||
"SMSG_LOOT_RELEASE_RESPONSE": "0x161",
|
||||
"SMSG_LOOT_REMOVED": "0x162",
|
||||
"SMSG_LOOT_MONEY_NOTIFY": "0x163",
|
||||
"SMSG_LOOT_CLEAR_MONEY": "0x165",
|
||||
"CMSG_ACTIVATETAXI": "0x1AD",
|
||||
"CMSG_GOSSIP_HELLO": "0x17B",
|
||||
"CMSG_GOSSIP_SELECT_OPTION": "0x17C",
|
||||
"SMSG_GOSSIP_MESSAGE": "0x17D",
|
||||
"SMSG_GOSSIP_COMPLETE": "0x17E",
|
||||
"SMSG_NPC_TEXT_UPDATE": "0x180",
|
||||
"CMSG_GAMEOBJ_USE": "0x0B1",
|
||||
"CMSG_QUESTGIVER_STATUS_QUERY": "0x182",
|
||||
"SMSG_QUESTGIVER_STATUS": "0x183",
|
||||
"CMSG_QUESTGIVER_HELLO": "0x184",
|
||||
"SMSG_QUESTGIVER_QUEST_LIST": "0x185",
|
||||
"CMSG_QUESTGIVER_QUERY_QUEST": "0x186",
|
||||
"SMSG_QUESTGIVER_QUEST_DETAILS": "0x188",
|
||||
"CMSG_QUESTGIVER_ACCEPT_QUEST": "0x189",
|
||||
"CMSG_QUESTGIVER_COMPLETE_QUEST": "0x18A",
|
||||
"SMSG_QUESTGIVER_REQUEST_ITEMS": "0x18B",
|
||||
"CMSG_QUESTGIVER_REQUEST_REWARD": "0x18C",
|
||||
"SMSG_QUESTGIVER_OFFER_REWARD": "0x18D",
|
||||
"CMSG_QUESTGIVER_CHOOSE_REWARD": "0x18E",
|
||||
"SMSG_QUESTGIVER_QUEST_INVALID": "0x18F",
|
||||
"SMSG_QUESTGIVER_QUEST_COMPLETE": "0x191",
|
||||
"CMSG_QUESTLOG_REMOVE_QUEST": "0x194",
|
||||
"SMSG_QUESTUPDATE_ADD_KILL": "0x199",
|
||||
"SMSG_QUESTUPDATE_COMPLETE": "0x198",
|
||||
"SMSG_QUEST_FORCE_REMOVE": "0x21E",
|
||||
"CMSG_QUEST_QUERY": "0x05C",
|
||||
"SMSG_QUEST_QUERY_RESPONSE": "0x05D",
|
||||
"SMSG_QUESTLOG_FULL": "0x195",
|
||||
"CMSG_LIST_INVENTORY": "0x19E",
|
||||
"SMSG_LIST_INVENTORY": "0x19F",
|
||||
"CMSG_SELL_ITEM": "0x1A0",
|
||||
"SMSG_SELL_ITEM": "0x1A1",
|
||||
"CMSG_BUY_ITEM": "0x1A2",
|
||||
"CMSG_BUYBACK_ITEM": "0x1A6",
|
||||
"SMSG_BUY_FAILED": "0x1A5",
|
||||
"CMSG_TRAINER_LIST": "0x1B0",
|
||||
"SMSG_TRAINER_LIST": "0x1B1",
|
||||
"CMSG_TRAINER_BUY_SPELL": "0x1B2",
|
||||
"SMSG_TRAINER_BUY_FAILED": "0x1B4",
|
||||
"CMSG_ITEM_QUERY_SINGLE": "0x056",
|
||||
"SMSG_ITEM_QUERY_SINGLE_RESPONSE": "0x058",
|
||||
"CMSG_USE_ITEM": "0x0AB",
|
||||
"CMSG_AUTOEQUIP_ITEM": "0x10A",
|
||||
"CMSG_SWAP_ITEM": "0x10C",
|
||||
"CMSG_SWAP_INV_ITEM": "0x10D",
|
||||
"SMSG_INVENTORY_CHANGE_FAILURE": "0x112",
|
||||
"CMSG_INSPECT": "0x114",
|
||||
"SMSG_INSPECT_RESULTS_UPDATE": "0x115",
|
||||
"CMSG_REPOP_REQUEST": "0x15A",
|
||||
"SMSG_RESURRECT_REQUEST": "0x15B",
|
||||
"CMSG_RESURRECT_RESPONSE": "0x15C",
|
||||
"CMSG_SPIRIT_HEALER_ACTIVATE": "0x21C",
|
||||
"SMSG_SPIRIT_HEALER_CONFIRM": "0x222",
|
||||
"MSG_MOVE_TELEPORT_ACK": "0x0C7",
|
||||
"SMSG_TRANSFER_PENDING": "0x03F",
|
||||
"SMSG_NEW_WORLD": "0x03E",
|
||||
"MSG_MOVE_WORLDPORT_ACK": "0x0DC",
|
||||
"SMSG_TRANSFER_ABORTED": "0x040",
|
||||
"SMSG_FORCE_RUN_SPEED_CHANGE": "0x0E2",
|
||||
"SMSG_CLIENT_CONTROL_UPDATE": "0x159",
|
||||
"CMSG_FORCE_RUN_SPEED_CHANGE_ACK": "0x0E3",
|
||||
"SMSG_SHOWTAXINODES": "0x1A9",
|
||||
"SMSG_ACTIVATETAXIREPLY": "0x1AE",
|
||||
"SMSG_NEW_TAXI_PATH": "0x1AF",
|
||||
"CMSG_ACTIVATETAXIEXPRESS": "0x312",
|
||||
"CMSG_TAXINODE_STATUS_QUERY": "0x1AA",
|
||||
"SMSG_TAXINODE_STATUS": "0x1AB",
|
||||
"SMSG_TRAINER_BUY_SUCCEEDED": "0x1B3",
|
||||
"SMSG_BINDPOINTUPDATE": "0x155",
|
||||
"SMSG_SET_PROFICIENCY": "0x127",
|
||||
"SMSG_ACTION_BUTTONS": "0x129",
|
||||
"SMSG_LEVELUP_INFO": "0x1D4",
|
||||
"SMSG_PLAY_SOUND": "0x2D2",
|
||||
"CMSG_UPDATE_ACCOUNT_DATA": "0x20B",
|
||||
"CMSG_BATTLEFIELD_LIST": "0x23C",
|
||||
"SMSG_BATTLEFIELD_LIST": "0x23D",
|
||||
"CMSG_BATTLEFIELD_JOIN": "0x23E",
|
||||
"CMSG_BATTLEFIELD_STATUS": "0x2D3",
|
||||
"SMSG_BATTLEFIELD_STATUS": "0x2D4",
|
||||
"CMSG_BATTLEFIELD_PORT": "0x2D5",
|
||||
"CMSG_BATTLEMASTER_HELLO": "0x2D7",
|
||||
"SMSG_SPELL_FAILED_OTHER": "0x2A6",
|
||||
"MSG_PVP_LOG_DATA": "0x2E0",
|
||||
"CMSG_LEAVE_BATTLEFIELD": "0x2E1",
|
||||
"SMSG_GROUP_JOINED_BATTLEGROUND": "0x2E8",
|
||||
"MSG_BATTLEGROUND_PLAYER_POSITIONS": "0x2E9",
|
||||
"SMSG_BATTLEGROUND_PLAYER_JOINED": "0x2EC",
|
||||
"SMSG_BATTLEGROUND_PLAYER_LEFT": "0x2ED",
|
||||
"CMSG_BATTLEMASTER_JOIN": "0x2EE",
|
||||
"SMSG_ADDON_INFO": "0x2EF",
|
||||
"CMSG_EMOTE": "0x102",
|
||||
"SMSG_EMOTE": "0x103",
|
||||
"CMSG_TEXT_EMOTE": "0x104",
|
||||
"SMSG_TEXT_EMOTE": "0x105",
|
||||
"CMSG_JOIN_CHANNEL": "0x097",
|
||||
"CMSG_LEAVE_CHANNEL": "0x098",
|
||||
"SMSG_CHANNEL_NOTIFY": "0x099",
|
||||
"CMSG_CHANNEL_LIST": "0x09A",
|
||||
"SMSG_CHANNEL_LIST": "0x09B",
|
||||
"SMSG_INSPECT_TALENT": "0x3F4",
|
||||
"SMSG_SHOW_MAILBOX": "0x297",
|
||||
"CMSG_GET_MAIL_LIST": "0x23A",
|
||||
"SMSG_MAIL_LIST_RESULT": "0x23B",
|
||||
"CMSG_SEND_MAIL": "0x238",
|
||||
"SMSG_SEND_MAIL_RESULT": "0x239",
|
||||
"CMSG_MAIL_TAKE_MONEY": "0x245",
|
||||
"CMSG_MAIL_TAKE_ITEM": "0x246",
|
||||
"CMSG_MAIL_DELETE": "0x249",
|
||||
"CMSG_MAIL_MARK_AS_READ": "0x247",
|
||||
"SMSG_RECEIVED_MAIL": "0x285",
|
||||
"MSG_QUERY_NEXT_MAIL_TIME": "0x284",
|
||||
"CMSG_BANKER_ACTIVATE": "0x1B7",
|
||||
"SMSG_SHOW_BANK": "0x1B8",
|
||||
"CMSG_BUY_BANK_SLOT": "0x1B9",
|
||||
"SMSG_BUY_BANK_SLOT_RESULT": "0x1BA",
|
||||
"CMSG_AUTOSTORE_BANK_ITEM": "0x282",
|
||||
"CMSG_AUTOBANK_ITEM": "0x283",
|
||||
"MSG_AUCTION_HELLO": "0x255",
|
||||
"CMSG_AUCTION_SELL_ITEM": "0x256",
|
||||
"CMSG_AUCTION_REMOVE_ITEM": "0x257",
|
||||
"CMSG_AUCTION_LIST_ITEMS": "0x258",
|
||||
"CMSG_AUCTION_LIST_OWNER_ITEMS": "0x259",
|
||||
"CMSG_AUCTION_PLACE_BID": "0x25A",
|
||||
"SMSG_AUCTION_COMMAND_RESULT": "0x25B",
|
||||
"SMSG_AUCTION_LIST_RESULT": "0x25C",
|
||||
"SMSG_AUCTION_OWNER_LIST_RESULT": "0x25D",
|
||||
"SMSG_AUCTION_OWNER_NOTIFICATION": "0x25E",
|
||||
"SMSG_AUCTION_BIDDER_NOTIFICATION": "0x260",
|
||||
"CMSG_AUCTION_LIST_BIDDER_ITEMS": "0x264",
|
||||
"SMSG_AUCTION_BIDDER_LIST_RESULT": "0x265",
|
||||
"MSG_MOVE_TIME_SKIPPED": "0x319",
|
||||
"SMSG_CANCEL_AUTO_REPEAT": "0x29C",
|
||||
"SMSG_WEATHER": "0x2F4",
|
||||
"SMSG_QUESTUPDATE_ADD_ITEM": "0x19A",
|
||||
"CMSG_GUILD_DISBAND": "0x08F",
|
||||
"CMSG_GUILD_LEADER": "0x090",
|
||||
"CMSG_GUILD_SET_PUBLIC_NOTE": "0x234",
|
||||
"CMSG_GUILD_SET_OFFICER_NOTE": "0x235"
|
||||
"_extends": "../classic/opcodes.json",
|
||||
"_remove": [
|
||||
"MSG_SET_DUNGEON_DIFFICULTY"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@
|
|||
"SMSG_SPLINE_MOVE_SET_RUN_BACK_SPEED": "SMSG_SPLINE_SET_RUN_BACK_SPEED",
|
||||
"SMSG_SPLINE_MOVE_SET_RUN_SPEED": "SMSG_SPLINE_SET_RUN_SPEED",
|
||||
"SMSG_SPLINE_MOVE_SET_SWIM_SPEED": "SMSG_SPLINE_SET_SWIM_SPEED",
|
||||
"SMSG_UPDATE_AURA_DURATION": "SMSG_EQUIPMENT_SET_SAVED",
|
||||
"SMSG_VICTIMSTATEUPDATE_OBSOLETE": "SMSG_BATTLEFIELD_PORT_DENIED"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,5 +41,4 @@
|
|||
{"SMSG_SPLINE_MOVE_SET_RUN_BACK_SPEED", "SMSG_SPLINE_SET_RUN_BACK_SPEED"},
|
||||
{"SMSG_SPLINE_MOVE_SET_RUN_SPEED", "SMSG_SPLINE_SET_RUN_SPEED"},
|
||||
{"SMSG_SPLINE_MOVE_SET_SWIM_SPEED", "SMSG_SPLINE_SET_SWIM_SPEED"},
|
||||
{"SMSG_UPDATE_AURA_DURATION", "SMSG_EQUIPMENT_SET_SAVED"},
|
||||
{"SMSG_VICTIMSTATEUPDATE_OBSOLETE", "SMSG_BATTLEFIELD_PORT_DENIED"},
|
||||
|
|
|
|||
|
|
@ -33,7 +33,10 @@ class OpcodeTable {
|
|||
public:
|
||||
/**
|
||||
* Load opcode mappings from a JSON file.
|
||||
* Format: { "CMSG_PING": "0x1DC", "SMSG_AUTH_CHALLENGE": "0x1EC", ... }
|
||||
* Format:
|
||||
* { "CMSG_PING": "0x1DC", "SMSG_AUTH_CHALLENGE": "0x1EC", ... }
|
||||
* or a delta file with:
|
||||
* { "_extends": "../classic/opcodes.json", "_remove": ["MSG_FOO"], ...overrides }
|
||||
*/
|
||||
bool loadFromJson(const std::string& path);
|
||||
|
||||
|
|
|
|||
|
|
@ -439,14 +439,16 @@ public:
|
|||
};
|
||||
|
||||
/**
|
||||
* Turtle WoW (build 7234) packet parsers.
|
||||
* Turtle WoW packet parsers.
|
||||
*
|
||||
* Turtle WoW is a heavily modified vanilla server that sends TBC-style
|
||||
* movement blocks (moveFlags2, transport timestamps, 8 speeds including flight)
|
||||
* while keeping all other Classic packet formats.
|
||||
* Turtle is Classic-based but not wire-identical to vanilla MaNGOS. It keeps
|
||||
* most Classic packet formats, while overriding the movement-bearing paths that
|
||||
* have proven to vary in live traffic:
|
||||
* - update-object movement blocks use a Turtle-specific hybrid layout
|
||||
* - update-object parsing falls back through Classic/TBC/WotLK movement layouts
|
||||
* - monster-move parsing falls back through Vanilla, TBC, and guarded WotLK layouts
|
||||
*
|
||||
* Inherits all Classic overrides (charEnum, chat, gossip, mail, items, etc.)
|
||||
* but delegates movement block parsing to TBC format.
|
||||
* Everything else inherits the Classic parser behavior.
|
||||
*/
|
||||
class TurtlePacketParsers : public ClassicPacketParsers {
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -4261,8 +4261,11 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
}
|
||||
|
||||
case Opcode::SMSG_ACTION_BUTTONS: {
|
||||
// packed: bits 0-23 = actionId, bits 24-31 = type
|
||||
// 0x00 = spell (when id != 0), 0x80 = item, 0x40 = macro (skip)
|
||||
// Slot encoding differs by expansion:
|
||||
// Classic/Turtle: uint16 actionId + uint8 type + uint8 misc
|
||||
// type: 0=spell, 1=item, 64=macro
|
||||
// TBC/WotLK: uint32 packed = actionId | (type << 24)
|
||||
// type: 0x00=spell, 0x80=item, 0x40=macro
|
||||
// Format differences:
|
||||
// Classic 1.12: no mode byte, 120 slots (480 bytes)
|
||||
// TBC 2.4.3: no mode byte, 132 slots (528 bytes)
|
||||
|
|
@ -4292,12 +4295,20 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
// so we don't wipe hardcoded fallbacks when the server sends zeros.
|
||||
continue;
|
||||
}
|
||||
uint8_t type = static_cast<uint8_t>((packed >> 24) & 0xFF);
|
||||
uint32_t id = packed & 0x00FFFFFFu;
|
||||
uint8_t type = 0;
|
||||
uint32_t id = 0;
|
||||
if (isClassicLikeExpansion()) {
|
||||
id = packed & 0x0000FFFFu;
|
||||
type = static_cast<uint8_t>((packed >> 16) & 0xFF);
|
||||
} else {
|
||||
type = static_cast<uint8_t>((packed >> 24) & 0xFF);
|
||||
id = packed & 0x00FFFFFFu;
|
||||
}
|
||||
if (id == 0) continue;
|
||||
ActionBarSlot slot;
|
||||
switch (type) {
|
||||
case 0x00: slot.type = ActionBarSlot::SPELL; slot.id = id; break;
|
||||
case 0x01: slot.type = ActionBarSlot::ITEM; slot.id = id; break;
|
||||
case 0x80: slot.type = ActionBarSlot::ITEM; slot.id = id; break;
|
||||
default: continue; // macro or unknown — leave as-is
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@
|
|||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <filesystem>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace wowee {
|
||||
namespace game {
|
||||
|
|
@ -47,6 +49,155 @@ static std::string_view canonicalOpcodeName(std::string_view name) {
|
|||
return name;
|
||||
}
|
||||
|
||||
static std::optional<uint16_t> resolveLogicalOpcodeIndex(std::string_view name) {
|
||||
const std::string_view canonical = canonicalOpcodeName(name);
|
||||
for (size_t i = 0; i < kOpcodeNameCount; ++i) {
|
||||
if (canonical == kOpcodeNames[i].name) {
|
||||
return static_cast<uint16_t>(kOpcodeNames[i].op);
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::optional<std::string> parseStringField(const std::string& json, const char* fieldName) {
|
||||
const std::string needle = std::string("\"") + fieldName + "\"";
|
||||
size_t keyPos = json.find(needle);
|
||||
if (keyPos == std::string::npos) return std::nullopt;
|
||||
|
||||
size_t colon = json.find(':', keyPos + needle.size());
|
||||
if (colon == std::string::npos) return std::nullopt;
|
||||
|
||||
size_t valueStart = json.find('"', colon + 1);
|
||||
if (valueStart == std::string::npos) return std::nullopt;
|
||||
size_t valueEnd = json.find('"', valueStart + 1);
|
||||
if (valueEnd == std::string::npos) return std::nullopt;
|
||||
return json.substr(valueStart + 1, valueEnd - valueStart - 1);
|
||||
}
|
||||
|
||||
static std::vector<std::string> parseStringArrayField(const std::string& json, const char* fieldName) {
|
||||
std::vector<std::string> values;
|
||||
const std::string needle = std::string("\"") + fieldName + "\"";
|
||||
size_t keyPos = json.find(needle);
|
||||
if (keyPos == std::string::npos) return values;
|
||||
|
||||
size_t colon = json.find(':', keyPos + needle.size());
|
||||
if (colon == std::string::npos) return values;
|
||||
|
||||
size_t arrayStart = json.find('[', colon + 1);
|
||||
if (arrayStart == std::string::npos) return values;
|
||||
size_t arrayEnd = json.find(']', arrayStart + 1);
|
||||
if (arrayEnd == std::string::npos) return values;
|
||||
|
||||
size_t pos = arrayStart + 1;
|
||||
while (pos < arrayEnd) {
|
||||
size_t valueStart = json.find('"', pos);
|
||||
if (valueStart == std::string::npos || valueStart >= arrayEnd) break;
|
||||
size_t valueEnd = json.find('"', valueStart + 1);
|
||||
if (valueEnd == std::string::npos || valueEnd > arrayEnd) break;
|
||||
values.push_back(json.substr(valueStart + 1, valueEnd - valueStart - 1));
|
||||
pos = valueEnd + 1;
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
static bool loadOpcodeJsonRecursive(const std::filesystem::path& path,
|
||||
std::unordered_map<uint16_t, uint16_t>& logicalToWire,
|
||||
std::unordered_map<uint16_t, uint16_t>& wireToLogical,
|
||||
std::unordered_set<std::string>& loadingStack) {
|
||||
const std::filesystem::path canonicalPath = std::filesystem::weakly_canonical(path);
|
||||
const std::string canonicalKey = canonicalPath.string();
|
||||
if (!loadingStack.insert(canonicalKey).second) {
|
||||
LOG_WARNING("OpcodeTable: inheritance cycle at ", canonicalKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ifstream f(canonicalPath);
|
||||
if (!f.is_open()) {
|
||||
LOG_WARNING("OpcodeTable: cannot open ", canonicalPath.string());
|
||||
loadingStack.erase(canonicalKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string json((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
bool ok = true;
|
||||
|
||||
if (auto extends = parseStringField(json, "_extends")) {
|
||||
ok = loadOpcodeJsonRecursive(canonicalPath.parent_path() / *extends,
|
||||
logicalToWire, wireToLogical, loadingStack) && ok;
|
||||
}
|
||||
|
||||
for (const std::string& removeName : parseStringArrayField(json, "_remove")) {
|
||||
auto logical = resolveLogicalOpcodeIndex(removeName);
|
||||
if (!logical) continue;
|
||||
auto it = logicalToWire.find(*logical);
|
||||
if (it != logicalToWire.end()) {
|
||||
const uint16_t oldWire = it->second;
|
||||
logicalToWire.erase(it);
|
||||
auto wireIt = wireToLogical.find(oldWire);
|
||||
if (wireIt != wireToLogical.end() && wireIt->second == *logical) {
|
||||
wireToLogical.erase(wireIt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t pos = 0;
|
||||
while (pos < json.size()) {
|
||||
size_t keyStart = json.find('"', pos);
|
||||
if (keyStart == std::string::npos) break;
|
||||
size_t keyEnd = json.find('"', keyStart + 1);
|
||||
if (keyEnd == std::string::npos) break;
|
||||
std::string key = json.substr(keyStart + 1, keyEnd - keyStart - 1);
|
||||
|
||||
size_t colon = json.find(':', keyEnd);
|
||||
if (colon == std::string::npos) break;
|
||||
|
||||
size_t valStart = colon + 1;
|
||||
while (valStart < json.size() && (json[valStart] == ' ' || json[valStart] == '\t' ||
|
||||
json[valStart] == '\r' || json[valStart] == '\n' || json[valStart] == '"'))
|
||||
++valStart;
|
||||
|
||||
size_t valEnd = json.find_first_of(",}\"\r\n", valStart);
|
||||
if (valEnd == std::string::npos) valEnd = json.size();
|
||||
std::string valStr = json.substr(valStart, valEnd - valStart);
|
||||
|
||||
uint16_t wire = 0;
|
||||
try {
|
||||
if (valStr.size() > 2 && (valStr[0] == '0' && (valStr[1] == 'x' || valStr[1] == 'X'))) {
|
||||
wire = static_cast<uint16_t>(std::stoul(valStr, nullptr, 16));
|
||||
} else {
|
||||
wire = static_cast<uint16_t>(std::stoul(valStr));
|
||||
}
|
||||
} catch (...) {
|
||||
pos = valEnd + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto logical = resolveLogicalOpcodeIndex(key);
|
||||
if (logical) {
|
||||
auto oldLogicalIt = logicalToWire.find(*logical);
|
||||
if (oldLogicalIt != logicalToWire.end()) {
|
||||
const uint16_t oldWire = oldLogicalIt->second;
|
||||
auto oldWireIt = wireToLogical.find(oldWire);
|
||||
if (oldWireIt != wireToLogical.end() && oldWireIt->second == *logical) {
|
||||
wireToLogical.erase(oldWireIt);
|
||||
}
|
||||
}
|
||||
auto oldWireIt = wireToLogical.find(wire);
|
||||
if (oldWireIt != wireToLogical.end() && oldWireIt->second != *logical) {
|
||||
logicalToWire.erase(oldWireIt->second);
|
||||
wireToLogical.erase(oldWireIt);
|
||||
}
|
||||
logicalToWire[*logical] = wire;
|
||||
wireToLogical[wire] = *logical;
|
||||
}
|
||||
|
||||
pos = valEnd + 1;
|
||||
}
|
||||
|
||||
loadingStack.erase(canonicalKey);
|
||||
return ok;
|
||||
}
|
||||
|
||||
std::optional<LogicalOpcode> OpcodeTable::nameToLogical(const std::string& name) {
|
||||
const std::string_view canonical = canonicalOpcodeName(name);
|
||||
for (size_t i = 0; i < kOpcodeNameCount; ++i) {
|
||||
|
|
@ -64,73 +215,18 @@ const char* OpcodeTable::logicalToName(LogicalOpcode op) {
|
|||
}
|
||||
|
||||
bool OpcodeTable::loadFromJson(const std::string& path) {
|
||||
std::ifstream f(path);
|
||||
if (!f.is_open()) {
|
||||
LOG_WARNING("OpcodeTable: cannot open ", path, ", using defaults");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string json((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
|
||||
// Start fresh — JSON is the single source of truth for opcode mappings.
|
||||
// Start fresh — resolved JSON inheritance is the single source of truth for opcode mappings.
|
||||
logicalToWire_.clear();
|
||||
wireToLogical_.clear();
|
||||
|
||||
// Parse simple JSON: { "NAME": "0xHEX", ... } or { "NAME": 123, ... }
|
||||
size_t pos = 0;
|
||||
size_t loaded = 0;
|
||||
while (pos < json.size()) {
|
||||
// Find next quoted key
|
||||
size_t keyStart = json.find('"', pos);
|
||||
if (keyStart == std::string::npos) break;
|
||||
size_t keyEnd = json.find('"', keyStart + 1);
|
||||
if (keyEnd == std::string::npos) break;
|
||||
std::string key = json.substr(keyStart + 1, keyEnd - keyStart - 1);
|
||||
|
||||
// Find colon then value
|
||||
size_t colon = json.find(':', keyEnd);
|
||||
if (colon == std::string::npos) break;
|
||||
|
||||
// Skip whitespace
|
||||
size_t valStart = colon + 1;
|
||||
while (valStart < json.size() && (json[valStart] == ' ' || json[valStart] == '\t' ||
|
||||
json[valStart] == '\r' || json[valStart] == '\n' || json[valStart] == '"'))
|
||||
++valStart;
|
||||
|
||||
size_t valEnd = json.find_first_of(",}\"\r\n", valStart);
|
||||
if (valEnd == std::string::npos) valEnd = json.size();
|
||||
std::string valStr = json.substr(valStart, valEnd - valStart);
|
||||
|
||||
// Parse hex or decimal value
|
||||
uint16_t wire = 0;
|
||||
try {
|
||||
if (valStr.size() > 2 && (valStr[0] == '0' && (valStr[1] == 'x' || valStr[1] == 'X'))) {
|
||||
wire = static_cast<uint16_t>(std::stoul(valStr, nullptr, 16));
|
||||
} else {
|
||||
wire = static_cast<uint16_t>(std::stoul(valStr));
|
||||
}
|
||||
} catch (...) {
|
||||
pos = valEnd + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto logOp = nameToLogical(key);
|
||||
if (logOp) {
|
||||
uint16_t logIdx = static_cast<uint16_t>(*logOp);
|
||||
logicalToWire_[logIdx] = wire;
|
||||
wireToLogical_[wire] = logIdx;
|
||||
++loaded;
|
||||
}
|
||||
|
||||
pos = valEnd + 1;
|
||||
}
|
||||
|
||||
if (loaded == 0) {
|
||||
std::unordered_set<std::string> loadingStack;
|
||||
if (!loadOpcodeJsonRecursive(std::filesystem::path(path),
|
||||
logicalToWire_, wireToLogical_, loadingStack) ||
|
||||
logicalToWire_.empty()) {
|
||||
LOG_WARNING("OpcodeTable: no opcodes loaded from ", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("OpcodeTable: loaded ", loaded, " opcodes from ", path);
|
||||
LOG_INFO("OpcodeTable: loaded ", logicalToWire_.size(), " opcodes from ", path);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2007,13 +2007,20 @@ bool TurtlePacketParsers::parseUpdateObject(network::Packet& packet, UpdateObjec
|
|||
}
|
||||
|
||||
bool TurtlePacketParsers::parseMonsterMove(network::Packet& packet, MonsterMoveData& data) {
|
||||
// Turtle realms can emit both vanilla-like and WotLK-like monster move bodies.
|
||||
// Try the canonical Turtle/vanilla parser first, then fall back to WotLK layout.
|
||||
// Turtle realms can emit vanilla-like, TBC-like, and WotLK-like monster move
|
||||
// bodies. Try the lower-expansion layouts first before the WotLK parser that
|
||||
// expects an extra unk byte after the packed GUID.
|
||||
size_t start = packet.getReadPos();
|
||||
if (MonsterMoveParser::parseVanilla(packet, data)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
packet.setReadPos(start);
|
||||
if (TbcPacketParsers::parseMonsterMove(packet, data)) {
|
||||
LOG_DEBUG("[Turtle] SMSG_MONSTER_MOVE parsed via TBC fallback layout");
|
||||
return true;
|
||||
}
|
||||
|
||||
auto looksLikeWotlkMonsterMove = [&](network::Packet& probe) -> bool {
|
||||
const size_t probeStart = probe.getReadPos();
|
||||
uint64_t guid = UpdateObjectParser::readPackedGuid(probe);
|
||||
|
|
|
|||
175
tools/diff_classic_turtle_opcodes.py
Normal file
175
tools/diff_classic_turtle_opcodes.py
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Report the semantic opcode diff between the Classic and Turtle expansion maps.
|
||||
|
||||
The report normalizes:
|
||||
- hex formatting differences (0x67 vs 0x067)
|
||||
- alias names that collapse to the same canonical opcode
|
||||
|
||||
It highlights:
|
||||
- true wire differences for the same canonical opcode
|
||||
- canonical opcodes present only in Classic or only in Turtle
|
||||
- name-only differences where the wire matches after aliasing
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterable, List, Tuple
|
||||
|
||||
from opcode_map_utils import load_opcode_map
|
||||
|
||||
|
||||
RE_OPCODE_NAME = re.compile(r"^(?:CMSG|SMSG|MSG)_[A-Z0-9_]+$")
|
||||
|
||||
|
||||
def read_aliases(path: Path) -> Dict[str, str]:
|
||||
data = json.loads(path.read_text())
|
||||
aliases = data.get("aliases", {})
|
||||
out: Dict[str, str] = {}
|
||||
for key, value in aliases.items():
|
||||
if isinstance(key, str) and isinstance(value, str):
|
||||
out[key] = value
|
||||
return out
|
||||
|
||||
|
||||
def canonicalize(name: str, aliases: Dict[str, str]) -> str:
|
||||
seen = set()
|
||||
current = name
|
||||
while current in aliases and current not in seen:
|
||||
seen.add(current)
|
||||
current = aliases[current]
|
||||
return current
|
||||
|
||||
|
||||
def load_map(path: Path) -> Dict[str, int]:
|
||||
data = load_opcode_map(path)
|
||||
out: Dict[str, int] = {}
|
||||
for key, value in data.items():
|
||||
if not isinstance(key, str) or not RE_OPCODE_NAME.match(key):
|
||||
continue
|
||||
if not isinstance(value, str) or not value.lower().startswith("0x"):
|
||||
continue
|
||||
out[key] = int(value, 16)
|
||||
return out
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CanonicalEntry:
|
||||
canonical_name: str
|
||||
raw_value: int
|
||||
raw_names: Tuple[str, ...]
|
||||
|
||||
|
||||
def build_canonical_entries(
|
||||
raw_map: Dict[str, int], aliases: Dict[str, str]
|
||||
) -> Dict[str, CanonicalEntry]:
|
||||
grouped: Dict[str, List[Tuple[str, int]]] = {}
|
||||
for raw_name, raw_value in raw_map.items():
|
||||
canonical_name = canonicalize(raw_name, aliases)
|
||||
grouped.setdefault(canonical_name, []).append((raw_name, raw_value))
|
||||
|
||||
out: Dict[str, CanonicalEntry] = {}
|
||||
for canonical_name, entries in grouped.items():
|
||||
raw_values = {raw_value for _, raw_value in entries}
|
||||
if len(raw_values) != 1:
|
||||
formatted = ", ".join(
|
||||
f"{name}=0x{raw_value:03X}" for name, raw_value in sorted(entries)
|
||||
)
|
||||
raise ValueError(
|
||||
f"Expansion map contains multiple wires for canonical opcode "
|
||||
f"{canonical_name}: {formatted}"
|
||||
)
|
||||
raw_value = next(iter(raw_values))
|
||||
raw_names = tuple(sorted(name for name, _ in entries))
|
||||
out[canonical_name] = CanonicalEntry(canonical_name, raw_value, raw_names)
|
||||
return out
|
||||
|
||||
|
||||
def format_hex(raw_value: int) -> str:
|
||||
return f"0x{raw_value:03X}"
|
||||
|
||||
|
||||
def emit_section(title: str, rows: Iterable[str], limit: int | None) -> None:
|
||||
rows = list(rows)
|
||||
print(f"{title}: {len(rows)}")
|
||||
if not rows:
|
||||
return
|
||||
shown = rows if limit is None else rows[:limit]
|
||||
for row in shown:
|
||||
print(f" {row}")
|
||||
if limit is not None and len(rows) > limit:
|
||||
print(f" ... {len(rows) - limit} more")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", default=".")
|
||||
parser.add_argument(
|
||||
"--limit",
|
||||
type=int,
|
||||
default=80,
|
||||
help="Maximum rows to print per section; use -1 for no limit.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
root = Path(args.root).resolve()
|
||||
aliases = read_aliases(root / "Data/opcodes/aliases.json")
|
||||
classic_raw = load_map(root / "Data/expansions/classic/opcodes.json")
|
||||
turtle_raw = load_map(root / "Data/expansions/turtle/opcodes.json")
|
||||
|
||||
classic = build_canonical_entries(classic_raw, aliases)
|
||||
turtle = build_canonical_entries(turtle_raw, aliases)
|
||||
|
||||
classic_names = set(classic)
|
||||
turtle_names = set(turtle)
|
||||
shared_names = classic_names & turtle_names
|
||||
|
||||
different_wire = []
|
||||
same_wire_name_only = []
|
||||
for canonical_name in sorted(shared_names):
|
||||
c = classic[canonical_name]
|
||||
t = turtle[canonical_name]
|
||||
if c.raw_value != t.raw_value:
|
||||
different_wire.append(
|
||||
f"{canonical_name}: classic={format_hex(c.raw_value)} "
|
||||
f"turtle={format_hex(t.raw_value)}"
|
||||
)
|
||||
elif c.raw_names != t.raw_names:
|
||||
same_wire_name_only.append(
|
||||
f"{canonical_name}: wire={format_hex(c.raw_value)} "
|
||||
f"classic_names={list(c.raw_names)} turtle_names={list(t.raw_names)}"
|
||||
)
|
||||
|
||||
classic_only = [
|
||||
f"{name}: {format_hex(classic[name].raw_value)} names={list(classic[name].raw_names)}"
|
||||
for name in sorted(classic_names - turtle_names)
|
||||
]
|
||||
turtle_only = [
|
||||
f"{name}: {format_hex(turtle[name].raw_value)} names={list(turtle[name].raw_names)}"
|
||||
for name in sorted(turtle_names - classic_names)
|
||||
]
|
||||
|
||||
limit = None if args.limit < 0 else args.limit
|
||||
|
||||
print(f"classic canonical entries: {len(classic)}")
|
||||
print(f"turtle canonical entries: {len(turtle)}")
|
||||
print(f"shared canonical entries: {len(shared_names)}")
|
||||
print()
|
||||
emit_section("Different wire", different_wire, limit)
|
||||
print()
|
||||
emit_section("Classic only", classic_only, limit)
|
||||
print()
|
||||
emit_section("Turtle only", turtle_only, limit)
|
||||
print()
|
||||
emit_section("Same wire, name-only differences", same_wire_name_only, limit)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
46
tools/opcode_map_utils.py
Normal file
46
tools/opcode_map_utils.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, Set
|
||||
|
||||
|
||||
RE_OPCODE_NAME = re.compile(r"^(?:CMSG|SMSG|MSG)_[A-Z0-9_]+$")
|
||||
|
||||
|
||||
def load_opcode_map(path: Path, _seen: Set[Path] | None = None) -> Dict[str, str]:
|
||||
if _seen is None:
|
||||
_seen = set()
|
||||
|
||||
path = path.resolve()
|
||||
if path in _seen:
|
||||
chain = " -> ".join(str(p) for p in list(_seen) + [path])
|
||||
raise ValueError(f"Opcode map inheritance cycle: {chain}")
|
||||
_seen.add(path)
|
||||
|
||||
data = json.loads(path.read_text())
|
||||
merged: Dict[str, str] = {}
|
||||
|
||||
extends = data.get("_extends")
|
||||
if isinstance(extends, str) and extends:
|
||||
merged.update(load_opcode_map(path.parent / extends, _seen))
|
||||
|
||||
remove = data.get("_remove", [])
|
||||
if isinstance(remove, list):
|
||||
for name in remove:
|
||||
if isinstance(name, str):
|
||||
merged.pop(name, None)
|
||||
|
||||
for key, value in data.items():
|
||||
if not isinstance(key, str) or not RE_OPCODE_NAME.match(key):
|
||||
continue
|
||||
if isinstance(value, str):
|
||||
merged[key] = value
|
||||
elif isinstance(value, int):
|
||||
merged[key] = str(value)
|
||||
|
||||
_seen.remove(path)
|
||||
return merged
|
||||
|
|
@ -17,6 +17,8 @@ import re
|
|||
from pathlib import Path
|
||||
from typing import Dict, Iterable, List, Set
|
||||
|
||||
from opcode_map_utils import load_opcode_map
|
||||
|
||||
|
||||
RE_OPCODE_NAME = re.compile(r"^(?:CMSG|SMSG|MSG)_[A-Z0-9_]+$")
|
||||
RE_CODE_REF = re.compile(r"\bOpcode::((?:CMSG|SMSG|MSG)_[A-Z0-9_]+)\b")
|
||||
|
|
@ -53,12 +55,8 @@ def iter_expansion_files(expansions_dir: Path) -> Iterable[Path]:
|
|||
|
||||
|
||||
def load_expansion_names(path: Path) -> Dict[str, str]:
|
||||
data = json.loads(path.read_text())
|
||||
out: Dict[str, str] = {}
|
||||
for k, v in data.items():
|
||||
if RE_OPCODE_NAME.match(k):
|
||||
out[k] = str(v)
|
||||
return out
|
||||
data = load_opcode_map(path)
|
||||
return {k: str(v) for k, v in data.items() if RE_OPCODE_NAME.match(k)}
|
||||
|
||||
|
||||
def collect_code_refs(root: Path) -> Set[str]:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue