steam-condenser 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +25 -0
- data/README.md +30 -0
- data/Rakefile +42 -0
- data/lib/abstract_class.rb +40 -0
- data/lib/byte_buffer.rb +126 -0
- data/lib/datagram_channel.rb +70 -0
- data/lib/exceptions/buffer_underflow_exception.rb +10 -0
- data/lib/exceptions/packet_format_exception.rb +12 -0
- data/lib/exceptions/rcon_no_auth_exception.rb +14 -0
- data/lib/exceptions/steam_condenser_exception.rb +8 -0
- data/lib/exceptions/timeout_exception.rb +10 -0
- data/lib/socket_channel.rb +54 -0
- data/lib/steam-condenser.rb +3 -0
- data/lib/steam/community/dods/dods_class.rb +32 -0
- data/lib/steam/community/dods/dods_stats.rb +47 -0
- data/lib/steam/community/dods/dods_weapon.rb +54 -0
- data/lib/steam/community/game_achievement.rb +26 -0
- data/lib/steam/community/game_class.rb +15 -0
- data/lib/steam/community/game_stats.rb +92 -0
- data/lib/steam/community/game_weapon.rb +29 -0
- data/lib/steam/community/l4d/l4d_explosive.rb +23 -0
- data/lib/steam/community/l4d/l4d_map.rb +34 -0
- data/lib/steam/community/l4d/l4d_stats.rb +166 -0
- data/lib/steam/community/l4d/l4d_weapon.rb +23 -0
- data/lib/steam/community/steam_group.rb +16 -0
- data/lib/steam/community/steam_id.rb +212 -0
- data/lib/steam/community/tf2/tf2_class.rb +31 -0
- data/lib/steam/community/tf2/tf2_class_factory.rb +38 -0
- data/lib/steam/community/tf2/tf2_engineer.rb +25 -0
- data/lib/steam/community/tf2/tf2_medic.rb +22 -0
- data/lib/steam/community/tf2/tf2_sniper.rb +20 -0
- data/lib/steam/community/tf2/tf2_spy.rb +23 -0
- data/lib/steam/community/tf2/tf2_stats.rb +39 -0
- data/lib/steam/packets/a2a_ack_packet.rb +21 -0
- data/lib/steam/packets/a2a_ping_packet.rb +18 -0
- data/lib/steam/packets/a2m_get_servers_batch2_packet.rb +25 -0
- data/lib/steam/packets/a2s_info_packet.rb +18 -0
- data/lib/steam/packets/a2s_player_packet.rb +22 -0
- data/lib/steam/packets/a2s_rules_packet.rb +21 -0
- data/lib/steam/packets/a2s_serverquery_getchallenge_packet.rb +18 -0
- data/lib/steam/packets/m2a_server_batch_packet.rb +37 -0
- data/lib/steam/packets/rcon/rcon_auth_request.rb +16 -0
- data/lib/steam/packets/rcon/rcon_auth_response.rb +16 -0
- data/lib/steam/packets/rcon/rcon_exec_request.rb +16 -0
- data/lib/steam/packets/rcon/rcon_exec_response.rb +20 -0
- data/lib/steam/packets/rcon/rcon_goldsrc_request.rb +20 -0
- data/lib/steam/packets/rcon/rcon_goldsrc_response.rb +20 -0
- data/lib/steam/packets/rcon/rcon_packet.rb +36 -0
- data/lib/steam/packets/rcon/rcon_packet_factory.rb +37 -0
- data/lib/steam/packets/request_with_challenge.rb +16 -0
- data/lib/steam/packets/s2a_info2_packet.rb +48 -0
- data/lib/steam/packets/s2a_info_base_packet.rb +29 -0
- data/lib/steam/packets/s2a_info_detailed_packet.rb +50 -0
- data/lib/steam/packets/s2a_player_packet.rb +32 -0
- data/lib/steam/packets/s2a_rules_packet.rb +33 -0
- data/lib/steam/packets/s2c_challenge_packet.rb +23 -0
- data/lib/steam/packets/steam_packet.rb +48 -0
- data/lib/steam/packets/steam_packet_factory.rb +90 -0
- data/lib/steam/servers/game_server.rb +222 -0
- data/lib/steam/servers/goldsrc_server.rb +36 -0
- data/lib/steam/servers/master_server.rb +60 -0
- data/lib/steam/servers/source_server.rb +65 -0
- data/lib/steam/sockets/goldsrc_socket.rb +108 -0
- data/lib/steam/sockets/master_server_socket.rb +20 -0
- data/lib/steam/sockets/rcon_socket.rb +50 -0
- data/lib/steam/sockets/source_socket.rb +72 -0
- data/lib/steam/sockets/steam_socket.rb +63 -0
- data/lib/steam/steam_player.rb +51 -0
- data/test/query_tests.rb +71 -0
- data/test/rcon_tests.rb +79 -0
- data/test/steam_community_tests.rb +36 -0
- metadata +132 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
|
4
|
+
require "abstract_class"
|
5
|
+
require "steam/packets/steam_packet"
|
6
|
+
|
7
|
+
# The S2A_INFO_BasePacket class represents the response to a A2S_INFO
|
8
|
+
# request send to the server.
|
9
|
+
class S2A_INFO_BasePacket < SteamPacket
|
10
|
+
|
11
|
+
include AbstractClass
|
12
|
+
|
13
|
+
# Creates a S2A_INFO[...] response object based on the data received.
|
14
|
+
def initialize(header, data)
|
15
|
+
super header, data
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the hash containing information on the server
|
19
|
+
public
|
20
|
+
def get_info_hash
|
21
|
+
return Hash[
|
22
|
+
*instance_variables.map { |var|
|
23
|
+
if var != "@content_data" && var != "@header_data"
|
24
|
+
[var[1..-1], instance_variable_get(var)]
|
25
|
+
end
|
26
|
+
}.compact.flatten
|
27
|
+
]
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2008-2009, Sebastian Staudt
|
5
|
+
|
6
|
+
require "steam/packets/s2a_info_base_packet"
|
7
|
+
|
8
|
+
# The S2A_INFO_DETAILED_Packet class represents the response to a A2S_INFO
|
9
|
+
# request send to a GoldSrc server.
|
10
|
+
# This is deprecated by 10/24/2008 for GoldSrc servers. They use the same
|
11
|
+
# format as Source servers (S2A_INFO2) now.
|
12
|
+
class S2A_INFO_DETAILED_Packet < S2A_INFO_BasePacket
|
13
|
+
|
14
|
+
# Creates a S2A_INFO_DETAILED response object based on the data received.
|
15
|
+
def initialize(data)
|
16
|
+
super SteamPacket::S2A_INFO_DETAILED_HEADER, data
|
17
|
+
|
18
|
+
@game_ip = @content_data.get_string
|
19
|
+
@server_name = @content_data.get_string
|
20
|
+
@map_name = @content_data.get_string
|
21
|
+
@game_directory = @content_data.get_string
|
22
|
+
@game_description = @content_data.get_string
|
23
|
+
@number_of_players = @content_data.get_byte
|
24
|
+
@max_players = @content_data.get_byte
|
25
|
+
@network_version = @content_data.get_byte
|
26
|
+
@dedicated = @content_data.get_byte.chr
|
27
|
+
@operating_system = @content_data.get_byte.chr
|
28
|
+
@password_needed = @content_data.get_byte == 1
|
29
|
+
@is_mod = @content_data.get_byte == 1
|
30
|
+
|
31
|
+
if @is_mod
|
32
|
+
@mod_info = Hash.new
|
33
|
+
@mod_info["url_info"] = @content_data.get_string
|
34
|
+
@mod_info["url_dl"] = @content_data.get_string
|
35
|
+
@content_data.get_byte
|
36
|
+
if @content_data.remaining == 12
|
37
|
+
@mod_info["mod_version"] = @content_data.get_long
|
38
|
+
@mod_info["mod_size"] = @content_data.get_long
|
39
|
+
@mod_info["sv_only"] = @content_data.get_byte == 1
|
40
|
+
@mod_info["cl_dll"] = @content_data.get_byte == 1
|
41
|
+
@secure = @content_data.get_byte == 1
|
42
|
+
@number_of_bots = @content_data.get_byte
|
43
|
+
end
|
44
|
+
else
|
45
|
+
@secure = @content_data.get_byte == 1
|
46
|
+
@number_of_bots = @content_data.get_byte
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2008-2009, Sebastian Staudt
|
5
|
+
|
6
|
+
require "steam/packets/steam_packet"
|
7
|
+
|
8
|
+
# The S2A_PLAYER_Packet class represents the response to a A2S_PLAYER
|
9
|
+
# request send to the server.
|
10
|
+
class S2A_PLAYER_Packet < SteamPacket
|
11
|
+
|
12
|
+
attr_reader :player_hash
|
13
|
+
|
14
|
+
# Creates a S2A_PLAYER response object based on the data received.
|
15
|
+
def initialize(content_data)
|
16
|
+
if content_data == nil
|
17
|
+
raise Exception.new("Wrong formatted S2A_PLAYER packet.")
|
18
|
+
end
|
19
|
+
|
20
|
+
super SteamPacket::S2A_PLAYER_HEADER, content_data
|
21
|
+
|
22
|
+
# Ignore player count
|
23
|
+
@content_data.get_byte
|
24
|
+
@player_hash = {}
|
25
|
+
|
26
|
+
while @content_data.remaining > 0
|
27
|
+
player_data = @content_data.get_byte, @content_data.get_string, @content_data.get_signed_long, @content_data.get_float
|
28
|
+
@player_hash[player_data[1]] = SteamPlayer.new(*player_data[0..3])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2008, Sebastian Staudt
|
5
|
+
#
|
6
|
+
# $Id$
|
7
|
+
|
8
|
+
require "steam/packets/steam_packet"
|
9
|
+
|
10
|
+
# The S2A_RULES_Packet class represents the response to a A2S_RULES
|
11
|
+
# request send to the server.
|
12
|
+
class S2A_RULES_Packet < SteamPacket
|
13
|
+
|
14
|
+
# Creates a S2A_RULES response object based on the data received.
|
15
|
+
def initialize(content_data)
|
16
|
+
if content_data == nil
|
17
|
+
raise Exception.new("Wrong formatted A2A_RULES response packet.")
|
18
|
+
end
|
19
|
+
|
20
|
+
super SteamPacket::S2A_RULES_HEADER, content_data
|
21
|
+
|
22
|
+
@rules_hash = Hash.new @content_data.get_short
|
23
|
+
|
24
|
+
while @content_data.remaining > 0
|
25
|
+
@rules_hash[@content_data.get_string] = @content_data.get_string
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the hash containing the server rules
|
30
|
+
def get_rules_hash
|
31
|
+
return @rules_hash
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2008, Sebastian Staudt
|
5
|
+
#
|
6
|
+
# $Id$
|
7
|
+
|
8
|
+
require "steam/packets/steam_packet"
|
9
|
+
|
10
|
+
# The S2C_CHALLENGE_Packet class represents the response
|
11
|
+
# to a A2S_SERVERQUERY_GETCHALLENGE request send to the server.
|
12
|
+
class S2C_CHALLENGE_Packet < SteamPacket
|
13
|
+
|
14
|
+
# Creates a AS2C_CHALLENGE response object based on the data received.
|
15
|
+
def initialize(challenge_number)
|
16
|
+
super S2C_CHALLENGE_HEADER, challenge_number
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the challenge number received from the server
|
20
|
+
def get_challenge_number
|
21
|
+
return @content_data.rewind.get_long
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2008, Sebastian Staudt
|
5
|
+
#
|
6
|
+
# $Id$
|
7
|
+
|
8
|
+
# This class represents a packet used by the Source query protocol
|
9
|
+
class SteamPacket
|
10
|
+
S2A_INFO_DETAILED_HEADER = 0x6D
|
11
|
+
A2S_INFO_HEADER = 0x54
|
12
|
+
S2A_INFO2_HEADER = 0x49
|
13
|
+
A2A_PING_HEADER = 0x69
|
14
|
+
A2A_ACK_HEADER = 0x6A
|
15
|
+
A2S_PLAYER_HEADER = 0x55
|
16
|
+
S2A_PLAYER_HEADER = 0x44
|
17
|
+
A2S_RULES_HEADER = 0x56
|
18
|
+
S2A_RULES_HEADER = 0x45
|
19
|
+
A2S_SERVERQUERY_GETCHALLENGE_HEADER = 0x57
|
20
|
+
S2C_CHALLENGE_HEADER = 0x41
|
21
|
+
A2M_GET_SERVERS_BATCH2_HEADER = 0x31
|
22
|
+
M2A_SERVER_BATCH_HEADER = 0x66
|
23
|
+
S2C_CONNREJECT_HEADER = 0x39
|
24
|
+
RCON_GOLDSRC_CHALLENGE_HEADER = 0x63
|
25
|
+
RCON_GOLDSRC_NO_CHALLENGE_HEADER = 0x39
|
26
|
+
RCON_GOLDSRC_RESPONSE_HEADER = 0x6c
|
27
|
+
|
28
|
+
# Creates a new SteamPacket object with given header and content data
|
29
|
+
def initialize(header_data, content_data = "")
|
30
|
+
@content_data = ByteBuffer.new content_data
|
31
|
+
@header_data = header_data
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns a packed string representing the packet's data
|
35
|
+
#
|
36
|
+
# TODO Has to automatically split packets greater than 1400 bytes
|
37
|
+
def to_s
|
38
|
+
packet_data = [0xFF, 0xFF, 0xFF].pack("ccc")
|
39
|
+
|
40
|
+
unless @split_packet
|
41
|
+
packet_data << [0xFF].pack("c")
|
42
|
+
else
|
43
|
+
packet_data << [0xFE].pack("c")
|
44
|
+
end
|
45
|
+
|
46
|
+
return packet_data << [@header_data, @content_data.array].pack("ca*")
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2008-2009, Sebastian Staudt
|
5
|
+
|
6
|
+
require 'zlib'
|
7
|
+
|
8
|
+
require "abstract_class"
|
9
|
+
require "steam/packets/s2a_info_detailed_packet"
|
10
|
+
require "steam/packets/a2s_info_packet"
|
11
|
+
require "steam/packets/s2a_info2_packet"
|
12
|
+
require "steam/packets/a2a_ping_packet"
|
13
|
+
require "steam/packets/a2a_ack_packet"
|
14
|
+
require "steam/packets/a2s_player_packet"
|
15
|
+
require "steam/packets/s2a_player_packet"
|
16
|
+
require "steam/packets/a2s_rules_packet"
|
17
|
+
require "steam/packets/s2a_rules_packet"
|
18
|
+
require "steam/packets/a2s_serverquery_getchallenge_packet"
|
19
|
+
require "steam/packets/s2c_challenge_packet"
|
20
|
+
require "steam/packets/a2m_get_servers_batch2_packet"
|
21
|
+
require "steam/packets/m2a_server_batch_packet"
|
22
|
+
require "steam/packets/rcon/rcon_goldsrc_response"
|
23
|
+
|
24
|
+
class SteamPacketFactory
|
25
|
+
|
26
|
+
include AbstractClass
|
27
|
+
|
28
|
+
# Creates a new packet object based on the header byte of the given raw data
|
29
|
+
def self.get_packet_from_data(raw_data)
|
30
|
+
header = raw_data[0].to_i;
|
31
|
+
data = raw_data[1..-1];
|
32
|
+
|
33
|
+
case header
|
34
|
+
when SteamPacket::S2A_INFO_DETAILED_HEADER
|
35
|
+
return S2A_INFO_DETAILED_Packet.new(data)
|
36
|
+
when SteamPacket::A2S_INFO_HEADER
|
37
|
+
return A2S_INFO_Packet.new;
|
38
|
+
when SteamPacket::S2A_INFO2_HEADER
|
39
|
+
return S2A_INFO2_Packet.new(data)
|
40
|
+
when SteamPacket::A2A_PING_HEADER
|
41
|
+
return A2A_PING_Packet.new
|
42
|
+
when SteamPacket::A2A_ACK_HEADER
|
43
|
+
return A2A_ACK_Packet.new(data)
|
44
|
+
when SteamPacket::A2S_PLAYER_HEADER
|
45
|
+
return A2S_PLAYER_Packet.new
|
46
|
+
when SteamPacket::S2A_PLAYER_HEADER
|
47
|
+
return S2A_PLAYER_Packet.new(data)
|
48
|
+
when SteamPacket::A2S_RULES_HEADER
|
49
|
+
return A2S_RULES_Packet
|
50
|
+
when SteamPacket::S2A_RULES_HEADER
|
51
|
+
return S2A_RULES_Packet.new(data)
|
52
|
+
when SteamPacket::A2S_SERVERQUERY_GETCHALLENGE_HEADER
|
53
|
+
return A2S_SERVERQUERY_GETCHALLENGE_Packet.new
|
54
|
+
when SteamPacket::S2C_CHALLENGE_HEADER
|
55
|
+
return S2C_CHALLENGE_Packet.new(data)
|
56
|
+
when SteamPacket::A2M_GET_SERVERS_BATCH2_HEADER
|
57
|
+
return A2M_GET_SERVERS_BATCH2.new(data)
|
58
|
+
when SteamPacket::M2A_SERVER_BATCH_HEADER
|
59
|
+
return M2A_SERVER_BATCH_Packet.new(data)
|
60
|
+
when SteamPacket::RCON_GOLDSRC_CHALLENGE_HEADER,
|
61
|
+
SteamPacket::RCON_GOLDSRC_NO_CHALLENGE_HEADER,
|
62
|
+
SteamPacket::RCON_GOLDSRC_RESPONSE_HEADER
|
63
|
+
return RCONGoldSrcResponse.new(data)
|
64
|
+
else
|
65
|
+
raise Exception.new("Unknown packet with header 0x#{header.to_s 16} received.")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.reassemble_packet(split_packets, is_compressed = false, packet_checksum = 0)
|
70
|
+
packet_data = split_packets.join ""
|
71
|
+
|
72
|
+
if is_compressed
|
73
|
+
begin
|
74
|
+
require 'bz2'
|
75
|
+
packet_data = BZ2.uncompress(packet_data)
|
76
|
+
rescue LoadError
|
77
|
+
raise SteamCondenserException.new("You need to install the libbzip2 interface for Ruby.")
|
78
|
+
end
|
79
|
+
|
80
|
+
if Zlib.crc32(packet_data) != packet_checksum
|
81
|
+
raise PacketFormatException.new("CRC32 checksum mismatch of uncompressed packet data.")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
packet_data = packet_data[4..-1]
|
86
|
+
|
87
|
+
return self.get_packet_from_data(packet_data)
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2008-2009, Sebastian Staudt
|
5
|
+
|
6
|
+
require "abstract_class"
|
7
|
+
require "exceptions/steam_condenser_exception"
|
8
|
+
require "steam/steam_player"
|
9
|
+
require "steam/packets/a2s_info_packet"
|
10
|
+
require "steam/packets/a2a_ping_packet"
|
11
|
+
require "steam/packets/a2s_player_packet"
|
12
|
+
require "steam/packets/a2s_rules_packet"
|
13
|
+
require "steam/packets/a2s_serverquery_getchallenge_packet"
|
14
|
+
require "steam/packets/s2a_info_base_packet"
|
15
|
+
require "steam/packets/s2a_player_packet"
|
16
|
+
require "steam/packets/s2a_rules_packet"
|
17
|
+
require "steam/packets/s2c_challenge_packet"
|
18
|
+
|
19
|
+
class GameServer
|
20
|
+
|
21
|
+
include AbstractClass
|
22
|
+
|
23
|
+
REQUEST_CHALLENGE = 0
|
24
|
+
REQUEST_INFO = 1
|
25
|
+
REQUEST_PLAYER = 2
|
26
|
+
REQUEST_RULES = 3
|
27
|
+
|
28
|
+
# Returns the last measured response time of this server
|
29
|
+
#
|
30
|
+
# If there's no data, update_ping is called to measure the current response
|
31
|
+
# time of the server.
|
32
|
+
#
|
33
|
+
# Whenever you want to get a new value for the ping time call update_ping.
|
34
|
+
def get_ping
|
35
|
+
if @ping == nil
|
36
|
+
self.update_ping
|
37
|
+
end
|
38
|
+
return @ping
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns an array of the player's currently playing on this server.
|
42
|
+
#
|
43
|
+
# If there's no data, update_player_info is called to get the current list of
|
44
|
+
# players.
|
45
|
+
#
|
46
|
+
# As the players and their scores change quite often be sure to update this
|
47
|
+
# list regularly by calling update_player_info.
|
48
|
+
def get_players(rcon_password = nil)
|
49
|
+
if @player_hash == nil
|
50
|
+
self.update_player_info(rcon_password)
|
51
|
+
end
|
52
|
+
|
53
|
+
return @player_hash
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns a hash of the settings applied on the server. These settings are
|
57
|
+
# also called rules.
|
58
|
+
# The hash has the format of +rule_name+ => +rule_value+
|
59
|
+
#
|
60
|
+
# If there's no data, update_rules_info is called to get the current list of
|
61
|
+
# rules.
|
62
|
+
#
|
63
|
+
# As the rules usually don't change often, there's almost no need to update
|
64
|
+
# this hash. But if you need to, you can achieve this by calling
|
65
|
+
# update_rules_info.
|
66
|
+
def get_rules
|
67
|
+
if @rules_hash == nil
|
68
|
+
self.update_rules_info
|
69
|
+
end
|
70
|
+
|
71
|
+
return @rules_hash
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns a hash with basic information on the server.
|
75
|
+
#
|
76
|
+
# If there's no data, update_server_info is called to get up-to-date
|
77
|
+
# information.
|
78
|
+
#
|
79
|
+
# The server information usually only changes on map change and when players
|
80
|
+
# join or leave. As the latter changes can be monitored by calling
|
81
|
+
# update_player_info, there's no need to call update_server_info very often.
|
82
|
+
def get_server_info
|
83
|
+
if @info_hash == nil
|
84
|
+
self.update_server_info
|
85
|
+
end
|
86
|
+
|
87
|
+
return @info_hash
|
88
|
+
end
|
89
|
+
|
90
|
+
def handle_response_for_request(request_type, repeat_on_failure = true)
|
91
|
+
begin
|
92
|
+
case request_type
|
93
|
+
when GameServer::REQUEST_CHALLENGE then
|
94
|
+
request_packet = A2S_SERVERQUERY_GETCHALLENGE_Packet.new
|
95
|
+
expected_response = S2C_CHALLENGE_Packet
|
96
|
+
when GameServer::REQUEST_INFO then
|
97
|
+
request_packet = A2S_INFO_Packet.new
|
98
|
+
expected_response = S2A_INFO_BasePacket
|
99
|
+
when GameServer::REQUEST_PLAYER then
|
100
|
+
request_packet = A2S_PLAYER_Packet.new(@challenge_number)
|
101
|
+
expected_response = S2A_PLAYER_Packet
|
102
|
+
when GameServer::REQUEST_RULES then
|
103
|
+
request_packet = A2S_RULES_Packet.new(@challenge_number)
|
104
|
+
expected_response = S2A_RULES_Packet
|
105
|
+
else
|
106
|
+
raise SteamCondenserException.new("Called with wrong request type.")
|
107
|
+
end
|
108
|
+
|
109
|
+
self.send_request request_packet
|
110
|
+
response_packet = self.get_reply
|
111
|
+
|
112
|
+
if response_packet.kind_of? S2A_INFO_BasePacket
|
113
|
+
@info_hash = response_packet.get_info_hash
|
114
|
+
elsif response_packet.kind_of? S2A_PLAYER_Packet
|
115
|
+
@player_hash = response_packet.player_hash
|
116
|
+
elsif response_packet.kind_of? S2A_RULES_Packet
|
117
|
+
@rules_hash = response_packet.get_rules_hash
|
118
|
+
elsif response_packet.kind_of? S2C_CHALLENGE_Packet
|
119
|
+
@challenge_number = response_packet.get_challenge_number
|
120
|
+
else
|
121
|
+
raise SteamCondenserException.new("Response of type #{response_packet.class} cannot be handled by this method.")
|
122
|
+
end
|
123
|
+
|
124
|
+
unless response_packet.kind_of? expected_response
|
125
|
+
warn "Expected #{expected_response}, got #{response_packet.class}."
|
126
|
+
self.handle_response_for_request(request_type, false) if repeat_on_failure
|
127
|
+
end
|
128
|
+
rescue TimeoutException
|
129
|
+
warn "Expected #{expected_response}, but timed out."
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def init
|
134
|
+
self.update_ping
|
135
|
+
self.update_server_info
|
136
|
+
self.update_challenge_number
|
137
|
+
end
|
138
|
+
|
139
|
+
def update_player_info(rcon_password = nil)
|
140
|
+
self.handle_response_for_request GameServer::REQUEST_PLAYER
|
141
|
+
|
142
|
+
unless rcon_password.nil? or @player_hash.empty?
|
143
|
+
rcon_auth(rcon_password)
|
144
|
+
players = rcon_exec('status').split("\n")[7..-1]
|
145
|
+
if is_a? GoldSrcServer
|
146
|
+
players.pop
|
147
|
+
end
|
148
|
+
|
149
|
+
players.each do |player|
|
150
|
+
player_data = split_player_status(player)
|
151
|
+
@player_hash[player_data[1]].add_info(*player_data)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def update_rules_info
|
157
|
+
self.handle_response_for_request GameServer::REQUEST_RULES
|
158
|
+
end
|
159
|
+
|
160
|
+
def update_server_info
|
161
|
+
self.handle_response_for_request GameServer::REQUEST_INFO
|
162
|
+
end
|
163
|
+
|
164
|
+
def update_challenge_number
|
165
|
+
self.handle_response_for_request GameServer::REQUEST_CHALLENGE
|
166
|
+
end
|
167
|
+
|
168
|
+
def update_ping
|
169
|
+
self.send_request A2A_PING_Packet.new
|
170
|
+
start_time = Time.now
|
171
|
+
self.get_reply
|
172
|
+
end_time = Time.now
|
173
|
+
return @ping = (end_time - start_time) * 1000
|
174
|
+
end
|
175
|
+
|
176
|
+
# Checks whether the listening port number of the server is in a valid range
|
177
|
+
def initialize(port_number = 27015)
|
178
|
+
unless port_number.to_i > 0 and port_number.to_i < 65536
|
179
|
+
raise ArgumentError.new("The listening port of the server has to be a number greater than 0 and less than 65536.")
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def to_s
|
184
|
+
return_string = ""
|
185
|
+
|
186
|
+
return_string << "Ping: #{@ping}\n"
|
187
|
+
return_string << "Challenge number: #{@challenge_number}\n"
|
188
|
+
|
189
|
+
if @info_hash != nil
|
190
|
+
return_string << "Info:\n"
|
191
|
+
@info_hash.each do |key, value|
|
192
|
+
return_string << " #{key}: #{value.inspect}\n"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
if @player_hash != nil
|
197
|
+
return_string << "Players:\n"
|
198
|
+
@player_hash.each do |player|
|
199
|
+
return_string << " #{player}\n"
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
if @rules_hash != nil
|
204
|
+
return_string << "Rules:\n"
|
205
|
+
@rules_hash.each do |key, value|
|
206
|
+
return_string << " #{key}: #{value}\n"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
return return_string
|
211
|
+
end
|
212
|
+
|
213
|
+
protected
|
214
|
+
|
215
|
+
def get_reply
|
216
|
+
@socket.get_reply
|
217
|
+
end
|
218
|
+
|
219
|
+
def send_request packet
|
220
|
+
@socket.send packet
|
221
|
+
end
|
222
|
+
end
|