amethyst/lib/data.ex
Kodi Craft c742fa7c97
All checks were successful
Build & Test / nix-build (push) Successful in 1m5s
Begin work on implementing handshake
2024-07-07 19:05:32 +02:00

156 lines
5.1 KiB
Elixir

# Amethyst - An experimental Minecraft server written in Elixir.
# Copyright (C) 2024 KodiCraft
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
defmodule Amethyst.Minecraft.Write do
import Bitwise
@moduledoc """
This module contains functions for writing certain Minecraft data types which are more complex
than simple binary data.
"""
def varint(value) when value in -2_147_483_648..2_147_483_647 do
<<value::32-unsigned>> = <<value::32-signed>> # This is a trick to allow the arithmetic shift to act as a logical shift
varnum("", value)
end
def varint(_) do
raise ArgumentError, "Value is out of range for a varint"
end
def varlong(value) when value in -9_223_372_036_854_775_808..9_223_372_036_854_775_807
do
<<value::64-unsigned>> = <<value::64-signed>>
varnum("", value)
end
def varlong(_) do
raise ArgumentError, "Value is out of range for a varlong"
end
defp varnum(acc, value) when value in 0..127 do
acc <> <<0::1, value::7-big>>
end
defp varnum(acc, value) do
acc <> <<1::1, value::7-big>> |> varnum(value >>> 7)
end
def string(value) do
<<varint(byte_size(value))::binary, value::binary>>
end
end
defmodule Amethyst.Minecraft.Read do
import Bitwise
@moduledoc """
This module contains functions for reading Minecraft data. Unlike Amethyst.Minecraft.Write, this
includes all supported data types in order to make the interface more consistent.
These functions allow you to chain them into eachother, at the end they will produce a list of all the
values they have read.
You may use the helper function Amethyst.Minecraft.Read.start/1 to start the chain with a binary buffer.
The return value of the chain is a tuple containing the list of values and the remaining binary buffer.
iex> alias Amethyst.Minecraft.Read
iex> {[_, _, _], ""} = Read.start(<<1, 999::16, 64>>) |> Read.bool() |> Read.short() |> Read.byte() |> Read.stop()
{[true, 999, 64], ""}
"""
def start(binary) do
{[], binary, :reversed}
end
def stop({acc, rest, :reversed}) do
{Enum.reverse(acc), rest}
end
def bool({acc, <<value, rest::binary>>, :reversed}) do
{[value != 0 | acc], rest, :reversed}
end
def byte({acc, <<value::big-signed, rest::binary>>, :reversed}) do
{[value | acc], rest, :reversed}
end
def ubyte({acc, <<value::big-unsigned, rest::binary>>, :reversed}) do
{[value | acc], rest, :reversed}
end
def short({acc, <<value::16-big-signed, rest::binary>>, :reversed}) do
{[value | acc], rest, :reversed}
end
def ushort({acc, <<value::16-big-unsigned, rest::binary>>, :reversed}) do
{[value | acc], rest, :reversed}
end
def int({acc, <<value::32-big-signed, rest::binary>>, :reversed}) do
{[value | acc], rest, :reversed}
end
def long({acc, <<value::64-big-signed, rest::binary>>, :reversed}) do
{[value | acc], rest, :reversed}
end
def float({acc, <<value::32-float-big, rest::binary>>, :reversed}) do
{[value | acc], rest, :reversed}
end
def double({acc, <<value::64-float-big, rest::binary>>, :reversed}) do
{[value | acc], rest, :reversed}
end
def varint(tuple, read \\ 0, nacc \\ 0)
def varint({acc, <<1::1, value::7, rest::binary>>, :reversed}, read, nacc) when read < 5 do
varint({acc, rest, :reversed}, read + 1, nacc + (value <<< (7 * read)))
end
def varint({acc, <<0::1, value::7, rest::binary>>, :reversed}, read, nacc) do
total = nacc + (value <<< (7 * read))
<<value::32-signed>> = <<total::32-unsigned>>
{[value | acc], rest, :reversed}
end
def varint(_, read, _) when read >= 5 do
raise RuntimeError, "Got a varint which is too big!"
end
def varint({_, ""}, _, _) do
raise RuntimeError, "Got an incomplete varint!"
end
def varlong(tuple, read \\ 0, nacc \\ 0)
def varlong({acc, <<1::1, value::7, rest::binary>>, :reversed}, read, nacc) when read < 10 do
varlong({acc, rest, :reversed}, read + 1, nacc + (value <<< (7 * read)))
end
def varlong({acc, <<0::1, value::7, rest::binary>>, :reversed}, read, nacc) do
total = nacc + (value <<< (7 * read))
<<value::64-signed>> = <<total::64-unsigned>>
{[value | acc], rest, :reversed}
end
def varlong(_, read, _) when read >= 10 do
raise RuntimeError, "Got a varint which is too big!"
end
def varlong({_, ""}, _, _) do
raise RuntimeError, "Got an incomplete varint!"
end
def string({acc, data, :reversed}) do
{[length], rest, :reversed} = start(data) |> varint()
<<value::binary-size(length), rest::binary>> = rest
{[value | acc], rest, :reversed}
end
end