Current section
Files
Jump to
Current section
Files
lib/mcping.ex
defmodule MCPing do
require Jason
use Bitwise
@mc_protocol_version 340
@moduledoc """
A utility library to ping other Minecraft: Java Edition servers.
"""
defp pack_varint(num) do
pack_varint(num, <<>>)
end
defp pack_varint(num, buf) do
b = num &&& 0x7f
d = num >>> 7
cond do
d > 0 ->
pack_varint(d, buf <> <<bor(b, 0x80)>>)
d == 0 ->
buf <> <<b>>
end
end
defp pack_port(port) do
<<port :: size(16)>>
end
defp pack_data(str) do
pack_varint(byte_size(str)) <> str
end
defp construct_handshake_packet(address, port) do
pack_varint(0)
<> pack_varint(@mc_protocol_version)
<> pack_data(address)
<> pack_port(port)
<> pack_varint(1)
end
defp unpack_varint(conn, timeout) do
unpack_varint(conn, 0, 0, timeout)
end
defp unpack_varint(conn, d, n, timeout) do
read = :gen_tcp.recv(conn, 1, timeout)
case read do
{:ok, read} ->
b = :binary.at(read, 0)
a = bor(d, (b &&& 0x7f) <<< (7*n))
cond do
(b &&& 0x80) == 0 ->
{:ok, a}
n > 4 ->
raise("supicious varint size (tried to read more than 5 bytes)")
true ->
unpack_varint(conn, a, n+1, timeout)
end
error ->
error
end
end
@doc """
Pings a remote Minecraft: Java Edition server.
## Examples
iex> MCPing.get_info("mc.hypixel.net")
{:ok, ...}
"""
def get_info(address, port \\ 25565, timeout \\ 3000) do
# gen_tcp uses Erlang strings (charlists), convert this beforehand
address_chars = to_charlist(address)
result = :gen_tcp.connect(address_chars, port, [:binary, active: false], timeout)
case result do
{:ok, conn} ->
try do
handshake = construct_handshake_packet(address, port) |> pack_data
:gen_tcp.send(conn, handshake)
:gen_tcp.send(conn, pack_data(<<0>>))
_ = unpack_varint(conn, timeout) # Ignore the returned packet size
_ = unpack_varint(conn, timeout) # Ignore the packet ID
{_, json_size} = unpack_varint(conn, timeout)
{:ok, json_packet} = :gen_tcp.recv(conn, json_size, timeout)
# Convert from iolist to binary, and then to a map
decoded = :erlang.iolist_to_binary(json_packet) |> Jason.decode!
{:ok, decoded}
after
:gen_tcp.close(conn)
end
error -> error
end
end
end