fix login issue*

This commit is contained in:
2026-02-25 12:26:26 -07:00
parent da581f5a20
commit 2c3d0ab580
37 changed files with 4708 additions and 721 deletions

View File

@@ -14,7 +14,8 @@ defmodule Odinsea.Login.Handler do
require Logger
alias Odinsea.Net.Packet.{In, Out}
alias Odinsea.Net.Cipher.LoginCrypto
alias Odinsea.Net.Cipher.{ClientCrypto, LoginCrypto}
alias Odinsea.Net.PacketLogger
alias Odinsea.Login.Packets
alias Odinsea.Constants.Server
alias Odinsea.Database.Context
@@ -79,19 +80,26 @@ defmodule Odinsea.Login.Handler do
Logger.info("Login attempt: username=#{username} from #{state.ip}")
# Check if IP/MAC is banned
features = Application.get_env(:odinsea, :features, [])
skip_maccheck = Keyword.get(features, :skip_maccheck, false)
ip_banned = Context.ip_banned?(state.ip)
mac_banned = Context.mac_banned?(state.mac)
mac_banned = if skip_maccheck or state.macs == [] do
false
else
Enum.any?(state.macs, &Context.mac_banned?/1)
end
if (ip_banned || mac_banned) do
Logger.warning("Banned IP/MAC attempted login: ip=#{state.ip}, mac=#{state.mac}")
Logger.warning("Banned IP/MAC attempted login: ip=#{state.ip}, macs=#{inspect(state.macs)}")
# If MAC banned, also ban the IP for enforcement
if mac_banned do
Context.ban_ip_address(state.ip, "Enforcing account ban, account #{username}", false, 4)
end
response = Packets.get_login_failed(3)
send_packet(state, response)
state = send_packet(state, response)
{:ok, state}
else
# Authenticate with database
@@ -106,7 +114,7 @@ defmodule Odinsea.Login.Handler do
format_timestamp(temp_ban_info.expires),
temp_ban_info.reason || ""
)
send_packet(state, response)
state = send_packet(state, response)
{:ok, state}
else
# Check if already logged in - kick other session
@@ -132,6 +140,8 @@ defmodule Odinsea.Login.Handler do
account_info.second_password
)
state = send_packet(state, response)
new_state =
state
|> Map.put(:logged_in, true)
@@ -142,8 +152,8 @@ defmodule Odinsea.Login.Handler do
|> Map.put(:second_password, account_info.second_password)
|> Map.put(:login_attempts, 0)
send_packet(state, response)
{:ok, new_state}
# Send world info immediately after auth success (Java: LoginWorker.registerClient)
on_world_info_request(new_state)
end
{:error, :invalid_credentials} ->
@@ -156,7 +166,7 @@ defmodule Odinsea.Login.Handler do
else
# Send login failed (reason 4 = incorrect password)
response = Packets.get_login_failed(4)
send_packet(state, response)
state = send_packet(state, response)
new_state = Map.put(state, :login_attempts, login_attempts)
{:ok, new_state}
@@ -165,7 +175,7 @@ defmodule Odinsea.Login.Handler do
{:error, :account_not_found} ->
# Send login failed (reason 5 = not registered ID)
response = Packets.get_login_failed(5)
send_packet(state, response)
state = send_packet(state, response)
{:ok, state}
{:error, :already_logged_in} ->
@@ -176,12 +186,12 @@ defmodule Odinsea.Login.Handler do
# Send login failed (reason 7 = already logged in) but client can retry
response = Packets.get_login_failed(7)
send_packet(state, response)
state = send_packet(state, response)
{:ok, state}
{:error, :banned} ->
response = Packets.get_perm_ban(0)
send_packet(state, response)
state = send_packet(state, response)
{:ok, state}
end
end
@@ -209,19 +219,19 @@ defmodule Odinsea.Login.Handler do
channel_load
)
send_packet(state, server_list)
state = send_packet(state, server_list)
# Send end of server list
end_list = Packets.get_end_of_server_list()
send_packet(state, end_list)
state = send_packet(state, end_list)
# Send latest connected world
latest_world = Packets.get_latest_connected_world(0)
send_packet(state, latest_world)
state = send_packet(state, latest_world)
# Send recommended world message
recommend = Packets.get_recommend_world_message(0, "Join now!")
send_packet(state, recommend)
state = send_packet(state, recommend)
{:ok, state}
end
@@ -246,7 +256,7 @@ defmodule Odinsea.Login.Handler do
end
response = Packets.get_server_status(status)
send_packet(state, response)
state = send_packet(state, response)
{:ok, state}
end
@@ -277,7 +287,7 @@ defmodule Odinsea.Login.Handler do
if world_id != 0 do
Logger.warning("Invalid world ID: #{world_id}")
response = Packets.get_login_failed(10)
send_packet(state, response)
state = send_packet(state, response)
{:ok, state}
else
# TODO: Check if channel is available
@@ -299,7 +309,7 @@ defmodule Odinsea.Login.Handler do
3 # character slots
)
send_packet(state, response)
state = send_packet(state, response)
new_state =
state
@@ -332,7 +342,7 @@ defmodule Odinsea.Login.Handler do
name_used = check_name_used(char_name, state)
response = Packets.get_char_name_response(char_name, name_used)
send_packet(state, response)
state = send_packet(state, response)
{:ok, state}
end
@@ -381,7 +391,7 @@ defmodule Odinsea.Login.Handler do
# Validate name is not forbidden and doesn't exist
if check_name_used(name, state) do
response = Packets.get_add_new_char_entry(nil, false)
send_packet(state, response)
state = send_packet(state, response)
{:ok, state}
else
# TODO: Validate appearance items are eligible for gender/job type
@@ -420,8 +430,8 @@ defmodule Odinsea.Login.Handler do
# Reload character with full data
char_data = Context.load_character(character.id)
response = Packets.get_add_new_char_entry(char_data, true)
send_packet(state, response)
state = send_packet(state, response)
# Add character ID to state's character list
new_char_ids = [character.id | Map.get(state, :character_ids, [])]
new_state = Map.put(state, :character_ids, new_char_ids)
@@ -430,7 +440,7 @@ defmodule Odinsea.Login.Handler do
{:error, changeset} ->
Logger.error("Failed to create character: #{inspect(changeset.errors)}")
response = Packets.get_add_new_char_entry(nil, false)
send_packet(state, response)
state = send_packet(state, response)
{:ok, state}
end
end
@@ -511,8 +521,8 @@ defmodule Odinsea.Login.Handler do
end
response = Packets.get_delete_char_response(character_id, result)
send_packet(state, response)
state = send_packet(state, response)
# Update state if successful
new_state =
if result == 0 do
@@ -559,7 +569,7 @@ defmodule Odinsea.Login.Handler do
# Validate second password length
if String.length(new_spw) < 6 || String.length(new_spw) > 16 do
response = Packets.get_second_pw_error(0x14)
send_packet(state, response)
state = send_packet(state, response)
{:ok, state}
else
# Update second password
@@ -605,9 +615,9 @@ defmodule Odinsea.Login.Handler do
# Send migration command
response = Packets.get_server_ip(false, channel_ip, channel_port, character_id)
send_packet(state, response)
new_state =
state = send_packet(state, response)
new_state =
state
|> Map.put(:character_id, character_id)
|> Map.put(:migration_token, migration_token)
@@ -643,7 +653,7 @@ defmodule Odinsea.Login.Handler do
else
# Failure - send error
response = Packets.get_second_pw_error(15) # Incorrect SPW
send_packet(state, response)
state = send_packet(state, response)
{:ok, state}
end
end
@@ -662,14 +672,12 @@ defmodule Odinsea.Login.Handler do
# TODO: Send damage cap packet if custom client
# Send login background
background = Application.get_env(:odinsea, :login_background, "MapLogin")
bg_response = Packets.get_login_background(background)
send_packet(state, bg_response)
bg_response = Packets.get_login_background(Server.maplogin_default())
state = send_packet(state, bg_response)
# Send RSA public key
pub_key = Application.get_env(:odinsea, :rsa_public_key, "")
key_response = Packets.get_rsa_key(pub_key)
send_packet(state, key_response)
key_response = Packets.get_rsa_key(Server.pub_key())
state = send_packet(state, key_response)
{:ok, state}
end
@@ -678,27 +686,40 @@ defmodule Odinsea.Login.Handler do
# Helper Functions
# ==================================================================================================
defp send_packet(%{socket: socket} = state, packet_data) do
# Add header (2 bytes: packet length)
packet_length = byte_size(packet_data)
header = <<packet_length::little-size(16)>>
full_packet = header <> packet_data
defp send_packet(%{socket: socket, crypto: crypto} = state, packet_data) do
# Flatten iodata to binary for pattern matching
packet_data = IO.iodata_to_binary(packet_data)
# Extract opcode from packet data (first 2 bytes)
<<opcode::little-16, rest::binary>> = packet_data
# Log the packet
context = %{
ip: state.ip,
server_type: :login
}
PacketLogger.log_server_packet(opcode, rest, context)
# Encrypt the data (Shanda then AES) and morph send IV
{updated_crypto, encrypted, header} = ClientCrypto.encrypt(crypto, packet_data)
# Send encrypted packet with 4-byte crypto header
full_packet = header <> encrypted
case :gen_tcp.send(socket, full_packet) do
:ok ->
Logger.debug("Sent packet: #{packet_length} bytes")
:ok
%{state | crypto: updated_crypto}
{:error, reason} ->
Logger.error("Failed to send packet: #{inspect(reason)}")
{:error, reason}
state
end
end
defp send_packet(_state, _packet_data) do
defp send_packet(state, _packet_data) do
# Socket not available in state
Logger.error("Cannot send packet: socket not in state")
:error
state
end
defp authenticate_user(username, password, _state) do