Files
odinsea-elixir/lib/odinsea/login/client.ex

141 lines
3.0 KiB
Elixir

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