Flattened writing interface
All checks were successful
Build & Test / nix-build (push) Successful in 1m12s

This commit is contained in:
Kodi Craft 2024-08-01 05:14:05 +02:00
parent 8e57427c99
commit 9f9526f5de
Signed by: kodi
GPG Key ID: 69D9EED60B242822
3 changed files with 63 additions and 18 deletions

View File

@ -18,21 +18,55 @@ defmodule Amethyst.Minecraft.Write do
import Bitwise import Bitwise
@moduledoc """ @moduledoc """
This module contains functions for writing certain Minecraft data types which are more complex This module contains functions for writing Minecraft data.
than simple binary data.
Each function in this module takes in an input of the proper type and returns a binary
of the encoded data.
""" """
def uuid(uuid) do def uuid(uuid) when is_binary(uuid) do
UUID.string_to_binary!(uuid) UUID.string_to_binary!(uuid)
end end
def bool(value) do def bool(value) when is_boolean(value) do
case value do case value do
true -> <<0x01::8>> true -> <<0x01::8>>
false -> <<0x00::8>> false -> <<0x00::8>>
end end
end end
def byte(value) when value in -128..127 do
<<value::8-signed-big>>
end
def ubyte(value) when value in 0..255 do
<<value::8-unsigned-big>>
end
def short(value) when value in -32_768..32_767 do
<<value::16-signed-big>>
end
def ushort(value) when value in 0..65_535 do
<<value::16-unsigned-big>>
end
def int(value) when value in -2_147_483_648..2_147_483_647 do
<<value::32-signed-big>>
end
def long(value) when value in -9_223_372_036_854_775_808..9_223_372_036_854_775_807 do
<<value::64-signed-big>>
end
def float(value) when is_number(value) do
<<value::32-float>>
end
def double(value) when is_number(value) do
<<value::64-float>>
end
def varint(value) when value in -2_147_483_648..2_147_483_647 do 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 <<value::32-unsigned>> = <<value::32-signed>> # This is a trick to allow the arithmetic shift to act as a logical shift
varnum("", value) varnum("", value)
@ -73,13 +107,12 @@ defmodule Amethyst.Minecraft.Read do
import Bitwise import Bitwise
@moduledoc """ @moduledoc """
This module contains functions for reading Minecraft data. Unlike Amethyst.Minecraft.Write, this This module contains functions for reading Minecraft data.
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 These functions allow you to chain them into eachother, at the end they will produce a list of all the
values they have read. values they have read.
You may use the helper function Amethyst.Minecraft.Read.start/1 to start the chain with a binary buffer. 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. 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> alias Amethyst.Minecraft.Read
@ -87,9 +120,15 @@ defmodule Amethyst.Minecraft.Read do
{[true, 999, 64], ""} {[true, 999, 64], ""}
""" """
@doc """
This function structures an input binary to be used by the functions in `Amethyst.Minecraft.Read`.
"""
def start(binary) do def start(binary) do
{[], binary, :reversed} {[], binary, :reversed}
end end
@doc """
This function structures the result of the functions in `Amethyst.Minecraft.Read` to be used in the same order they were read.
"""
def stop({acc, rest, :reversed}) do def stop({acc, rest, :reversed}) do
{Enum.reverse(acc), rest} {Enum.reverse(acc), rest}
end end
@ -139,6 +178,9 @@ defmodule Amethyst.Minecraft.Read do
{[data | acc], rest, :reversed} {[data | acc], rest, :reversed}
end end
@doc """
Reads a varint. `read` tracks the number of bytes read and `nacc` tracks the number being read.
"""
def varint(tuple, read \\ 0, nacc \\ 0) def varint(tuple, read \\ 0, nacc \\ 0)
def varint({acc, <<1::1, value::7, rest::binary>>, :reversed}, read, nacc) when read < 5 do 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))) varint({acc, rest, :reversed}, read + 1, nacc + (value <<< (7 * read)))
@ -155,6 +197,9 @@ defmodule Amethyst.Minecraft.Read do
raise RuntimeError, "Got an incomplete varint!" raise RuntimeError, "Got an incomplete varint!"
end end
@doc """
Reads a varlong. `read` tracks the number of bytes read and `nacc` tracks the number being read.
"""
def varlong(tuple, read \\ 0, nacc \\ 0) def varlong(tuple, read \\ 0, nacc \\ 0)
def varlong({acc, <<1::1, value::7, rest::binary>>, :reversed}, read, nacc) when read < 10 do 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))) varlong({acc, rest, :reversed}, read + 1, nacc + (value <<< (7 * read)))
@ -165,10 +210,10 @@ defmodule Amethyst.Minecraft.Read do
{[value | acc], rest, :reversed} {[value | acc], rest, :reversed}
end end
def varlong(_, read, _) when read >= 10 do def varlong(_, read, _) when read >= 10 do
raise RuntimeError, "Got a varint which is too big!" raise RuntimeError, "Got a varlong which is too big!"
end end
def varlong({_, ""}, _, _) do def varlong({_, ""}, _, _) do
raise RuntimeError, "Got an incomplete varint!" raise RuntimeError, "Got an incomplete varlong!"
end end
def string({acc, data, :reversed}) do def string({acc, data, :reversed}) do

View File

@ -44,17 +44,17 @@ defmodule Amethyst.Server.Play do
reduce_debug, enable_respawn_screen, limited_crafting, reduce_debug, enable_respawn_screen, limited_crafting,
dim_type, dim_name, hashed_seed, gamemode, prev_gm, dim_type, dim_name, hashed_seed, gamemode, prev_gm,
is_debug, is_flat, death_loc, portal_cooldown, enforce_chat}) do is_debug, is_flat, death_loc, portal_cooldown, enforce_chat}) do
# TODO: this singlehandedly made me regret not making the write API better, please rework :( # TODO: This is a big unreadable slab of serialization, it needs a proper rework at some point
Write.varint(0x2B) <> Write.varint(0x2B) <>
<<eid::big-signed-32>> <> if(hardcore, do: <<1::big-8>>, else: <<0::big-8>>) <> Write.int(eid) <> Write.bool(hardcore) <>
Write.varint(length(dimensions)) <> Enum.reduce(dimensions, "", fn dim, acc -> acc <> Write.string(dim) end) <> Write.varint(length(dimensions)) <> Enum.reduce(dimensions, "", fn dim, acc -> acc <> Write.string(dim) end) <>
Write.varint(max_players) <> Write.varint(view_distance) <> Write.varint(simulation_distance) <> if(reduce_debug, do: <<1::big-8>>, else: <<0::big-8>>) <> Write.varint(max_players) <> Write.varint(view_distance) <> Write.varint(simulation_distance) <> Write.bool(reduce_debug) <>
if(enable_respawn_screen, do: <<1::big-8>>, else: <<0::big-8>>) Write.bool(enable_respawn_screen) <>
if(limited_crafting, do: <<1::big-8>>, else: <<0::big-8>>) <> Write.varint(dim_type) <> Write.string(dim_name) <> Write.bool(limited_crafting) <> Write.varint(dim_type) <> Write.string(dim_name) <>
hashed_seed <> <<gamemode_id(gamemode)::unsigned-big-8>> <> <<gamemode_id(prev_gm)::signed-big-8>> <> hashed_seed <> Write.ubyte(gamemode_id(gamemode)) <> Write.byte(gamemode_id(prev_gm)) <>
if(is_debug, do: <<1::big-8>>, else: <<0::big-8>>) <> if(is_flat, do: <<1::big-8>>, else: <<0::big-8>>) <> Write.bool(is_debug) <> Write.bool(is_flat) <>
if(death_loc == nil, do: <<0::big-8>>, else: <<1::big-8>> <> Write.string(elem(death_loc, 0)) <> Write.position(elem(death_loc, 1))) <> if(death_loc == nil, do: <<0::big-8>>, else: <<1::big-8>> <> Write.string(elem(death_loc, 0)) <> Write.position(elem(death_loc, 1))) <>
Write.varint(portal_cooldown) <> if(enforce_chat, do: <<1::big-8>>, else: <<0::big-8>>) Write.varint(portal_cooldown) <> Write.bool(enforce_chat)
end end
def serialize(packet) do def serialize(packet) do
raise ArgumentError, "Tried serializing unknown packet #{inspect(packet)}" raise ArgumentError, "Tried serializing unknown packet #{inspect(packet)}"

View File

@ -51,7 +51,7 @@ defmodule Amethyst.Server.Status do
Write.varint(0x00) <> Write.string(data) Write.varint(0x00) <> Write.string(data)
end end
def serialize({:ping_response, payload}) do def serialize({:ping_response, payload}) do
Write.varint(0x01) <> <<payload::64-big-signed>> Write.varint(0x01) <> Write.long(payload)
end end
def serialize(packet) do def serialize(packet) do
raise ArgumentError, "Tried serializing unknown packet #{inspect(packet)}" raise ArgumentError, "Tried serializing unknown packet #{inspect(packet)}"