2024-07-07 11:47:48 +02:00
|
|
|
# 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/>.
|
|
|
|
|
2024-07-07 11:41:21 +02:00
|
|
|
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.
|
|
|
|
|
2024-07-07 13:35:21 +02:00
|
|
|
You may use the helper function Amethyst.Minecraft.Read.start/1 to start the chain with a binary buffer.
|
2024-07-07 19:05:32 +02:00
|
|
|
The return value of the chain is a tuple containing the list of values and the remaining binary buffer.
|
2024-07-07 11:41:21 +02:00
|
|
|
|
|
|
|
iex> alias Amethyst.Minecraft.Read
|
2024-07-07 19:05:32 +02:00
|
|
|
iex> {[_, _, _], ""} = Read.start(<<1, 999::16, 64>>) |> Read.bool() |> Read.short() |> Read.byte() |> Read.stop()
|
|
|
|
{[true, 999, 64], ""}
|
2024-07-07 11:41:21 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
def start(binary) do
|
2024-07-07 19:05:32 +02:00
|
|
|
{[], binary, :reversed}
|
|
|
|
end
|
|
|
|
def stop({acc, rest, :reversed}) do
|
|
|
|
{Enum.reverse(acc), rest}
|
2024-07-07 11:41:21 +02:00
|
|
|
end
|
|
|
|
|
2024-07-07 19:05:32 +02:00
|
|
|
def bool({acc, <<value, rest::binary>>, :reversed}) do
|
|
|
|
{[value != 0 | acc], rest, :reversed}
|
2024-07-07 11:41:21 +02:00
|
|
|
end
|
|
|
|
|
2024-07-07 19:05:32 +02:00
|
|
|
def byte({acc, <<value::big-signed, rest::binary>>, :reversed}) do
|
|
|
|
{[value | acc], rest, :reversed}
|
2024-07-07 11:41:21 +02:00
|
|
|
end
|
|
|
|
|
2024-07-07 19:05:32 +02:00
|
|
|
def ubyte({acc, <<value::big-unsigned, rest::binary>>, :reversed}) do
|
|
|
|
{[value | acc], rest, :reversed}
|
2024-07-07 11:41:21 +02:00
|
|
|
end
|
|
|
|
|
2024-07-07 19:05:32 +02:00
|
|
|
def short({acc, <<value::16-big-signed, rest::binary>>, :reversed}) do
|
|
|
|
{[value | acc], rest, :reversed}
|
2024-07-07 11:41:21 +02:00
|
|
|
end
|
|
|
|
|
2024-07-07 19:05:32 +02:00
|
|
|
def ushort({acc, <<value::16-big-unsigned, rest::binary>>, :reversed}) do
|
|
|
|
{[value | acc], rest, :reversed}
|
2024-07-07 11:41:21 +02:00
|
|
|
end
|
|
|
|
|
2024-07-07 19:05:32 +02:00
|
|
|
def int({acc, <<value::32-big-signed, rest::binary>>, :reversed}) do
|
|
|
|
{[value | acc], rest, :reversed}
|
2024-07-07 11:41:21 +02:00
|
|
|
end
|
|
|
|
|
2024-07-07 19:05:32 +02:00
|
|
|
def long({acc, <<value::64-big-signed, rest::binary>>, :reversed}) do
|
|
|
|
{[value | acc], rest, :reversed}
|
2024-07-07 11:41:21 +02:00
|
|
|
end
|
|
|
|
|
2024-07-07 19:05:32 +02:00
|
|
|
def float({acc, <<value::32-float-big, rest::binary>>, :reversed}) do
|
|
|
|
{[value | acc], rest, :reversed}
|
2024-07-07 11:41:21 +02:00
|
|
|
end
|
|
|
|
|
2024-07-07 19:05:32 +02:00
|
|
|
def double({acc, <<value::64-float-big, rest::binary>>, :reversed}) do
|
|
|
|
{[value | acc], rest, :reversed}
|
2024-07-07 11:41:21 +02:00
|
|
|
end
|
2024-07-07 18:23:14 +02:00
|
|
|
|
|
|
|
def varint(tuple, read \\ 0, nacc \\ 0)
|
2024-07-07 19:05:32 +02:00
|
|
|
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)))
|
2024-07-07 18:23:14 +02:00
|
|
|
end
|
2024-07-07 19:05:32 +02:00
|
|
|
def varint({acc, <<0::1, value::7, rest::binary>>, :reversed}, read, nacc) do
|
2024-07-07 18:23:14 +02:00
|
|
|
total = nacc + (value <<< (7 * read))
|
|
|
|
<<value::32-signed>> = <<total::32-unsigned>>
|
2024-07-07 19:05:32 +02:00
|
|
|
{[value | acc], rest, :reversed}
|
2024-07-07 18:23:14 +02:00
|
|
|
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)
|
2024-07-07 19:05:32 +02:00
|
|
|
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)))
|
2024-07-07 18:23:14 +02:00
|
|
|
end
|
2024-07-07 19:05:32 +02:00
|
|
|
def varlong({acc, <<0::1, value::7, rest::binary>>, :reversed}, read, nacc) do
|
2024-07-07 18:23:14 +02:00
|
|
|
total = nacc + (value <<< (7 * read))
|
|
|
|
<<value::64-signed>> = <<total::64-unsigned>>
|
2024-07-07 19:05:32 +02:00
|
|
|
{[value | acc], rest, :reversed}
|
2024-07-07 18:23:14 +02:00
|
|
|
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
|
|
|
|
|
2024-07-07 19:05:32 +02:00
|
|
|
def string({acc, data, :reversed}) do
|
|
|
|
{[length], rest, :reversed} = start(data) |> varint()
|
2024-07-07 18:23:14 +02:00
|
|
|
<<value::binary-size(length), rest::binary>> = rest
|
2024-07-07 19:05:32 +02:00
|
|
|
{[value | acc], rest, :reversed}
|
2024-07-07 18:23:14 +02:00
|
|
|
end
|
2024-07-07 11:41:21 +02:00
|
|
|
end
|