defmodule Odinsea.Util.BitTools do @moduledoc """ Utility functions for bit and byte manipulation. Provides helper functions for working with binary data, similar to Java's BitTools. Ported from: src/tools/BitTools.java """ use Bitwise @doc """ Reads a 16-bit short integer (little-endian) from a byte array at the given index. ## Parameters - array: Binary array - index: Starting position (0-based) ## Returns - 16-bit integer value (0-65535) """ @spec get_short(binary(), non_neg_integer()) :: non_neg_integer() def get_short(array, index) when is_binary(array) do <<_skip::binary-size(index), value::little-unsigned-16, _rest::binary>> = array value end @doc """ Reads a string from a byte array at the given index with specified length. ## Parameters - array: Binary array - index: Starting position - length: Number of bytes to read ## Returns - String extracted from the byte array """ @spec get_string(binary(), non_neg_integer(), non_neg_integer()) :: String.t() def get_string(array, index, length) when is_binary(array) do <<_skip::binary-size(index), string_data::binary-size(length), _rest::binary>> = array to_string(string_data) end @doc """ Reads a MapleStory-convention string from a byte array. Format: 2-byte little-endian length prefix + string data ## Parameters - array: Binary array - index: Starting position ## Returns - String extracted from the byte array """ @spec get_maple_string(binary(), non_neg_integer()) :: String.t() def get_maple_string(array, index) when is_binary(array) do length = get_short(array, index) get_string(array, index + 2, length) end @doc """ Rotates bits of a byte left by count positions. ## Parameters - byte_val: Byte value (0-255) - count: Number of positions to rotate ## Returns - Rotated byte value """ @spec roll_left(byte(), non_neg_integer()) :: byte() def roll_left(byte_val, count) when is_integer(byte_val) and byte_val >= 0 and byte_val <= 255 do tmp = byte_val &&& 0xFF rotated = tmp <<< rem(count, 8) ((rotated &&& 0xFF) ||| (rotated >>> 8)) &&& 0xFF end @doc """ Rotates bits of a byte right by count positions. ## Parameters - byte_val: Byte value (0-255) - count: Number of positions to rotate ## Returns - Rotated byte value """ @spec roll_right(byte(), non_neg_integer()) :: byte() def roll_right(byte_val, count) when is_integer(byte_val) and byte_val >= 0 and byte_val <= 255 do tmp = byte_val &&& 0xFF rotated = (tmp <<< 8) >>> rem(count, 8) ((rotated &&& 0xFF) ||| (rotated >>> 8)) &&& 0xFF end @doc """ Repeats the first `count` bytes of `input` `mul` times. ## Parameters - input: Binary input - count: Number of bytes to repeat from the input - mul: Number of times to repeat ## Returns - Binary with repeated bytes """ @spec multiply_bytes(binary(), non_neg_integer(), non_neg_integer()) :: binary() def multiply_bytes(input, count, mul) when is_binary(input) do # Take first `count` bytes chunk = binary_part(input, 0, min(count, byte_size(input))) # Repeat `mul` times 1..mul |> Enum.map(fn _ -> chunk end) |> IO.iodata_to_binary() end @doc """ Converts a double-precision float to a short by extracting high bits. ## Parameters - d: Double-precision float ## Returns - 16-bit integer extracted from the high bits """ @spec double_to_short_bits(float()) :: integer() def double_to_short_bits(d) when is_float(d) do # Convert double to 64-bit integer representation <> = <> # Extract high 16 bits (shift right by 48) (long_bits >>> 48) &&& 0xFFFF end end