diff --git a/apps/amethyst/lib/apps/connection_handler.ex b/apps/amethyst/lib/apps/connection_handler.ex index 0c921e7..ee562f7 100644 --- a/apps/amethyst/lib/apps/connection_handler.ex +++ b/apps/amethyst/lib/apps/connection_handler.ex @@ -87,7 +87,7 @@ alias ElixirSense.Log if is_map(newstate) do newstate else - Logger.warning("State change to #{newstate} is not a map! Did you forget to return :ok?") + Logger.warning("State change to #{inspect(newstate)} is not a map! Did you forget to return :ok?") state end end @@ -100,9 +100,15 @@ alias ElixirSense.Log end defp send_packet(socket, connstate, packet, version) do - data = connstate.serialize(packet, version) - length = byte_size(data) |> Amethyst.Minecraft.Write.varint() - :gen_tcp.send(socket, length <> data) + try do + data = connstate.serialize(packet, version) + length = byte_size(data) |> Amethyst.Minecraft.Write.varint() + :gen_tcp.send(socket, length <> data) + rescue + e -> + Logger.error("Error sending packet #{inspect(packet)} in state #{connstate}: #{Exception.format(:error, e, __STACKTRACE__)}") + send(self(), {:disconnect, "§cError sending packet #{inspect(packet)}:\n#{Exception.format(:error, e, __STACKTRACE__)}"}) + end end defp disconnect(socket, reason, connstate, version) do diff --git a/apps/amethyst/lib/nbt.ex b/apps/amethyst/lib/nbt.ex index f3f75fe..a2ba779 100644 --- a/apps/amethyst/lib/nbt.ex +++ b/apps/amethyst/lib/nbt.ex @@ -27,6 +27,20 @@ defmodule Amethyst.NBT.Write do <> end + def check_type({:byte, value}) when is_integer(value) and value in -128..127, do: true + def check_type({:short, value}) when is_integer(value) and value in -32_768..32_767, do: true + def check_type({:int, value}) when is_integer(value) and value in -2_147_483_648..2_147_483_647, do: true + def check_type({:long, value}) when is_integer(value) and value in -9_223_372_036_854_775_808..9_223_372_036_854_775_807, do: true + def check_type({:float, value}) when is_float(value), do: true + def check_type({:double, value}) when is_float(value), do: true + def check_type({:byte_array, values}) when is_list(values), do: Enum.all?(values, &is_integer/1) + def check_type({:string, value}) when is_binary(value), do: true + def check_type({:list, {type, values}}) when is_list(values), do: Enum.all?(values, &check_type({type, &1})) + def check_type({:compound, values}) when is_map(values), do: Enum.all?(values, fn {name, {type, value}} -> check_type({type, value}) end) + def check_type({:int_array, values}) when is_list(values), do: Enum.all?(values, &is_integer/1) + def check_type({:long_array, values}) when is_list(values), do: Enum.all?(values, &is_integer/1) + def check_type(_), do: false + defp type_id(:end), do: 0 defp type_id(:byte), do: 1 defp type_id(:short), do: 2 diff --git a/apps/amethyst/lib/states/configuration.ex b/apps/amethyst/lib/states/configuration.ex index 50e43b1..32a2dc9 100644 --- a/apps/amethyst/lib/states/configuration.ex +++ b/apps/amethyst/lib/states/configuration.ex @@ -61,7 +61,7 @@ defmodule Amethyst.ConnectionState.Configuration do ] Macros.defpacket_clientbound :clientbound_known_packs, 0x0E, 767, [ packs: {:array, [ - nameshape: :string, + namespace: :string, id: :string, version: :string ]} @@ -269,8 +269,9 @@ defmodule Amethyst.ConnectionState.Configuration do send(self(), {:set_state, Amethyst.ConnectionState.Play}) game = Application.fetch_env!(:amethyst, :default_game) |> Amethyst.GameCoordinator.find_or_create() state = state |> Map.put(:game, game) - if Amethyst.Api.Game.login(game, state) == :reject do + if Amethyst.API.Game.login(game, state) == :reject do send(self(), {:disconnect, "Default game rejected connection"}) + :ok else send(self(), {:send_packet, %{ packet_type: :login, @@ -294,6 +295,7 @@ defmodule Amethyst.ConnectionState.Configuration do portal_cooldown: 0, enforces_secure_chat: false }}) + state end end diff --git a/apps/amethyst/lib/states/macros.ex b/apps/amethyst/lib/states/macros.ex index 5f97301..0cd5722 100644 --- a/apps/amethyst/lib/states/macros.ex +++ b/apps/amethyst/lib/states/macros.ex @@ -15,6 +15,7 @@ # along with this program. If not, see . defmodule Amethyst.ConnectionState.Macros do + require Logger defmacro defpacket_serverbound(name, id, version, signature) do quote do def deserialize(unquote(id), unquote(version), data) do @@ -27,7 +28,11 @@ defmodule Amethyst.ConnectionState.Macros do defmacro defpacket_clientbound(name, id, version, signature) do quote do def serialize(%{packet_type: unquote(name)} = packet, unquote(version)) do - Amethyst.Minecraft.Write.varint(unquote(id)) <> Amethyst.ConnectionState.Macros.write_signature(packet, unquote(signature)) + if Amethyst.ConnectionState.Macros.check_type(packet, unquote(signature)) do + Amethyst.Minecraft.Write.varint(unquote(id)) <> Amethyst.ConnectionState.Macros.write_signature(packet, unquote(signature)) + else + raise "Invalid packet type for #{unquote(name)}! Got #{inspect(packet)}" + end end end end @@ -56,11 +61,15 @@ defmodule Amethyst.ConnectionState.Macros do end {:array, signature} -> {[count], rest} = Read.start(rest) |> Read.varint() |> Read.stop() - {items, rest} = Enum.reduce(1..count, {[], rest}, fn _, {acc, rest} -> - {item, rest} = read_signature(rest, signature) - {[item | acc], rest} - end) - {[Enum.reverse(items) | acc], rest, :reversed} + if count == 0 do + {[[] | acc], rest, :reversed} + else + {items, rest} = Enum.reduce(1..count, {[], rest}, fn _, {acc, rest} -> + {item, rest} = read_signature(rest, signature) + {[item | acc], rest} + end) + {[Enum.reverse(items) | acc], rest, :reversed} + end t -> apply(Read, t, [{acc, rest, :reversed}]) end end) |> Read.stop() @@ -91,4 +100,56 @@ defmodule Amethyst.ConnectionState.Macros do end end) end + + def check_type(packet, signature) do + try do + Enum.all?(signature, fn {name, type} -> + case Map.get(packet, name, :missing) do + :missing -> throw {:missing, name} + value -> case type_matches(value, type) do + true -> true + false -> throw {:mismatch, name, value, type} + end + end + end) + catch + reason -> + Logger.debug("Found invalid packet type: #{inspect(reason)}") + false + end + end + + def type_matches(value, :bool) when is_boolean(value), do: true + def type_matches(value, :byte) when is_integer(value) and value in -128..127, do: true + def type_matches(value, :ubyte) when is_integer(value) and value in 0..255, do: true + def type_matches(value, :short) when is_integer(value) and value in -32768..32767, do: true + def type_matches(value, :ushort) when is_integer(value) and value in 0..65535, do: true + def type_matches(value, :int) when is_integer(value) and value in -2147483648..2147483647, do: true + def type_matches(value, :long) when is_integer(value) and value in -9223372036854775808..9223372036854775807, do: true + def type_matches(value, :float) when is_float(value), do: true + def type_matches(value, :double) when is_float(value), do: true + def type_matches(value, :varint) when is_integer(value) and value in -2147483648..2147483647, do: true + def type_matches(value, :varlong) when is_integer(value) and value in -9223372036854775808..9223372036854775807, do: true + def type_matches(value, :uuid) when is_binary(value) and byte_size(value) == 36, do: true + def type_matches(value, :string) when is_binary(value), do: true + def type_matches(value, :raw) when is_binary(value), do: true + def type_matches(value, :byte_array) when is_binary(value), do: true + def type_matches({x, y, z}, :position) when + is_integer(x) and x in -33554432..33554431 and + is_integer(y) and y in -2048..2047 and + is_integer(z) and z in -33554432..33554431, do: true + def type_matches(value, :nbt), do: Amethyst.NBT.Write.check_type(value) + def type_matches(value, :json) do + case Jason.encode(value) do + {:ok, _} -> true + _ -> false + end + end + def type_matches(value, {:optional, type}) when is_nil(value), do: true + def type_matches(value, {:optional, type}), do: type_matches(value, type) + def type_matches(value, {:array, signature}) when is_list(value), do: Enum.all?(value, fn item -> check_type(item, signature) end) + def type_matches(value, {:compound, signature}) when is_map(value), do: check_type(value, signature) + def type_matches(_, _) do + false + end end