106 lines
4.3 KiB
Elixir
106 lines
4.3 KiB
Elixir
|
# 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
|