fix login issue*
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user