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