Start repo, claude & kimi still vibing tho
This commit is contained in:
413
lib/odinsea/net/processor.ex
Normal file
413
lib/odinsea/net/processor.ex
Normal file
@@ -0,0 +1,413 @@
|
||||
defmodule Odinsea.Net.Processor do
|
||||
@moduledoc """
|
||||
Central packet routing/dispatch system.
|
||||
Ported from Java PacketProcessor.java
|
||||
|
||||
Routes incoming packets to appropriate handlers based on:
|
||||
- Server type (Login, Channel, Shop)
|
||||
- Packet opcode
|
||||
"""
|
||||
|
||||
require Logger
|
||||
|
||||
alias Odinsea.Net.Packet.In
|
||||
alias Odinsea.Net.Opcodes
|
||||
|
||||
@type server_type :: :login | :channel | :shop
|
||||
@type client_state :: map()
|
||||
|
||||
# ==================================================================================================
|
||||
# Main Entry Point
|
||||
# ==================================================================================================
|
||||
|
||||
@doc """
|
||||
Main packet handler entry point.
|
||||
Routes packets to appropriate server-specific handlers.
|
||||
|
||||
## Parameters
|
||||
- `opcode` - The packet opcode (integer)
|
||||
- `packet` - The InPacket struct (already read past opcode)
|
||||
- `client_state` - The client's state map
|
||||
- `server_type` - :login | :channel | :shop
|
||||
|
||||
## Returns
|
||||
- `{:ok, new_state}` - Success with updated state
|
||||
- `{:error, reason, state}` - Error with reason
|
||||
- `{:disconnect, reason}` - Client should be disconnected
|
||||
"""
|
||||
def handle(opcode, %In{} = packet, client_state, server_type) do
|
||||
# Pre-process common packets that apply to all server types
|
||||
case preprocess(opcode, packet, client_state) do
|
||||
{:handled, new_state} ->
|
||||
{:ok, new_state}
|
||||
|
||||
:continue ->
|
||||
# Route to server-specific handler
|
||||
case server_type do
|
||||
:login ->
|
||||
handle_login(opcode, packet, client_state)
|
||||
|
||||
:shop ->
|
||||
handle_shop(opcode, packet, client_state)
|
||||
|
||||
:channel ->
|
||||
handle_channel(opcode, packet, client_state)
|
||||
|
||||
_ ->
|
||||
Logger.warning("Unknown server type: #{inspect(server_type)}")
|
||||
{:ok, client_state}
|
||||
end
|
||||
end
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("Packet processing error: #{inspect(e)}")
|
||||
{:error, :processing_error, client_state}
|
||||
end
|
||||
|
||||
# ==================================================================================================
|
||||
# Pre-Processing (Common Packets)
|
||||
# ==================================================================================================
|
||||
|
||||
@doc """
|
||||
Pre-processes packets that are common across all server types.
|
||||
Returns {:handled, state} if the packet was fully handled.
|
||||
Returns :continue if the packet should be routed to server-specific handlers.
|
||||
"""
|
||||
# Define opcodes as module attributes for use in guards
|
||||
@cp_security_packet Opcodes.cp_security_packet()
|
||||
@cp_alive_ack Opcodes.cp_alive_ack()
|
||||
@cp_client_dump_log Opcodes.cp_client_dump_log()
|
||||
@cp_hardware_info Opcodes.cp_hardware_info()
|
||||
@cp_inject_packet Opcodes.cp_inject_packet()
|
||||
@cp_set_code_page Opcodes.cp_set_code_page()
|
||||
@cp_window_focus Opcodes.cp_window_focus()
|
||||
@cp_exception_log Opcodes.cp_exception_log()
|
||||
|
||||
defp preprocess(opcode, packet, state) do
|
||||
case opcode do
|
||||
# Security packet - always handled in pre-process
|
||||
@cp_security_packet ->
|
||||
handle_security_packet(packet, state)
|
||||
{:handled, state}
|
||||
|
||||
# Alive acknowledgement - keep connection alive
|
||||
@cp_alive_ack ->
|
||||
handle_alive_ack(state)
|
||||
{:handled, state}
|
||||
|
||||
# Client dump log - debugging/crash reports
|
||||
@cp_client_dump_log ->
|
||||
handle_dump_log(packet, state)
|
||||
{:handled, state}
|
||||
|
||||
# Hardware info - client machine identification
|
||||
@cp_hardware_info ->
|
||||
handle_hardware_info(packet, state)
|
||||
{:handled, state}
|
||||
|
||||
# Packet injection attempt - security violation
|
||||
@cp_inject_packet ->
|
||||
handle_inject_packet(packet, state)
|
||||
{:disconnect, :packet_injection}
|
||||
|
||||
# Code page settings
|
||||
@cp_set_code_page ->
|
||||
handle_code_page(packet, state)
|
||||
{:handled, state}
|
||||
|
||||
# Window focus - anti-cheat detection
|
||||
@cp_window_focus ->
|
||||
handle_window_focus(packet, state)
|
||||
{:handled, state}
|
||||
|
||||
# Exception log from client
|
||||
@cp_exception_log ->
|
||||
handle_exception_log(packet, state)
|
||||
{:handled, state}
|
||||
|
||||
# Not a common packet, continue to server-specific handling
|
||||
_ ->
|
||||
:continue
|
||||
end
|
||||
end
|
||||
|
||||
# Login opcodes as module attributes
|
||||
@cp_client_hello Opcodes.cp_client_hello()
|
||||
@cp_check_password Opcodes.cp_check_password()
|
||||
@cp_world_info_request Opcodes.cp_world_info_request()
|
||||
@cp_select_world Opcodes.cp_select_world()
|
||||
@cp_check_user_limit Opcodes.cp_check_user_limit()
|
||||
@cp_check_duplicated_id Opcodes.cp_check_duplicated_id()
|
||||
@cp_create_new_character Opcodes.cp_create_new_character()
|
||||
@cp_create_ultimate Opcodes.cp_create_ultimate()
|
||||
@cp_delete_character Opcodes.cp_delete_character()
|
||||
@cp_select_character Opcodes.cp_select_character()
|
||||
@cp_check_spw_request Opcodes.cp_check_spw_request()
|
||||
@cp_rsa_key Opcodes.cp_rsa_key()
|
||||
|
||||
# ==================================================================================================
|
||||
# Login Server Packet Handlers
|
||||
# ==================================================================================================
|
||||
|
||||
@doc """
|
||||
Routes packets for the Login server.
|
||||
Delegates to Odinsea.Login.Handler module.
|
||||
"""
|
||||
defp handle_login(opcode, packet, state) do
|
||||
alias Odinsea.Login.Handler
|
||||
|
||||
case opcode do
|
||||
# Permission request (client hello / initial handshake)
|
||||
@cp_client_hello ->
|
||||
Handler.on_permission_request(packet, state)
|
||||
|
||||
# Password check (login authentication)
|
||||
@cp_check_password ->
|
||||
Handler.on_check_password(packet, state)
|
||||
|
||||
# World info request (server list)
|
||||
@cp_world_info_request ->
|
||||
Handler.on_world_info_request(state)
|
||||
|
||||
# Select world
|
||||
@cp_select_world ->
|
||||
Handler.on_select_world(packet, state)
|
||||
|
||||
# Check user limit (channel population check)
|
||||
@cp_check_user_limit ->
|
||||
Handler.on_check_user_limit(state)
|
||||
|
||||
# Check duplicated ID (character name availability)
|
||||
@cp_check_duplicated_id ->
|
||||
Handler.on_check_duplicated_id(packet, state)
|
||||
|
||||
# Create new character
|
||||
@cp_create_new_character ->
|
||||
Handler.on_create_new_character(packet, state)
|
||||
|
||||
# Create ultimate (Cygnus Knights)
|
||||
@cp_create_ultimate ->
|
||||
Handler.on_create_ultimate(packet, state)
|
||||
|
||||
# Delete character
|
||||
@cp_delete_character ->
|
||||
Handler.on_delete_character(packet, state)
|
||||
|
||||
# Select character (enter game)
|
||||
@cp_select_character ->
|
||||
Handler.on_select_character(packet, state)
|
||||
|
||||
# Second password check
|
||||
@cp_check_spw_request ->
|
||||
Handler.on_check_spw_request(packet, state)
|
||||
|
||||
# RSA key request
|
||||
@cp_rsa_key ->
|
||||
Handler.on_rsa_key(packet, state)
|
||||
|
||||
# Unhandled login packet
|
||||
_ ->
|
||||
Logger.debug("Unhandled login packet: opcode=0x#{Integer.to_string(opcode, 16)}")
|
||||
{:ok, state}
|
||||
end
|
||||
end
|
||||
|
||||
# Channel opcodes as module attributes
|
||||
@cp_migrate_in Opcodes.cp_migrate_in()
|
||||
@cp_move_player Opcodes.cp_move_player()
|
||||
@cp_general_chat Opcodes.cp_general_chat()
|
||||
@cp_change_map Opcodes.cp_change_map()
|
||||
|
||||
# ==================================================================================================
|
||||
# Channel Server Packet Handlers
|
||||
# ==================================================================================================
|
||||
|
||||
@doc """
|
||||
Routes packets for the Channel server (game world).
|
||||
Delegates to appropriate handler modules.
|
||||
"""
|
||||
defp handle_channel(opcode, packet, state) do
|
||||
# TODO: Implement channel packet routing
|
||||
# Will route to:
|
||||
# - Odinsea.Channel.Handler.Player (movement, attacks, skills)
|
||||
# - Odinsea.Channel.Handler.Inventory (items, equipment)
|
||||
# - Odinsea.Channel.Handler.Mob (monster interactions)
|
||||
# - Odinsea.Channel.Handler.NPC (NPC dialogs, shops)
|
||||
# - Odinsea.Channel.Handler.Chat (chat, party, guild)
|
||||
# - etc.
|
||||
|
||||
case opcode do
|
||||
# Migrate in from login server
|
||||
@cp_migrate_in ->
|
||||
{character_id, _} = In.decode_int(packet)
|
||||
handle_migrate_in(character_id, state)
|
||||
|
||||
# Player movement
|
||||
@cp_move_player ->
|
||||
{:ok, state} # TODO: Implement
|
||||
|
||||
# General chat
|
||||
@cp_general_chat ->
|
||||
{:ok, state} # TODO: Implement
|
||||
|
||||
# Change map
|
||||
@cp_change_map ->
|
||||
{:ok, state} # TODO: Implement
|
||||
|
||||
# Unhandled channel packet
|
||||
_ ->
|
||||
Logger.debug("Unhandled channel packet: opcode=0x#{Integer.to_string(opcode, 16)}")
|
||||
{:ok, state}
|
||||
end
|
||||
end
|
||||
|
||||
# Shop opcodes as module attributes
|
||||
@cp_buy_cs_item Opcodes.cp_buy_cs_item()
|
||||
@cp_coupon_code Opcodes.cp_coupon_code()
|
||||
@cp_cs_update Opcodes.cp_cs_update()
|
||||
|
||||
# ==================================================================================================
|
||||
# Cash Shop Server Packet Handlers
|
||||
# ==================================================================================================
|
||||
|
||||
@doc """
|
||||
Routes packets for the Cash Shop server.
|
||||
Delegates to Odinsea.Shop.Handler module.
|
||||
"""
|
||||
defp handle_shop(opcode, packet, state) do
|
||||
# TODO: Implement cash shop packet routing
|
||||
|
||||
case opcode do
|
||||
# Migrate in from channel server
|
||||
@cp_migrate_in ->
|
||||
{character_id, _} = In.decode_int(packet)
|
||||
handle_migrate_in(character_id, state)
|
||||
|
||||
# Buy cash item
|
||||
@cp_buy_cs_item ->
|
||||
{:ok, state} # TODO: Implement
|
||||
|
||||
# Coupon code
|
||||
@cp_coupon_code ->
|
||||
{:ok, state} # TODO: Implement
|
||||
|
||||
# Cash shop update
|
||||
@cp_cs_update ->
|
||||
{:ok, state} # TODO: Implement
|
||||
|
||||
# Leave cash shop
|
||||
@cp_change_map ->
|
||||
{:ok, state} # TODO: Implement
|
||||
|
||||
# Unhandled shop packet
|
||||
_ ->
|
||||
Logger.debug("Unhandled shop packet: opcode=0x#{Integer.to_string(opcode, 16)}")
|
||||
{:ok, state}
|
||||
end
|
||||
end
|
||||
|
||||
# ==================================================================================================
|
||||
# Common Packet Implementations
|
||||
# ==================================================================================================
|
||||
|
||||
defp handle_security_packet(_packet, _state) do
|
||||
# Security packet - just acknowledge
|
||||
# In the Java version, this is a no-op that returns true in preProcess
|
||||
:ok
|
||||
end
|
||||
|
||||
defp handle_alive_ack(state) do
|
||||
# Update last alive time
|
||||
new_state = Map.put(state, :last_alive, System.system_time(:millisecond))
|
||||
Logger.debug("Alive ack received from #{state.ip}")
|
||||
{:ok, new_state}
|
||||
end
|
||||
|
||||
defp handle_dump_log(packet, state) do
|
||||
{call_type, packet} = In.decode_short(packet)
|
||||
{error_code, packet} = In.decode_int(packet)
|
||||
{backup_buffer_size, packet} = In.decode_short(packet)
|
||||
{raw_seq, packet} = In.decode_int(packet)
|
||||
{p_type, packet} = In.decode_short(packet)
|
||||
{backup_buffer, _packet} = In.decode_buffer(packet, backup_buffer_size - 6)
|
||||
|
||||
log_msg = "[ClientDumpLog] RawSeq: #{raw_seq} CallType: #{call_type} " <>
|
||||
"ErrorCode: #{error_code} BufferSize: #{backup_buffer_size} " <>
|
||||
"Type: 0x#{Integer.to_string(p_type, 16)} " <>
|
||||
"Packet: #{inspect(backup_buffer, limit: :infinity)}"
|
||||
|
||||
Logger.warning(log_msg)
|
||||
:ok
|
||||
end
|
||||
|
||||
defp handle_hardware_info(packet, state) do
|
||||
{hardware_info, _packet} = In.decode_string(packet)
|
||||
new_state = Map.put(state, :hardware_info, hardware_info)
|
||||
Logger.debug("Hardware info: #{hardware_info}")
|
||||
{:ok, new_state}
|
||||
end
|
||||
|
||||
defp handle_inject_packet(packet, state) do
|
||||
start = packet.position
|
||||
finish = byte_size(packet.data) - 2
|
||||
|
||||
# Read opcode at end
|
||||
packet = %{packet | position: finish}
|
||||
{opcode, _packet} = In.decode_short(packet)
|
||||
|
||||
# Get hex string of injected packet
|
||||
injected_data = binary_part(packet.data, start, finish - start)
|
||||
|
||||
player_name = Map.get(state, :character_name, "<none>")
|
||||
player_id = Map.get(state, :character_id, 0)
|
||||
|
||||
Logger.error(
|
||||
"[InjectPacket] [Session #{state.ip}] [Player #{player_name} - #{player_id}] " <>
|
||||
"[OpCode 0x#{Integer.to_string(opcode, 16)}] #{inspect(injected_data, limit: :infinity)}"
|
||||
)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp handle_code_page(packet, state) do
|
||||
{code_page, packet} = In.decode_int(packet)
|
||||
{code_page_read, _packet} = In.decode_int(packet)
|
||||
|
||||
new_state =
|
||||
state
|
||||
|> Map.put(:code_page, code_page)
|
||||
|> Map.put(:code_page_read, code_page_read)
|
||||
|
||||
Logger.debug("Code page set: #{code_page}, read: #{code_page_read}")
|
||||
{:ok, new_state}
|
||||
end
|
||||
|
||||
defp handle_window_focus(packet, state) do
|
||||
{focus, _packet} = In.decode_byte(packet)
|
||||
|
||||
if focus == 0 do
|
||||
player_name = Map.get(state, :character_name, "<none>")
|
||||
player_id = Map.get(state, :character_id, 0)
|
||||
|
||||
Logger.warning(
|
||||
"[WindowFocus] Client lost focus [Session #{state.ip}] [Player #{player_name} - #{player_id}]"
|
||||
)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp handle_exception_log(packet, state) do
|
||||
{exception_msg, _packet} = In.decode_string(packet)
|
||||
|
||||
Logger.warning("[ClientExceptionLog] [Session #{state.ip}] #{exception_msg}")
|
||||
:ok
|
||||
end
|
||||
|
||||
defp handle_migrate_in(character_id, state) do
|
||||
# TODO: Load character from database, restore session
|
||||
Logger.info("Migrate in: character_id=#{character_id}")
|
||||
new_state = Map.put(state, :character_id, character_id)
|
||||
{:ok, new_state}
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user