191 lines
5.3 KiB
Elixir
191 lines
5.3 KiB
Elixir
defmodule Odinsea.Net.Packet.Out do
|
|
@moduledoc """
|
|
Outgoing packet writer ported from Java OutPacket.
|
|
Handles little-endian encoding of MapleStory packet data.
|
|
"""
|
|
|
|
defstruct data: <<>>
|
|
|
|
@type t :: %__MODULE__{
|
|
data: iodata()
|
|
}
|
|
|
|
@doc """
|
|
Creates a new outgoing packet.
|
|
"""
|
|
@spec new() :: t()
|
|
def new do
|
|
%__MODULE__{data: []}
|
|
end
|
|
|
|
@doc """
|
|
Creates a new outgoing packet with an opcode.
|
|
"""
|
|
@spec new(integer()) :: t()
|
|
def new(opcode) when is_integer(opcode) do
|
|
%__MODULE__{data: [<<opcode::signed-integer-little-16>>]}
|
|
end
|
|
|
|
@doc """
|
|
Encodes a byte to the packet.
|
|
"""
|
|
@spec encode_byte(t(), integer()) :: t()
|
|
def encode_byte(%__MODULE__{data: data}, value) when is_integer(value) do
|
|
%__MODULE__{data: [data | <<value::signed-integer-little-8>>]}
|
|
end
|
|
|
|
@doc """
|
|
Encodes a short (2 bytes) to the packet.
|
|
"""
|
|
@spec encode_short(t(), integer()) :: t()
|
|
def encode_short(%__MODULE__{data: data}, value) when is_integer(value) do
|
|
%__MODULE__{data: [data | <<value::signed-integer-little-16>>]}
|
|
end
|
|
|
|
@doc """
|
|
Encodes an int (4 bytes) to the packet.
|
|
"""
|
|
@spec encode_int(t(), integer()) :: t()
|
|
def encode_int(%__MODULE__{data: data}, value) when is_integer(value) do
|
|
%__MODULE__{data: [data | <<value::signed-integer-little-32>>]}
|
|
end
|
|
|
|
@doc """
|
|
Encodes a long (8 bytes) to the packet.
|
|
"""
|
|
@spec encode_long(t(), integer()) :: t()
|
|
def encode_long(%__MODULE__{data: data}, value) when is_integer(value) do
|
|
%__MODULE__{data: [data | <<value::signed-integer-little-64>>]}
|
|
end
|
|
|
|
@doc """
|
|
Encodes a MapleStory ASCII string.
|
|
Format: [2-byte length][ASCII bytes]
|
|
"""
|
|
@spec encode_string(t(), String.t()) :: t()
|
|
def encode_string(%__MODULE__{data: data}, value) when is_binary(value) do
|
|
length = byte_size(value)
|
|
%__MODULE__{data: [data | <<length::signed-integer-little-16, value::binary>>]}
|
|
end
|
|
|
|
@doc """
|
|
Encodes a boolean (1 byte, 0 = false, 1 = true).
|
|
"""
|
|
@spec encode_bool(t(), boolean()) :: t()
|
|
def encode_bool(%__MODULE__{data: data}, true) do
|
|
%__MODULE__{data: [data | <<1::signed-integer-little-8>>]}
|
|
end
|
|
|
|
def encode_bool(%__MODULE__{data: data}, false) do
|
|
%__MODULE__{data: [data | <<0::signed-integer-little-8>>]}
|
|
end
|
|
|
|
@doc """
|
|
Encodes a raw buffer to the packet.
|
|
"""
|
|
@spec encode_buffer(t(), binary()) :: t()
|
|
def encode_buffer(%__MODULE__{data: data}, buffer) when is_binary(buffer) do
|
|
%__MODULE__{data: [data | buffer]}
|
|
end
|
|
|
|
@doc """
|
|
Encodes a fixed-size buffer, padding with zeros if necessary.
|
|
"""
|
|
@spec encode_fixed_buffer(t(), binary(), non_neg_integer()) :: t()
|
|
def encode_fixed_buffer(%__MODULE__{data: data}, buffer, size) when is_binary(buffer) do
|
|
current_size = byte_size(buffer)
|
|
|
|
padding =
|
|
if current_size < size do
|
|
<<0::size((size - current_size) * 8)>>
|
|
else
|
|
<<>>
|
|
end
|
|
|
|
truncated = binary_part(buffer, 0, min(current_size, size))
|
|
%__MODULE__{data: [data | truncated]}
|
|
|> then(&%__MODULE__{data: [&1.data | padding]})
|
|
end
|
|
|
|
@doc """
|
|
Encodes a timestamp (current time in milliseconds).
|
|
"""
|
|
@spec encode_timestamp(t()) :: t()
|
|
def encode_timestamp(%__MODULE__{data: data}) do
|
|
timestamp = System.system_time(:millisecond)
|
|
%__MODULE__{data: [data | <<timestamp::signed-integer-little-64>>]}
|
|
end
|
|
|
|
@doc """
|
|
Encodes a file time (Windows FILETIME format).
|
|
"""
|
|
@spec encode_filetime(t(), integer()) :: t()
|
|
def encode_filetime(%__MODULE__{data: data}, value) when is_integer(value) do
|
|
%__MODULE__{data: [data | <<value::signed-integer-little-64>>]}
|
|
end
|
|
|
|
@doc """
|
|
Encodes a file time for "zero" (infinite/no expiration).
|
|
"""
|
|
@spec encode_filetime_zero(t()) :: t()
|
|
def encode_filetime_zero(%__MODULE__{data: data}) do
|
|
%__MODULE__{data: [data | <<0x00::64>>]}
|
|
end
|
|
|
|
@doc """
|
|
Encodes a file time for "infinite".
|
|
"""
|
|
@spec encode_filetime_infinite(t()) :: t()
|
|
def encode_filetime_infinite(%__MODULE__{data: data}) do
|
|
%__MODULE__{data: [data | <<0xDD15F1C0::little-32, 0x11CE::little-16, 0xC0C0::little-16>>]}
|
|
end
|
|
|
|
@doc """
|
|
Encodes a position (2 ints: x, y).
|
|
"""
|
|
@spec encode_position(t(), {integer(), integer()}) :: t()
|
|
def encode_position(%__MODULE__{data: data}, {x, y}) do
|
|
%__MODULE__{data: [data | <<x::signed-integer-little-32, y::signed-integer-little-32>>]}
|
|
end
|
|
|
|
@doc """
|
|
Encodes a short position (2 shorts: x, y).
|
|
"""
|
|
@spec encode_short_position(t(), {integer(), integer()}) :: t()
|
|
def encode_short_position(%__MODULE__{data: data}, {x, y}) do
|
|
%__MODULE__{data: [data | <<x::signed-integer-little-16, y::signed-integer-little-16>>]}
|
|
end
|
|
|
|
@doc """
|
|
Returns the packet as a binary.
|
|
"""
|
|
@spec to_binary(t()) :: binary()
|
|
def to_binary(%__MODULE__{data: data}) do
|
|
IO.iodata_to_binary(data)
|
|
end
|
|
|
|
@doc """
|
|
Returns the packet as iodata (for efficient writing).
|
|
"""
|
|
@spec to_iodata(t()) :: iodata()
|
|
def to_iodata(%__MODULE__{data: data}) do
|
|
data
|
|
end
|
|
|
|
@doc """
|
|
Converts the packet to a hex string for debugging.
|
|
"""
|
|
@spec to_string(t()) :: String.t()
|
|
def to_string(%__MODULE__{data: data}) do
|
|
data |> IO.iodata_to_binary() |> Odinsea.Net.Hex.encode()
|
|
end
|
|
|
|
@doc """
|
|
Returns the current packet size in bytes.
|
|
"""
|
|
@spec length(t()) :: non_neg_integer()
|
|
def length(%__MODULE__{data: data}) do
|
|
IO.iodata_length(data)
|
|
end
|
|
end
|