Start repo, claude & kimi still vibing tho
This commit is contained in:
140
lib/odinsea/login/client.ex
Normal file
140
lib/odinsea/login/client.ex
Normal file
@@ -0,0 +1,140 @@
|
||||
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
|
||||
Reference in New Issue
Block a user