151 lines
3.8 KiB
Elixir
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
|