diff --git a/apps/amethyst/lib/apps/connection_handler.ex b/apps/amethyst/lib/apps/connection_handler.ex index 569e7ce..92f1cfc 100644 --- a/apps/amethyst/lib/apps/connection_handler.ex +++ b/apps/amethyst/lib/apps/connection_handler.ex @@ -105,7 +105,7 @@ defmodule Amethyst.ConnectionHandler do end loop(socket, connstate, version, state) {:send_packet, packet} -> - Logger.debug("Sending packet #{inspect(packet)}") + # Logger.debug("Sending packet #{inspect(packet)}") send_packet(socket, connstate, packet, version, state) loop(socket, connstate, version, state) after 0 -> @@ -258,7 +258,6 @@ defmodule Amethyst.ConnectionHandler do defp send_packet(socket, connstate, packet, version, state) do try do data = connstate.serialize(packet, version) - Logger.debug("#{inspect(Map.get(state, :compression))}") container = if Map.get(state, :compression) == nil do # Packet ID is included in data Write.varint(byte_size(data)) <> data @@ -273,7 +272,6 @@ defmodule Amethyst.ConnectionHandler do Write.varint(byte_size(compressed)) <> compressed end end - Logger.debug(inspect(container)) encrypted = if Map.get(state, :encryption_state) == nil do container else diff --git a/apps/amethyst/lib/sha1.ex b/apps/amethyst/lib/sha1.ex new file mode 100644 index 0000000..6b38c85 --- /dev/null +++ b/apps/amethyst/lib/sha1.ex @@ -0,0 +1,26 @@ +# 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.Minecraft.Sha1 do + @moduledoc """ + This module implements the awkward non-standard hex digest hash that Minecraft uses + """ + def hash(input) do + # this sucks but so does the fact that mojang makes me do this so it fits + <> = :crypto.hash(:sha, input) + inspect(number, base: :hex) |> String.replace("0x", "") |> String.downcase() + end +end diff --git a/apps/amethyst/lib/states/configuration.ex b/apps/amethyst/lib/states/configuration.ex index a8bb380..5c38294 100644 --- a/apps/amethyst/lib/states/configuration.ex +++ b/apps/amethyst/lib/states/configuration.ex @@ -310,6 +310,16 @@ defmodule Amethyst.ConnectionState.Configuration do chunk_z: div(floor(z), 16) }}) send(self(), {:set_position, {x, y, z}}) + send(self(), {:send_packet, %{packet_type: :player_info_update_add_player, + players: [ + %{ + uuid: Map.get(state, :uuid), + name: Map.get(state, :name), + properties: Map.get(state, :properties) |> + Enum.map(fn prop -> %{name: prop["name"], value: prop["value"], signature: Map.get(prop, "signature")} end) + } + ] + }}) # Begin keepalive loop # TODO: Put it under some supervisor me = self() diff --git a/apps/amethyst/lib/states/login.ex b/apps/amethyst/lib/states/login.ex index f5e9c3e..14f005b 100644 --- a/apps/amethyst/lib/states/login.ex +++ b/apps/amethyst/lib/states/login.ex @@ -97,14 +97,41 @@ defmodule Amethyst.ConnectionState.Login do send(self(), {:set_compression, threshold}) end - send(self(), {:send_packet, %{ - packet_type: :login_success, - uuid: Map.get(state, :uuid), - username: Map.get(state, :name), - properties: [], - strict_error_handling: true - }}) - :ok + if Application.get_env(:amethyst, :auth, false) == false do + # Don't check authentication + send(self(), {:send_packet, %{ + packet_type: :login_success, + uuid: Map.get(state, :uuid), + username: Map.get(state, :name), + properties: [], + strict_error_handling: true + }}) + Map.put(state, :authenticated, false) + else + # Check authentication + pubkey = Amethyst.Keys.get_pub() + hash = Amethyst.Minecraft.Sha1.hash(secret <> pubkey) + url = Application.get_env(:amethyst, :session_server, "https://sessionserver.mojang.com") <> "/session/minecraft/hasJoined?username=" <> + Map.get(state, :name) <> "&serverId=" <> hash # I don't think we need to verify the IP in the use case of Amethyst... + + response = Req.get!(url, + headers: [ + {"user-agent", "Amethyst/1.0"} + ]).body + + <> = response["id"] + uuid = [c1, c2, c3, c4, c5] |> Enum.join("-") + + send(self(), {:send_packet, %{ + packet_type: :login_success, + uuid: uuid, + username: response["name"], + properties: response["properties"] |> + Enum.map(fn prop -> %{name: prop["name"], value: prop["value"], signature: Map.get(prop, "signature")} end), + strict_error_handling: true + }}) + Map.put(state, :authenticated, true) |> Map.put(:uuid, uuid) |> Map.put(:name, response["name"]) |> Map.put(:properties, response["properties"]) + end else raise RuntimeError, "Invalid verify token. Broken encryption?" end diff --git a/apps/amethyst/lib/states/macros.ex b/apps/amethyst/lib/states/macros.ex index d52f767..a21ce1f 100644 --- a/apps/amethyst/lib/states/macros.ex +++ b/apps/amethyst/lib/states/macros.ex @@ -95,7 +95,7 @@ defmodule Amethyst.ConnectionState.Macros do Enum.reduce(Map.get(packet, name), "", fn item, acc -> acc <> write_signature(item, signature) end) - {:literal, {type, value}} -> acc <> apply(Write, type, [value]) + {:literal, type, value} -> acc <> apply(Write, type, [value]) t -> acc <> apply(Write, t, [Map.get(packet, name)]) end end) @@ -106,7 +106,7 @@ defmodule Amethyst.ConnectionState.Macros do Enum.all?(signature, fn {name, type} -> case Map.get(packet, name, :missing) do :missing -> - if elem(type, 0) == :literal do + if is_tuple(type) && elem(type, 0) == :literal do true else throw {:missing, name} diff --git a/apps/amethyst/lib/states/play.ex b/apps/amethyst/lib/states/play.ex index 69d70d7..b1730e7 100644 --- a/apps/amethyst/lib/states/play.ex +++ b/apps/amethyst/lib/states/play.ex @@ -71,8 +71,8 @@ defmodule Amethyst.ConnectionState.Play do portal_cooldown: :varint, enforces_secure_chat: :bool, ] - Macros.defpacket_clientbound :player_info_update_add_player, 0x2E, 767, [ - actions: {:literal, 0x01}, + Macros.defpacket_clientbound :player_info_update_add_player, 0x3E, 767, [ + actions: {:literal, :byte, 0x01}, players: {:array, [ uuid: :uuid, name: :string, @@ -83,8 +83,8 @@ defmodule Amethyst.ConnectionState.Play do ]} ]} ] - Macros.defpacket_clientbound :player_info_update_initialize_chat, 0x2E, 767, [ - actions: {:literal, 0x02}, + Macros.defpacket_clientbound :player_info_update_initialize_chat, 0x3E, 767, [ + actions: {:literal, :byte, 0x02}, players: {:array, [ uuid: :uuid, data: {:optional, {:compound, [ @@ -95,29 +95,29 @@ defmodule Amethyst.ConnectionState.Play do ]}} ]} ] - Macros.defpacket_clientbound :player_info_update_update_game_mode, 0x2E, 767, [ - actions: {:literal, 0x04}, + Macros.defpacket_clientbound :player_info_update_update_game_mode, 0x3E, 767, [ + actions: {:literal, :byte, 0x04}, players: {:array, [ uuid: :uuid, gamemode: :varint ]} ] - Macros.defpacket_clientbound :player_info_update_update_listed, 0x2E, 767, [ - actions: {:literal, 0x08}, + Macros.defpacket_clientbound :player_info_update_update_listed, 0x3E, 767, [ + actions: {:literal, :byte, 0x08}, players: {:array, [ uuid: :uuid, listed: :bool ]} ] - Macros.defpacket_clientbound :player_info_update_update_latency, 0x2E, 767, [ - actions: {:literal, 0x10}, + Macros.defpacket_clientbound :player_info_update_update_latency, 0x3E, 767, [ + actions: {:literal, :byte, 0x10}, players: {:array, [ uuid: :uuid, ping: :varint, # Milliseconds ]} ] - Macros.defpacket_clientbound :player_info_update_update_display_name, 0x2E, 767, [ - actions: {:literal, 0x20}, + Macros.defpacket_clientbound :player_info_update_update_display_name, 0x3E, 767, [ + actions: {:literal, :byte, 0x20}, players: {:array, [ uuid: :uuid, display_name: {:optional, :nbt} diff --git a/apps/amethyst/mix.exs b/apps/amethyst/mix.exs index 6f5a605..df15eb4 100644 --- a/apps/amethyst/mix.exs +++ b/apps/amethyst/mix.exs @@ -39,7 +39,8 @@ defmodule Amethyst.MixProject do {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, {:uuid, "~> 1.1"}, {:ex_doc, "~> 0.22", only: :dev, runtime: false}, - {:jason, "~> 1.4"} + {:jason, "~> 1.4"}, + {:req, "~> 0.5.6", runtime: true} ] end end diff --git a/apps/amethyst/test/hash_test.exs b/apps/amethyst/test/hash_test.exs new file mode 100644 index 0000000..3daa09b --- /dev/null +++ b/apps/amethyst/test/hash_test.exs @@ -0,0 +1,10 @@ +defmodule Sha1Test do + alias Amethyst.Minecraft.Sha1 + use ExUnit.Case, async: true + + test "Mojang sha1 hash digest" do + assert Sha1.hash("Notch") == "4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48" + assert Sha1.hash("jeb_") == "-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1" + assert Sha1.hash("simon") == "88e16a1019277b15d58faf0541e11910eb756f6" + end +end diff --git a/config/runtime.exs b/config/runtime.exs index a3c94b6..ba6ab65 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -3,6 +3,7 @@ import Config config :amethyst, port: 25599, # Bogus port for testing, avoids unexpected conflicts encryption: true, # Whether or not to request encryption from clients. - auth: false, # Whether or not users should be authenticated with Mojang. + auth: true, # Whether or not users should be authenticated with Mojang. + session_server: "https://sessionserver.mojang.com", # Base URL to the Mojang session server compression: 256, # Packets larger than this amount are sent compressed. Set to nil to disable compression. default_game: Example.Game # Which game new players should be sent to diff --git a/mix.lock b/mix.lock index 40e969f..f795067 100644 --- a/mix.lock +++ b/mix.lock @@ -5,11 +5,19 @@ "elixir_math": {:hex, :elixir_math, "0.1.2", "5655bdf7f34e30906f31cdcf3031b43dd522ce8d2936b60ad4696b2c752bf5c9", [:mix], [], "hexpm", "34f4e4384903097a8ec566784fa8e9aa2b741247d225741f07cc48250c2aa64c"}, "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"}, "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, + "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, + "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, + "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, + "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "pre_commit": {:hex, :pre_commit, "0.3.4", "e2850f80be8090d50ad8019ef2426039307ff5dfbe70c736ad0d4d401facf304", [:mix], [], "hexpm", "16f684ba4f1fed1cba6b19e082b0f8d696e6f1c679285fedf442296617ba5f4e"}, + "req": {:hex, :req, "0.5.6", "8fe1eead4a085510fe3d51ad854ca8f20a622aae46e97b302f499dfb84f726ac", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cfaa8e720945d46654853de39d368f40362c2641c4b2153c886418914b372185"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"}, } diff --git a/mix.nix b/mix.nix index 41e3c40..7bc9690 100644 --- a/mix.nix +++ b/mix.nix @@ -47,6 +47,19 @@ let beamDeps = []; }; + elixir_math = buildMix rec { + name = "elixir_math"; + version = "0.1.2"; + + src = fetchHex { + pkg = "elixir_math"; + version = "${version}"; + sha256 = "34f4e4384903097a8ec566784fa8e9aa2b741247d225741f07cc48250c2aa64c"; + }; + + beamDeps = []; + }; + ex_doc = buildMix rec { name = "ex_doc"; version = "0.34.2"; @@ -73,6 +86,32 @@ let beamDeps = []; }; + finch = buildMix rec { + name = "finch"; + version = "0.19.0"; + + src = fetchHex { + pkg = "finch"; + version = "${version}"; + sha256 = "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"; + }; + + beamDeps = [ mime mint nimble_options nimble_pool telemetry ]; + }; + + hpax = buildMix rec { + name = "hpax"; + version = "1.0.0"; + + src = fetchHex { + pkg = "hpax"; + version = "${version}"; + sha256 = "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"; + }; + + beamDeps = []; + }; + jason = buildMix rec { name = "jason"; version = "1.4.4"; @@ -125,6 +164,45 @@ let beamDeps = [ makeup ]; }; + mime = buildMix rec { + name = "mime"; + version = "2.0.6"; + + src = fetchHex { + pkg = "mime"; + version = "${version}"; + sha256 = "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"; + }; + + beamDeps = []; + }; + + mint = buildMix rec { + name = "mint"; + version = "1.6.2"; + + src = fetchHex { + pkg = "mint"; + version = "${version}"; + sha256 = "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"; + }; + + beamDeps = [ hpax ]; + }; + + nimble_options = buildMix rec { + name = "nimble_options"; + version = "1.1.1"; + + src = fetchHex { + pkg = "nimble_options"; + version = "${version}"; + sha256 = "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"; + }; + + beamDeps = []; + }; + nimble_parsec = buildMix rec { name = "nimble_parsec"; version = "1.4.0"; @@ -138,6 +216,58 @@ let beamDeps = []; }; + nimble_pool = buildMix rec { + name = "nimble_pool"; + version = "1.1.0"; + + src = fetchHex { + pkg = "nimble_pool"; + version = "${version}"; + sha256 = "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"; + }; + + beamDeps = []; + }; + + pre_commit = buildMix rec { + name = "pre_commit"; + version = "0.3.4"; + + src = fetchHex { + pkg = "pre_commit"; + version = "${version}"; + sha256 = "16f684ba4f1fed1cba6b19e082b0f8d696e6f1c679285fedf442296617ba5f4e"; + }; + + beamDeps = []; + }; + + req = buildMix rec { + name = "req"; + version = "0.5.6"; + + src = fetchHex { + pkg = "req"; + version = "${version}"; + sha256 = "cfaa8e720945d46654853de39d368f40362c2641c4b2153c886418914b372185"; + }; + + beamDeps = [ finch jason mime ]; + }; + + telemetry = buildRebar3 rec { + name = "telemetry"; + version = "1.3.0"; + + src = fetchHex { + pkg = "telemetry"; + version = "${version}"; + sha256 = "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"; + }; + + beamDeps = []; + }; + uuid = buildMix rec { name = "uuid"; version = "1.1.8";