diff --git a/apps/amethyst/lib/apps/connection_handler.ex b/apps/amethyst/lib/apps/connection_handler.ex index 95089be..b0f9b94 100644 --- a/apps/amethyst/lib/apps/connection_handler.ex +++ b/apps/amethyst/lib/apps/connection_handler.ex @@ -34,7 +34,7 @@ alias ElixirSense.Log def start(socket, connstate, version) do {:ok, spawn(fn -> Process.set_label("ConnectionHandler for #{inspect(socket)}") - loop(socket, connstate, version) + loop(socket, connstate, version, %{}) end)} end @@ -42,12 +42,12 @@ alias ElixirSense.Log def start_link(socket, connstate, version) do {:ok, spawn_link(fn -> Process.set_label("ConnectionHandler for #{inspect(socket)}") - loop(socket, connstate, version) + loop(socket, connstate, version, %{}) end)} end - @spec loop(:gen_tcp.socket(), atom(), pos_integer()) :: no_return() - defp loop(socket, connstate, version) do + @spec loop(:gen_tcp.socket(), atom(), pos_integer(), map()) :: no_return() + defp loop(socket, connstate, version, state) do receive do :closed -> Logger.info("Connection #{inspect(socket)} closed.") @@ -57,38 +57,45 @@ alias ElixirSense.Log Process.exit(self(), :normal) {:set_state, newstate} -> Logger.debug("Switching to state #{newstate} from #{connstate}") - loop(socket, newstate, version) + loop(socket, newstate, version, state) {:set_version, newversion} -> Logger.debug("Switching to version #{newversion} from #{version}") - loop(socket, connstate, newversion) + loop(socket, connstate, newversion, state) {:send_packet, packet} -> Logger.debug("Sending packet #{inspect(packet)}") send_packet(socket, connstate, packet, version) - loop(socket, connstate, version) + loop(socket, connstate, version, state) after 0 -> receive do {:packet, id, data} -> - handle_packet(id, data, connstate, version) - loop(socket, connstate, version) + state = handle_packet(id, data, connstate, version, state) + loop(socket, connstate, version, state) end end end - defp handle_packet(id, data, connstate, version) do + defp handle_packet(id, data, connstate, version, state) do try do packet = connstate.deserialize(id, version, data) - case connstate.handle(packet, version) do - :ok -> :ok + case connstate.handle(packet, version, state) do + :ok -> state {:error, reason} -> Logger.error("Error handling packet with ID #{id} in state #{connstate}: #{reason}") - send(self(), {:disconnect, "Error handling packet #{id}: #{reason}"}) - _ -> - Logger.warn("Unknown return value from handle_packet for packet #{id} in state #{connstate}") + send(self(), {:disconnect, "Error handling packet #{id}:\n#{reason}"}) + state + newstate -> + if is_map(newstate) do + newstate + else + Logger.warning("State change to #{newstate} is not a map! Did you forget to return :ok?") + state + end end rescue e -> Logger.error("Error handling packet with ID #{id} in state #{connstate}: #{Exception.format(:error, e, __STACKTRACE__)}") - send(self(), {:disconnect, "Error handling packet #{id}: #{Exception.format(:error, e, __STACKTRACE__)}"}) + send(self(), {:disconnect, "Error handling packet #{id}:\n#{e.message}"}) + state end end diff --git a/apps/amethyst/lib/data.ex b/apps/amethyst/lib/data.ex index b330aa2..db3c94c 100644 --- a/apps/amethyst/lib/data.ex +++ b/apps/amethyst/lib/data.ex @@ -135,6 +135,10 @@ defmodule Amethyst.Minecraft.Write do v -> bool(true) <> callback.(v) end end + + def nbt(value) do + Amethyst.NBT.Write.write_net(value) + end end defmodule Amethyst.Minecraft.Read do @@ -259,4 +263,7 @@ defmodule Amethyst.Minecraft.Read do {[value], rest, :reversed} = string({[], data, :reversed}) {[Jason.decode!(value) | acc], rest, :reversed} end + def raw({acc, data, :reversed}) do + {[data | acc], "", :reversed} + end end diff --git a/apps/amethyst/lib/states/configuration.ex b/apps/amethyst/lib/states/configuration.ex new file mode 100644 index 0000000..761eea2 --- /dev/null +++ b/apps/amethyst/lib/states/configuration.ex @@ -0,0 +1,115 @@ +# 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.ConnectionState.Configuration do + require Amethyst.ConnectionState.Macros + alias Amethyst.ConnectionState.Macros + + require Logger + + @moduledoc """ + This module contains the packets and logic for the Configuration state. + """ + Macros.defpacket_clientbound :cookie_request, 0x00, 767, [identifier: :string] + Macros.defpacket_clientbound :clientbound_plugin_message, 0x01, 767, [channel: :string, data: :raw] + Macros.defpacket_clientbound :disconnect, 0x02, 767, [reason: :json] + Macros.defpacket_clientbound :finish_configuration, 0x03, 767, [] + Macros.defpacket_clientbound :clientbound_keep_alive, 0x04, 767, [id: :long] + Macros.defpacket_clientbound :ping, 0x05, 767, [id: :int] + Macros.defpacket_clientbound :reset_chat, 0x06, 767, [] + Macros.defpacket_clientbound :registry_data, 0x07, 767, [ + id: :string, + entries: {:array, [ + id: :string, + data: {:optional, :nbt} + ]} + ] + Macros.defpacket_clientbound :remove_resource_pack, 0x08, 767, [ + uuid: {:optional, :uuid} + ] + Macros.defpacket_clientbound :add_resource_pack, 0x09, 767, [ + uuid: :uuid, + url: :string, + hash: :string, + forced: :bool, + prompt_message: {:optional, :string} + ] + Macros.defpacket_clientbound :store_cookie, 0x0A, 767, [identifier: :string, payload: :byte_array] + Macros.defpacket_clientbound :transfer, 0x0B, 767, [host: :string, port: :varint] + Macros.defpacket_clientbound :feature_flags, 0x0C, 767, [flags: {:array, [flag: :string]}] + Macros.defpacket_clientbound :update_tags, 0x0D, 767, [ + tags: {:array, [ + registry: :string, + tags: {:array, [ + name: :string, + entries: {:array, [id: :varint]} + ]} + ]} + ] + Macros.defpacket_clientbound :clientbound_known_packs, 0x0E, 767, [ + packs: {:array, [ + nameshape: :string, + id: :string, + version: :string + ]} + ] + Macros.defpacket_clientbound :custom_report_details, 0x0F, 767, [ + details: {:array, [ + title: :string, + desctioption: :string + ]} + ] + Macros.defpacket_clientbound :server_links, 0x10, 767, [ + links: {:array, [ + is_builtin: :bool, + label: :string, + url: :string + ]} + ] + + Macros.defpacket_serverbound :client_information, 0x00, 767, [ + locale: :string, + view_distance: :byte, + chat_mode: :varint, + chat_colors: :bool, + displayed_skin_parts: :byte, + main_hand: :varint, + text_filtering: :bool, + allow_server_listings: :bool + ] + Macros.defpacket_serverbound :cookie_response, 0x01, 767, [ + key: :string, + payload: {:optional, :byte_array} + ] + Macros.defpacket_serverbound :serverbound_plugin_message, 0x02, 767, [channel: :string, data: :raw] + Macros.defpacket_serverbound :acknowledge_finish_configuration, 0x03, 767, [] + Macros.defpacket_serverbound :serverbound_keep_alive, 0x04, 767, [id: :long] + Macros.defpacket_serverbound :pong, 0x05, 767, [id: :int] + Macros.defpacket_serverbound :resource_pack_response, 0x06, 767, [uuid: :uuid, result: :varint] + Macros.defpacket_serverbound :serverbound_known_packs, 0x07, 767, [ + packs: {:array, [ + namespace: :string, + id: :string, + version: :string + ]} + ] + + def disconnect(reason) do + %{packet_type: :disconnect, reason: %{ + "text" => reason + }} + end +end diff --git a/apps/amethyst/lib/states/handhsake.ex b/apps/amethyst/lib/states/handhsake.ex index 73f7270..1aa6f88 100644 --- a/apps/amethyst/lib/states/handhsake.ex +++ b/apps/amethyst/lib/states/handhsake.ex @@ -31,7 +31,7 @@ defmodule Amethyst.ConnectionState.Handshake do next: :varint ] - def handle(%{packet_type: :handshake, version: 767, address: address, port: port, next: next}, 0) do + def handle(%{packet_type: :handshake, version: 767, address: address, port: port, next: next}, 0, _state) do Logger.debug("Received handshake for #{address}:#{port} with version 767") case next do 1 -> diff --git a/apps/amethyst/lib/states/login.ex b/apps/amethyst/lib/states/login.ex index e3bc961..e058a89 100644 --- a/apps/amethyst/lib/states/login.ex +++ b/apps/amethyst/lib/states/login.ex @@ -23,7 +23,7 @@ defmodule Amethyst.ConnectionState.Login do @moduledoc """ This module contains the packets and logic for the Login state. """ - Macros.defpacket_clientbound :disconnect, 0x00, 767, [reason: :json] + Macros.defpacket_clientbound :disconnect, 0x00, 767, [reason: :nbt] Macros.defpacket_clientbound :encryption_request, 0x01, 767, [ server_id: :string, public_key: :byte_array, @@ -60,10 +60,33 @@ defmodule Amethyst.ConnectionState.Login do Macros.defpacket_serverbound :login_acknowledged, 0x03, 767, [] Macros.defpacket_serverbound :cookie_response, 0x04, 767, [identifier: :string, payload: {:optional, :byte_array}] + def handle(%{packet_type: :login_start, name: name, player_uuid: player_uuid}, 767, state) do + Logger.debug("Received login start for #{name} with UUID #{player_uuid}") + if Application.fetch_env!(:amethyst, :encryption) do + raise RuntimeError, "Encryption is not currently supported" + else + send(self(), {:send_packet, %{ + packet_type: :login_success, + uuid: player_uuid, + username: name, + properties: [], + strict_error_handling: false + }}) + :ok + end + end + + def handle(%{packet_type: :login_acknowledged}, 767, _state) do + Logger.debug("Received login acknowledged") + send(self(), {:set_state, Amethyst.ConnectionState.Configuration}) + :ok + end + def disconnect(reason) do - %{packet_type: :disconnect, reason: %{ - "text" => "You have been disconnected:\n#{reason}", - "color" => "red" - }} + %{packet_type: :disconnect, reason: + {:compound, %{ + "text" => {:string, reason} + }} + } end end diff --git a/apps/amethyst/lib/states/status.ex b/apps/amethyst/lib/states/status.ex index eaa7158..2b7d489 100644 --- a/apps/amethyst/lib/states/status.ex +++ b/apps/amethyst/lib/states/status.ex @@ -29,7 +29,7 @@ defmodule Amethyst.ConnectionState.Status do Macros.defpacket_serverbound :status_request, 0x00, 767, [] Macros.defpacket_serverbound :ping_request, 0x01, 767, [payload: :long] - def handle(%{packet_type: :status_request}, 767) do + def handle(%{packet_type: :status_request}, 767, _state) do Logger.debug("Received status request") send(self(), {:send_packet, %{ packet_type: :status_response, @@ -45,7 +45,7 @@ defmodule Amethyst.ConnectionState.Status do :ok end - def handle(%{packet_type: :ping_request, payload: payload}, 767) do + def handle(%{packet_type: :ping_request, payload: payload}, 767, _state) do Logger.debug("Received ping request") send(self(), {:send_packet, %{ packet_type: :pong_response,