Module: Rex::Proto::Steam

Defined in:
lib/rex/proto/steam/message.rb

Overview

Steam protocol support, taken from developer.valvesoftware.com/wiki/Server_queries

Constant Summary collapse

FRAGMENTED_HEADER =

The Steam header ussed when the message is fragmented.

0xFFFFFFFE
UNFRAGMENTED_HEADER =

The Steam header ussed when the message is not fragmented.

0xFFFFFFFF

Instance Method Summary collapse

Instance Method Details

#a2s_infoString

Builds an A2S_INFO message

Returns:

  • (String)

    the A2S_INFO message


49
50
51
# File 'lib/rex/proto/steam/message.rb', line 49

def a2s_info
  encode_message('T', "Source Engine Query\x00")
end

#a2s_info_decode(response) ⇒ Hash

Decodes an A2S_INFO response message

Parameters:

  • response (String)

    the A2S_INFO resposne to decode

Returns:

  • (Hash)

    the fields extracted from the response


57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/rex/proto/steam/message.rb', line 57

def a2s_info_decode(response)
  # abort if it is impossibly short
  return nil if response.length < 19
  message_type, body = decode_message(response)
  # abort if it isn't a valid Steam response
  return nil if message_type != 0x49 # 'I'
  info = {}
  info[:version], info[:name], info[:map], info[:folder], info[:game_name],
    info[:game_id], players, players_max, info[:bots],
    type, env, vis, vac, info[:game_version], _edf = body.unpack("CZ*Z*Z*Z*SCCCCCCCZ*C")

  # translate type
  case type
  when 100 # d
    server_type = 'Dedicated'
  when 108 # l
    server_type = 'Non-dedicated'
  when 112 # p
    server_type = 'SourceTV relay (proxy)'
  else
    server_type = "Unknown (#{type})"
  end
  info[:type] = server_type

  # translate environment
  case env
  when 108 # l
    server_env = 'Linux'
  when 119 # w
    server_env = 'Windows'
  when 109 # m
  when 111 # o
    server_env = 'Mac'
  else
    server_env = "Unknown (#{env})"
  end
  info[:environment] = server_env

  # translate visibility
  case vis
  when 0
    server_vis = 'public'
  when 1
    server_vis = 'private'
  else
    server_vis = "Unknown (#{vis})"
  end
  info[:visibility] = server_vis

  # translate VAC
  case vac
  when 0
    server_vac = 'unsecured'
  when 1
    server_vac = 'secured'
  else
    server_vac = "Unknown (#{vac})"
  end
  info[:VAC] = server_vac

  # format players/max
  info[:players] = "#{players}/#{players_max}"

  # TODO: parse EDF
  info
end

#decode_message(message) ⇒ Array

Decodes a Steam response message.

Parameters:

  • message (String)

    the message to decode

Returns:

  • (Array)

    the message type and body


20
21
22
23
24
25
26
27
# File 'lib/rex/proto/steam/message.rb', line 20

def decode_message(message)
  # minimum size is header (4) + type (1)
  return if message.length < 5
  header, type = message.unpack('NC')
  # TODO: handle fragmented responses
  return if header != UNFRAGMENTED_HEADER
  [type, message[5, message.length]]
end

#encode_message(type, body) ⇒ String

Encodes a Steam message.

Parameters:

  • type (String, Integer)

    the message type

  • body (String)

    the message body

Returns:

  • (String)

    the encoded Steam message


34
35
36
37
38
39
40
41
42
43
44
# File 'lib/rex/proto/steam/message.rb', line 34

def encode_message(type, body)
  if type.is_a? Integer
    type_num = type
  elsif type.is_a? String
    type_num = type.ord
  else
    fail ArgumentError, 'type must be a String or Integer'
  end

  [UNFRAGMENTED_HEADER, type_num ].pack('NC') + body
end