From 7526ffae624ef25ccb2537c29cb27c05432c75b2 Mon Sep 17 00:00:00 2001 From: Kodi Craft Date: Tue, 9 Jul 2024 16:10:00 +0200 Subject: [PATCH] Define packets for Configuration --- lib/servers/configuration.ex | 233 +++++++++++++++++++++++++++++++++++ lib/servers/login.ex | 2 +- 2 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 lib/servers/configuration.ex 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