# -*- coding: binary -*- module Rex module Proto ## # # Steam protocol support, taken from https://developer.valvesoftware.com/wiki/Server_queries # ## module Steam # The Steam header ussed when the message is fragmented. FRAGMENTED_HEADER = 0xFFFFFFFE # The Steam header ussed when the message is not fragmented. UNFRAGMENTED_HEADER = 0xFFFFFFFF # Decodes a Steam response message. # # @param message [String] the message to decode # @return [Array] the message type and body 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 # Encodes a Steam message. # # @param type [String, Fixnum] the message type # @param body [String] the message body # @return [String] the encoded Steam message def encode_message(type, body) if type.is_a? Fixnum type_num = type elsif type.is_a? String type_num = type.ord else fail ArgumentError, 'type must be a String or Fixnum' end [UNFRAGMENTED_HEADER, type_num ].pack('NC') + body end # Builds an A2S_INFO message # # @return [String] the A2S_INFO message def a2s_info encode_message('T', "Source Engine Query\x00") end # Decodes an A2S_INFO response message # # @parameter response [String] the A2S_INFO resposne to decode # @return [Hash] the fields extracted from the response 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 end end end