* Init ExDoc

This commit is contained in:
Anthony Mineo 2021-10-07 16:06:22 -04:00
parent 3b22db761b
commit 6ab3e06e78
8 changed files with 277 additions and 172 deletions

View file

@ -1,11 +1,58 @@
defmodule T2ServerQuery.PacketParser do
@moduledoc """
Documentation for `T2ServerQuery.PacketParser`.
This module does the heavy lifting with parsing a Tribes 2 query response packet.
## UDP Packet Anatomy
### Info Packet
<<
_header :: size(192),
server_name :: bitstring
>>
### Status Packet
<<
_header :: size(48),
game_type_length :: little-integer,
game_type :: binary-size(game_type_length),
mission_type_length :: little-integer,
mission_type :: binary-size(mission_type_length),
map_name_length :: little-integer,
map_name :: binary-size(map_name_length),
_skip_a :: size(8),
player_count :: little-integer,
max_player_count :: little-integer,
bot_count :: little-integer,
_skip_b :: size(16),
server_description_length :: little-integer,
server_description :: binary-size(server_description_length),
_skip_c :: size(16),
team_count :: binary-size(1),
rest :: bitstring
>>
Notice the `_skip_(a|b|c)` mappings. I havn't quite figured out what they refer to yet but they don't seem that important. They likely relate to a few server flags like `tournament_mode`, `cpu_speed`, `is_linux`.
Refer to `T2ServerQuery.QueryResult` for what a typical struct would look like.
"""
alias T2ServerQuery.QueryResult
@doc """
This function expects both an `info` and `status` packet to be passed in that is in a `Base.encode16` format.
Normally you wouldn't need to run this function manually since it's called in a pipeline from the main `T2ServerQuery.query`
"""
def init({:error, host}, _) do
results = %QueryResult{}

View file

@ -1,51 +1,51 @@
defmodule T2ServerQuery.QueryResult do
@moduledoc """
Shape of the server query result struct.
## Struct Shape
%T2ServerQuery.QueryResult{
server_status: :online,
bot_count: 30,
game_type: "Classic",
map_name: "Cold as Ice [b]",
max_player_count: 64,
mission_type: "Capture the Flag",
player_count: 29,
players: [
%{player: "Rooster128", score: "0", team: "Storm"},
%{player: "sneakygnome", score: "0", team: "Inferno"},
%{player: "Waldred ", score: "0", team: "Inferno"},
%{player: "HDPTetchy ", score: "0", team: "Storm"},
%{player: "0wnj0o", score: "0", team: "Inferno"},
%{player: "idjit ", score: "0", team: "Storm"},
%{player: "JesusChrist ", score: "0", team: "Storm"},
%{player: "Sofaking--bakeD ", score: "0", team: "Inferno"},
%{player: "saKe ", score: "0", team: "Inferno"},
%{player: "ZurkinWood497", score: "0", team: "Storm"},
%{player: "TerryTC ", score: "0", team: "Inferno"},
%{player: "WankBullet ", score: "0", team: "Storm"},
%{player: "CyClones", score: "0", team: "Inferno"},
%{player: "huntergirl10", score: "0", team: "Storm"},
%{player: "ChocoTaco", score: "0", team: "Inferno"},
%{player: "Dirk", score: "0", team: "Storm"},
%{player: "Krell", score: "0", team: "Storm"},
%{player: "high5slayer", score: "0", team: "Inferno"},
%{player: "Red Fraction ", score: "0", team: "Inferno"},
%{player: "-MaLice--", score: "0", team: "Storm"},
%{player: "wiltedflower ", score: "0", team: "Inferno"},
%{player: "Glarm ", score: "0", team: "Storm"},
%{player: "AlphaSentinel", score: "0", team: "Inferno"},
%{player: "The-Punisher ", score: "0", team: "Storm"},
%{player: "2SmOkeD", score: "0", team: "Inferno"},
%{player: "iPrecision", score: "0", team: "Storm"},
%{player: "Halo 2 ", score: "0", team: "Storm"},
%{player: "Sami-FIN ", score: "0", team: "Inferno"},
%{player: "rileygarbels", score: "0", team: "Storm"}
],
server_description: "This server is using bots that are adapted to playing Classic. http://tribes2bots.byethost4.com/forum/index.php?topic=57.msg234",
server_name: "Classic Bots Server",
team_count: 2,
teams: [%{name: "Storm", score: "0"}, %{name: "Inferno", score: "0"}]
}
%T2ServerQuery.QueryResult{
server_status: :online,
bot_count: 30,
game_type: "Classic",
map_name: "Cold as Ice [b]",
max_player_count: 64,
mission_type: "Capture the Flag",
player_count: 29,
players: [
%{player: "Rooster128", score: "0", team: "Storm"},
%{player: "sneakygnome", score: "0", team: "Inferno"},
%{player: "Waldred ", score: "0", team: "Inferno"},
%{player: "HDPTetchy ", score: "0", team: "Storm"},
%{player: "0wnj0o", score: "0", team: "Inferno"},
%{player: "idjit ", score: "0", team: "Storm"},
%{player: "JesusChrist ", score: "0", team: "Storm"},
%{player: "Sofaking--bakeD ", score: "0", team: "Inferno"},
%{player: "saKe ", score: "0", team: "Inferno"},
%{player: "ZurkinWood497", score: "0", team: "Storm"},
%{player: "TerryTC ", score: "0", team: "Inferno"},
%{player: "WankBullet ", score: "0", team: "Storm"},
%{player: "CyClones", score: "0", team: "Inferno"},
%{player: "huntergirl10", score: "0", team: "Storm"},
%{player: "ChocoTaco", score: "0", team: "Inferno"},
%{player: "Dirk", score: "0", team: "Storm"},
%{player: "Krell", score: "0", team: "Storm"},
%{player: "high5slayer", score: "0", team: "Inferno"},
%{player: "Red Fraction ", score: "0", team: "Inferno"},
%{player: "-MaLice--", score: "0", team: "Storm"},
%{player: "wiltedflower ", score: "0", team: "Inferno"},
%{player: "Glarm ", score: "0", team: "Storm"},
%{player: "AlphaSentinel", score: "0", team: "Inferno"},
%{player: "The-Punisher ", score: "0", team: "Storm"},
%{player: "2SmOkeD", score: "0", team: "Inferno"},
%{player: "iPrecision", score: "0", team: "Storm"},
%{player: "Halo 2 ", score: "0", team: "Storm"},
%{player: "Sami-FIN ", score: "0", team: "Inferno"},
%{player: "rileygarbels", score: "0", team: "Storm"}
],
server_description: "This server is using bots that are adapted to playing Classic. http://tribes2bots.byethost4.com/forum/index.php?topic=57.msg234",
server_name: "Classic Bots Server",
team_count: 2,
teams: [%{name: "Storm", score: "0"}, %{name: "Inferno", score: "0"}]
}
"""
defstruct [

View file

@ -1,94 +0,0 @@
defmodule T2ServerQuery.UdpServer do
@moduledoc """
Documentation for `UdpServer`.
"""
require Logger
alias T2ServerQuery.PacketParser
@doc """
Perform a server query.
Results should be in the form of a tuple
- {:ok, %T2ServerQuery.QueryResult{}}
- {:error, %T2ServerQuery.QueryResult{}}
Querying a Tribes 2 server actually requires sending 2 different packets to the server where the first byte is denoting what we're asking for in response. The first is called the 'info' packet which doesnt contain much more then the server name. The second is called the 'status' packet which contains all the meat and potatoes.
## Examples
iex> T2ServerQuery.UdpServer.query("35.239.88.241")
{:ok,
%T2ServerQuery.QueryResult{
bot_count: 0,
game_type: "Classic",
map_name: "Canker",
max_player_count: 64,
mission_type: "LakRabbit",
player_count: 0,
players: [%{}],
server_description: "Celebrating 20 Years of Tribes2! More information in Discord. <a:playt2.com/discord>playt2.com/discord</a>",
server_name: "Discord PUB",
server_status: :online,
team_count: 1,
teams: [%{name: "Storm", score: 0}]
}}
iex> T2ServerQuery.UdpServer.query("127.0.0.1")
{:error,
%T2ServerQuery.QueryResult{
bot_count: 0,
game_type: "",
map_name: "",
max_player_count: 0,
mission_type: "",
player_count: 0,
players: [],
server_description: "Host unreachable, timed out.",
server_name: "127.0.0.1:28000",
server_status: :offline,
team_count: 0,
teams: []
}}
"""
def query(server_ip, port \\ 28_000, timeout \\ 3_500) do
Logger.info "query: #{server_ip}"
{:ok, socket} = :gen_udp.open(0, [:binary, {:active, false}])
# Convert a string ip from "127.0.0.1" into {127, 0, 0, 1}
{:ok, s_ip} = server_ip
|> to_charlist()
|> :inet.parse_address()
qry_info_packet = <<14, 2, 1, 2, 3, 4>>
qry_status_packet = <<18, 2, 1, 2, 3, 4>>
# Requst info packet
:gen_udp.send(socket, s_ip, port, qry_info_packet)
hex_info_packet = :gen_udp.recv(socket, 0, timeout)
|> handle_udp_response(server_ip, port)
# Request status packet
:gen_udp.send(socket, s_ip, port, qry_status_packet)
hex_status_packet = :gen_udp.recv(socket, 0, timeout)
|> handle_udp_response(server_ip, port)
# Combine and parse results
PacketParser.init(hex_info_packet, hex_status_packet)
end
defp handle_udp_response({:ok, {_ip, _port, packet}}, _server_ip, _port) do
packet
|> Base.encode16
end
defp handle_udp_response({:error, :timeout}, server_ip, port) do
Logger.error "TIMEOUT --> #{server_ip}:#{port}"
{:error, "#{server_ip}:#{port}"}
end
end