Files
odinsea-elixir/lib/odinsea/net/cipher/shanda_cipher.ex
2026-02-25 12:26:26 -07:00

151 lines
3.8 KiB
Elixir

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