diff --git a/lib/servers/configuration.ex b/lib/servers/configuration.ex
new file mode 100644
index 0000000..cfeb402
--- /dev/null
+++ b/lib/servers/configuration.ex
@@ -0,0 +1,233 @@
+# 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.Server.Configuration do
+ @moduledoc """
+ This module contains the logic for the Configuration stage of the server.
+ """
+ require Logger
+ use Amethyst.Server
+
+ alias Amethyst.Minecraft.Read
+ alias Amethyst.Minecraft.Write
+
+ ## DESERIALIZATION
+ # Client Information https://wiki.vg/Protocol#Client_Information
+ def deserialize(0x00, data) do
+ {[locale, view_dist, chat_mode, chat_colors, displayed_skin_parts, main_hand, text_filtering, allow_listing], ""} =
+ Read.start(data) |> Read.string |> Read.byte |> Read.varint |> Read.bool |> Read.ubyte |> Read.varint |> Read.bool |> Read.bool |> Read.stop
+ chat_mode = case chat_mode do
+ 0 -> :enabled
+ 1 -> :commands_only
+ 2 -> :hidden
+ _ -> raise RuntimeError, "Unknown chat mode #{chat_mode}"
+ end
+ main_hand = case main_hand do
+ 0 -> :left
+ 1 -> :right
+ _ -> raise RuntimeError, "Unknown main hand #{main_hand}"
+ end
+ {:client_information, locale, view_dist, chat_mode, chat_colors, displayed_skin_parts, main_hand, text_filtering, allow_listing}
+ end
+ # Cookie Response https://wiki.vg/Protocol#Cookie_Response_(configuration)
+ def deserialize(0x01, data) do
+ {[key, exists], rest} = Read.start(data) |> Read.string |> Read.bool |> Read.stop
+ if exists do
+ {[length], rest} = Read.start(rest) |> Read.varint |> Read.stop
+ {[data], _} = Read.start(rest) |> Read.raw(length) |> Read.stop
+ {:cookie_response, key, data}
+ else
+ {:cookie_response, key, nil}
+ end
+ end
+ # Serverbound Plugin Message https://wiki.vg/Protocol#Serverbound_Plugin_Message_(configuration)
+ def deserialize(0x02, data) do
+ {[channel], rest} = Read.start(data) |> Read.string |> Read.stop
+ {:serverbound_plugin_message, channel, rest}
+ end
+ # Acknowledge Finish Configuration https://wiki.vg/Protocol#Acknowledge_Finish_Configuration
+ def deserialize(0x03, "") do
+ {:acknowledge_finish_configuration}
+ end
+ # Serverbound Keep Alive https://wiki.vg/Protocol#Serverbound_Keep_Alive_(configuration)
+ def deserialize(0x04, data) do
+ {[id], ""} = Read.start(data) |> Read.long |> Read.stop
+ {:serverbound_keep_alive, id}
+ end
+ # Pong https://wiki.vg/Protocol#Pong_(configuration)
+ def deserialize(0x05, data) do
+ {[id], ""} = Read.start(data) |> Read.int |> Read.stop
+ {:pong, id}
+ end
+ # Resource Pack Response https://wiki.vg/Protocol#Resource_Pack_Response_(configuration)
+ def deserialize(0x06, data) do
+ {[uuid, result], ""} = Read.start(data) |> Read.uuid |> Read.varint |> Read.stop
+ result = case result do
+ 0 -> :successfully_downloaded
+ 1 -> :declined
+ 2 -> :failed_to_download
+ 3 -> :accepted
+ 4 -> :downloaded
+ 5 -> :invalid_url
+ 6 -> :failed_to_reload
+ 7 -> :discarded
+ _ -> raise RuntimeError, "Unknown resource pack response #{result}"
+ end
+ {:resource_pack_response, uuid, result}
+ end
+ # Serverbound Known Packs https://wiki.vg/Protocol#Serverbound_Known_Packs
+ def deserialize(0x07, data) do
+ {[count], rest} = Read.start(data) |> Read.varint |> Read.stop
+ {packs, _} = Enum.reduce(0..count, {[], rest}, fn _, {acc, rest} ->
+ {[namespace, id, version], rest} = Read.start(rest) |> Read.string |> Read.string |> Read.string |> Read.stop
+ {[{namespace, id, version} | acc], rest}
+ end)
+ {:resource_pack_stack, packs}
+ end
+ def deserialize(type, _) do
+ raise RuntimeError, "Got unknown packet type #{type}!"
+ end
+
+ ## SERIALIZATION
+ # Cookie Request https://wiki.vg/Protocol#Cookie_Request_(configuration)
+ def serialize({:cookie_request, id}) do
+ Write.varint(0x00) <> Write.string(id)
+ end
+ # Clientbound Plugin Message https://wiki.vg/Protocol#Clientbound_Plugin_Message_(configuration)
+ def serialize({:clientbound_plugin_message, channel, data}) do
+ Write.varint(0x01) <> Write.string(channel) <> data
+ end
+ # Disconnect https://wiki.vg/Protocol#Disconnect_(configuration)
+ def serialize({:disconnect, reason}) do
+ Write.varint(0x02) <> Write.string(reason)
+ end
+ # Finish Configuration https://wiki.vg/Protocol#Finish_Configuration
+ def serialize({:finish_configuration}) do
+ Write.varint(0x03)
+ end
+ # Clientbound Keep Alive https://wiki.vg/Protocol#Clientbound_Keep_Alive_(configuration)
+ def serialize({:clientbound_keep_alive, id}) do
+ Write.varint(0x04) <> <>
+ end
+ # Ping https://wiki.vg/Protocol#Ping_(configuration)
+ def serialize({:ping, id}) do
+ Write.varint(0x05) <> <>
+ end
+ # Reset Chat https://wiki.vg/Protocol#Reset_Chat
+ def serialize({:reset_chat}) do
+ Write.varint(0x06)
+ end
+ # Registry Data https://wiki.vg/Protocol#Registry_Data
+ def serialize({:registry_data, _id, _entries}) do
+ raise ArgumentError, "Packet 'Registry Data' is not yet implemented"
+ end
+ # Remove Resource Pack https://wiki.vg/Protocol#Remove_Resource_Pack_(configuration)
+ def serialize({:remove_resource_pack, id}) do
+ if id == nil do
+ Write.varint(0x08) <> <<0x00>>
+ else
+ Write.varint(0x08) <> <<0x01>> <> Write.string(id)
+ end
+ end
+ # Add Resource Pack https://wiki.vg/Protocol#Add_Resource_Pack_(configuration)
+ def serialize({:add_resource_pack, id, url, hash, forced, msg}) do
+ Write.varint(0x09) <> Write.string(id) <> Write.string(url) <> Write.string(hash) <>
+ if(forced, do: <<0x01>>, else: <<0x00>>) <> if(msg == nil, do: <<0x00>>, else: <<0x01>> <> Write.string(msg))
+ end
+ # Store Cookie https://wiki.vg/Protocol#Store_Cookie_(configuration)
+ def serialize({:store_cookie, id, data}) do
+ Write.varint(0x0A) <> Write.string(id) <> Write.string(data)
+ end
+ # Transfer https://wiki.vg/Protocol#Transfer_(configuration)
+ def serialize({:transfer, addr, port}) do
+ Write.varint(0x0B) <> Write.string(addr) <> Write.varint(port)
+ end
+ # Feature Flags https://wiki.vg/Protocol#Feature_Flags
+ def serialize({:feature_flags, flags}) do
+ Write.varint(0x0C) <> Write.varint(length(flags)) <> Enum.reduce(flags, "", fn id, acc -> acc <> Write.string(id) end)
+ end
+ # Update Tags https://wiki.vg/Protocol#Update_Tags
+ def serialize({:update_tags, tags}) do
+ Write.varint(0x0D) <> Write.varint(length(tags)) <>
+ Enum.reduce(tags, "", &serialize_tag/2)
+ end
+ # Clientbound Known Packs https://wiki.vg/Protocol#Clientbound_Known_Packs
+ def serialize({:clientbound_known_packs, packs}) do
+ Write.varint(0x0E) <> Write.varint(length(packs)) <>
+ Enum.reduce(packs, "", fn {namespace, id, version}, acc -> acc <> Write.string(namespace) <> Write.string(id) <> Write.string(version) end)
+ end
+ # Custom Report Details https://wiki.vg/Protocol#Custom_Report_Details
+ def serialize({:custom_report_details, details}) do
+ Write.varint(0x0F) <> Write.varint(length(details)) <>
+ Enum.reduce(details, "", fn {id, data}, acc -> acc <> Write.string(id) <> Write.string(data) end)
+ end
+ # Server Links https://wiki.vg/Protocol#Server_Links_(configuration)
+ def serialize({:server_links, links}) do
+ Write.varint(0x10) <> Write.varint(length(links)) <>
+ Enum.reduce(links, "", fn {label, url}, acc ->
+ acc <> serialize_link_label(label) <> Write.string(url)
+ end)
+ end
+ def serialize(packet) do
+ raise ArgumentError, "Tried serializing unknown packet #{inspect(packet)}"
+ end
+
+ defp serialize_tag({id, elements}, acc) do
+ acc <> Write.string(id) <> Write.varint(length(elements)) <> serialize_elements(elements)
+ end
+ defp serialize_elements(elements) do
+ Enum.reduce(elements, "", fn {id, ids}, acc -> acc <> Write.string(id) <> Write.varint(length(ids)) <>
+ Enum.reduce(ids, "", fn id, acc -> acc <> Write.varint(id) end) end)
+ end
+ defp serialize_link_label(:bug_report) do
+ <<0x01>> <> Write.varint(0x00)
+ end
+ defp serialize_link_label(:community_guidelines) do
+ <<0x01>> <> Write.varint(0x01)
+ end
+ defp serialize_link_label(:support) do
+ <<0x01>> <> Write.varint(0x02)
+ end
+ defp serialize_link_label(:status) do
+ <<0x01>> <> Write.varint(0x03)
+ end
+ defp serialize_link_label(:feedback) do
+ <<0x01>> <> Write.varint(0x04)
+ end
+ defp serialize_link_label(:community) do
+ <<0x01>> <> Write.varint(0x05)
+ end
+ defp serialize_link_label(:website) do
+ <<0x01>> <> Write.varint(0x06)
+ end
+ defp serialize_link_label(:forums) do
+ <<0x01>> <> Write.varint(0x07)
+ end
+ defp serialize_link_label(:news) do
+ <<0x01>> <> Write.varint(0x08)
+ end
+ defp serialize_link_label(:announcements) do
+ <<0x01>> <> Write.varint(0x09)
+ end
+ defp serialize_link_label(other) do
+ <<0x00>> <> Write.string(other)
+ end
+
+ ## HANDLING
+ def handle(tuple, _) do
+ Logger.error("Unhandled but known packet #{elem(tuple, 0)}")
+ end
+end
diff --git a/lib/servers/login.ex b/lib/servers/login.ex
index 3d87307..6d015bf 100644
--- a/lib/servers/login.ex
+++ b/lib/servers/login.ex
@@ -16,7 +16,7 @@
defmodule Amethyst.Server.Login do
@moduledoc """
- This module contains the logic for the Handshake stage of the server.
+ This module contains the logic for the Login stage of the server.
"""
require Logger
use Amethyst.Server