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

@@ -0,0 +1,60 @@
defmodule Odinsea.CryptoSimpleTest do
@moduledoc """
Simple test module for MapleStory crypto implementation.
These tests don't require the full application to start.
"""
use ExUnit.Case
alias Odinsea.Net.Cipher.{IGCipher, ShandaCipher, AESCipher}
describe "IGCipher (IV transformation)" do
test "inno_hash transforms IV correctly" do
iv = <<0x34, 0x9A, 0x0F, 0x0C>>
result = IGCipher.inno_hash(iv)
# Result should be 4 bytes
assert byte_size(result) == 4
# Result should be different from input (usually)
assert result != iv
end
test "inno_hash produces deterministic results" do
iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
result1 = IGCipher.inno_hash(iv)
result2 = IGCipher.inno_hash(iv)
assert result1 == result2
end
end
describe "ShandaCipher" do
test "encrypt and decrypt are inverse operations" do
original = <<1, 0, 7, 112, 0, 4, 0>> # CP_PermissionRequest payload
encrypted = ShandaCipher.encrypt(original)
decrypted = ShandaCipher.decrypt(encrypted)
assert decrypted == original
end
test "encrypt produces different output" do
original = <<1, 0, 7, 112, 0, 4, 0>>
encrypted = ShandaCipher.encrypt(original)
assert encrypted != original
end
end
describe "AESCipher" do
test "crypt is self-inverse (XOR property)" do
data = <<1, 0, 7, 112, 0, 4, 0>>
iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
encrypted = AESCipher.crypt(data, iv)
decrypted = AESCipher.crypt(encrypted, iv)
assert decrypted == data
end
end
end

185
test/crypto_test.exs Normal file
View File

@@ -0,0 +1,185 @@
defmodule Odinsea.CryptoTest do
@moduledoc """
Test module for MapleStory crypto implementation.
Run with: mix test test/crypto_test.exs
"""
use ExUnit.Case
alias Odinsea.Net.Cipher.{ClientCrypto, IGCipher, ShandaCipher, AESCipher}
alias Odinsea.Util.BitTools
describe "IGCipher (IV transformation)" do
test "inno_hash transforms IV correctly" do
iv = <<0x34, 0x9A, 0x0F, 0x0C>>
result = IGCipher.inno_hash(iv)
# Result should be 4 bytes
assert byte_size(result) == 4
# Result should be different from input (usually)
assert result != iv
end
test "inno_hash produces deterministic results" do
iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
result1 = IGCipher.inno_hash(iv)
result2 = IGCipher.inno_hash(iv)
assert result1 == result2
end
end
describe "ShandaCipher" do
test "encrypt and decrypt are inverse operations" do
original = <<1, 0, 7, 112, 0, 4, 0>> # CP_PermissionRequest payload
encrypted = ShandaCipher.encrypt(original)
decrypted = ShandaCipher.decrypt(encrypted)
assert decrypted == original
end
test "encrypt produces different output" do
original = <<1, 0, 7, 112, 0, 4, 0>>
encrypted = ShandaCipher.encrypt(original)
assert encrypted != original
end
end
describe "AESCipher" do
test "crypt is self-inverse (XOR property)" do
data = <<1, 0, 7, 112, 0, 4, 0>>
iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
encrypted = AESCipher.crypt(data, iv)
decrypted = AESCipher.crypt(encrypted, iv)
assert decrypted == data
end
end
describe "ClientCrypto" do
test "new creates crypto with random IVs" do
crypto = ClientCrypto.new(112)
assert byte_size(crypto.send_iv) == 4
assert byte_size(crypto.recv_iv) == 4
assert crypto.version == 112
end
test "new_from_ivs creates crypto with specified IVs" do
send_iv = <<0x34, 0x9A, 0x0F, 0x0C>>
recv_iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
crypto = ClientCrypto.new_from_ivs(112, send_iv, recv_iv)
assert crypto.send_iv == send_iv
assert crypto.recv_iv == recv_iv
end
test "new_from_client_ivs swaps IVs correctly" do
# Client's perspective
client_send_iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
client_recv_iv = <<0x34, 0x9A, 0x0F, 0x0C>>
# Server's perspective (should be swapped)
crypto = ClientCrypto.new_from_client_ivs(112, client_send_iv, client_recv_iv)
# Server's send = client's recv
assert crypto.send_iv == client_recv_iv
# Server's recv = client's send
assert crypto.recv_iv == client_send_iv
end
test "decrypt updates recv_iv" do
crypto = ClientCrypto.new(112)
original_recv_iv = crypto.recv_iv
encrypted_data = <<0x7C, 0xA8, 0x7B, 0xA8, 0xBF, 0x0A, 0xCD, 0xDE>>
{new_crypto, _decrypted} = ClientCrypto.decrypt(crypto, encrypted_data)
# IV should be updated after decryption
assert new_crypto.recv_iv != original_recv_iv
end
test "encrypt updates send_iv" do
crypto = ClientCrypto.new(112)
original_send_iv = crypto.send_iv
data = <<1, 0, 7, 112, 0, 4, 0>>
{new_crypto, _encrypted, _header} = ClientCrypto.encrypt(crypto, data)
# IV should be updated after encryption
assert new_crypto.send_iv != original_send_iv
end
test "header encoding produces 4 bytes" do
crypto = ClientCrypto.new(112)
data = <<1, 0, 7, 112, 0, 4, 0>>
{_new_crypto, _encrypted, header} = ClientCrypto.encrypt(crypto, data)
assert byte_size(header) == 4
end
test "header validation works correctly" do
crypto = ClientCrypto.new(112)
data = <<1, 0, 7, 112, 0, 4, 0>>
{new_crypto, encrypted, header} = ClientCrypto.encrypt(crypto, data)
# Extract raw_seq from header
<<raw_seq::little-16, _raw_len::little-16>> = header
# Validation should pass
assert ClientCrypto.decode_header_valid?(new_crypto, raw_seq)
end
test "full encrypt/decrypt roundtrip" do
# Create crypto instances with same IVs
send_iv = <<0x34, 0x9A, 0x0F, 0x0C>>
recv_iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
# Server's crypto (for sending)
server_crypto = ClientCrypto.new_from_ivs(112, send_iv, recv_iv)
# Client's crypto would have swapped IVs
# For testing, we'll use the same crypto to verify roundtrip
original_data = <<1, 0, 7, 112, 0, 4, 0>>
# Encrypt
{server_crypto_after, encrypted, header} = ClientCrypto.encrypt(server_crypto, original_data)
# Decrypt (using recv_iv which should match server's send_iv after swap)
# For this test, we'll create a matching client crypto
client_crypto = ClientCrypto.new_from_ivs(112, recv_iv, send_iv)
{client_crypto_after, decrypted} = ClientCrypto.decrypt(client_crypto, encrypted)
assert decrypted == original_data
end
end
describe "BitTools" do
test "roll_left rotates bits correctly" do
# 0b11001101 = 205
# rotate left by 3: 0b01101101 = 109 (wrapping around)
result = BitTools.roll_left(205, 3)
assert result == 109
end
test "roll_right rotates bits correctly" do
# Test that roll_right is inverse of roll_left
original = 205
rotated = BitTools.roll_left(original, 3)
back = BitTools.roll_right(rotated, 3)
assert back == original
end
test "multiply_bytes repeats correctly" do
input = <<1, 2, 3, 4>>
result = BitTools.multiply_bytes(input, 4, 2)
assert result == <<1, 2, 3, 4, 1, 2, 3, 4>>
end
end
end

80
test/debug_crypto.exs Normal file
View File

@@ -0,0 +1,80 @@
# Debug script to trace crypto operations
# Run with: cd /home/ra/odinsea-elixir && elixir test/debug_crypto.exs
Code.require_file("lib/odinsea/util/bit_tools.ex", ".")
Code.require_file("lib/odinsea/net/cipher/ig_cipher.ex", ".")
Code.require_file("lib/odinsea/net/cipher/aes_cipher.ex", ".")
Code.require_file("lib/odinsea/net/cipher/shanda_cipher.ex", ".")
alias Odinsea.Net.Cipher.{IGCipher, AESCipher, ShandaCipher}
alias Odinsea.Util.BitTools
import Bitwise
IO.puts("=== Debugging Crypto ===\n")
# From the packet log:
# Server's recv_iv = <<0xA8, 0xBC, 0x0D, 0xB3>> (client's send_iv)
recv_iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
# First encrypted packet from client:
# [Data] 7C A8 7B A8 BF 0A CD DE C7 71 AC
packet = <<0x7C, 0xA8, 0x7B, 0xA8, 0xBF, 0x0A, 0xCD, 0xDE, 0xC7, 0x71, 0xAC>>
IO.puts("Raw packet: #{Base.encode16(packet)}")
IO.puts("Server recv_iv: #{Base.encode16(recv_iv)}")
# Extract header
<<raw_seq::little-16, raw_len::little-16, payload::binary>> = packet
IO.puts("\nHeader:")
IO.puts(" raw_seq: 0x#{Integer.to_string(raw_seq, 16)} (#{raw_seq})")
IO.puts(" raw_len: 0x#{Integer.to_string(raw_len, 16)} (#{raw_len})")
IO.puts(" payload: #{Base.encode16(payload)} (#{byte_size(payload)} bytes)")
# Validate header
<<_r0, _r1, r2, r3>> = recv_iv
enc_version = 112
seq_base = ((r2 &&& 0xFF) ||| ((r3 <<< 8) &&& 0xFF00)) &&& 0xFFFF
calculated_version = Bitwise.bxor(raw_seq &&& 0xFFFF, seq_base)
IO.puts("\nHeader validation:")
IO.puts(" r2: 0x#{Integer.to_string(r2, 16)} (#{r2})")
IO.puts(" r3: 0x#{Integer.to_string(r3, 16)} (#{r3})")
IO.puts(" seq_base: 0x#{Integer.to_string(seq_base, 16)} (#{seq_base})")
IO.puts(" calculated_version: #{calculated_version}")
IO.puts(" expected_version: #{enc_version}")
IO.puts(" valid: #{calculated_version == enc_version}")
# Get packet length
packet_len = Bitwise.bxor(raw_seq, raw_len) &&& 0xFFFF
IO.puts("\nPacket length: #{packet_len}")
# Decrypt with AES
IO.puts("\n=== AES Decryption ===")
aes_decrypted = AESCipher.crypt(payload, recv_iv)
IO.puts("After AES: #{Base.encode16(aes_decrypted)}")
# Decrypt with Shanda
IO.puts("\n=== Shanda Decryption ===")
shanda_decrypted = ShandaCipher.decrypt(aes_decrypted)
IO.puts("After Shanda: #{Base.encode16(shanda_decrypted)}")
# Expected opcode 0x01 = 1
if byte_size(shanda_decrypted) >= 2 do
<<opcode::little-16, rest::binary>> = shanda_decrypted
IO.puts("\n=== Result ===")
IO.puts("Opcode: 0x#{Integer.to_string(opcode, 16)} (#{opcode})")
IO.puts("Rest: #{Base.encode16(rest)}")
if opcode == 1 do
IO.puts("\n✓ SUCCESS! This is CP_PermissionRequest (0x01)")
else
IO.puts("\n✗ FAILED! Expected opcode 0x01, got 0x#{Integer.to_string(opcode, 16)}")
end
end
# Update IV
new_recv_iv = IGCipher.inno_hash(recv_iv)
IO.puts("\nUpdated recv_iv: #{Base.encode16(new_recv_iv)}")
IO.puts("\n=== Done ===")

101
test/debug_crypto2.exs Normal file
View File

@@ -0,0 +1,101 @@
# Debug script to trace crypto operations with CORRECT IV
# Run with: cd /home/ra/odinsea-elixir && elixir test/debug_crypto2.exs
Code.require_file("lib/odinsea/util/bit_tools.ex", ".")
Code.require_file("lib/odinsea/net/cipher/ig_cipher.ex", ".")
Code.require_file("lib/odinsea/net/cipher/aes_cipher.ex", ".")
Code.require_file("lib/odinsea/net/cipher/shanda_cipher.ex", ".")
alias Odinsea.Net.Cipher.{IGCipher, AESCipher, ShandaCipher}
import Bitwise
IO.puts("=== Debugging Crypto (Corrected IV) ===\n")
# From the hello packet sent by server:
# [Data] 0E 00 70 00 01 00 34 9A 0F 0C A8 BC 0D B3 E6 07
# Server sends: recv_iv=34 9A 0F 0C, send_iv=A8 BC 0D B3
#
# Client SWAPS these:
# Client's send_iv = server's recv_iv = 34 9A 0F 0C
# Client's recv_iv = server's send_iv = A8 BC 0D B3
#
# Client encrypts with its send_iv (34 9A 0F 0C)
# Server must decrypt with its recv_iv = 34 9A 0F 0C
server_send_iv = <<0xA8, 0xBC, 0x0D, 0xB3>> # Server sends this, client uses as recv
server_recv_iv = <<0x34, 0x9A, 0x0F, 0x0C>> # Server sends this, client uses as send
IO.puts("Server perspective:")
IO.puts(" server_send_iv: #{Base.encode16(server_send_iv)}")
IO.puts(" server_recv_iv: #{Base.encode16(server_recv_iv)}")
IO.puts("\nClient perspective (after swapping):")
IO.puts(" client_send_iv = server_recv_iv: #{Base.encode16(server_recv_iv)}")
IO.puts(" client_recv_iv = server_send_iv: #{Base.encode16(server_send_iv)}")
# First encrypted packet from client:
# [Data] 7C A8 7B A8 BF 0A CD DE C7 71 AC
packet = <<0x7C, 0xA8, 0x7B, 0xA8, 0xBF, 0x0A, 0xCD, 0xDE, 0xC7, 0x71, 0xAC>>
IO.puts("\nRaw packet: #{Base.encode16(packet)}")
# Server decrypts with server_recv_iv
recv_iv = server_recv_iv
IO.puts("Server decrypts with recv_iv: #{Base.encode16(recv_iv)}")
# Extract header
<<raw_seq::little-16, raw_len::little-16, payload::binary>> = packet
IO.puts("\nHeader:")
IO.puts(" raw_seq: 0x#{Integer.to_string(raw_seq, 16)} (#{raw_seq})")
IO.puts(" raw_len: 0x#{Integer.to_string(raw_len, 16)} (#{raw_len})")
IO.puts(" payload: #{Base.encode16(payload)} (#{byte_size(payload)} bytes)")
# Validate header
<<_r0, _r1, r2, r3>> = recv_iv
enc_version = 112
seq_base = ((r2 &&& 0xFF) ||| ((r3 <<< 8) &&& 0xFF00)) &&& 0xffff
calculated_version = bxor(raw_seq &&& 0xFFFF, seq_base)
IO.puts("\nHeader validation:")
IO.puts(" r2: 0x#{Integer.to_string(r2, 16)} (#{r2})")
IO.puts(" r3: 0x#{Integer.to_string(r3, 16)} (#{r3})")
IO.puts(" seq_base: 0x#{Integer.to_string(seq_base, 16)} (#{seq_base})")
IO.puts(" raw_seq ^ seq_base: #{calculated_version}")
IO.puts(" expected_version: #{enc_version}")
IO.puts(" valid: #{calculated_version == enc_version}")
# Get packet length
packet_len = bxor(raw_seq, raw_len) &&& 0xFFFF
IO.puts("\nPacket length: #{packet_len}")
# Decrypt with AES
IO.puts("\n=== AES Decryption ===")
IO.puts("Input: #{Base.encode16(payload)}")
aes_decrypted = AESCipher.crypt(payload, recv_iv)
IO.puts("After AES: #{Base.encode16(aes_decrypted)}")
# Decrypt with Shanda
IO.puts("\n=== Shanda Decryption ===")
shanda_decrypted = ShandaCipher.decrypt(aes_decrypted)
IO.puts("After Shanda: #{Base.encode16(shanda_decrypted)}")
# Expected opcode 0x01 = 1
if byte_size(shanda_decrypted) >= 2 do
<<opcode::little-16, rest::binary>> = shanda_decrypted
IO.puts("\n=== Result ===")
IO.puts("Opcode: 0x#{Integer.to_string(opcode, 16)} (#{opcode})")
IO.puts("Rest: #{Base.encode16(rest)}")
if opcode == 1 do
IO.puts("\n✓ SUCCESS! This is CP_PermissionRequest (0x01)")
else
IO.puts("\n✗ FAILED! Expected opcode 0x01, got 0x#{Integer.to_string(opcode, 16)}")
end
end
# Update IV
new_recv_iv = IGCipher.inno_hash(recv_iv)
IO.puts("\nUpdated recv_iv: #{Base.encode16(new_recv_iv)}")
IO.puts("\n=== Done ===")

53
test/find_iv.exs Normal file
View File

@@ -0,0 +1,53 @@
# Find the correct IV by trying all combinations from hello packet
# Hello packet: 0E 00 70 00 01 00 34 9A 0F 0C A8 BC 0D B3 E6 07
import Bitwise
# Raw packet from client
packet = <<0x7C, 0xA8, 0x7B, 0xA8, 0xBF, 0x0A, 0xCD, 0xDE, 0xC7, 0x71, 0xAC>>
<<raw_seq::little-16, _raw_len::little-16, _payload::binary>> = packet
IO.puts("Raw packet: #{Base.encode16(packet)}")
IO.puts("raw_seq: 0x#{Integer.to_string(raw_seq, 16)} (#{raw_seq})")
IO.puts("")
# For header validation to pass: raw_seq ^ seq_base == 112
target_seq_base = bxor(raw_seq, 112)
IO.puts("Need seq_base: 0x#{Integer.to_string(target_seq_base, 16)} (#{target_seq_base})")
IO.puts("")
# seq_base = (r2 & 0xFF) | ((r3 << 8) & 0xFF00)
# So: r2 = lower byte, r3 = upper byte
target_r2 = target_seq_base &&& 0xFF
target_r3 = (target_seq_base >>> 8) &&& 0xFF
IO.puts("Need recv_iv[2] = 0x#{Integer.to_string(target_r2, 16)} (#{target_r2})")
IO.puts("Need recv_iv[3] = 0x#{Integer.to_string(target_r3, 16)} (#{target_r3})")
IO.puts("")
# Bytes available in hello packet (positions 6-13):
# 34 9A 0F 0C A8 BC 0D B3
bytes = [0x34, 0x9A, 0x0F, 0x0C, 0xA8, 0xBC, 0x0D, 0xB3]
IO.puts("Available bytes from hello packet:")
Enum.each(Enum.with_index(bytes), fn {b, i} ->
IO.puts(" [#{i}]: 0x#{Integer.to_string(b, 16)}")
end)
IO.puts("")
# Find matching bytes
IO.puts("Looking for matches...")
Enum.each(Enum.with_index(bytes), fn {b2, i2} ->
Enum.each(Enum.with_index(bytes), fn {b3, i3} ->
if b2 == target_r2 and b3 == target_r3 do
IO.puts("Found match! recv_iv[2]=0x#{Integer.to_string(b2, 16)} at [#{i2}], recv_iv[3]=0x#{Integer.to_string(b3, 16)} at [#{i3}]")
# Construct full IV (need to determine r0 and r1 too)
# Try different combinations for r0 and r1
Enum.each(Enum.with_index(bytes), fn {b0, i0} ->
Enum.each(Enum.with_index(bytes), fn {b1, i1} ->
iv = <<b0, b1, b2, b3>>
IO.puts(" Possible IV: #{Base.encode16(iv)} (bytes[#{i0}][#{i1}][#{i2}][#{i3}])")
end)
end)
end
end)
end)

48
test/test_aes.exs Normal file
View File

@@ -0,0 +1,48 @@
# Test AES cipher with known values
Code.require_file("lib/odinsea/util/bit_tools.ex", ".")
Code.require_file("lib/odinsea/net/cipher/ig_cipher.ex", ".")
Code.require_file("lib/odinsea/net/cipher/aes_cipher.ex", ".")
alias Odinsea.Net.Cipher.AESCipher
import Bitwise
# Test: AES crypt should be self-inverse (since it's just XOR)
iv = <<0x0F, 0x0C, 0x0C, 0xA8>>
data = <<0xBF, 0x0A, 0xCD, 0xDE, 0xC7, 0x71, 0xAC>>
IO.puts("Testing AES cipher:")
IO.puts("IV: #{Base.encode16(iv)}")
IO.puts("Data: #{Base.encode16(data)}")
IO.puts("")
# Expand IV to 16 bytes
expanded_iv = :binary.copy(iv, 4)
IO.puts("Expanded IV (16 bytes): #{Base.encode16(expanded_iv)}")
# The AES key (16 bytes)
aes_key = <<
0x13, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00,
0xB4, 0x00, 0x00, 0x00
>>
# Encrypt the expanded IV to get keystream
keystream = :crypto.crypto_one_time(:aes_128_ecb, aes_key, expanded_iv, true)
IO.puts("Keystream: #{Base.encode16(keystream)}")
# XOR data with keystream (only use as many bytes as data)
keystream_bytes = :binary.bin_to_list(keystream) |> Enum.take(byte_size(data))
data_bytes = :binary.bin_to_list(data)
result_bytes = Enum.zip_with(data_bytes, keystream_bytes, fn x, y -> bxor(x, y) end)
result = :binary.list_to_bin(result_bytes)
IO.puts("XOR result: #{Base.encode16(result)}")
IO.puts("")
# Compare with AESCipher.crypt
aes_result = AESCipher.crypt(data, iv)
IO.puts("AESCipher.crypt result: #{Base.encode16(aes_result)}")
IO.puts("Match: #{result == aes_result}")

55
test/test_ivs.exs Normal file
View File

@@ -0,0 +1,55 @@
# Test specific IV candidates
Code.require_file("lib/odinsea/util/bit_tools.ex", ".")
Code.require_file("lib/odinsea/net/cipher/ig_cipher.ex", ".")
Code.require_file("lib/odinsea/net/cipher/aes_cipher.ex", ".")
Code.require_file("lib/odinsea/net/cipher/shanda_cipher.ex", ".")
alias Odinsea.Net.Cipher.{IGCipher, AESCipher, ShandaCipher}
import Bitwise
packet = <<0x7C, 0xA8, 0x7B, 0xA8, 0xBF, 0x0A, 0xCD, 0xDE, 0xC7, 0x71, 0xAC>>
<<raw_seq::little-16, raw_len::little-16, payload::binary>> = packet
IO.puts("Testing IV candidates for packet: #{Base.encode16(packet)}")
IO.puts("raw_seq: 0x#{Integer.to_string(raw_seq, 16)}, raw_len: 0x#{Integer.to_string(raw_len, 16)}")
IO.puts("")
# IV candidates based on bytes [2]=0x0F, [3]=0x0C, [4]=0xA8, [5]=0xBC
candidates = [
<<0x0F, 0x0C, 0x0C, 0xA8>>, # [2][3][3][4] - overlapping
<<0x0C, 0x0C, 0x0C, 0xA8>>, # [3][3][3][4]
<<0x0C, 0xA8, 0x0C, 0xA8>>, # [3][4][3][4] - repeating pattern
<<0x0F, 0x0C, 0xA8, 0xBC>>, # [2][3][4][5] - recv_iv from hello?
<<0x34, 0x9A, 0x0C, 0xA8>>, # [0][1][3][4]
<<0x9A, 0x0F, 0x0C, 0xA8>>, # [1][2][3][4]
<<0x0C, 0xA8, 0xBC, 0x0D>>, # [3][4][5][6]
<<0xA8, 0xBC, 0x0C, 0xA8>>, # [4][5][3][4]
]
Enum.each(candidates, fn iv ->
<<_r0, _r1, r2, r3>> = iv
seq_base = ((r2 &&& 0xFF) ||| ((r3 <<< 8) &&& 0xFF00)) &&& 0xFFFF
valid = bxor(raw_seq &&& 0xFFFF, seq_base) == 112
IO.puts("IV: #{Base.encode16(iv)}")
IO.puts(" seq_base: 0x#{Integer.to_string(seq_base, 16)}, valid: #{valid}")
if valid do
# Try full decryption
aes_result = AESCipher.crypt(payload, iv)
shanda_result = ShandaCipher.decrypt(aes_result)
if byte_size(shanda_result) >= 2 do
<<opcode::little-16, _rest::binary>> = shanda_result
IO.puts(" *** VALID IV! ***")
IO.puts(" Decrypted opcode: 0x#{Integer.to_string(opcode, 16)} (#{opcode})")
if opcode == 1 do
IO.puts(" *** CORRECT OPCODE! ***")
end
end
end
IO.puts("")
end)

54
test/test_shanda.exs Normal file
View File

@@ -0,0 +1,54 @@
# Test Shanda cipher - encrypt then decrypt should give original
Code.require_file("lib/odinsea/util/bit_tools.ex", ".")
Code.require_file("lib/odinsea/net/cipher/shanda_cipher.ex", ".")
alias Odinsea.Net.Cipher.ShandaCipher
alias Odinsea.Util.BitTools
import Bitwise
# Test with simple data
data = <<0x01, 0x00, 0x07, 0x70, 0x00, 0x04, 0x00>>
IO.puts("Original data: #{Base.encode16(data)}")
# Encrypt
encrypted = ShandaCipher.encrypt(data)
IO.puts("Encrypted: #{Base.encode16(encrypted)}")
# Decrypt
decrypted = ShandaCipher.decrypt(encrypted)
IO.puts("Decrypted: #{Base.encode16(decrypted)}")
IO.puts("Match: #{data == decrypted}")
IO.puts("")
# If they don't match, let's debug step by step
if data != decrypted do
IO.puts("MISMATCH! Let's debug...")
IO.puts("")
# Manual encrypt - first pass only
bytes = :binary.bin_to_list(data)
data_len = length(bytes)
data_length = data_len &&& 0xFF
IO.puts("Data length: #{data_len}")
IO.puts("Initial bytes: #{inspect(bytes)}")
IO.puts("")
# First pass (j=0, forward)
IO.puts("=== Pass 0 (forward) ===")
{result0, remember0} = Enum.reduce(Enum.with_index(bytes), {[], 0}, fn {cur, i}, {acc, remember} ->
cur = BitTools.roll_left(cur, 3)
cur = (cur + data_length) &&& 0xFF
cur = bxor(cur, remember)
new_remember = cur
cur = BitTools.roll_right(cur, data_length &&& 0xFF)
cur = bxor(cur, 0xFF)
cur = (cur + 0x48) &&& 0xFF
{[cur | acc], new_remember}
end)
result0 = Enum.reverse(result0)
IO.puts("After pass 0: #{inspect(result0)}, remember: #{remember0}")
end