2024-07-08 16:57:49 +02:00
# 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 """
2024-07-09 16:10:00 +02:00
This module contains the logic for the Login stage of the server .
2024-07-08 16:57:49 +02:00
"""
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
2024-07-08 22:45:57 +02:00
def serialize ( { :encryption_request , server_id , pubkey , verify_token , auth } ) do
Write . varint ( 0x01 ) <>
Write . string ( server_id ) <>
Write . varint ( byte_size ( pubkey ) ) <> pubkey <>
Write . varint ( byte_size ( verify_token ) ) <> verify_token <>
Write . bool ( auth )
2024-07-08 16:57:49 +02:00
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 )
2024-07-08 17:59:44 +02:00
end end ) <> Write . bool ( strict )
2024-07-08 16:57:49 +02:00
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
2024-07-08 17:59:44 +02:00
# Login Start https://wiki.vg/Protocol#Login_Start
def handle ( { :login_start , name , uuid } , client ) do
Logger . info ( " Logging in #{ name } ( #{ uuid } ) " )
if Application . fetch_env! ( :amethyst , :encryption ) do
2024-07-09 14:18:48 +02:00
raise RuntimeError , " Encryption is currently unsupported. " # TODO: Implement encryption
# verify_token = :crypto.strong_rand_bytes(4)
# pubkey = Amethyst.Keys.get_pub()
# auth = Application.fetch_env!(:amethyst, :auth)
# transmit({:encryption_request, "amethyst", pubkey, verify_token, auth}, client) # This is broken for some reason? java.lang.IllegalStateException: Protocol Error
2024-07-08 17:59:44 +02:00
else
transmit ( { :login_success , uuid , name , [ ] , false } , client )
end
end
def handle ( { :login_acknowledged } , _client ) do
raise RuntimeError , " Configuration stage is not implemented. "
end
2024-07-08 16:57:49 +02:00
def handle ( tuple , _ ) do
Logger . error ( " Unhandled but known packet #{ elem ( tuple , 0 ) } " )
end
end