defmodule Odinsea.Login.Client do @moduledoc """ Client connection handler for the login server. Manages the login session state. """ use GenServer, restart: :temporary require Logger alias Odinsea.Net.Packet.In alias Odinsea.Net.Opcodes defstruct [ :socket, :ip, :state, :account_id, :account_name, :character_id, :world, :channel, :logged_in, :login_attempts, :second_password, :gender, :is_gm, :hardware_info ] def start_link(socket) do GenServer.start_link(__MODULE__, socket) end @impl true def init(socket) do {:ok, {ip, _port}} = :inet.peername(socket) ip_string = format_ip(ip) Logger.info("Login client connected from #{ip_string}") state = %__MODULE__{ socket: socket, ip: ip_string, state: :connected, account_id: nil, account_name: nil, character_id: nil, world: nil, channel: nil, logged_in: false, login_attempts: 0, second_password: nil, gender: 0, is_gm: false, hardware_info: nil } # Start receiving packets send(self(), :receive) {:ok, state} end @impl true def handle_info(:receive, %{socket: socket} = state) do case :gen_tcp.recv(socket, 0, 30_000) do {:ok, data} -> # Handle packet new_state = handle_packet(data, state) send(self(), :receive) {:noreply, new_state} {:error, :closed} -> Logger.info("Login client disconnected: #{state.ip}") {:stop, :normal, state} {:error, reason} -> Logger.warning("Login client error: #{inspect(reason)}") {:stop, :normal, state} end end @impl true def handle_info({:disconnect, reason}, state) do Logger.info("Disconnecting client #{state.ip}: #{inspect(reason)}") {:stop, :normal, state} end @impl true def terminate(_reason, state) do if state.socket do :gen_tcp.close(state.socket) end :ok end defp handle_packet(data, state) do packet = In.new(data) # Read opcode (first 2 bytes) case In.decode_short(packet) do {opcode, packet} -> Logger.debug("Login packet received: opcode=0x#{Integer.to_string(opcode, 16)}") dispatch_packet(opcode, packet, state) :error -> Logger.warning("Failed to read packet opcode") state end end defp dispatch_packet(opcode, packet, state) do # Use PacketProcessor to route packets alias Odinsea.Net.Processor case Processor.handle(opcode, packet, state, :login) do {:ok, new_state} -> new_state {:error, reason, new_state} -> Logger.error("Packet processing error: #{inspect(reason)}") new_state {:disconnect, reason} -> Logger.warning("Client disconnected: #{inspect(reason)}") send(self(), {:disconnect, reason}) state end end defp format_ip({a, b, c, d}) do "#{a}.#{b}.#{c}.#{d}" end defp format_ip({a, b, c, d, e, f, g, h}) do "#{a}:#{b}:#{c}:#{d}:#{e}:#{f}:#{g}:#{h}" end end