# 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 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 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, <>}) do {[value != 0 | acc], rest} end def byte({acc, <>}) do {[value | acc], rest} end def ubyte({acc, <>}) do {[value | acc], rest} end def short({acc, <>}) do {[value | acc], rest} end def ushort({acc, <>}) do {[value | acc], rest} end def int({acc, <>}) do {[value | acc], rest} end def long({acc, <>}) do {[value | acc], rest} end def float({acc, <>}) do {[value | acc], rest} end def double({acc, <>}) do {[value | acc], rest} end end