# 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 . 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 uuid(uuid) do UUID.string_to_binary!(uuid) end def bool(value) do case value do true -> <<0x01::8>> false -> <<0x00::8>> end end def varint(value) when value in -2_147_483_648..2_147_483_647 do <> = <> # 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 <> = <> 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 <> 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, <>, :reversed}) do {[value != 0 | acc], rest, :reversed} end def byte({acc, <>, :reversed}) do {[value | acc], rest, :reversed} end def ubyte({acc, <>, :reversed}) do {[value | acc], rest, :reversed} end def short({acc, <>, :reversed}) do {[value | acc], rest, :reversed} end def ushort({acc, <>, :reversed}) do {[value | acc], rest, :reversed} end def int({acc, <>, :reversed}) do {[value | acc], rest, :reversed} end def long({acc, <>, :reversed}) do {[value | acc], rest, :reversed} end def float({acc, <>, :reversed}) do {[value | acc], rest, :reversed} end def double({acc, <>, :reversed}) do {[value | acc], rest, :reversed} end def uuid({acc, <>, :reversed}) do {[UUID.binary_to_string!(uuid) | acc], rest, :reversed} end def raw({acc, data, :reversed}, amount) do <> = data {[data | 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 | 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 | 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() <> = rest {[value | acc], rest, :reversed} end end