steam-condenser 0.12.0 → 0.13.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.
Files changed (42) hide show
  1. data/README.md +3 -1
  2. data/lib/exceptions/web_api_exception.rb +49 -0
  3. data/lib/steam/community/app_news.rb +69 -0
  4. data/lib/steam/community/game_achievement.rb +29 -0
  5. data/lib/steam/community/steam_id.rb +1 -1
  6. data/lib/steam/community/tf2/tf2_golden_wrench.rb +43 -0
  7. data/lib/steam/community/tf2/tf2_inventory.rb +51 -0
  8. data/lib/steam/community/tf2/tf2_item.rb +106 -0
  9. data/lib/steam/community/tf2/tf2_stats.rb +11 -0
  10. data/lib/steam/community/web_api.rb +96 -0
  11. data/lib/steam/packets/a2s_player_packet.rb +1 -1
  12. data/lib/steam/packets/a2s_rules_packet.rb +3 -3
  13. data/lib/steam/packets/c2m_checkmd5_packet.rb +24 -0
  14. data/lib/steam/packets/m2c_isvalidmd5_packet.rb +25 -0
  15. data/lib/steam/packets/m2s_requestrestart_packet.rb +24 -0
  16. data/lib/steam/packets/rcon/rcon_packet.rb +1 -1
  17. data/lib/steam/packets/rcon/rcon_packet_factory.rb +0 -1
  18. data/lib/steam/packets/rcon/rcon_terminator.rb +20 -0
  19. data/lib/steam/packets/s2a_info2_packet.rb +3 -3
  20. data/lib/steam/packets/s2a_logstring_packet.rb +24 -0
  21. data/lib/steam/packets/s2a_player_packet.rb +1 -1
  22. data/lib/steam/packets/s2a_rules_packet.rb +2 -2
  23. data/lib/steam/packets/s2c_challenge_packet.rb +2 -2
  24. data/lib/steam/packets/s2m_heartbeat2_packet.rb +57 -0
  25. data/lib/steam/packets/steam_packet.rb +6 -3
  26. data/lib/steam/packets/steam_packet_factory.rb +12 -9
  27. data/lib/steam/servers/game_server.rb +57 -16
  28. data/lib/steam/servers/goldsrc_server.rb +7 -12
  29. data/lib/steam/servers/master_server.rb +63 -15
  30. data/lib/steam/servers/server.rb +66 -0
  31. data/lib/steam/servers/source_server.rb +14 -20
  32. data/lib/steam/sockets/goldsrc_socket.rb +13 -9
  33. data/lib/steam/sockets/rcon_socket.rb +35 -24
  34. data/lib/steam/sockets/steam_socket.rb +27 -21
  35. data/lib/steam/steam_player.rb +30 -15
  36. data/lib/steam-condenser/community.rb +17 -16
  37. data/lib/steam-condenser/version.rb +2 -2
  38. metadata +39 -15
  39. data/lib/datagram_channel.rb +0 -67
  40. data/lib/socket_channel.rb +0 -55
  41. data/lib/steam/packets/a2a_ack_packet.rb +0 -24
  42. data/lib/steam/packets/a2a_ping_packet.rb +0 -19
@@ -0,0 +1,24 @@
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) 2011, Sebastian Staudt
5
+
6
+ require 'steam/packets/steam_packet'
7
+
8
+ # The S2A_LOGSTRING packet type is used to transfer log messages.
9
+ class S2A_LOGSTRING_Packet
10
+
11
+ include SteamPacket
12
+
13
+ # The log message contained in this packet
14
+ attr_reader :message
15
+
16
+ # Creates a new log message packet
17
+ def initialize(data)
18
+ super S2A_LOGSTRING_HEADER, data
19
+
20
+ @content_data.byte
21
+ @message = @content_data.string
22
+ end
23
+
24
+ end
@@ -1,7 +1,7 @@
1
1
  # This code is free software; you can redistribute it and/or modify it under the
2
2
  # terms of the new BSD License.
3
3
  #
4
- # Copyright (c) 2008-2010, Sebastian Staudt
4
+ # Copyright (c) 2008-2011, Sebastian Staudt
5
5
 
6
6
  require 'steam/packets/steam_packet'
7
7
 
@@ -1,7 +1,7 @@
1
1
  # This code is free software; you can redistribute it and/or modify it under the
2
2
  # terms of the new BSD License.
3
3
  #
4
- # Copyright (c) 2008-2010, Sebastian Staudt
4
+ # Copyright (c) 2008-2011, Sebastian Staudt
5
5
 
6
6
  require 'steam/packets/steam_packet'
7
7
 
@@ -15,7 +15,7 @@ class S2A_RULES_Packet
15
15
 
16
16
  # Creates a S2A_RULES response object based on the data received.
17
17
  def initialize(content_data)
18
- raise Exception.new('Wrong formatted A2A_RULES response packet.') if content_data.nil?
18
+ raise Exception.new('Wrong formatted S2A_RULES response packet.') if content_data.nil?
19
19
 
20
20
  super SteamPacket::S2A_RULES_HEADER, content_data
21
21
 
@@ -1,7 +1,7 @@
1
1
  # This code is free software; you can redistribute it and/or modify it under the
2
2
  # terms of the new BSD License.
3
3
  #
4
- # Copyright (c) 2008-2010, Sebastian Staudt
4
+ # Copyright (c) 2008-2011, Sebastian Staudt
5
5
 
6
6
  require 'steam/packets/steam_packet'
7
7
 
@@ -11,7 +11,7 @@ class S2C_CHALLENGE_Packet
11
11
 
12
12
  include SteamPacket
13
13
 
14
- # Creates a AS2C_CHALLENGE response object based on the data received.
14
+ # Creates a S2C_CHALLENGE response object based on the data received.
15
15
  def initialize(challenge_number)
16
16
  super S2C_CHALLENGE_HEADER, challenge_number
17
17
  end
@@ -0,0 +1,57 @@
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2010-2011, Sebastian Staudt
5
+
6
+ require 'steam/packets/steam_packet'
7
+
8
+ # The S2M_HEARTBEAT2 packet type is used to signal a game servers availability
9
+ # and status to the master servers.
10
+ class S2M_HEARTBEAT2_Packet
11
+
12
+ include SteamPacket
13
+
14
+ DEFAULT_DATA = {
15
+ :appid => 320,
16
+ :bots => 0,
17
+ :challenge => nil,
18
+ :dedicated => 0,
19
+ :gamedir => 'hl2mp',
20
+ :gameport => 27015,
21
+ :gametype => 'ctf',
22
+ :lan => 1,
23
+ :map => 'null',
24
+ :max => 24,
25
+ :os => 'l',
26
+ :password => 0,
27
+ :players => 0,
28
+ :product => 'hl2dm',
29
+ :protocol => 7,
30
+ :region => 255,
31
+ :secure => 0,
32
+ :specport => 0,
33
+ :type => 'd',
34
+ :version => '1.0.0.0'
35
+ }
36
+
37
+ # Creates a new heartbeat packet to send to a master server
38
+ def initialize(data = {})
39
+ data = DEFAULT_DATA.merge data
40
+
41
+ if data[:challenge].nil?
42
+ raise SteamCondenserException.new 'You have to provide a challenge number when sending a heartbeat to a master server.'
43
+ end
44
+
45
+ bytes = 0x0A.chr
46
+ data.each { |k, v| bytes += "\\#{k}\\#{v}" }
47
+ bytes += 0x0A.chr
48
+
49
+ super S2M_HEARTBEAT2_HEADER, bytes
50
+ end
51
+
52
+ # Returns a byte array representation of the packet data
53
+ def to_s
54
+ [@header_data, @content_data.string].pack('ca*')
55
+ end
56
+
57
+ end
@@ -1,30 +1,33 @@
1
1
  # This code is free software; you can redistribute it and/or modify it under the
2
2
  # terms of the new BSD License.
3
3
  #
4
- # Copyright (c) 2008-2010, Sebastian Staudt
4
+ # Copyright (c) 2008-2011, Sebastian Staudt
5
5
 
6
6
  require 'stringio_additions'
7
7
 
8
8
  # This class represents a packet used by the Source query protocol
9
9
  module SteamPacket
10
10
 
11
- A2A_ACK_HEADER = 0x6A
12
- A2A_PING_HEADER = 0x69
13
11
  A2M_GET_SERVERS_BATCH2_HEADER = 0x31
14
12
  A2S_INFO_HEADER = 0x54
15
13
  A2S_PLAYER_HEADER = 0x55
16
14
  A2S_RULES_HEADER = 0x56
17
15
  A2S_SERVERQUERY_GETCHALLENGE_HEADER = 0x57
16
+ C2M_CHECKMD5_HEADER = 0x4D
18
17
  M2A_SERVER_BATCH_HEADER = 0x66
18
+ M2C_ISVALIDMD5_HEADER = 0x4E
19
+ M2S_REQUESTRESTART_HEADER = 0x4F
19
20
  RCON_GOLDSRC_CHALLENGE_HEADER = 0x63
20
21
  RCON_GOLDSRC_NO_CHALLENGE_HEADER = 0x39
21
22
  RCON_GOLDSRC_RESPONSE_HEADER = 0x6c
22
23
  S2A_INFO_DETAILED_HEADER = 0x6D
23
24
  S2A_INFO2_HEADER = 0x49
25
+ S2A_LOGSTRING_HEADER = 0x52
24
26
  S2A_PLAYER_HEADER = 0x44
25
27
  S2A_RULES_HEADER = 0x45
26
28
  S2C_CONNREJECT_HEADER = 0x39
27
29
  S2C_CHALLENGE_HEADER = 0x41
30
+ S2M_HEARTBEAT2_HEADER = 0x30
28
31
 
29
32
  # Creates a new SteamPacket object with given header and content data
30
33
  def initialize(header_data, content_data = '')
@@ -1,16 +1,12 @@
1
1
  # This code is free software; you can redistribute it and/or modify it under the
2
2
  # terms of the new BSD License.
3
3
  #
4
- # Copyright (c) 2008-2010, Sebastian Staudt
5
-
6
- require 'zlib'
4
+ # Copyright (c) 2008-2011, Sebastian Staudt
7
5
 
8
6
  require 'exceptions/steam_condenser_exception'
9
7
  require 'steam/packets/s2a_info_detailed_packet'
10
8
  require 'steam/packets/a2s_info_packet'
11
9
  require 'steam/packets/s2a_info2_packet'
12
- require 'steam/packets/a2a_ping_packet'
13
- require 'steam/packets/a2a_ack_packet'
14
10
  require 'steam/packets/a2s_player_packet'
15
11
  require 'steam/packets/s2a_player_packet'
16
12
  require 'steam/packets/a2s_rules_packet'
@@ -19,6 +15,9 @@ require 'steam/packets/a2s_serverquery_getchallenge_packet'
19
15
  require 'steam/packets/s2c_challenge_packet'
20
16
  require 'steam/packets/a2m_get_servers_batch2_packet'
21
17
  require 'steam/packets/m2a_server_batch_packet'
18
+ require 'steam/packets/m2c_isvalidmd5_packet'
19
+ require 'steam/packets/m2s_requestrestart_packet'
20
+ require 'steam/packets/s2a_logstring_packet'
22
21
  require 'steam/packets/rcon/rcon_goldsrc_response'
23
22
 
24
23
  module SteamPacketFactory
@@ -35,10 +34,6 @@ module SteamPacketFactory
35
34
  return A2S_INFO_Packet.new
36
35
  when SteamPacket::S2A_INFO2_HEADER
37
36
  return S2A_INFO2_Packet.new(data)
38
- when SteamPacket::A2A_PING_HEADER
39
- return A2A_PING_Packet.new
40
- when SteamPacket::A2A_ACK_HEADER
41
- return A2A_ACK_Packet.new(data)
42
37
  when SteamPacket::A2S_PLAYER_HEADER
43
38
  return A2S_PLAYER_Packet.new
44
39
  when SteamPacket::S2A_PLAYER_HEADER
@@ -55,10 +50,16 @@ module SteamPacketFactory
55
50
  return A2M_GET_SERVERS_BATCH2_Packet.new(data)
56
51
  when SteamPacket::M2A_SERVER_BATCH_HEADER
57
52
  return M2A_SERVER_BATCH_Packet.new(data)
53
+ when SteamPacket::M2C_ISVALIDMD5_HEADER
54
+ return M2C_ISVALIDMD5_Packet.new(data)
55
+ when SteamPacket::M2S_REQUESTRESTART_HEADER
56
+ return M2S_REQUESTRESTART_Packet.new(data)
58
57
  when SteamPacket::RCON_GOLDSRC_CHALLENGE_HEADER,
59
58
  SteamPacket::RCON_GOLDSRC_NO_CHALLENGE_HEADER,
60
59
  SteamPacket::RCON_GOLDSRC_RESPONSE_HEADER
61
60
  return RCONGoldSrcResponse.new(data)
61
+ when SteamPacket::S2A_LOGSTRING_HEADER
62
+ return S2A_LOGSTRING_Packet.new(data)
62
63
  else
63
64
  raise SteamCondenserException.new("Unknown packet with header 0x#{header.to_s(16)} received.")
64
65
  end
@@ -68,6 +69,8 @@ module SteamPacketFactory
68
69
  packet_data = split_packets.join ''
69
70
 
70
71
  if is_compressed
72
+ require 'zlib'
73
+
71
74
  begin
72
75
  require 'bz2'
73
76
  packet_data = BZ2.uncompress(packet_data)
@@ -1,12 +1,11 @@
1
1
  # This code is free software; you can redistribute it and/or modify it under the
2
2
  # terms of the new BSD License.
3
3
  #
4
- # Copyright (c) 2008-2010, Sebastian Staudt
4
+ # Copyright (c) 2008-2011, Sebastian Staudt
5
5
 
6
6
  require 'exceptions/steam_condenser_exception'
7
7
  require 'steam/steam_player'
8
8
  require 'steam/packets/a2s_info_packet'
9
- require 'steam/packets/a2a_ping_packet'
10
9
  require 'steam/packets/a2s_player_packet'
11
10
  require 'steam/packets/a2s_rules_packet'
12
11
  require 'steam/packets/a2s_serverquery_getchallenge_packet'
@@ -14,14 +13,59 @@ require 'steam/packets/s2a_info_base_packet'
14
13
  require 'steam/packets/s2a_player_packet'
15
14
  require 'steam/packets/s2a_rules_packet'
16
15
  require 'steam/packets/s2c_challenge_packet'
16
+ require 'steam/servers/server'
17
17
 
18
18
  module GameServer
19
19
 
20
+ include Server
21
+
20
22
  REQUEST_CHALLENGE = 0
21
23
  REQUEST_INFO = 1
22
24
  REQUEST_PLAYER = 2
23
25
  REQUEST_RULES = 3
24
26
 
27
+ # Parses the player attribute names supplied by +rcon status+
28
+ def self.player_status_attributes(status_header)
29
+ status_header.split.map do |attribute|
30
+ case attribute
31
+ when '#'
32
+ :id
33
+ when 'connected'
34
+ :time
35
+ when 'frag'
36
+ :score
37
+ else
38
+ attribute.to_sym
39
+ end
40
+ end
41
+ end
42
+
43
+ # Splits the player status obtained with +rcon status+
44
+ def self.split_player_status(attributes, player_status)
45
+ data = player_status[1..-1].strip.split '"'
46
+ data = [ data[0].split, data[1], data[2].split ]
47
+ data.flatten!
48
+
49
+ if attributes.size > data.size + 1
50
+ data.insert 1, nil
51
+ data.insert 4, nil, nil, nil
52
+ end
53
+
54
+ player_data = {}
55
+ data.each_index do |i|
56
+ player_data[attributes[i]] = data[i]
57
+ end
58
+
59
+ player_data
60
+ end
61
+
62
+ # Creates a new instance of a game server object
63
+ #
64
+ # The port defaults to 27015 for game servers
65
+ def initialize(address, port = 27015)
66
+ super
67
+ end
68
+
25
69
  # Returns the last measured response time of this server
26
70
  #
27
71
  # If there's no data, update_ping is called to measure the current response
@@ -125,14 +169,18 @@ module GameServer
125
169
  def update_player_info(rcon_password = nil)
126
170
  handle_response_for_request GameServer::REQUEST_PLAYER
127
171
 
128
- unless rcon_password.nil? or @player_hash.empty?
129
- rcon_auth(rcon_password)
130
- players = rcon_exec('status').split("\n")[7..-1]
131
- players.pop if is_a? GoldSrcServer
172
+ unless rcon_password.nil? || @player_hash.nil? || @player_hash.empty?
173
+ rcon_auth rcon_password
174
+ players = rcon_exec('status').lines.select do |line|
175
+ line.start_with?('#') && line != "#end\n"
176
+ end
177
+ attributes = GameServer.player_status_attributes players.shift
132
178
 
133
179
  players.each do |player|
134
- player_data = self.class.split_player_status(player)
135
- @player_hash[player_data[1]].add_info(*player_data) if @player_hash.key?(player_data[1])
180
+ player_data = GameServer.split_player_status(attributes, player)
181
+ if @player_hash.key? player_data[:name]
182
+ @player_hash[player_data[:name]].add_info player_data
183
+ end
136
184
  end
137
185
  end
138
186
  end
@@ -150,20 +198,13 @@ module GameServer
150
198
  end
151
199
 
152
200
  def update_ping
153
- send_request A2A_PING_Packet.new
201
+ send_request A2S_INFO_Packet.new
154
202
  start_time = Time.now
155
203
  reply
156
204
  end_time = Time.now
157
205
  @ping = (end_time - start_time) * 1000
158
206
  end
159
207
 
160
- # Checks whether the listening port number of the server is in a valid range
161
- def initialize(port_number = 27015)
162
- unless port_number.to_i > 0 and port_number.to_i < 65536
163
- raise ArgumentError.new('The listening port of the server has to be a number greater than 0 and less than 65536.')
164
- end
165
- end
166
-
167
208
  def to_s
168
209
  return_string = ''
169
210
 
@@ -10,20 +10,15 @@ class GoldSrcServer
10
10
 
11
11
  include GameServer
12
12
 
13
- # Splits the player status obtained with +rcon status+
14
- def self.split_player_status(player_status)
15
- player_data = player_status.match(/# *(\d+) +"(.*)" +(\d+) +(.*)/).to_a[1..-1]
16
- player_data[3] = player_data[3].split
17
- player_data.flatten!
18
- player_data[0] = player_data[2]
19
- player_data.delete_at(2)
20
- player_data.delete_at(4)
21
- player_data
13
+ def initialize(address, port = 27015, is_hltv = false)
14
+ super address, port
15
+
16
+ @is_hltv = is_hltv
22
17
  end
23
18
 
24
- def initialize(ip_address, port_number = 27015, is_hltv = false)
25
- super port_number
26
- @socket = GoldSrcSocket.new ip_address, port_number, is_hltv
19
+ # Initializes the socket to communicate with the GoldSrc server
20
+ def init_socket
21
+ @socket = GoldSrcSocket.new @ip_address, @port, @is_hltv
27
22
  end
28
23
 
29
24
  def rcon_auth(password)
@@ -1,13 +1,18 @@
1
1
  # This code is free software; you can redistribute it and/or modify it under the
2
2
  # terms of the new BSD License.
3
3
  #
4
- # Copyright (c) 2008-2010, Sebastian Staudt
4
+ # Copyright (c) 2008-2011, Sebastian Staudt
5
5
 
6
6
  require 'steam/packets/a2m_get_servers_batch2_packet'
7
+ require 'steam/packets/c2m_checkmd5_packet'
8
+ require 'steam/packets/s2m_heartbeat2_packet'
9
+ require 'steam/servers/server'
7
10
  require 'steam/sockets/master_server_socket'
8
11
 
9
12
  class MasterServer
10
13
 
14
+ include Server
15
+
11
16
  GOLDSRC_MASTER_SERVER = 'hl1master.steampowered.com', 27010
12
17
  SOURCE_MASTER_SERVER = 'hl2master.steampowered.com', 27011
13
18
 
@@ -21,11 +26,30 @@ class MasterServer
21
26
  REGION_AFRICA = 0x07
22
27
  REGION_ALL = 0xFF
23
28
 
24
- def initialize(master_server_address, master_server_port)
25
- @socket = MasterServerSocket.new master_server_address, master_server_port
29
+ # Creates a new instance of a master server object
30
+ def initialize(address, port)
31
+ super
32
+
26
33
  @server_array = []
27
34
  end
28
35
 
36
+ # Request a challenge number from the master server. This is used for further
37
+ # communication with the master server.
38
+ #
39
+ # Please note that this is NOT needed for finding servers using the +servers+
40
+ # method
41
+ def challenge
42
+ failsafe do
43
+ @socket.send C2M_CHECKMD5_Packet.new
44
+ @socket.reply.challenge
45
+ end
46
+ end
47
+
48
+ # Initializes the socket to communicate with the master server
49
+ def init_socket
50
+ @socket = MasterServerSocket.new @ip_address, @port
51
+ end
52
+
29
53
  def servers(region_code = MasterServer::REGION_ALL, filters = '')
30
54
  update_servers region_code, filters if @server_array.empty?
31
55
  @server_array
@@ -36,23 +60,47 @@ class MasterServer
36
60
  finished = false
37
61
  current_server = '0.0.0.0:0'
38
62
 
39
- begin
40
- @socket.send A2M_GET_SERVERS_BATCH2_Packet.new(region_code, current_server, filters)
63
+ failsafe do
41
64
  begin
42
- servers = @socket.reply.servers
43
- servers.each do |server|
44
- if server == '0.0.0.0:0'
45
- finished = true
46
- else
47
- current_server = server
48
- @server_array << server.split(':')
65
+ @socket.send A2M_GET_SERVERS_BATCH2_Packet.new(region_code, current_server, filters)
66
+ begin
67
+ servers = @socket.reply.servers
68
+ servers.each do |server|
69
+ if server == '0.0.0.0:0'
70
+ finished = true
71
+ else
72
+ current_server = server
73
+ @server_array << server.split(':')
74
+ end
49
75
  end
76
+ fail_count = 0
77
+ rescue TimeoutException
78
+ raise $! if (fail_count += 1) == 3
50
79
  end
51
- fail_count = 0
80
+ end while !finished
81
+ end
82
+ end
83
+
84
+ # Sends a constructed heartbeat to the master server
85
+ #
86
+ # This can be used to check server versions externally.
87
+ #
88
+ # The reply from the master server – usually zero or more packets. Zero means
89
+ # either the heartbeat was accepted by the master or there was a timeout. So
90
+ # usually it's best to repeat a heartbeat a few times when not receiving any
91
+ # packets.
92
+ def send_heartbeat(data)
93
+ failsafe do
94
+ @socket.send S2M_HEARTBEAT2_Packet.new(data)
95
+
96
+ reply_packets = []
97
+ begin
98
+ loop { reply_packets << @socket.reply }
52
99
  rescue TimeoutException
53
- raise $! if (fail_count += 1) == 3
54
100
  end
55
- end while !finished
101
+ end
102
+
103
+ reply_packets
56
104
  end
57
105
 
58
106
  end
@@ -0,0 +1,66 @@
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2011, Sebastian Staudt
5
+
6
+ require 'socket'
7
+
8
+ # This module is included by all classes implementing server functionality
9
+ #
10
+ # It provides basic name resolution features.
11
+ module Server
12
+
13
+ # Creates a new server instance with the given address and port
14
+ def initialize(address, port = nil)
15
+ address = address.to_s
16
+ address, port = address.split(':', 2) if address.include? ':'
17
+
18
+ @host_names = []
19
+ @ip_addresses = []
20
+ @ip_index = 0
21
+ @port = port.to_i
22
+
23
+ Socket.getaddrinfo(address, port, Socket::AF_INET, Socket::SOCK_DGRAM).
24
+ each do |address|
25
+ @host_names << address[2]
26
+ @ip_addresses << address[3]
27
+ end
28
+
29
+ @ip_address = @ip_addresses.first
30
+
31
+ init_socket
32
+ end
33
+
34
+ # Rotate this server's IP address to the next one in the IP list
35
+ #
36
+ # This method will return +true+, if the IP list reached its end. If the list
37
+ # contains only one IP address, this method will instantly return +true+.
38
+ def rotate_ip
39
+ return true if @ip_addresses.size == 1
40
+
41
+ @ip_index = (@ip_index + 1) % @ip_addresses.size
42
+ @ip_address = @ip_addresses[@ip_index]
43
+
44
+ init_socket
45
+
46
+ @ip_index == 0
47
+ end
48
+
49
+ protected
50
+
51
+ # Execute an action in the context of this server's current IP address
52
+ #
53
+ # Any failure, i.e. an exception, will cause the IP to rotate to the next IP
54
+ # in the server's IP list and the execution will be repeated for the next IP
55
+ # address. If the IP rotation reaches the end of the list, the exception will
56
+ # be reraised.
57
+ def failsafe(&proc)
58
+ begin
59
+ proc.call
60
+ rescue
61
+ raise $! if rotate_ip
62
+ failsafe &proc
63
+ end
64
+ end
65
+
66
+ end
@@ -1,12 +1,13 @@
1
1
  # This code is free software; you can redistribute it and/or modify it under the
2
2
  # terms of the new BSD License.
3
3
  #
4
- # Copyright (c) 2008-2010, Sebastian Staudt
4
+ # Copyright (c) 2008-2011, Sebastian Staudt
5
5
 
6
6
  require 'exceptions/rcon_no_auth_exception'
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
+ require 'steam/packets/rcon/rcon_terminator'
10
11
  require 'steam/servers/game_server'
11
12
  require 'steam/sockets/rcon_socket'
12
13
  require 'steam/sockets/source_socket'
@@ -15,19 +16,14 @@ class SourceServer
15
16
 
16
17
  include GameServer
17
18
 
18
- # Splits the player status obtained with +rcon status+
19
- def self.split_player_status(player_status)
20
- player_data = player_status.match(/# *(\d+) +"(.*)" +(.*)/).to_a[1..-1]
21
- player_data[2] = player_data[2].split
22
- player_data.flatten!
23
- player_data.delete_at(3)
24
- player_data
19
+ def initialize(address, port = 27015)
20
+ super
25
21
  end
26
22
 
27
- def initialize(ip_address, port_number = 27015)
28
- super port_number
29
- @rcon_socket = RCONSocket.new ip_address, port_number
30
- @socket = SourceSocket.new ip_address, port_number
23
+ # Initializes the sockets to communicate with the Source server
24
+ def init_socket
25
+ @rcon_socket = RCONSocket.new @ip_address, @port
26
+ @socket = SourceSocket.new @ip_address, @port
31
27
  end
32
28
 
33
29
  def rcon_auth(password)
@@ -44,17 +40,15 @@ class SourceServer
44
40
 
45
41
  def rcon_exec(command)
46
42
  @rcon_socket.send RCONExecRequest.new(@rcon_request_id, command)
43
+ @rcon_socket.send RCONTerminator.new(@rcon_request_id)
47
44
  response_packets = []
48
45
 
49
46
  begin
50
- begin
51
- response_packet = @rcon_socket.reply
52
- raise RCONNoAuthException.new if response_packet.is_a? RCONAuthResponse
53
- response_packets << response_packet
54
- end while true
55
- rescue TimeoutException
56
- raise $! if response_packets.empty?
57
- end
47
+ response_packet = @rcon_socket.reply
48
+ redo if response_packet.nil?
49
+ raise RCONNoAuthException.new if response_packet.is_a? RCONAuthResponse
50
+ response_packets << response_packet
51
+ end while response_packet.response.size > 0
58
52
 
59
53
  response = ''
60
54
  response_packets.each do |packet|
@@ -1,9 +1,11 @@
1
1
  # This code is free software; you can redistribute it and/or modify it under the
2
2
  # terms of the new BSD License.
3
3
  #
4
- # Copyright (c) 2008-2010, Sebastian Staudt
4
+ # Copyright (c) 2008-2011, Sebastian Staudt
5
5
 
6
6
  require 'stringio_additions'
7
+ require 'exceptions/rcon_ban_exception'
8
+ require 'exceptions/rcon_no_auth_exception'
7
9
  require 'steam/packets/steam_packet_factory'
8
10
  require 'steam/packets/rcon/rcon_goldsrc_request'
9
11
  require 'steam/sockets/steam_socket'
@@ -66,6 +68,7 @@ class GoldSrcSocket
66
68
  rcon_challenge if @rcon_challenge.nil? or @is_hltv
67
69
 
68
70
  rcon_send "rcon #{@rcon_challenge} #{password} #{command}"
71
+ rcon_send "rcon #{@rcon_challenge} #{password}"
69
72
  if @is_hltv
70
73
  begin
71
74
  response = reply.response
@@ -76,17 +79,16 @@ class GoldSrcSocket
76
79
  response = reply.response
77
80
  end
78
81
 
79
- if response.strip == 'Bad rcon_password.' or response.strip == 'You have been banned from this server.'
82
+ if response.strip == 'Bad rcon_password.'
80
83
  raise RCONNoAuthException
84
+ elsif response.strip == 'You have been banned from this server.'
85
+ raise RCONBanException
81
86
  end
82
87
 
83
88
  begin
84
- loop do
85
- response_part = reply.response
86
- response << response_part
87
- end
88
- rescue TimeoutException
89
- end
89
+ response_part = reply.response
90
+ response << response_part
91
+ end while response_part.size > 0
90
92
 
91
93
  response
92
94
  end
@@ -95,7 +97,9 @@ class GoldSrcSocket
95
97
  rcon_send 'challenge rcon'
96
98
  response = reply.response.strip
97
99
 
98
- raise RCONNoAuthException if response == 'You have been banned from this server.'
100
+ if response == 'You have been banned from this server.'
101
+ raise RCONBanException
102
+ end
99
103
 
100
104
  @rcon_challenge = response[14..-1]
101
105
  end