defmodule Odinsea.Net.PacketLogger do @moduledoc """ Comprehensive packet logging system for debugging MapleStory protocol. Provides detailed packet logging similar to the Java version with: - Direction (client/loopback) - Opcode name and value (decimal/hex) - Raw hex data - ASCII text representation - Context information (session, thread) """ require Logger alias Odinsea.Net.{Hex, Opcodes} @doc """ Logs an incoming client packet with full details. ## Parameters - `opcode` - The packet opcode (integer) - `data` - The packet data (binary, excluding opcode bytes) - `context` - Map with :ip, :server_type, etc. """ def log_client_packet(opcode, data, context \\ %{}) do if packet_logging_enabled?() do opcode_name = get_client_opcode_name(opcode) ip = Map.get(context, :ip, "unknown") server_type = Map.get(context, :server_type, :unknown) # Format opcode header opcode_header = format_opcode_header("client", opcode_name, opcode) # Full packet data includes opcode full_data = <> <> data # Format hex and text hex_str = Hex.encode(full_data) text_str = Hex.to_ascii(full_data) # Log the packet Logger.info(""" #{opcode_header} [Data] #{hex_str} [Text] #{text_str} [Context] IP=#{ip} Server=#{server_type} Size=#{byte_size(full_data)} bytes """) :ok else :ok end end @doc """ Logs an outgoing server packet with full details. ## Parameters - `opcode` - The packet opcode (integer) - `data` - The packet data (binary, excluding opcode bytes) - `context` - Map with :ip, :server_type, etc. """ def log_server_packet(opcode, data, context \\ %{}) do if packet_logging_enabled?() do opcode_name = get_server_opcode_name(opcode) ip = Map.get(context, :ip, "unknown") server_type = Map.get(context, :server_type, :unknown) # Format opcode header opcode_header = format_opcode_header("loopback", opcode_name, opcode) # Full packet data includes opcode full_data = <> <> data # Format hex and text hex_str = Hex.encode(full_data) text_str = Hex.to_ascii(full_data) # Log the packet Logger.info(""" #{opcode_header} [Data] #{hex_str} [Text] #{text_str} [Context] IP=#{ip} Server=#{server_type} Size=#{byte_size(full_data)} bytes """) :ok else :ok end end @doc """ Logs raw packet data (used for handshake/hello packets that don't follow normal format). """ def log_raw_packet(direction, label, data, context \\ %{}) do if packet_logging_enabled?() do ip = Map.get(context, :ip, "unknown") data = IO.iodata_to_binary(data) hex_str = Hex.encode(data) text_str = Hex.to_ascii(data) Logger.info(""" [#{direction}] [#{label}] [Data] #{hex_str} [Text] #{text_str} [Context] IP=#{ip} Size=#{byte_size(data)} bytes """) :ok else :ok end end # ================================================================================================== # Private Helper Functions # ================================================================================================== defp packet_logging_enabled? do features = Application.get_env(:odinsea, :features, []) Keyword.get(features, :log_packet, false) end defp format_opcode_header(direction, opcode_name, opcode) do opcode_hex = Integer.to_string(opcode, 16) |> String.upcase() |> String.pad_leading(2, "0") "[#{direction}] [#{opcode_name}] #{opcode} / 0x#{opcode_hex}" end # ================================================================================================== # Opcode Name Resolution # ================================================================================================== defp get_client_opcode_name(opcode) do case opcode do # Connection/Security 0x16 -> "CP_AliveAck" # Login/Account 0x01 -> "CP_PermissionRequest" 0x02 -> "CP_CheckPassword" 0x04 -> "CP_ServerlistRequest" 0x05 -> "CP_SelectWorld" 0x06 -> "CP_CheckUserLimit" 0x0E -> "CP_CheckCharName" 0x12 -> "CP_CreateChar" 0x14 -> "CP_CreateUltimate" 0x15 -> "CP_DeleteChar" 0x17 -> "CP_ExceptionLog" 0x18 -> "CP_SecurityPacket" 0x19 -> "CP_CharSelect" 0x1A -> "CP_AuthSecondPassword" 0x1D -> "CP_ClientDumpLog" 0x1E -> "CP_CreateSecurityHandle" 0x20 -> "RSA_KEY" 0x5001 -> "CP_HardwareInfo" 0x5004 -> "CP_WindowFocus" # Migration/Channel 0x0D -> "CP_PlayerLoggedIn" 0x23 -> "CP_ChangeMap" 0x24 -> "CP_ChangeChannel" 0x25 -> "CP_EnterCashShop" 0x26 -> "CP_EnterPvp" 0x27 -> "CP_EnterPvpParty" 0x29 -> "CP_LeavePvp" 0xB4 -> "CP_EnterMts" # Player Movement/Actions 0x2A -> "CP_MovePlayer" 0x2C -> "CP_CancelChair" 0x2D -> "CP_UseChair" 0x2F -> "CP_CloseRangeAttack" 0x30 -> "CP_RangedAttack" 0x31 -> "CP_MagicAttack" 0x32 -> "CP_PassiveEnergy" 0x34 -> "CP_TakeDamage" 0x35 -> "CP_PvpAttack" 0x36 -> "CP_GeneralChat" 0x37 -> "CP_CloseChalkboard" 0x38 -> "CP_FaceExpression" 0x75 -> "CP_CharInfoRequest" 0x76 -> "CP_SpawnPet" 0x78 -> "CP_CancelDebuff" # NPC Interaction 0x40 -> "CP_NpcTalk" 0x41 -> "CP_NpcMove" 0x42 -> "CP_NpcTalkMore" 0x43 -> "CP_NpcShop" 0x44 -> "CP_Storage" 0x45 -> "CP_UseHiredMerchant" 0x47 -> "CP_MerchItemStore" # Inventory/Items 0x4D -> "CP_ItemSort" 0x4E -> "CP_ItemGather" 0x4F -> "CP_ItemMove" 0x53 -> "CP_UseItem" 0x10C -> "CP_ItemPickup" # Stats/Skills 0x6A -> "CP_DistributeAp" 0x6B -> "CP_AutoAssignAp" 0x6E -> "CP_DistributeSp" 0x6F -> "CP_SpecialMove" # Social 0xA0 -> "CP_PartyChat" 0xA1 -> "CP_Whisper" 0xA4 -> "CP_PartyOperation" 0xA8 -> "CP_GuildOperation" # Cash Shop 0x135 -> "CP_CsUpdate" 0x136 -> "CP_BuyCsItem" 0x137 -> "CP_CouponCode" # Custom 0x5002 -> "CP_InjectPacket" 0x5003 -> "CP_SetCodePage" _ -> "UNKNOWN" end end defp get_server_opcode_name(opcode) do case opcode do # General 0x0D -> "LP_AliveReq" 0x0C -> "LP_ChangeChannel" 0x15 -> "LP_LatestConnectedWorld" 0x16 -> "LP_RecommendWorldMessage" # Login 0x00 -> "HELLO" 0x01 -> "LOGIN_STATUS" 0x03 -> "LP_ServerStatus" 0x06 -> "SERVERLIST" 0x07 -> "LP_CharList" 0x08 -> "LP_ServerIp" 0x09 -> "LP_CharNameResponse" 0x0A -> "LP_AddNewCharEntry" 0x0B -> "LP_DeleteCharResponse" 0x10 -> "LP_ChannelSelected" 0x12 -> "LP_RelogResponse" 0x13 -> "RSA_KEY" 0x17 -> "LOGIN_AUTH" 0x18 -> "LP_SecondPwError" # Inventory/Stats 0x19 -> "LP_ModifyInventoryItem" 0x1A -> "LP_UpdateInventorySlot" 0x1B -> "LP_UpdateStats" 0x1C -> "LP_GiveBuff" 0x1D -> "LP_CancelBuff" 0x20 -> "LP_UpdateSkills" 0x22 -> "LP_FameResponse" 0x23 -> "LP_ShowStatusInfo" 0x25 -> "LP_TrockLocations" # Social/Party/Guild 0x38 -> "LP_PartyOperation" 0x3A -> "LP_ExpeditionOperation" 0x3B -> "LP_BuddyList" 0x3D -> "LP_GuildOperation" 0x3E -> "LP_AllianceOperation" # Map Effects/Environment 0x3F -> "LP_SpawnPortal" 0x40 -> "LP_MechPortal" 0x41 -> "LP_ServerMessage" 0x4A -> "LP_YellowChat" # Family 0x6D -> "LP_SendPedigree" 0x6E -> "LP_OpenFamily" 0x73 -> "LP_Family" # Misc UI/Messages 0x7E -> "LP_TopMsg" 0x7F -> "LP_MidMsg" 0x80 -> "LP_ClearMidMsg" # Warps/Shops 0x90 -> "LP_WarpToMap" 0x91 -> "LP_MtsOpen" 0x92 -> "LP_CsOpen" # Effects 0x99 -> "LP_ShowEquipEffect" 0x9A -> "LP_MultiChat" 0x9B -> "LP_Whisper" 0xA1 -> "LP_MapEffect" 0xA6 -> "LP_Clock" # Players 0xB8 -> "LP_SpawnPlayer" 0xB9 -> "LP_RemovePlayerFromMap" 0xBA -> "LP_ChatText" 0xBC -> "LP_Chalkboard" # Pets 0xD1 -> "LP_SpawnPet" 0xD4 -> "LP_MovePet" 0xD5 -> "LP_PetChat" # Player Actions 0xE2 -> "LP_MovePlayer" 0xE4 -> "LP_CloseRangeAttack" 0xE5 -> "LP_RangedAttack" 0xE6 -> "LP_MagicAttack" 0xE8 -> "LP_SkillEffect" 0xEB -> "LP_DamagePlayer" 0xEC -> "LP_FacialExpression" 0xF0 -> "LP_ShowChair" 0xF1 -> "LP_UpdateCharLook" # Summons 0x131 -> "LP_SpawnSummon" 0x132 -> "LP_RemoveSummon" 0x133 -> "LP_MoveSummon" 0x134 -> "LP_SummonAttack" # Monsters 0x13A -> "LP_SpawnMonster" 0x13B -> "LP_KillMonster" 0x13C -> "LP_SpawnMonsterControl" 0x13D -> "LP_MoveMonster" 0x144 -> "LP_DamageMonster" # NPCs 0x156 -> "LP_SpawnNpc" 0x157 -> "LP_RemoveNpc" 0x158 -> "LP_SpawnNpcRequestController" 0x159 -> "LP_NpcAction" 0x1A3 -> "LP_NpcTalk" 0x1A5 -> "LP_OpenNpcShop" # Merchants 0x161 -> "LP_SpawnHiredMerchant" 0x162 -> "LP_DestroyHiredMerchant" # Map Objects 0x165 -> "LP_DropItemFromMapObject" 0x167 -> "LP_RemoveItemFromMap" 0x16B -> "LP_SpawnMist" 0x16C -> "LP_RemoveMist" 0x16D -> "LP_SpawnDoor" 0x16E -> "LP_RemoveDoor" # Reactors 0x171 -> "LP_ReactorHit" 0x173 -> "LP_ReactorSpawn" 0x174 -> "LP_ReactorDestroy" # NPC/Shop Interactions 0x1A6 -> "LP_ConfirmShopTransaction" 0x1A9 -> "LP_OpenStorage" 0x1AB -> "LP_MerchItemStore" # Cash Shop 0x1B8 -> "LP_CsUpdate" 0x1B9 -> "LP_CsOperation" # Input 0x1C5 -> "LP_Keymap" # Custom 0x5000 -> "LP_DamageSkin" 0x5001 -> "LP_OpenWebsite" 0x15 -> "LP_LatestConnectedWorld" 0x16 -> "LP_RecommendWorldMessage" _ -> "UNKNOWN" end end end