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,12 @@ defmodule Odinsea.Net.Cipher.AESCipher do
Ported from: src/handling/netty/cipher/AESCipher.java
"""
import Bitwise
@block_size 1460
# MapleStory AES key (32 bytes, expanded from the Java version)
# MapleStory AES key (32 bytes = AES-256)
# Must match the Java AES_KEY exactly
@aes_key <<
0x13, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00,
@@ -21,7 +24,14 @@ defmodule Odinsea.Net.Cipher.AESCipher do
>>
@doc """
Encrypts or decrypts packet data in place using AES-ECB with IV.
Encrypts or decrypts packet data using AES-ECB with IV.
The algorithm (per block of 1456/1460 bytes):
1. Expand the 4-byte IV to 16 bytes by repeating it 4 times
2. Use AES-ECB to encrypt the expanded IV, producing a 16-byte keystream
3. XOR the next 16 bytes of data with the keystream
4. The encrypted IV becomes the new IV for the next 16-byte chunk
5. Repeat until the block is processed
## Parameters
- data: Binary data to encrypt/decrypt
@@ -32,74 +42,52 @@ defmodule Odinsea.Net.Cipher.AESCipher do
"""
@spec crypt(binary(), binary()) :: binary()
def crypt(data, <<_::binary-size(4)>> = iv) when is_binary(data) do
crypt_recursive(data, iv, 0, byte_size(data), @block_size - 4)
data_list = :binary.bin_to_list(data)
result = crypt_blocks(data_list, 0, @block_size - 4, iv)
:binary.list_to_bin(result)
end
# Recursive encryption/decryption function
@spec crypt_recursive(binary(), binary(), non_neg_integer(), non_neg_integer(), non_neg_integer()) :: binary()
defp crypt_recursive(data, _iv, start, remaining, _length) when remaining <= 0 do
# Return the portion of data we've processed
binary_part(data, 0, start)
end
# Process data in blocks (first block: 1456 bytes, subsequent: 1460 bytes)
defp crypt_blocks([], _start, _length, _iv), do: []
defp crypt_recursive(data, iv, start, remaining, length) do
# Multiply the IV by 4
seq_iv = multiply_bytes(iv, byte_size(iv), 4)
# Adjust length if remaining is smaller
defp crypt_blocks(data, start, length, iv) do
remaining = Kernel.length(data)
actual_length = min(remaining, length)
# Extract the portion of data to process
data_bytes = :binary.bin_to_list(data)
# Expand 4-byte IV to 16 bytes (repeat 4 times)
seq_iv = :binary.copy(iv, 4)
seq_iv_list = :binary.bin_to_list(seq_iv)
# Process the data chunk
{new_data_bytes, _final_seq_iv} =
process_chunk(data_bytes, seq_iv, start, start + actual_length, 0)
# Process this block's bytes, re-encrypting keystream every 16 bytes
{block, rest} = Enum.split(data, actual_length)
{processed, _final_iv} = process_block_bytes(block, seq_iv_list, 0)
# Convert back to binary
new_data = :binary.list_to_bin(new_data_bytes)
# Continue with next chunk
new_start = start + actual_length
new_remaining = remaining - actual_length
new_length = @block_size
crypt_recursive(new_data, iv, new_start, new_remaining, new_length)
# Continue with next block using fresh IV expansion
processed ++ crypt_blocks(rest, start + actual_length, @block_size, iv)
end
# Process a single chunk of data
@spec process_chunk(list(byte()), binary(), non_neg_integer(), non_neg_integer(), non_neg_integer()) ::
{list(byte()), binary()}
defp process_chunk(data_bytes, seq_iv, x, end_x, _offset) when x >= end_x do
{data_bytes, seq_iv}
end
# Process bytes within a single block, re-encrypting keystream every 16 bytes
defp process_block_bytes([], iv_list, _offset), do: {[], iv_list}
defp process_chunk(data_bytes, seq_iv, x, end_x, offset) do
# Check if we need to re-encrypt the IV
{new_seq_iv, new_offset} =
if rem(offset, byte_size(seq_iv)) == 0 do
# Encrypt the IV using AES
encrypted_iv = aes_encrypt_block(seq_iv)
{encrypted_iv, 0}
defp process_block_bytes(data, iv_list, offset) do
# Re-encrypt keystream at every 16-byte boundary
iv_list =
if rem(offset, 16) == 0 do
new_iv = aes_encrypt_block(:binary.list_to_bin(iv_list))
:binary.bin_to_list(new_iv)
else
{seq_iv, offset}
iv_list
end
# XOR the data byte with the IV byte
seq_iv_bytes = :binary.bin_to_list(new_seq_iv)
iv_index = rem(new_offset, length(seq_iv_bytes))
iv_byte = Enum.at(seq_iv_bytes, iv_index)
data_byte = Enum.at(data_bytes, x)
xor_byte = Bitwise.bxor(data_byte, iv_byte)
[byte | rest] = data
iv_byte = Enum.at(iv_list, rem(offset, 16))
xored = bxor(byte, iv_byte)
# Update the data
updated_data = List.replace_at(data_bytes, x, xor_byte)
# Continue processing
process_chunk(updated_data, new_seq_iv, x + 1, end_x, new_offset + 1)
{rest_result, final_iv} = process_block_bytes(rest, iv_list, offset + 1)
{[xored | rest_result], final_iv}
end
# Encrypt a single 16-byte block using AES-ECB
# Encrypt a single 16-byte block using AES-256-ECB
@spec aes_encrypt_block(binary()) :: binary()
defp aes_encrypt_block(block) do
# Pad or truncate to 16 bytes for AES
@@ -110,15 +98,9 @@ defmodule Odinsea.Net.Cipher.AESCipher do
size when size > 16 -> binary_part(block, 0, 16)
end
# Perform AES encryption in ECB mode
:crypto.crypto_one_time(:aes_128_ecb, @aes_key, padded_block, true)
end
# Multiply bytes - repeats the first `count` bytes of `input` `mul` times
@spec multiply_bytes(binary(), non_neg_integer(), non_neg_integer()) :: binary()
defp multiply_bytes(input, count, mul) do
# Take first `count` bytes and repeat them `mul` times
chunk = binary_part(input, 0, min(count, byte_size(input)))
:binary.copy(chunk, mul)
# Perform AES encryption in ECB mode (AES-256)
result = :crypto.crypto_one_time(:aes_256_ecb, @aes_key, padded_block, true)
# Take only first 16 bytes (OpenSSL may add PKCS padding)
binary_part(result, 0, 16)
end
end

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

View File

@@ -6,7 +6,28 @@ defmodule Odinsea.Net.Cipher.IGCipher do
Ported from: src/handling/netty/cipher/IGCipher.java
"""
use Bitwise
import Bitwise
# Shuffle table - 256 bytes used for IV transformation
# Must be defined before functions that use it
@shuffle_table {
0xEC, 0x3F, 0x77, 0xA4, 0x45, 0xD0, 0x71, 0xBF, 0xB7, 0x98, 0x20, 0xFC, 0x4B, 0xE9, 0xB3, 0xE1,
0x5C, 0x22, 0xF7, 0x0C, 0x44, 0x1B, 0x81, 0xBD, 0x63, 0x8D, 0xD4, 0xC3, 0xF2, 0x10, 0x19, 0xE0,
0xFB, 0xA1, 0x6E, 0x66, 0xEA, 0xAE, 0xD6, 0xCE, 0x06, 0x18, 0x4E, 0xEB, 0x78, 0x95, 0xDB, 0xBA,
0xB6, 0x42, 0x7A, 0x2A, 0x83, 0x0B, 0x54, 0x67, 0x6D, 0xE8, 0x65, 0xE7, 0x2F, 0x07, 0xF3, 0xAA,
0x27, 0x7B, 0x85, 0xB0, 0x26, 0xFD, 0x8B, 0xA9, 0xFA, 0xBE, 0xA8, 0xD7, 0xCB, 0xCC, 0x92, 0xDA,
0xF9, 0x93, 0x60, 0x2D, 0xDD, 0xD2, 0xA2, 0x9B, 0x39, 0x5F, 0x82, 0x21, 0x4C, 0x69, 0xF8, 0x31,
0x87, 0xEE, 0x8E, 0xAD, 0x8C, 0x6A, 0xBC, 0xB5, 0x6B, 0x59, 0x13, 0xF1, 0x04, 0x00, 0xF6, 0x5A,
0x35, 0x79, 0x48, 0x8F, 0x15, 0xCD, 0x97, 0x57, 0x12, 0x3E, 0x37, 0xFF, 0x9D, 0x4F, 0x51, 0xF5,
0xA3, 0x70, 0xBB, 0x14, 0x75, 0xC2, 0xB8, 0x72, 0xC0, 0xED, 0x7D, 0x68, 0xC9, 0x2E, 0x0D, 0x62,
0x46, 0x17, 0x11, 0x4D, 0x6C, 0xC4, 0x7E, 0x53, 0xC1, 0x25, 0xC7, 0x9A, 0x1C, 0x88, 0x58, 0x2C,
0x89, 0xDC, 0x02, 0x64, 0x40, 0x01, 0x5D, 0x38, 0xA5, 0xE2, 0xAF, 0x55, 0xD5, 0xEF, 0x1A, 0x7C,
0xA7, 0x5B, 0xA6, 0x6F, 0x86, 0x9F, 0x73, 0xE6, 0x0A, 0xDE, 0x2B, 0x99, 0x4A, 0x47, 0x9C, 0xDF,
0x09, 0x76, 0x9E, 0x30, 0x0E, 0xE4, 0xB2, 0x94, 0xA0, 0x3B, 0x34, 0x1D, 0x28, 0x0F, 0x36, 0xE3,
0x23, 0xB4, 0x03, 0xD8, 0x90, 0xC8, 0x3C, 0xFE, 0x5E, 0x32, 0x24, 0x50, 0x1F, 0x3A, 0x43, 0x8A,
0x96, 0x41, 0x74, 0xAC, 0x52, 0x33, 0xF0, 0xD9, 0x29, 0x80, 0xB1, 0x16, 0xD3, 0xAB, 0x91, 0xB9,
0x84, 0x7F, 0x61, 0x1E, 0xCF, 0xC5, 0xD1, 0x56, 0x3D, 0xCA, 0xF4, 0x05, 0xC6, 0xE5, 0x08, 0x49
}
@doc """
Applies the InnoGames hash transformation to a 4-byte IV.
@@ -39,11 +60,12 @@ defmodule Odinsea.Net.Cipher.IGCipher do
input = value &&& 0xFF
table = shuffle_byte(input)
# Apply the transformation operations
new_k0 = k0 + shuffle_byte(k1) - input
new_k1 = k1 - bxor(k2, table)
new_k2 = bxor(k2, shuffle_byte(k3) + input)
new_k3 = k3 - (k0 - table)
# Apply the transformation operations SEQUENTIALLY
# Java modifies key[0] first, then uses modified key[0] for key[3]
new_k0 = (k0 + (shuffle_byte(k1) - input)) &&& 0xFF
new_k1 = (k1 - bxor(k2, table)) &&& 0xFF
new_k2 = bxor(k2, (shuffle_byte(k3) + input) &&& 0xFF)
new_k3 = (k3 - (new_k0 - table)) &&& 0xFF
# Combine into 32-bit value (little-endian)
val =
@@ -69,24 +91,4 @@ defmodule Odinsea.Net.Cipher.IGCipher do
defp shuffle_byte(index) do
elem(@shuffle_table, index &&& 0xFF)
end
# Shuffle table - 256 bytes used for IV transformation
@shuffle_table {
0xEC, 0x3F, 0x77, 0xA4, 0x45, 0xD0, 0x71, 0xBF, 0xB7, 0x98, 0x20, 0xFC, 0x4B, 0xE9, 0xB3, 0xE1,
0x5C, 0x22, 0xF7, 0x0C, 0x44, 0x1B, 0x81, 0xBD, 0x63, 0x8D, 0xD4, 0xC3, 0xF2, 0x10, 0x19, 0xE0,
0xFB, 0xA1, 0x6E, 0x66, 0xEA, 0xAE, 0xD6, 0xCE, 0x06, 0x18, 0x4E, 0xEB, 0x78, 0x95, 0xDB, 0xBA,
0xB6, 0x42, 0x7A, 0x2A, 0x83, 0x0B, 0x54, 0x67, 0x6D, 0xE8, 0x65, 0xE7, 0x2F, 0x07, 0xF3, 0xAA,
0x27, 0x7B, 0x85, 0xB0, 0x26, 0xFD, 0x8B, 0xA9, 0xFA, 0xBE, 0xA8, 0xD7, 0xCB, 0xCC, 0x92, 0xDA,
0xF9, 0x93, 0x60, 0x2D, 0xDD, 0xD2, 0xA2, 0x9B, 0x39, 0x5F, 0x82, 0x21, 0x4C, 0x69, 0xF8, 0x31,
0x87, 0xEE, 0x8E, 0xAD, 0x8C, 0x6A, 0xBC, 0xB5, 0x6B, 0x59, 0x13, 0xF1, 0x04, 0x00, 0xF6, 0x5A,
0x35, 0x79, 0x48, 0x8F, 0x15, 0xCD, 0x97, 0x57, 0x12, 0x3E, 0x37, 0xFF, 0x9D, 0x4F, 0x51, 0xF5,
0xA3, 0x70, 0xBB, 0x14, 0x75, 0xC2, 0xB8, 0x72, 0xC0, 0xED, 0x7D, 0x68, 0xC9, 0x2E, 0x0D, 0x62,
0x46, 0x17, 0x11, 0x4D, 0x6C, 0xC4, 0x7E, 0x53, 0xC1, 0x25, 0xC7, 0x9A, 0x1C, 0x88, 0x58, 0x2C,
0x89, 0xDC, 0x02, 0x64, 0x40, 0x01, 0x5D, 0x38, 0xA5, 0xE2, 0xAF, 0x55, 0xD5, 0xEF, 0x1A, 0x7C,
0xA7, 0x5B, 0xA6, 0x6F, 0x86, 0x9F, 0x73, 0xE6, 0x0A, 0xDE, 0x2B, 0x99, 0x4A, 0x47, 0x9C, 0xDF,
0x09, 0x76, 0x9E, 0x30, 0x0E, 0xE4, 0xB2, 0x94, 0xA0, 0x3B, 0x34, 0x1D, 0x28, 0x0F, 0x36, 0xE3,
0x23, 0xB4, 0x03, 0xD8, 0x90, 0xC8, 0x3C, 0xFE, 0x5E, 0x32, 0x24, 0x50, 0x1F, 0x3A, 0x43, 0x8A,
0x96, 0x41, 0x74, 0xAC, 0x52, 0x33, 0xF0, 0xD9, 0x29, 0x80, 0xB1, 0x16, 0xD3, 0xAB, 0x91, 0xB9,
0x84, 0x7F, 0x61, 0x1E, 0xCF, 0xC5, 0xD1, 0x56, 0x3D, 0xCA, 0xF4, 0x05, 0xC6, 0xE5, 0x08, 0x49
}
end

View File

@@ -0,0 +1,150 @@
defmodule Odinsea.Net.Cipher.ShandaCipher do
@moduledoc """
Shanda cipher implementation for MapleStory packet encryption.
Direct port from Java ShandaCipher.java
"""
import Bitwise
alias Odinsea.Util.BitTools
@doc """
Encrypts data using the Shanda cipher.
"""
@spec encrypt(binary()) :: binary()
def encrypt(data) when is_binary(data) do
bytes = :binary.bin_to_list(data)
len = length(bytes)
result =
Enum.reduce(0..5, bytes, fn j, acc ->
do_encrypt_pass(acc, len, j)
end)
:binary.list_to_bin(result)
end
defp do_encrypt_pass(data, len, j) do
data_length = len &&& 0xFF
if rem(j, 2) == 0 do
# Forward pass: iterate 0..length-1
{result, _remember, _dl} =
Enum.reduce(data, {[], 0, data_length}, fn cur, {out, remember, dl} ->
new_cur =
cur
|> BitTools.roll_left(3)
|> Kernel.+(dl)
|> band(0xFF)
|> bxor(remember)
new_remember = new_cur
final_cur =
new_cur
|> BitTools.roll_right(dl &&& 0xFF)
|> bxor(0xFF)
|> Kernel.+(0x48)
|> band(0xFF)
{[final_cur | out], new_remember, (dl - 1) &&& 0xFF}
end)
Enum.reverse(result)
else
# Backward pass: iterate length-1..0
# Process reversed list, prepend results -> naturally restores forward order
{result, _remember, _dl} =
Enum.reduce(Enum.reverse(data), {[], 0, data_length}, fn cur, {out, remember, dl} ->
new_cur =
cur
|> BitTools.roll_left(4)
|> Kernel.+(dl)
|> band(0xFF)
|> bxor(remember)
new_remember = new_cur
final_cur =
new_cur
|> bxor(0x13)
|> BitTools.roll_right(3)
{[final_cur | out], new_remember, (dl - 1) &&& 0xFF}
end)
# Do NOT reverse - prepending from reversed input naturally gives forward order
result
end
end
@doc """
Decrypts data using the Shanda cipher.
"""
@spec decrypt(binary()) :: binary()
def decrypt(data) when is_binary(data) do
bytes = :binary.bin_to_list(data)
len = length(bytes)
# Java: for (int j = 1; j <= 6; j++)
result =
Enum.reduce(1..6, bytes, fn j, acc ->
do_decrypt_pass(acc, len, j)
end)
:binary.list_to_bin(result)
end
defp do_decrypt_pass(data, len, j) do
data_length = len &&& 0xFF
if rem(j, 2) == 0 do
# Forward pass (even j in decrypt = matches encrypt's forward)
{result, _remember, _dl} =
Enum.reduce(data, {[], 0, data_length}, fn cur, {out, remember, dl} ->
new_cur =
cur
|> Kernel.-(0x48)
|> band(0xFF)
|> bxor(0xFF)
|> BitTools.roll_left(dl &&& 0xFF)
next_remember = new_cur
final_cur =
new_cur
|> bxor(remember)
|> Kernel.-(dl)
|> band(0xFF)
|> BitTools.roll_right(3)
{[final_cur | out], next_remember, (dl - 1) &&& 0xFF}
end)
Enum.reverse(result)
else
# Backward pass (odd j in decrypt = matches encrypt's backward)
{result, _remember, _dl} =
Enum.reduce(Enum.reverse(data), {[], 0, data_length}, fn cur, {out, remember, dl} ->
new_cur =
cur
|> BitTools.roll_left(3)
|> bxor(0x13)
next_remember = new_cur
final_cur =
new_cur
|> bxor(remember)
|> Kernel.-(dl)
|> band(0xFF)
|> BitTools.roll_right(4)
{[final_cur | out], next_remember, (dl - 1) &&& 0xFF}
end)
# Do NOT reverse - prepending from reversed input naturally gives forward order
result
end
end
end

View File

@@ -19,24 +19,31 @@ defmodule Odinsea.Net.Opcodes do
# Login/Account
def cp_client_hello(), do: 0x01
@spec cp_login_password() :: 2
def cp_login_password(), do: 0x02
# Note: 0x03 is CP_ViewServerList in MSEA v112.4 (not used in OdinSea but reserved)
@spec cp_view_server_list() :: 3
def cp_view_server_list(), do: 0x03
@spec cp_serverlist_request() :: 4
def cp_serverlist_request(), do: 0x04
def cp_charlist_request(), do: 0x05
def cp_serverstatus_request(), do: 0x06
# CP_SelectWorld(5) - Java: ClientPacket.java
@spec cp_select_world() :: 5
def cp_select_world(), do: 0x05
def cp_check_char_name(), do: 0x0E
def cp_create_char(), do: 0x12
def cp_create_ultimate(), do: 0x14
def cp_delete_char(), do: 0x15
def cp_exception_log(), do: 0x17
def cp_security_packet(), do: 0x18
def cp_hardware_info(), do: 0x70
def cp_window_focus(), do: 0x71
def cp_hardware_info(), do: 0x5001 # Java: CP_HardwareInfo(0x5001) - was 0x70 (collided with cp_cancel_buff)
def cp_window_focus(), do: 0x5004 # Java: CP_WindowFocus(0x5004) - was 0x71 (collided with cp_skill_effect)
def cp_char_select(), do: 0x19
def cp_auth_second_password(), do: 0x1A
def cp_rsa_key(), do: 0x20
def cp_client_dump_log(), do: 0x1D
def cp_create_security_handle(), do: 0x1E
def cp_select_world(), do: 0x03
# CP_CheckUserLimit(6) - Java: ClientPacket.java
@spec cp_check_user_limit() :: 6
def cp_check_user_limit(), do: 0x06
# Migration/Channel
@@ -75,7 +82,7 @@ defmodule Odinsea.Net.Opcodes do
# NPC Interaction
def cp_npc_talk(), do: 0x40
def cp_npc_move(), do: 0x41 # NPC animation/movement (added for compatibility)
def cp_npc_move(), do: 0x106 # Java: CP_NpcMove(262) - alias for cp_npc_action. Was 0x41 (wrong)
def cp_npc_talk_more(), do: 0x42
def cp_npc_shop(), do: 0x43
def cp_storage(), do: 0x44
@@ -255,6 +262,12 @@ defmodule Odinsea.Net.Opcodes do
# Cash Shop
def cp_cs_update(), do: 0x135
# Alias for cp_cs_update (used in some places)
def cp_cash_shop_update(), do: cp_cs_update()
# Public NPC (recv - currently unimplemented in Java, using same value as send)
def cp_public_npc(), do: 0xB7
def cp_buy_cs_item(), do: 0x136
def cp_coupon_code(), do: 0x137
@@ -263,6 +276,9 @@ defmodule Odinsea.Net.Opcodes do
def cp_touching_mts(), do: 0x159
def cp_mts_tab(), do: 0x15A
# MTS operation opcode (client -> server)
def cp_mts_operation(), do: 0xB4
# Custom (server-specific)
def cp_inject_packet(), do: 0x5002
def cp_set_code_page(), do: 0x5003
@@ -279,16 +295,16 @@ defmodule Odinsea.Net.Opcodes do
# General
def lp_alive_req(), do: 0x0D
def lp_enable_action(), do: 0x0C
def lp_set_field(), do: 0x14
def lp_set_cash_shop_opened(), do: 0x15
def lp_migrate_command(), do: 0x16
def lp_enable_action(), do: 0x1B # Java: enableActions() uses UPDATE_STATS(27) - was 0x0C (collided with lp_change_channel)
def lp_set_field(), do: 0x90 # Java: LP_SetField(144) - alias for lp_warp_to_map. Was 0x14 (wrong)
def lp_set_cash_shop_opened(), do: 0x92 # Java: LP_SetCashShop(146) - was 0x15 (collided with lp_latest_connected_world)
def lp_migrate_command(), do: 0x0C # Java: CHANGE_CHANNEL(12) - was 0x16 (collided with lp_recommend_world_message)
# Login
def lp_login_status(), do: 0x01
def lp_serverstatus(), do: 0x03
def lp_serverlist(), do: 0x06
def lp_charlist(), do: 0x07
def lp_check_password_result(), do: 0x01
def lp_server_status(), do: 0x03
def lp_server_list(), do: 0x06
def lp_char_list(), do: 0x07
def lp_server_ip(), do: 0x08
def lp_char_name_response(), do: 0x09
def lp_add_new_char_entry(), do: 0x0A
@@ -298,10 +314,10 @@ defmodule Odinsea.Net.Opcodes do
def lp_channel_selected(), do: 0x10
def lp_relog_response(), do: 0x12
def lp_rsa_key(), do: 0x13
def lp_enable_recommended(), do: 0x15
def lp_send_recommended(), do: 0x16
def lp_latest_connected_world(), do: 0x15
def lp_recommend_world_message(), do: 0x16
def lp_login_auth(), do: 0x17
def lp_secondpw_error(), do: 0x18
def lp_check_spw_result(), do: 0x18
# Inventory/Stats
def lp_modify_inventory_item(), do: 0x19
@@ -390,6 +406,13 @@ defmodule Odinsea.Net.Opcodes do
# Warps/Shops
def lp_warp_to_map(), do: 0x90
def lp_mts_open(), do: 0x91
# Alias for lp_mts_open (MTS opened packet)
def lp_set_mts_opened(), do: lp_mts_open()
# Cash shop initialization (SET_CASH_SHOP from Java)
def lp_set_cash_shop(), do: 0x92
def lp_cs_open(), do: 0x92
def lp_login_welcome(), do: 0x94
def lp_server_blocked(), do: 0x97
@@ -397,6 +420,9 @@ defmodule Odinsea.Net.Opcodes do
# Effects
def lp_show_equip_effect(), do: 0x99
# Weather effect (BLOW_WEATHER/MAP_EFFECT)
def lp_blow_weather(), do: 0xA1
def lp_multichat(), do: 0x9A
def lp_whisper(), do: 0x9B
def lp_boss_env(), do: 0x9D
@@ -614,6 +640,10 @@ defmodule Odinsea.Net.Opcodes do
# Cash Shop
def lp_cs_update(), do: 0x1B8
# Alias for lp_cs_update (cash shop update)
def lp_cash_shop_update(), do: lp_cs_update()
def lp_cs_operation(), do: 0x1B9
def lp_xmas_surprise(), do: 0x1BD
@@ -657,16 +687,26 @@ defmodule Odinsea.Net.Opcodes do
# ==================================================================================================
@doc """
Returns a human-readable name for a given opcode value.
Returns a human-readable name for a given client opcode value.
Useful for debugging and logging.
"""
def name_for(opcode) when is_integer(opcode) do
def name_for_client(opcode) when is_integer(opcode) do
case opcode do
# Client opcodes (common ones for debugging)
0x01 -> "CP_CLIENT_HELLO"
0x02 -> "CP_LOGIN_PASSWORD"
0x03 -> "CP_VIEW_SERVER_LIST"
0x04 -> "CP_SERVERLIST_REQUEST"
0x05 -> "CP_SELECT_WORLD"
0x06 -> "CP_CHECK_USER_LIMIT"
0x0D -> "CP_PLAYER_LOGGEDIN"
0x0E -> "CP_CHECK_CHAR_NAME"
0x12 -> "CP_CREATE_CHAR"
0x14 -> "CP_CREATE_ULTIMATE"
0x15 -> "CP_DELETE_CHAR"
0x19 -> "CP_CHAR_SELECT"
0x1A -> "CP_AUTH_SECOND_PASSWORD"
0x20 -> "CP_RSA_KEY"
0x23 -> "CP_CHANGE_MAP"
0x24 -> "CP_CHANGE_CHANNEL"
0x2A -> "CP_MOVE_PLAYER"
@@ -675,11 +715,24 @@ defmodule Odinsea.Net.Opcodes do
0xA0 -> "CP_PARTYCHAT"
0xA1 -> "CP_WHISPER"
_ -> "CP_UNKNOWN_0x#{Integer.to_string(opcode, 16) |> String.upcase()}"
end
end
@doc """
Returns a human-readable name for a given server opcode value.
Useful for debugging and logging.
"""
def name_for_server(opcode) when is_integer(opcode) do
case opcode do
# Server opcodes (common ones for debugging)
0x01 -> "LP_LOGIN_STATUS"
0x06 -> "LP_SERVERLIST"
0x07 -> "LP_CHARLIST"
0x08 -> "LP_SERVER_IP"
0x0D -> "LP_ALIVE_REQ"
0x13 -> "LP_RSA_KEY"
0x17 -> "LP_LOGIN_AUTH"
0xB8 -> "LP_SPAWN_PLAYER"
0xB9 -> "LP_REMOVE_PLAYER_FROM_MAP"
0xBA -> "LP_CHATTEXT"
@@ -688,21 +741,30 @@ defmodule Odinsea.Net.Opcodes do
0x9B -> "LP_WHISPER"
0x1A3 -> "LP_NPC_TALK"
_ -> "UNKNOWN_0x#{Integer.to_string(opcode, 16) |> String.upcase()}"
_ -> "LP_UNKNOWN_0x#{Integer.to_string(opcode, 16) |> String.upcase()}"
end
end
@doc """
Returns a human-readable name for a given opcode value.
Deprecated: Use name_for_client/1 or name_for_server/1 instead.
"""
def name_for(opcode) when is_integer(opcode) do
name_for_client(opcode)
end
@doc """
Validates if an opcode is a known client packet.
"""
def valid_client_opcode?(opcode) when is_integer(opcode) do
opcode in [
# Add all valid client opcodes here for validation
0x01,
0x02,
0x04,
0x05,
0x06,
# Login opcodes
0x01, # CP_ClientHello
0x02, # CP_LoginPassword
0x03, # CP_ViewServerList
0x04, # CP_ServerListRequest
0x05, # CP_SelectWorld
0x06, # CP_CheckUserLimit
0x0D,
0x0E,
0x12,
@@ -895,7 +957,11 @@ defmodule Odinsea.Net.Opcodes do
0x143,
0x144,
0x159,
0x15A
0x15A,
0x5001, # CP_HardwareInfo
0x5002, # CP_InjectPacket
0x5003, # CP_SetCodePage
0x5004 # CP_WindowFocus
]
end

View File

@@ -68,6 +68,18 @@ defmodule Odinsea.Net.Packet.Out do
%__MODULE__{data: [data | <<length::signed-integer-little-16, value::binary>>]}
end
@doc """
Encodes a fixed-length MapleStory ASCII string.
Format: [2-byte length][ASCII bytes][padding to fixed length]
The third argument specifies the fixed field length.
"""
@spec encode_string(t(), String.t(), non_neg_integer()) :: t()
def encode_string(%__MODULE__{data: data}, value, fixed_length) when is_binary(value) and is_integer(fixed_length) and fixed_length > 0 do
length = byte_size(value)
padding = max(0, fixed_length - length)
%__MODULE__{data: [data | <<length::signed-integer-little-16, value::binary, 0::size(padding * 8)>>]}
end
@doc """
Encodes a boolean (1 byte, 0 = false, 1 = true).
"""
@@ -88,6 +100,12 @@ defmodule Odinsea.Net.Packet.Out do
%__MODULE__{data: [data | buffer]}
end
@doc """
Alias for encode_buffer/2.
"""
@spec encode_bytes(t(), binary()) :: t()
def encode_bytes(packet, data), do: encode_buffer(packet, data)
@doc """
Encodes a fixed-size buffer, padding with zeros if necessary.
"""
@@ -172,6 +190,12 @@ defmodule Odinsea.Net.Packet.Out do
data
end
@doc """
Alias for to_iodata/1.
"""
@spec to_data(t()) :: iodata()
def to_data(packet), do: to_iodata(packet)
@doc """
Converts the packet to a hex string for debugging.
"""

View File

@@ -0,0 +1,380 @@
defmodule Odinsea.Net.PacketLogger do
@moduledoc """
Comprehensive packet logging system for debugging MapleStory protocol.
Provides detailed packet logging similar to the Java version with:
- Direction (client/loopback)
- Opcode name and value (decimal/hex)
- Raw hex data
- ASCII text representation
- Context information (session, thread)
"""
require Logger
alias Odinsea.Net.{Hex, Opcodes}
@doc """
Logs an incoming client packet with full details.
## Parameters
- `opcode` - The packet opcode (integer)
- `data` - The packet data (binary, excluding opcode bytes)
- `context` - Map with :ip, :server_type, etc.
"""
def log_client_packet(opcode, data, context \\ %{}) do
if packet_logging_enabled?() do
opcode_name = get_client_opcode_name(opcode)
ip = Map.get(context, :ip, "unknown")
server_type = Map.get(context, :server_type, :unknown)
# Format opcode header
opcode_header = format_opcode_header("client", opcode_name, opcode)
# Full packet data includes opcode
full_data = <<opcode::little-16>> <> data
# Format hex and text
hex_str = Hex.encode(full_data)
text_str = Hex.to_ascii(full_data)
# Log the packet
Logger.info("""
#{opcode_header}
[Data] #{hex_str}
[Text] #{text_str}
[Context] IP=#{ip} Server=#{server_type} Size=#{byte_size(full_data)} bytes
""")
:ok
else
:ok
end
end
@doc """
Logs an outgoing server packet with full details.
## Parameters
- `opcode` - The packet opcode (integer)
- `data` - The packet data (binary, excluding opcode bytes)
- `context` - Map with :ip, :server_type, etc.
"""
def log_server_packet(opcode, data, context \\ %{}) do
if packet_logging_enabled?() do
opcode_name = get_server_opcode_name(opcode)
ip = Map.get(context, :ip, "unknown")
server_type = Map.get(context, :server_type, :unknown)
# Format opcode header
opcode_header = format_opcode_header("loopback", opcode_name, opcode)
# Full packet data includes opcode
full_data = <<opcode::little-16>> <> data
# Format hex and text
hex_str = Hex.encode(full_data)
text_str = Hex.to_ascii(full_data)
# Log the packet
Logger.info("""
#{opcode_header}
[Data] #{hex_str}
[Text] #{text_str}
[Context] IP=#{ip} Server=#{server_type} Size=#{byte_size(full_data)} bytes
""")
:ok
else
:ok
end
end
@doc """
Logs raw packet data (used for handshake/hello packets that don't follow normal format).
"""
def log_raw_packet(direction, label, data, context \\ %{}) do
if packet_logging_enabled?() do
ip = Map.get(context, :ip, "unknown")
data = IO.iodata_to_binary(data)
hex_str = Hex.encode(data)
text_str = Hex.to_ascii(data)
Logger.info("""
[#{direction}] [#{label}]
[Data] #{hex_str}
[Text] #{text_str}
[Context] IP=#{ip} Size=#{byte_size(data)} bytes
""")
:ok
else
:ok
end
end
# ==================================================================================================
# Private Helper Functions
# ==================================================================================================
defp packet_logging_enabled? do
features = Application.get_env(:odinsea, :features, [])
Keyword.get(features, :log_packet, false)
end
defp format_opcode_header(direction, opcode_name, opcode) do
opcode_hex = Integer.to_string(opcode, 16) |> String.upcase() |> String.pad_leading(2, "0")
"[#{direction}] [#{opcode_name}] #{opcode} / 0x#{opcode_hex}"
end
# ==================================================================================================
# Opcode Name Resolution
# ==================================================================================================
defp get_client_opcode_name(opcode) do
case opcode do
# Connection/Security
0x16 -> "CP_AliveAck"
# Login/Account
0x01 -> "CP_PermissionRequest"
0x02 -> "CP_CheckPassword"
0x04 -> "CP_ServerlistRequest"
0x05 -> "CP_SelectWorld"
0x06 -> "CP_CheckUserLimit"
0x0E -> "CP_CheckCharName"
0x12 -> "CP_CreateChar"
0x14 -> "CP_CreateUltimate"
0x15 -> "CP_DeleteChar"
0x17 -> "CP_ExceptionLog"
0x18 -> "CP_SecurityPacket"
0x19 -> "CP_CharSelect"
0x1A -> "CP_AuthSecondPassword"
0x1D -> "CP_ClientDumpLog"
0x1E -> "CP_CreateSecurityHandle"
0x20 -> "RSA_KEY"
0x5001 -> "CP_HardwareInfo"
0x5004 -> "CP_WindowFocus"
# Migration/Channel
0x0D -> "CP_PlayerLoggedIn"
0x23 -> "CP_ChangeMap"
0x24 -> "CP_ChangeChannel"
0x25 -> "CP_EnterCashShop"
0x26 -> "CP_EnterPvp"
0x27 -> "CP_EnterPvpParty"
0x29 -> "CP_LeavePvp"
0xB4 -> "CP_EnterMts"
# Player Movement/Actions
0x2A -> "CP_MovePlayer"
0x2C -> "CP_CancelChair"
0x2D -> "CP_UseChair"
0x2F -> "CP_CloseRangeAttack"
0x30 -> "CP_RangedAttack"
0x31 -> "CP_MagicAttack"
0x32 -> "CP_PassiveEnergy"
0x34 -> "CP_TakeDamage"
0x35 -> "CP_PvpAttack"
0x36 -> "CP_GeneralChat"
0x37 -> "CP_CloseChalkboard"
0x38 -> "CP_FaceExpression"
0x75 -> "CP_CharInfoRequest"
0x76 -> "CP_SpawnPet"
0x78 -> "CP_CancelDebuff"
# NPC Interaction
0x40 -> "CP_NpcTalk"
0x41 -> "CP_NpcMove"
0x42 -> "CP_NpcTalkMore"
0x43 -> "CP_NpcShop"
0x44 -> "CP_Storage"
0x45 -> "CP_UseHiredMerchant"
0x47 -> "CP_MerchItemStore"
# Inventory/Items
0x4D -> "CP_ItemSort"
0x4E -> "CP_ItemGather"
0x4F -> "CP_ItemMove"
0x53 -> "CP_UseItem"
0x10C -> "CP_ItemPickup"
# Stats/Skills
0x6A -> "CP_DistributeAp"
0x6B -> "CP_AutoAssignAp"
0x6E -> "CP_DistributeSp"
0x6F -> "CP_SpecialMove"
# Social
0xA0 -> "CP_PartyChat"
0xA1 -> "CP_Whisper"
0xA4 -> "CP_PartyOperation"
0xA8 -> "CP_GuildOperation"
# Cash Shop
0x135 -> "CP_CsUpdate"
0x136 -> "CP_BuyCsItem"
0x137 -> "CP_CouponCode"
# Custom
0x5002 -> "CP_InjectPacket"
0x5003 -> "CP_SetCodePage"
_ -> "UNKNOWN"
end
end
defp get_server_opcode_name(opcode) do
case opcode do
# General
0x0D -> "LP_AliveReq"
0x0C -> "LP_ChangeChannel"
0x15 -> "LP_LatestConnectedWorld"
0x16 -> "LP_RecommendWorldMessage"
# Login
0x00 -> "HELLO"
0x01 -> "LOGIN_STATUS"
0x03 -> "LP_ServerStatus"
0x06 -> "SERVERLIST"
0x07 -> "LP_CharList"
0x08 -> "LP_ServerIp"
0x09 -> "LP_CharNameResponse"
0x0A -> "LP_AddNewCharEntry"
0x0B -> "LP_DeleteCharResponse"
0x10 -> "LP_ChannelSelected"
0x12 -> "LP_RelogResponse"
0x13 -> "RSA_KEY"
0x17 -> "LOGIN_AUTH"
0x18 -> "LP_SecondPwError"
# Inventory/Stats
0x19 -> "LP_ModifyInventoryItem"
0x1A -> "LP_UpdateInventorySlot"
0x1B -> "LP_UpdateStats"
0x1C -> "LP_GiveBuff"
0x1D -> "LP_CancelBuff"
0x20 -> "LP_UpdateSkills"
0x22 -> "LP_FameResponse"
0x23 -> "LP_ShowStatusInfo"
0x25 -> "LP_TrockLocations"
# Social/Party/Guild
0x38 -> "LP_PartyOperation"
0x3A -> "LP_ExpeditionOperation"
0x3B -> "LP_BuddyList"
0x3D -> "LP_GuildOperation"
0x3E -> "LP_AllianceOperation"
# Map Effects/Environment
0x3F -> "LP_SpawnPortal"
0x40 -> "LP_MechPortal"
0x41 -> "LP_ServerMessage"
0x4A -> "LP_YellowChat"
# Family
0x6D -> "LP_SendPedigree"
0x6E -> "LP_OpenFamily"
0x73 -> "LP_Family"
# Misc UI/Messages
0x7E -> "LP_TopMsg"
0x7F -> "LP_MidMsg"
0x80 -> "LP_ClearMidMsg"
# Warps/Shops
0x90 -> "LP_WarpToMap"
0x91 -> "LP_MtsOpen"
0x92 -> "LP_CsOpen"
# Effects
0x99 -> "LP_ShowEquipEffect"
0x9A -> "LP_MultiChat"
0x9B -> "LP_Whisper"
0xA1 -> "LP_MapEffect"
0xA6 -> "LP_Clock"
# Players
0xB8 -> "LP_SpawnPlayer"
0xB9 -> "LP_RemovePlayerFromMap"
0xBA -> "LP_ChatText"
0xBC -> "LP_Chalkboard"
# Pets
0xD1 -> "LP_SpawnPet"
0xD4 -> "LP_MovePet"
0xD5 -> "LP_PetChat"
# Player Actions
0xE2 -> "LP_MovePlayer"
0xE4 -> "LP_CloseRangeAttack"
0xE5 -> "LP_RangedAttack"
0xE6 -> "LP_MagicAttack"
0xE8 -> "LP_SkillEffect"
0xEB -> "LP_DamagePlayer"
0xEC -> "LP_FacialExpression"
0xF0 -> "LP_ShowChair"
0xF1 -> "LP_UpdateCharLook"
# Summons
0x131 -> "LP_SpawnSummon"
0x132 -> "LP_RemoveSummon"
0x133 -> "LP_MoveSummon"
0x134 -> "LP_SummonAttack"
# Monsters
0x13A -> "LP_SpawnMonster"
0x13B -> "LP_KillMonster"
0x13C -> "LP_SpawnMonsterControl"
0x13D -> "LP_MoveMonster"
0x144 -> "LP_DamageMonster"
# NPCs
0x156 -> "LP_SpawnNpc"
0x157 -> "LP_RemoveNpc"
0x158 -> "LP_SpawnNpcRequestController"
0x159 -> "LP_NpcAction"
0x1A3 -> "LP_NpcTalk"
0x1A5 -> "LP_OpenNpcShop"
# Merchants
0x161 -> "LP_SpawnHiredMerchant"
0x162 -> "LP_DestroyHiredMerchant"
# Map Objects
0x165 -> "LP_DropItemFromMapObject"
0x167 -> "LP_RemoveItemFromMap"
0x16B -> "LP_SpawnMist"
0x16C -> "LP_RemoveMist"
0x16D -> "LP_SpawnDoor"
0x16E -> "LP_RemoveDoor"
# Reactors
0x171 -> "LP_ReactorHit"
0x173 -> "LP_ReactorSpawn"
0x174 -> "LP_ReactorDestroy"
# NPC/Shop Interactions
0x1A6 -> "LP_ConfirmShopTransaction"
0x1A9 -> "LP_OpenStorage"
0x1AB -> "LP_MerchItemStore"
# Cash Shop
0x1B8 -> "LP_CsUpdate"
0x1B9 -> "LP_CsOperation"
# Input
0x1C5 -> "LP_Keymap"
# Custom
0x5000 -> "LP_DamageSkin"
0x5001 -> "LP_OpenWebsite"
0x15 -> "LP_LatestConnectedWorld"
0x16 -> "LP_RecommendWorldMessage"
_ -> "UNKNOWN"
end
end
end

View File

@@ -163,11 +163,11 @@ defmodule Odinsea.Net.Processor do
# Password check (login authentication)
@cp_login_password ->
Handler.on_login_password(packet, state)
Handler.on_check_password(packet, state)
# World info request (server list)
@cp_serverlist_request ->
Handler.on_serverlist_request(state)
Handler.on_world_info_request(state)
# Select world
@cp_select_world ->
@@ -179,11 +179,11 @@ defmodule Odinsea.Net.Processor do
# Check duplicated ID (character name availability)
@cp_check_char_name ->
Handler.on_check_char_name(packet, state)
Handler.on_check_duplicated_id(packet, state)
# Create new character
@cp_create_char ->
Handler.on_create_char(packet, state)
Handler.on_create_new_character(packet, state)
# Create ultimate (Cygnus Knights)
@cp_create_ultimate ->
@@ -191,15 +191,15 @@ defmodule Odinsea.Net.Processor do
# Delete character
@cp_delete_char ->
Handler.on_delete_char(packet, state)
Handler.on_delete_character(packet, state)
# Select character (enter game)
@cp_char_select ->
Handler.on_char_select(packet, state)
Handler.on_select_character(packet, state)
# Second password check
@cp_auth_second_password ->
Handler.on_auth_second_password(packet, state)
Handler.on_check_spw_request(packet, state)
# RSA key request
@cp_rsa_key ->