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

@@ -6,9 +6,9 @@ defmodule Odinsea.Net.Cipher.ClientCrypto do
Ported from: src/handling/netty/ClientCrypto.java
"""
use Bitwise
import Bitwise
alias Odinsea.Net.Cipher.{AESCipher, IGCipher}
alias Odinsea.Net.Cipher.{AESCipher, IGCipher, ShandaCipher}
defstruct [
:version,
@@ -32,7 +32,7 @@ defmodule Odinsea.Net.Cipher.ClientCrypto do
Creates a new ClientCrypto instance with random IVs.
## Parameters
- version: MapleStory version number (e.g., 342)
- version: MapleStory version number (e.g., 112)
- use_custom_crypt: If false, uses AES encryption. If true, uses basic XOR with 0x69
## Returns
@@ -50,38 +50,97 @@ defmodule Odinsea.Net.Cipher.ClientCrypto do
}
end
@doc """
Creates a new ClientCrypto instance from existing IVs (for server handshake).
The server generates its own IVs and sends them to the client.
## Parameters
- version: MapleStory version number
- send_iv: 4-byte binary for send IV (server encrypts with this)
- recv_iv: 4-byte binary for recv IV (server decrypts with this)
## Returns
- New ClientCrypto struct
"""
@spec new_from_ivs(integer(), binary(), binary()) :: t()
def new_from_ivs(version, send_iv, recv_iv) do
%__MODULE__{
version: version,
use_custom_crypt: false,
send_iv: send_iv,
send_iv_old: <<0, 0, 0, 0>>,
recv_iv: recv_iv,
recv_iv_old: <<0, 0, 0, 0>>
}
end
@doc """
Creates a new ClientCrypto instance from client's IVs (after handshake).
The IVs must be SWAPPED because:
- Server's send IV = Client's recv IV
- Server's recv IV = Client's send IV
## Parameters
- version: MapleStory version number
- client_send_iv: Client's send IV (from client's hello packet)
- client_recv_iv: Client's recv IV (from client's hello packet)
## Returns
- New ClientCrypto struct with properly swapped IVs
"""
@spec new_from_client_ivs(integer(), binary(), binary()) :: t()
def new_from_client_ivs(version, client_send_iv, client_recv_iv) do
# Swap the IVs: server's send = client's recv, server's recv = client's send
%__MODULE__{
version: version,
use_custom_crypt: false,
send_iv: client_recv_iv,
send_iv_old: <<0, 0, 0, 0>>,
recv_iv: client_send_iv,
recv_iv_old: <<0, 0, 0, 0>>
}
end
@doc """
Encrypts outgoing packet data and updates the send IV.
Applies Shanda encryption first, then AES encryption.
## Parameters
- crypto: ClientCrypto state
- data: Binary packet data to encrypt
## Returns
- {updated_crypto, encrypted_data}
- {updated_crypto, encrypted_data, header}
"""
@spec encrypt(t(), binary()) :: {t(), binary()}
@spec encrypt(t(), binary()) :: {t(), binary(), binary()}
def encrypt(%__MODULE__{} = crypto, data) when is_binary(data) do
# Backup current send IV
updated_crypto = %{crypto | send_iv_old: crypto.send_iv}
# Encrypt the data
# Generate header BEFORE encryption (uses current IV)
header = encode_header_len(updated_crypto, byte_size(data))
# Apply Shanda encryption first
shanda_encrypted = ShandaCipher.encrypt(data)
# Apply AES encryption
encrypted_data =
if crypto.use_custom_crypt do
basic_cipher(data)
basic_cipher(shanda_encrypted)
else
AESCipher.crypt(data, crypto.send_iv)
AESCipher.crypt(shanda_encrypted, crypto.send_iv)
end
# Update the send IV using InnoGames hash
# Update the send IV using InnoGames hash (AFTER encryption)
new_send_iv = IGCipher.inno_hash(crypto.send_iv)
final_crypto = %{updated_crypto | send_iv: new_send_iv}
{final_crypto, encrypted_data}
{final_crypto, encrypted_data, header}
end
@doc """
Decrypts incoming packet data and updates the recv IV.
Applies AES decryption first, then Shanda decryption.
## Parameters
- crypto: ClientCrypto state
@@ -95,15 +154,18 @@ defmodule Odinsea.Net.Cipher.ClientCrypto do
# Backup current recv IV
updated_crypto = %{crypto | recv_iv_old: crypto.recv_iv}
# Decrypt the data
decrypted_data =
# Apply AES decryption
aes_decrypted =
if crypto.use_custom_crypt do
basic_cipher(data)
else
AESCipher.crypt(data, crypto.recv_iv)
end
# Update the recv IV using InnoGames hash
# Apply Shanda decryption
decrypted_data = ShandaCipher.decrypt(aes_decrypted)
# Update the recv IV using InnoGames hash (AFTER decryption)
new_recv_iv = IGCipher.inno_hash(crypto.recv_iv)
final_crypto = %{updated_crypto | recv_iv: new_recv_iv}
@@ -123,13 +185,14 @@ defmodule Odinsea.Net.Cipher.ClientCrypto do
"""
@spec encode_header_len(t(), non_neg_integer()) :: binary()
def encode_header_len(%__MODULE__{} = crypto, data_len) do
<<s0, s1, s2, s3>> = crypto.send_iv
<<_s0, _s1, s2, s3>> = crypto.send_iv
# Calculate the encoded version
new_version = -(crypto.version + 1) &&& 0xFFFF
enc_version = (((new_version >>> 8) &&& 0xFF) ||| ((new_version <<< 8) &&& 0xFF00)) &&& 0xFFFF
# Calculate raw sequence from send IV
# Note: Using s3 and s2 (high bytes) as in Java version
raw_seq = bxor((((s3 &&& 0xFF) ||| ((s2 <<< 8) &&& 0xFF00)) &&& 0xFFFF), enc_version)
# Calculate raw length
@@ -155,8 +218,8 @@ defmodule Odinsea.Net.Cipher.ClientCrypto do
## Returns
- Decoded packet length
"""
@spec decode_header_len(integer(), integer()) :: integer()
def decode_header_len(raw_seq, raw_len) do
@spec decode_header_len(t(), integer(), integer()) :: integer()
def decode_header_len(%__MODULE__{}, raw_seq, raw_len) do
bxor(raw_seq, raw_len) &&& 0xFFFF
end
@@ -175,6 +238,7 @@ defmodule Odinsea.Net.Cipher.ClientCrypto do
<<_r0, _r1, r2, r3>> = crypto.recv_iv
enc_version = crypto.version &&& 0xFFFF
# Note: Using r2 and r3 as in Java version
seq_base = ((r2 &&& 0xFF) ||| ((r3 <<< 8) &&& 0xFF00)) &&& 0xFFFF
bxor((raw_seq &&& 0xFFFF), seq_base) == enc_version
@@ -197,7 +261,7 @@ defmodule Odinsea.Net.Cipher.ClientCrypto do
defp basic_cipher(data) do
data
|> :binary.bin_to_list()
|> Enum.map(fn byte -> Bitwise.bxor(byte, 0x69) end)
|> Enum.map(fn byte -> bxor(byte, 0x69) end)
|> :binary.list_to_bin()
end
end