steam-condenser 0.12.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
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