amethyst/lib/data.ex
2024-07-07 11:47:48 +02:00

126 lines
3.8 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 and Amethyst.Minecraft.Read.stop/1
to get the data out of the chain. If you pass the option `reverse: true` to stop/1, the list will be reversed, this may
provide better performance if you do not mind the inconvenience of the reversed list.
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}
end
def stop({acc, rest}, opts \\ []) do
if Keyword.get(opts, :force_empty, false) and bit_size(rest) != 0 do
raise ArgumentError, "Expected no more data, but there is still #{bit_size(rest)} bits left"
end
case Keyword.get(opts, :reverse, false) do
true -> acc
false -> Enum.reverse(acc)
end
end
def bool({acc, <<value, rest::bitstring>>}) do
{[value != 0 | acc], rest}
end
def byte({acc, <<value::big-signed, rest::binary>>}) do
{[value | acc], rest}
end
def ubyte({acc, <<value::big-unsigned, rest::binary>>}) do
{[value | acc], rest}
end
def short({acc, <<value::16-big-signed, rest::binary>>}) do
{[value | acc], rest}
end
def ushort({acc, <<value::16-big-unsigned, rest::binary>>}) do
{[value | acc], rest}
end
def int({acc, <<value::32-big-signed, rest::binary>>}) do
{[value | acc], rest}
end
def long({acc, <<value::64-big-signed, rest::binary>>}) do
{[value | acc], rest}
end
def float({acc, <<value::32-float-big, rest::binary>>}) do
{[value | acc], rest}
end
def double({acc, <<value::64-float-big, rest::binary>>}) do
{[value | acc], rest}
end
end