diff --git a/lib/apps/tcp_listener.ex b/lib/apps/tcp_listener.ex index 9920c7b..d7594f9 100644 --- a/lib/apps/tcp_listener.ex +++ b/lib/apps/tcp_listener.ex @@ -34,18 +34,4 @@ defmodule Amethyst.TCPListener do Logger.info("Received connection from #{inspect(client)}, assigned to PID #{inspect(pid)}") loop_acceptor(socket) end - - defp serve(socket) do - socket |> read_line() |> write_line(socket) - serve(socket) - end - - defp read_line(socket) do - {:ok, data} = :gen_tcp.recv(socket, 0) - data - end - - defp write_line(line, socket) do - :gen_tcp.send(socket, line) - end end diff --git a/lib/servers/stage1.ex b/lib/servers/stage1.ex index ca9e12b..50c60d4 100644 --- a/lib/servers/stage1.ex +++ b/lib/servers/stage1.ex @@ -24,14 +24,16 @@ defmodule Amethyst.Server.Stage1 do @spec serve(:gen_tcp.socket()) :: no_return() def serve(client) do {id, data} = Amethyst.Server.Generic.get_packet(client) - case deserialize(id, data) do - {:handshake, ver, addr, port, next} -> Logger.info("Got handshake on ver #{ver}, #{addr}:#{port} moving to #{next}") - end + packet = deserialize(id, data) + Logger.debug("Got packet #{inspect(packet)}") + handle(packet, client) serve(client) end ## DESERIALIZATION # Handshake https://wiki.vg/Protocol#Handshake + @spec deserialize(0, binary()) :: + {:handshake, any(), any(), any(), :login | :status | :transfer} def deserialize(0x00, <>) do {[ver, addr, port, next], ""} = Read.start(data) |> Read.varint() |> Read.string() |> Read.ushort() |> Read.varint() |> Read.stop() next = case next do @@ -45,4 +47,18 @@ defmodule Amethyst.Server.Stage1 do def deserialize(type, _) do raise RuntimeError, "Got unknown packet type #{type}!" end + + ## HANDLING + # Handshake https://wiki.vg/Protocol#Handshake + @spec handle(any(), any()) :: no_return() + def handle({:handshake, ver, addr, port, next}, client) do + Logger.info("Got handshake, version #{ver} on #{addr}:#{port}. Wants to move to #{next}") + case next do + :status -> Amethyst.Server.Status.serve(client) + _ -> raise RuntimeError, "Unhandled move to next mode #{next}" + end + end + def handle(tuple, _) do + Logger.error("Unhandled but known packet #{elem(tuple, 0)}") + end end diff --git a/lib/servers/status.ex b/lib/servers/status.ex new file mode 100644 index 0000000..e0e8bb9 --- /dev/null +++ b/lib/servers/status.ex @@ -0,0 +1,89 @@ +# 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.Status do + @moduledoc """ + This module contains the Status logic, this is not really its own login state since the client + is only asking a bit of information. + """ + require Logger + alias Amethyst.Minecraft.Read + alias Amethyst.Minecraft.Write + + @spec serve(:gen_tcp.socket()) :: no_return() + def serve(client) do + {id, data} = Amethyst.Server.Generic.get_packet(client) + packet = deserialize(id, data) + Logger.debug("Got packet #{inspect(packet)}") + handle(packet, client) + serve(client) + end + + def transmit(packet, client) do + Logger.debug("Transmitting #{inspect(packet)}") + data = serialize(packet) + length = byte_size(data) |> Write.varint() + :gen_tcp.send(client, length <> data) + end + + ## DESERIALIZATION + # Status Request https://wiki.vg/Protocol#Status_Request + def deserialize(0x00, _) do + {:status_request} + end + # Ping Request https://wiki.vg/Protocol#Ping_Request + def deserialize(0x01, <>) do + {[payload], ""} = Read.start(data) |> Read.long() |> Read.stop() + {:ping_request, payload} + end + def deserialize(type, _) do + raise RuntimeError, "Got unknown packet type #{type}!" + end + + ## SERIALIZATION + # Status Response https://wiki.vg/Protocol#Status_Response + def serialize({:status_response, data}) do + Write.varint(0x00) <> Write.string(data) + end + def serialize({:ping_response, payload}) do + Write.varint(0x01) <> <> + end + def serialize(packet) do + raise ArgumentError, "Tried serializing unknown packet #{inspect(packet)}" + end + + ## HANDLING + # Status Request https://wiki.vg/Protocol#Status_Request + def handle({:status_request}, client) do + # We want to make this more dynamic in the future, but this works for now + packet = {:status_response, ~s({ +"version": {"name": "1.21", "protocol": 767}, +"players": {"max": -1, "online": 69, "sample": [{"name": "§dAmethyst§r", "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20"}]}, +"description": {"text":"Amethyst is an experimental server written in Elixir"}, +"enforcesSecureChat": false, +"previewsChat": false, +"preventsChatReports": true +})} + transmit(packet, client) + end + def handle({:ping_request, payload}, client) do + packet = {:ping_response, payload} + transmit(packet, client) + end + def handle(tuple, _) do + Logger.error("Unhandled but known packet #{elem(tuple, 0)}") + end +end