Compare commits
2 Commits
1ce9b7c454
...
9f9abf277d
Author | SHA1 | Date | |
---|---|---|---|
9f9abf277d | |||
809a37c644 |
@ -29,7 +29,7 @@ defmodule Amethyst.TCPListener do
|
||||
@spec loop_acceptor(socket :: :gen_tcp.socket()) :: no_return()
|
||||
defp loop_acceptor(socket) do
|
||||
{:ok, client} = :gen_tcp.accept(socket)
|
||||
{:ok, pid} = Task.Supervisor.start_child(Amethyst.ConnectionSupervisor, fn -> Amethyst.Server.Stage1.serve(client) end)
|
||||
{:ok, pid} = Task.Supervisor.start_child(Amethyst.ConnectionSupervisor, fn -> Amethyst.Server.Handshake.serve(client) end)
|
||||
:ok = :gen_tcp.controlling_process(client, pid)
|
||||
Logger.info("Received connection from #{inspect(client)}, assigned to PID #{inspect(pid)}")
|
||||
loop_acceptor(socket)
|
||||
|
13
lib/data.ex
13
lib/data.ex
@ -22,6 +22,10 @@ defmodule Amethyst.Minecraft.Write do
|
||||
than simple binary data.
|
||||
"""
|
||||
|
||||
def uuid(uuid) do
|
||||
UUID.string_to_binary!(uuid)
|
||||
end
|
||||
|
||||
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
|
||||
varnum("", value)
|
||||
@ -115,6 +119,15 @@ defmodule Amethyst.Minecraft.Read do
|
||||
{[value | acc], rest, :reversed}
|
||||
end
|
||||
|
||||
def uuid({acc, <<uuid::16-binary, rest::binary>>, :reversed}) do
|
||||
{[UUID.binary_to_string!(uuid) | acc], rest, :reversed}
|
||||
end
|
||||
|
||||
def raw({acc, data, :reversed}, amount) do
|
||||
<<data::binary-size(amount), rest::binary>> = data
|
||||
{[data | acc], rest, :reversed}
|
||||
end
|
||||
|
||||
def varint(tuple, read \\ 0, nacc \\ 0)
|
||||
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)))
|
||||
|
@ -14,9 +14,9 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
defmodule Amethyst.Server.Stage1 do
|
||||
defmodule Amethyst.Server.Handshake do
|
||||
@moduledoc """
|
||||
This module contains the stage 1 (Handshaking) server logic.
|
||||
This module contains the logic for the Handshake stage of the server.
|
||||
"""
|
||||
require Logger
|
||||
use Amethyst.Server
|
||||
@ -53,6 +53,7 @@ defmodule Amethyst.Server.Stage1 do
|
||||
Logger.info("Got handshake, version #{ver} on #{addr}:#{port}. Wants to move to #{next}")
|
||||
case next do
|
||||
:status -> Amethyst.Server.Status.serve(client)
|
||||
:login -> Amethyst.Server.Login.serve(client)
|
||||
_ -> raise RuntimeError, "Unhandled move to next mode #{next}"
|
||||
end
|
||||
end
|
105
lib/servers/login.ex
Normal file
105
lib/servers/login.ex
Normal file
@ -0,0 +1,105 @@
|
||||
# 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
defmodule Amethyst.Server.Login do
|
||||
@moduledoc """
|
||||
This module contains the logic for the Handshake stage of the server.
|
||||
"""
|
||||
require Logger
|
||||
use Amethyst.Server
|
||||
|
||||
alias Amethyst.Minecraft.Read
|
||||
alias Amethyst.Minecraft.Write
|
||||
|
||||
## DESERIALIZATION
|
||||
# Login Start https://wiki.vg/Protocol#Login_Start
|
||||
def deserialize(0x00, data) do
|
||||
{[name, uuid], ""} = Read.start(data) |> Read.string() |> Read.uuid() |> Read.stop()
|
||||
{:login_start, name, uuid}
|
||||
end
|
||||
# Encryption Response https://wiki.vg/Protocol#Encryption_Response
|
||||
def deserialize(0x01, data) do
|
||||
{[secret_length], rest} = Read.start(data) |> Read.varint() |> Read.stop()
|
||||
{[secret, verify_token_length], rest} = Read.start(rest) |> Read.raw(secret_length) |> Read.varint() |> Read.stop()
|
||||
{[verify_token], ""} = Read.start(rest) |> Read.raw(verify_token_length) |> Read.stop()
|
||||
{:encryption_response, secret, verify_token}
|
||||
end
|
||||
# Login Plugin Response https://wiki.vg/Protocol#Login_Plugin_Response
|
||||
def deserialize(0x02, data) do
|
||||
{[message_id, success], rest} = Read.start(data) |> Read.varint() |> Read.bool() |> Read.stop()
|
||||
if success do
|
||||
{:login_plugin_response, message_id, rest}
|
||||
else
|
||||
{:login_plugin_response, message_id, nil}
|
||||
end
|
||||
end
|
||||
# Login Acknowledged https://wiki.vg/Protocol#Login_Acknowledged
|
||||
def deserialize(0x03, "") do
|
||||
{:login_acknowledged}
|
||||
end
|
||||
# Cookie Response https://wiki.vg/Protocol#Cookie_Response_(login)
|
||||
def deserialize(0x04, 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
|
||||
def deserialize(type, _) do
|
||||
raise RuntimeError, "Got unknown packet type #{type}!"
|
||||
end
|
||||
|
||||
## SERIALIZATION
|
||||
# Disconnect (login) https://wiki.vg/Protocol#Disconnect_(login)
|
||||
def serialize({:disconnect, reason}) do
|
||||
Write.varint(0x00) <> Write.string(reason)
|
||||
end
|
||||
# Encryption Request https://wiki.vg/Protocol#Encryption_Request
|
||||
def serialize({:encryption_request, id, pubkey, verify_token, auth}) do
|
||||
Write.varint(0x01) <> Write.string(id) <> Write.varint(byte_size(pubkey)) <> pubkey <> Write.varint(byte_size(verify_token)) <> verify_token <> <<auth::8>>
|
||||
end
|
||||
# Login Success https://wiki.vg/Protocol#Login_Success
|
||||
def serialize({:login_success, uuid, username, props, strict}) do
|
||||
Write.varint(0x02) <> Write.uuid(uuid) <> Write.string(username) <> Write.varint(length(props)) <>
|
||||
Enum.reduce(props, "", fn {name, value, signature}, acc -> acc <> Write.string(name) <> Write.string(value) <> case signature do
|
||||
nil -> <<0x00>>
|
||||
signature -> <<0x01>> <> Write.string(signature)
|
||||
end end) <> <<strict::8>>
|
||||
end
|
||||
# Set Compression https://wiki.vg/Protocol#Set_Compression
|
||||
def serialize({:set_compression, threshold}) do
|
||||
Write.varint(0x03) <> Write.varint(threshold)
|
||||
end
|
||||
# Login Plugin Request https://wiki.vg/Protocol#Login_Plugin_Request
|
||||
def serialize({:login_plugin_request, id, channel, data}) do
|
||||
Write.varint(0x04) <> Write.varint(id) <> Write.string(channel) <> data
|
||||
end
|
||||
# Cookie Request (login) https://wiki.vg/Protocol#Cookie_Request_(login)
|
||||
def serialize({:cookie_request_login, id}) do
|
||||
Write.varint(0x05) <> Write.string(id)
|
||||
end
|
||||
def serialize(packet) do
|
||||
raise ArgumentError, "Tried serializing unknown packet #{inspect(packet)}"
|
||||
end
|
||||
|
||||
## HANDLING
|
||||
def handle(tuple, _) do
|
||||
Logger.error("Unhandled but known packet #{elem(tuple, 0)}")
|
||||
end
|
||||
end
|
@ -16,8 +16,7 @@
|
||||
|
||||
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.
|
||||
This module contains the logic for the Status stage of the server.
|
||||
"""
|
||||
require Logger
|
||||
use Amethyst.Server
|
||||
|
3
mix.exs
3
mix.exs
@ -22,7 +22,8 @@ defmodule Amethyst.MixProject do
|
||||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:credo, "~> 1.7", only: [:dev, :test], runtime: false}
|
||||
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
|
||||
{:uuid, "~> 1.1"}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
1
mix.lock
1
mix.lock
@ -3,4 +3,5 @@
|
||||
"credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"},
|
||||
"file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
|
||||
"jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"},
|
||||
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"},
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user