steam-condenser 0.14.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -0
- data/Gemfile.lock +22 -0
- data/LICENSE +1 -1
- data/README.md +14 -6
- data/Rakefile +35 -0
- data/lib/{stringio_additions.rb → core_ext/stringio.rb} +1 -1
- data/lib/{exceptions/packet_format_exception.rb → errors/packet_format_error.rb} +5 -5
- data/lib/{exceptions/rcon_ban_exception.rb → errors/rcon_ban_error.rb} +5 -5
- data/lib/{exceptions/rcon_no_auth_exception.rb → errors/rcon_no_auth_error.rb} +5 -5
- data/lib/{exceptions/steam_condenser_exception.rb → errors/steam_condenser_error.rb} +3 -3
- data/lib/errors/timeout_error.rb +28 -0
- data/lib/{exceptions/web_api_exception.rb → errors/web_api_error.rb} +8 -8
- data/lib/steam/community/alien_swarm/alien_swarm_mission.rb +86 -11
- data/lib/steam/community/alien_swarm/alien_swarm_stats.rb +38 -15
- data/lib/steam/community/alien_swarm/alien_swarm_weapon.rb +29 -8
- data/lib/steam/community/app_news.rb +91 -27
- data/lib/steam/community/cacheable.rb +65 -21
- data/lib/steam/community/css/css_map.rb +39 -9
- data/lib/steam/community/css/css_stats.rb +32 -12
- data/lib/steam/community/css/css_weapon.rb +46 -10
- data/lib/steam/community/defense_grid/defense_grid_stats.rb +129 -17
- data/lib/steam/community/dods/dods_class.rb +66 -10
- data/lib/steam/community/dods/dods_stats.rb +20 -7
- data/lib/steam/community/dods/dods_weapon.rb +35 -24
- data/lib/steam/community/game_achievement.rb +50 -19
- data/lib/steam/community/game_class.rb +16 -5
- data/lib/steam/community/game_inventory.rb +37 -4
- data/lib/steam/community/game_item.rb +64 -3
- data/lib/steam/community/game_stats.rb +81 -16
- data/lib/steam/community/game_weapon.rb +29 -11
- data/lib/steam/community/l4d/abstract_l4d_stats.rb +91 -65
- data/lib/steam/community/l4d/abstract_l4d_weapon.rb +38 -8
- data/lib/steam/community/l4d/l4d2_map.rb +30 -5
- data/lib/steam/community/l4d/l4d2_stats.rb +83 -45
- data/lib/steam/community/l4d/l4d2_weapon.rb +20 -6
- data/lib/steam/community/l4d/l4d_explosive.rb +13 -5
- data/lib/steam/community/l4d/l4d_map.rb +35 -7
- data/lib/steam/community/l4d/l4d_stats.rb +23 -10
- data/lib/steam/community/l4d/l4d_weapon.rb +11 -7
- data/lib/steam/community/portal2/portal2_inventory.rb +4 -0
- data/lib/steam/community/portal2/portal2_item.rb +13 -1
- data/lib/steam/community/portal2/portal2_stats.rb +10 -6
- data/lib/steam/community/steam_game.rb +74 -0
- data/lib/steam/community/steam_group.rb +48 -14
- data/lib/steam/community/steam_id.rb +295 -64
- data/lib/steam/community/tf2/tf2_class.rb +66 -7
- data/lib/steam/community/tf2/tf2_class_factory.rb +14 -7
- data/lib/steam/community/tf2/tf2_engineer.rb +26 -6
- data/lib/steam/community/tf2/tf2_golden_wrench.rb +37 -10
- data/lib/steam/community/tf2/tf2_inventory.rb +4 -0
- data/lib/steam/community/tf2/tf2_item.rb +13 -1
- data/lib/steam/community/tf2/tf2_medic.rb +20 -5
- data/lib/steam/community/tf2/tf2_sniper.rb +15 -5
- data/lib/steam/community/tf2/tf2_spy.rb +20 -6
- data/lib/steam/community/tf2/tf2_stats.rb +20 -6
- data/lib/steam/community/web_api.rb +50 -32
- data/lib/steam/packets/c2m_checkmd5_packet.rb +1 -1
- data/lib/steam/packets/m2a_server_batch_packet.rb +3 -2
- data/lib/steam/packets/m2s_requestrestart_packet.rb +3 -3
- data/lib/steam/packets/rcon/rcon_packet_factory.rb +4 -4
- data/lib/steam/packets/request_with_challenge.rb +1 -1
- data/lib/steam/packets/s2a_info_base_packet.rb +1 -1
- data/lib/steam/packets/s2a_info_detailed_packet.rb +10 -10
- data/lib/steam/packets/s2a_player_packet.rb +5 -1
- data/lib/steam/packets/s2a_rules_packet.rb +4 -3
- data/lib/steam/packets/s2m_heartbeat2_packet.rb +2 -2
- data/lib/steam/packets/steam_packet.rb +1 -1
- data/lib/steam/packets/steam_packet_factory.rb +12 -16
- data/lib/steam/servers/game_server.rb +38 -32
- data/lib/steam/servers/goldsrc_server.rb +10 -1
- data/lib/steam/servers/master_server.rb +42 -21
- data/lib/steam/servers/server.rb +4 -5
- data/lib/steam/servers/source_server.rb +20 -5
- data/lib/steam/sockets/goldsrc_socket.rb +53 -22
- data/lib/steam/sockets/master_server_socket.rb +14 -4
- data/lib/steam/sockets/rcon_socket.rb +39 -6
- data/lib/steam/sockets/source_socket.rb +19 -15
- data/lib/steam/sockets/steam_socket.rb +33 -23
- data/lib/steam/steam_player.rb +86 -11
- data/lib/steam-condenser/community.rb +24 -24
- data/lib/steam-condenser/servers.rb +2 -2
- data/lib/steam-condenser/version.rb +3 -3
- data/steam-condenser.gemspec +23 -0
- data/test/query_tests.rb +12 -12
- data/test/rcon_tests.rb +3 -3
- data/test/steam/communtiy/steam_community_test_suite.rb +12 -0
- data/test/steam/communtiy/steam_group_tests.rb +6 -5
- data/test/steam/communtiy/steam_id_tests.rb +6 -5
- data/test/steam_community_tests.rb +3 -3
- data/test/stringio_additions_tests.rb +7 -7
- metadata +61 -43
- data/lib/exceptions/timeout_exception.rb +0 -24
- data/test/datagram_channel_tests.rb +0 -42
- data/test/socket_channel_tests.rb +0 -43
@@ -4,6 +4,7 @@
|
|
4
4
|
# Copyright (c) 2008-2011, Sebastian Staudt
|
5
5
|
|
6
6
|
require 'steam/servers/game_server'
|
7
|
+
require 'steam/servers/master_server'
|
7
8
|
require 'steam/sockets/goldsrc_socket'
|
8
9
|
|
9
10
|
# This class represents a GoldSrc game server and can be used to query
|
@@ -19,13 +20,21 @@ class GoldSrcServer
|
|
19
20
|
|
20
21
|
include GameServer
|
21
22
|
|
23
|
+
# Returns a master server instance for the default master server for GoldSrc
|
24
|
+
# games
|
25
|
+
#
|
26
|
+
# @return [MasterServer] The GoldSrc master server
|
27
|
+
def self.master
|
28
|
+
MasterServer.new *MasterServer::GOLDSRC_MASTER_SERVER
|
29
|
+
end
|
30
|
+
|
22
31
|
# Creates a new instance of a GoldSrc server object
|
23
32
|
#
|
24
33
|
# @param [String] address Either an IP address, a DNS name or one of them
|
25
34
|
# combined with the port number. If a port number is given, e.g.
|
26
35
|
# 'server.example.com:27016' it will override the second argument.
|
27
36
|
# @param [Fixnum] port The port the server is listening on
|
28
|
-
# @raise [
|
37
|
+
# @raise [SteamCondenserError] if an host name cannot be resolved
|
29
38
|
# @param [Boolean] is_hltv HLTV servers need special treatment, so this is
|
30
39
|
# used to determine if the server is a HLTV server
|
31
40
|
def initialize(address, port = 27015, is_hltv = false)
|
@@ -3,6 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# Copyright (c) 2008-2011, Sebastian Staudt
|
5
5
|
|
6
|
+
require 'errors/timeout_error'
|
6
7
|
require 'steam/packets/a2m_get_servers_batch2_packet'
|
7
8
|
require 'steam/packets/c2m_checkmd5_packet'
|
8
9
|
require 'steam/packets/s2m_heartbeat2_packet'
|
@@ -21,6 +22,9 @@ class MasterServer
|
|
21
22
|
|
22
23
|
include Server
|
23
24
|
|
25
|
+
# The default number of allowed retries
|
26
|
+
@@retries = 3
|
27
|
+
|
24
28
|
# The master server address to query for GoldSrc game servers
|
25
29
|
GOLDSRC_MASTER_SERVER = 'hl1master.steampowered.com', 27010
|
26
30
|
|
@@ -54,13 +58,21 @@ class MasterServer
|
|
54
58
|
# The region code for the whole world
|
55
59
|
REGION_ALL = 0xFF
|
56
60
|
|
61
|
+
# Sets the number of consecutive requests that may fail, before getting
|
62
|
+
# the server list is cancelled (default: 3)
|
63
|
+
#
|
64
|
+
# @param [Fixnum] The number of allowed retries
|
65
|
+
def self.retries=(retries)
|
66
|
+
@@retries = retries
|
67
|
+
end
|
68
|
+
|
57
69
|
# Request a challenge number from the master server.
|
58
70
|
#
|
59
71
|
# This is used for further communication with the master server.
|
60
72
|
#
|
61
73
|
# @note Please note that this is **not** needed for finding servers using
|
62
74
|
# {#servers}.
|
63
|
-
# @return The challenge number from the master server
|
75
|
+
# @return [Fixnum] The challenge number from the master server
|
64
76
|
# @see #send_heartbeat
|
65
77
|
def challenge
|
66
78
|
failsafe do
|
@@ -80,8 +92,8 @@ class MasterServer
|
|
80
92
|
#
|
81
93
|
# Filtering:
|
82
94
|
# Instead of filtering the results sent by the master server locally, you
|
83
|
-
# should at least use the following filters to narrow down the results sent
|
84
|
-
# the master server.
|
95
|
+
# should at least use the following filters to narrow down the results sent
|
96
|
+
# by the master server.
|
85
97
|
#
|
86
98
|
# Available filters:
|
87
99
|
#
|
@@ -99,33 +111,42 @@ class MasterServer
|
|
99
111
|
# @param [Fixnum] region_code The region code to specify a location of the
|
100
112
|
# game servers
|
101
113
|
# @param [String] filters The filters that game servers should match
|
114
|
+
# @param [Boolean] force Return a list of servers even if an error occured
|
115
|
+
# while fetching them from the master server
|
116
|
+
# @raise [SteamCondenser::TimeoutError] if too many timeouts occur while
|
117
|
+
# querying the master server
|
102
118
|
# @return [Array<Array<String>>] A list of game servers matching the given
|
103
119
|
# region and filters
|
104
120
|
# @see A2M_GET_SERVERS_BATCH2_Packet
|
105
|
-
|
121
|
+
# @see MasterServer.retries=
|
122
|
+
def servers(region_code = MasterServer::REGION_ALL, filters = '', force = false)
|
106
123
|
fail_count = 0
|
107
124
|
finished = false
|
108
125
|
current_server = '0.0.0.0:0'
|
109
126
|
server_array = []
|
110
127
|
|
111
|
-
|
112
|
-
|
113
|
-
@socket.send A2M_GET_SERVERS_BATCH2_Packet.new(region_code, current_server, filters)
|
128
|
+
begin
|
129
|
+
failsafe do
|
114
130
|
begin
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
131
|
+
@socket.send A2M_GET_SERVERS_BATCH2_Packet.new(region_code, current_server, filters)
|
132
|
+
begin
|
133
|
+
servers = @socket.reply.servers
|
134
|
+
servers.each do |server|
|
135
|
+
if server == '0.0.0.0:0'
|
136
|
+
finished = true
|
137
|
+
else
|
138
|
+
current_server = server
|
139
|
+
server_array << server.split(':')
|
140
|
+
end
|
122
141
|
end
|
142
|
+
fail_count = 0
|
143
|
+
rescue SteamCondenser::TimeoutError
|
144
|
+
raise $! if (fail_count += 1) == @@retries
|
123
145
|
end
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
end while !finished
|
146
|
+
end while !finished
|
147
|
+
end
|
148
|
+
rescue SteamCondenser::TimeoutError
|
149
|
+
raise $! unless force
|
129
150
|
end
|
130
151
|
|
131
152
|
server_array
|
@@ -136,7 +157,7 @@ class MasterServer
|
|
136
157
|
# This can be used to check server versions externally.
|
137
158
|
#
|
138
159
|
# @param [Hash<Symbol, Object>] The data to send with the heartbeat request
|
139
|
-
# @raise [
|
160
|
+
# @raise [SteamCondenserError] if heartbeat data is missing the
|
140
161
|
# challenge number or the reply cannot be parsed
|
141
162
|
# @return [Array<SteamPacket>] Zero or more reply packets from the server.
|
142
163
|
# Zero means either the heartbeat was accepted by the master or there
|
@@ -150,7 +171,7 @@ class MasterServer
|
|
150
171
|
reply_packets = []
|
151
172
|
begin
|
152
173
|
loop { reply_packets << @socket.reply }
|
153
|
-
rescue
|
174
|
+
rescue SteamCondenser::TimeoutError
|
154
175
|
end
|
155
176
|
end
|
156
177
|
|
data/lib/steam/servers/server.rb
CHANGED
@@ -30,7 +30,7 @@ module Server
|
|
30
30
|
# 'server.example.com:27016' it will override the second argument.
|
31
31
|
# @param [Fixnum] port The port the server is listening on
|
32
32
|
# @see init_socket
|
33
|
-
# @raise [
|
33
|
+
# @raise [SteamCondenserError] if an host name cannot be resolved
|
34
34
|
def initialize(address, port = nil)
|
35
35
|
address = address.to_s
|
36
36
|
address, port = address.split(':', 2) if address.include? ':'
|
@@ -79,10 +79,9 @@ module Server
|
|
79
79
|
|
80
80
|
# Execute an action in the context of this server's current IP address
|
81
81
|
#
|
82
|
-
# Any failure
|
83
|
-
#
|
84
|
-
#
|
85
|
-
# be reraised.
|
82
|
+
# Any failure will cause the IP to rotate to the next IP in the server's IP
|
83
|
+
# list and the execution will be repeated for the next IP address. If the IP
|
84
|
+
# rotation reaches the end of the list, the error will be reraised.
|
86
85
|
#
|
87
86
|
# @param [Proc] proc The action to be executed in a failsafe way
|
88
87
|
# @see #rotate_ip
|
@@ -3,12 +3,13 @@
|
|
3
3
|
#
|
4
4
|
# Copyright (c) 2008-2011, Sebastian Staudt
|
5
5
|
|
6
|
-
require '
|
6
|
+
require 'errors/rcon_no_auth_error'
|
7
7
|
require 'steam/packets/rcon/rcon_auth_request'
|
8
8
|
require 'steam/packets/rcon/rcon_auth_response'
|
9
9
|
require 'steam/packets/rcon/rcon_exec_request'
|
10
10
|
require 'steam/packets/rcon/rcon_terminator'
|
11
11
|
require 'steam/servers/game_server'
|
12
|
+
require 'steam/servers/master_server'
|
12
13
|
require 'steam/sockets/rcon_socket'
|
13
14
|
require 'steam/sockets/source_socket'
|
14
15
|
|
@@ -25,6 +26,14 @@ class SourceServer
|
|
25
26
|
|
26
27
|
include GameServer
|
27
28
|
|
29
|
+
# Returns a master server instance for the default master server for Source
|
30
|
+
# games
|
31
|
+
#
|
32
|
+
# @return [MasterServer] The Source master server
|
33
|
+
def self.master
|
34
|
+
MasterServer.new *MasterServer::GOLDSRC_MASTER_SERVER
|
35
|
+
end
|
36
|
+
|
28
37
|
# Creates a new instance of a server object representing a Source server,
|
29
38
|
# i.e. SrcDS instance
|
30
39
|
#
|
@@ -32,7 +41,7 @@ class SourceServer
|
|
32
41
|
# combined with the port number. If a port number is given, e.g.
|
33
42
|
# 'server.example.com:27016' it will override the second argument.
|
34
43
|
# @param [Fixnum] port The port the server is listening on
|
35
|
-
# @raise [
|
44
|
+
# @raise [SteamCondenserError] if an host name cannot be resolved
|
36
45
|
def initialize(address, port = 27015)
|
37
46
|
super
|
38
47
|
end
|
@@ -50,6 +59,7 @@ class SourceServer
|
|
50
59
|
#
|
51
60
|
# @param [String] password The RCON password of the server
|
52
61
|
# @return [Boolean] whether authentication was successful
|
62
|
+
# @see #rcon_authenticated?
|
53
63
|
# @see #rcon_exec
|
54
64
|
def rcon_auth(password)
|
55
65
|
@rcon_request_id = rand 2**16
|
@@ -58,9 +68,9 @@ class SourceServer
|
|
58
68
|
@rcon_socket.reply
|
59
69
|
reply = @rcon_socket.reply
|
60
70
|
|
61
|
-
raise
|
71
|
+
raise RCONNoAuthError if reply.request_id == -1
|
62
72
|
|
63
|
-
reply.request_id == @rcon_request_id
|
73
|
+
@rcon_authenticated = reply.request_id == @rcon_request_id
|
64
74
|
end
|
65
75
|
|
66
76
|
# Remotely executes a command on the server via RCON
|
@@ -69,13 +79,18 @@ class SourceServer
|
|
69
79
|
# @return [String] The output of the executed command
|
70
80
|
# @see #rcon_auth
|
71
81
|
def rcon_exec(command)
|
82
|
+
raise RCONNoAuthError unless @rcon_authenticated
|
83
|
+
|
72
84
|
@rcon_socket.send RCONExecRequest.new(@rcon_request_id, command)
|
73
85
|
@rcon_socket.send RCONTerminator.new(@rcon_request_id)
|
74
86
|
|
75
87
|
response = ''
|
76
88
|
begin
|
77
89
|
response_packet = @rcon_socket.reply
|
78
|
-
|
90
|
+
if response_packet.is_a? RCONAuthResponse
|
91
|
+
@rcon_authenticated = false
|
92
|
+
raise RCONNoAuthError
|
93
|
+
end
|
79
94
|
response << response_packet.response
|
80
95
|
end while response.length == 0 || response_packet.response.size > 0
|
81
96
|
|
@@ -1,52 +1,65 @@
|
|
1
|
-
# This code is free software; you can redistribute it and/or modify it under
|
2
|
-
# terms of the new BSD License.
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
3
|
#
|
4
4
|
# Copyright (c) 2008-2011, Sebastian Staudt
|
5
5
|
|
6
|
-
require '
|
7
|
-
require '
|
8
|
-
require '
|
6
|
+
require 'core_ext/stringio'
|
7
|
+
require 'errors/rcon_ban_error'
|
8
|
+
require 'errors/rcon_no_auth_error'
|
9
|
+
require 'errors/timeout_error'
|
9
10
|
require 'steam/packets/steam_packet_factory'
|
10
11
|
require 'steam/packets/rcon/rcon_goldsrc_request'
|
11
12
|
require 'steam/sockets/steam_socket'
|
12
13
|
|
13
|
-
#
|
14
|
-
#
|
14
|
+
# This class represents a socket used to communicate with game servers based on
|
15
|
+
# the GoldSrc engine (e.g. Half-Life, Counter-Strike)
|
16
|
+
#
|
17
|
+
# @author Sebastian Staudt
|
15
18
|
class GoldSrcSocket
|
16
19
|
|
17
20
|
include SteamSocket
|
18
21
|
|
22
|
+
# Creates a new socket to communicate with the server on the given IP address
|
23
|
+
# and port
|
24
|
+
#
|
25
|
+
# @param [String] ipaddress Either the IP address or the DNS name of the
|
26
|
+
# server
|
27
|
+
# @param [Fixnum] port_number The port the server is listening on
|
28
|
+
# @param [Boolean] is_hltv `true` if the target server is a HTLV instance.
|
29
|
+
# HLTV behaves slightly different for RCON commands, this flag
|
30
|
+
# increases compatibility.
|
19
31
|
def initialize(ipaddress, port_number = 27015, is_hltv = false)
|
20
32
|
super ipaddress, port_number
|
33
|
+
|
21
34
|
@is_hltv = is_hltv
|
22
35
|
end
|
23
36
|
|
24
|
-
# Reads a packet from the
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
37
|
+
# Reads a packet from the socket
|
38
|
+
#
|
39
|
+
# The Source query protocol specifies a maximum packet size of 1,400 bytes.
|
40
|
+
# Bigger packets will be split over several UDP packets. This method
|
41
|
+
# reassembles split packets into single packet objects.
|
42
|
+
#
|
43
|
+
# @return [SteamPacket] The packet replied from the server
|
28
44
|
def reply
|
29
|
-
|
45
|
+
receive_packet 1400
|
30
46
|
|
31
47
|
if @buffer.long == 0xFFFFFFFE
|
32
48
|
split_packets = []
|
33
49
|
begin
|
34
|
-
# Parsing of split packet headers
|
35
50
|
request_id = @buffer.long
|
36
51
|
packet_number_and_count = @buffer.byte
|
37
52
|
packet_count = packet_number_and_count & 0xF
|
38
53
|
packet_number = (packet_number_and_count >> 4) + 1
|
39
54
|
|
40
|
-
# Caching of split packet data
|
41
55
|
split_packets[packet_number - 1] = @buffer.get
|
42
56
|
|
43
57
|
puts "Received packet #{packet_number} of #{packet_count} for request ##{request_id}" if $DEBUG
|
44
58
|
|
45
|
-
# Receiving the next packet
|
46
59
|
if split_packets.size < packet_count
|
47
60
|
begin
|
48
61
|
bytes_read = receive_packet
|
49
|
-
rescue
|
62
|
+
rescue SteamCondenser::TimeoutError
|
50
63
|
bytes_read = 0
|
51
64
|
end
|
52
65
|
else
|
@@ -64,15 +77,25 @@ class GoldSrcSocket
|
|
64
77
|
packet
|
65
78
|
end
|
66
79
|
|
80
|
+
# Executes the given command on the server via RCON
|
81
|
+
#
|
82
|
+
# @param [String] password The password to authenticate with the server
|
83
|
+
# @param [String] command The command to execute on the server
|
84
|
+
# @raise [RCONBanError] if the IP of the local machine has been banned on the
|
85
|
+
# game server
|
86
|
+
# @raise [RCONNoAuthError] if the password is incorrect
|
87
|
+
# @return [RCONGoldSrcResponse] The response replied by the server
|
88
|
+
# @see #rcon_challenge
|
89
|
+
# @see #rcon_send
|
67
90
|
def rcon_exec(password, command)
|
68
|
-
rcon_challenge if @rcon_challenge.nil?
|
91
|
+
rcon_challenge if @rcon_challenge.nil? || @is_hltv
|
69
92
|
|
70
93
|
rcon_send "rcon #{@rcon_challenge} #{password} #{command}"
|
71
94
|
rcon_send "rcon #{@rcon_challenge} #{password}"
|
72
95
|
if @is_hltv
|
73
96
|
begin
|
74
97
|
response = reply.response
|
75
|
-
rescue
|
98
|
+
rescue SteamCondenser::TimeoutError
|
76
99
|
response = ''
|
77
100
|
end
|
78
101
|
else
|
@@ -80,9 +103,9 @@ class GoldSrcSocket
|
|
80
103
|
end
|
81
104
|
|
82
105
|
if response.strip == 'Bad rcon_password.'
|
83
|
-
raise
|
106
|
+
raise RCONNoAuthError
|
84
107
|
elsif response.strip == 'You have been banned from this server.'
|
85
|
-
raise
|
108
|
+
raise RCONBanError
|
86
109
|
end
|
87
110
|
|
88
111
|
begin
|
@@ -93,17 +116,25 @@ class GoldSrcSocket
|
|
93
116
|
response
|
94
117
|
end
|
95
118
|
|
119
|
+
# Requests a challenge number from the server to be used for further requests
|
120
|
+
#
|
121
|
+
# @raise [RCONBanError] if the IP of the local machine has been banned on the
|
122
|
+
# game server
|
123
|
+
# @see #rcon_send
|
96
124
|
def rcon_challenge
|
97
125
|
rcon_send 'challenge rcon'
|
98
126
|
response = reply.response.strip
|
99
127
|
|
100
|
-
if response == 'You have been banned from this server.'
|
101
|
-
raise
|
128
|
+
if response.strip == 'You have been banned from this server.'
|
129
|
+
raise RCONBanError
|
102
130
|
end
|
103
131
|
|
104
132
|
@rcon_challenge = response[14..-1]
|
105
133
|
end
|
106
134
|
|
135
|
+
# Wraps the given command in a RCON request packet and send it to the server
|
136
|
+
#
|
137
|
+
# @param [String] command The RCON command to send to the server
|
107
138
|
def rcon_send(command)
|
108
139
|
send RCONGoldSrcRequest.new(command)
|
109
140
|
end
|
@@ -1,18 +1,28 @@
|
|
1
|
-
# This code is free software; you can redistribute it and/or modify it under
|
2
|
-
# terms of the new BSD License.
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
3
|
#
|
4
|
-
# Copyright (c) 2008-
|
4
|
+
# Copyright (c) 2008-2011, Sebastian Staudt
|
5
5
|
|
6
|
+
require 'errors/packet_format_error'
|
6
7
|
require 'steam/sockets/steam_socket'
|
7
8
|
|
9
|
+
# This class represents a socket used to communicate with master servers
|
10
|
+
#
|
11
|
+
# @author Sebastian Staudt
|
8
12
|
class MasterServerSocket
|
9
13
|
|
10
14
|
include SteamSocket
|
11
15
|
|
16
|
+
# Reads a single packet from the socket
|
17
|
+
#
|
18
|
+
# @raise [PacketFormatError] if the packet has the wrong format
|
19
|
+
# @return [SteamPacket] The packet replied from the server
|
12
20
|
def reply
|
13
21
|
receive_packet 1500
|
14
22
|
|
15
|
-
|
23
|
+
unless @buffer.long == 0xFFFFFFFF
|
24
|
+
raise PacketFormatError, 'Master query response has wrong packet header.'
|
25
|
+
end
|
16
26
|
|
17
27
|
SteamPacketFactory.packet_from_data(@buffer.get)
|
18
28
|
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
# This code is free software; you can redistribute it and/or modify it under
|
2
|
-
# terms of the new BSD License.
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
3
|
#
|
4
4
|
# Copyright (c) 2008-2011, Sebastian Staudt
|
5
5
|
|
@@ -7,15 +7,29 @@ require 'ipaddr'
|
|
7
7
|
require 'socket'
|
8
8
|
require 'timeout'
|
9
9
|
|
10
|
-
require '
|
10
|
+
require 'errors/rcon_ban_error'
|
11
|
+
require 'errors/timeout_error'
|
11
12
|
require 'steam/packets/rcon/rcon_packet'
|
12
13
|
require 'steam/packets/rcon/rcon_packet_factory'
|
13
14
|
require 'steam/sockets/steam_socket'
|
14
15
|
|
16
|
+
# This class represents a socket used for RCON communication with game servers
|
17
|
+
# based on the Source engine (e.g. Team Fortress 2, Counter-Strike: Source)
|
18
|
+
#
|
19
|
+
# The Source engine uses a stateful TCP connection for RCON communication and
|
20
|
+
# uses an additional socket of this type to handle RCON requests.
|
21
|
+
#
|
22
|
+
# @author Sebastian Staudt
|
15
23
|
class RCONSocket
|
16
24
|
|
17
25
|
include SteamSocket
|
18
26
|
|
27
|
+
# Creates a new TCP socket to communicate with the server on the given IP
|
28
|
+
# address and port
|
29
|
+
#
|
30
|
+
# @param [String, IPAddr] ip Either the IP address or the DNS name of the
|
31
|
+
# server
|
32
|
+
# @param [Fixnum] port The port the server is listening on
|
19
33
|
def initialize(ip, port)
|
20
34
|
ip = IPSocket.getaddress(ip) unless ip.is_a? IPAddr
|
21
35
|
|
@@ -24,27 +38,46 @@ class RCONSocket
|
|
24
38
|
@socket = nil
|
25
39
|
end
|
26
40
|
|
27
|
-
# Closes the underlying socket if it exists
|
41
|
+
# Closes the underlying TCP socket if it exists
|
42
|
+
#
|
43
|
+
# SteamSocket#close
|
28
44
|
def close
|
29
45
|
super unless @socket.nil?
|
30
46
|
end
|
31
47
|
|
48
|
+
# Connects a new TCP socket to the server
|
49
|
+
#
|
50
|
+
# @raise [SteamCondenser::TimeoutError] if the connection could not be
|
51
|
+
# established
|
32
52
|
def connect
|
33
53
|
begin
|
34
54
|
timeout(@@timeout / 1000.0) { @socket = TCPSocket.new @ip, @port }
|
35
55
|
rescue Timeout::Error
|
36
|
-
raise
|
56
|
+
raise SteamCondenser::TimeoutError
|
37
57
|
end
|
38
58
|
end
|
39
59
|
|
60
|
+
# Sends the given RCON packet to the server
|
61
|
+
#
|
62
|
+
# @param [RCONPacket] data_packet The RCON packet to send to the server
|
63
|
+
# @see #connect
|
40
64
|
def send(data_packet)
|
41
65
|
connect if @socket.nil? || @socket.closed?
|
42
66
|
|
43
67
|
super
|
44
68
|
end
|
45
69
|
|
70
|
+
# Reads a packet from the socket
|
71
|
+
#
|
72
|
+
# The Source RCON protocol allows packets of an arbitrary sice transmitted
|
73
|
+
# using multiple TCP packets. The data is received in chunks and concatenated
|
74
|
+
# into a single response packet.
|
75
|
+
#
|
76
|
+
# @raise [RCONBanError] if the IP of the local machine has been banned on the
|
77
|
+
# game server
|
78
|
+
# @return [RCONPacket] The packet replied from the server
|
46
79
|
def reply
|
47
|
-
raise
|
80
|
+
raise RCONBanError if receive_packet(4) == 0
|
48
81
|
|
49
82
|
@buffer.rewind
|
50
83
|
remaining_bytes = @buffer.long
|