steamrb 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/.rubocop.yml +4 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/README.md +116 -0
- data/Rakefile +21 -0
- data/bin/console +7 -0
- data/bin/setup +8 -0
- data/lib/ext/string.rb +42 -0
- data/lib/steam/byte_reader.rb +77 -0
- data/lib/steam/byte_writer.rb +96 -0
- data/lib/steam/client.rb +228 -0
- data/lib/steam/crypto.rb +113 -0
- data/lib/steam/emsg_util.rb +29 -0
- data/lib/steam/handler/auth.rb +78 -0
- data/lib/steam/handler/base.rb +79 -0
- data/lib/steam/handler/collection.rb +60 -0
- data/lib/steam/handler/game_coordinator.rb +80 -0
- data/lib/steam/handler/steam_apps.rb +35 -0
- data/lib/steam/handler/steam_user.rb +170 -0
- data/lib/steam/handler.rb +15 -0
- data/lib/steam/local_ip.rb +32 -0
- data/lib/steam/logger.rb +27 -0
- data/lib/steam/networking/connection.rb +178 -0
- data/lib/steam/networking/packet.rb +77 -0
- data/lib/steam/networking/packet_list.rb +27 -0
- data/lib/steam/networking.rb +14 -0
- data/lib/steam/plugins.rb +106 -0
- data/lib/steam/protocol/client_message.rb +13 -0
- data/lib/steam/protocol/gc_message.rb +9 -0
- data/lib/steam/protocol/gc_protobuf_message.rb +18 -0
- data/lib/steam/protocol/message.rb +99 -0
- data/lib/steam/protocol/protobuf_message.rb +54 -0
- data/lib/steam/protocol.rb +19 -0
- data/lib/steam/sentry_file.rb +42 -0
- data/lib/steam/server.rb +26 -0
- data/lib/steam/server_list.rb +34 -0
- data/lib/steam/version.rb +14 -0
- data/lib/steam.rb +46 -0
- data/steamrb.gemspec +37 -0
- metadata +213 -0
data/lib/steam/logger.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Steam
|
5
|
+
# Small wrapper around the Ruby Logger class
|
6
|
+
class Logger < ::Logger
|
7
|
+
# Log level WARN
|
8
|
+
WARN = ::Logger::WARN
|
9
|
+
# Log level INFO
|
10
|
+
INFO = ::Logger::INFO
|
11
|
+
# Log level ERROR
|
12
|
+
ERROR = ::Logger::ERROR
|
13
|
+
# Log level DEBUG
|
14
|
+
DEBUG = ::Logger::DEBUG
|
15
|
+
# Log level FATAL
|
16
|
+
FATAL = ::Logger::FATAL
|
17
|
+
|
18
|
+
# Create a Logger that logs to a given IO object
|
19
|
+
#
|
20
|
+
# @param io [#read] The io object
|
21
|
+
# @param level [Integer] log lvel
|
22
|
+
def initialize(io, level = DEBUG)
|
23
|
+
self.level = level
|
24
|
+
super(io)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'socket'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
module Steam
|
6
|
+
module Networking
|
7
|
+
# Represents a connection to Steam. Holds the session key, yields
|
8
|
+
# packets to the caller
|
9
|
+
#
|
10
|
+
# @example Creating a Connection
|
11
|
+
# connection = Connection.new(Server.new(ip, port))
|
12
|
+
# connection.each_packet do |packet|
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# @see Packet
|
16
|
+
class Connection
|
17
|
+
# The internal list of Packet objects read from the TCP socket
|
18
|
+
attr_reader :packets
|
19
|
+
|
20
|
+
def initialize(server, socket = nil)
|
21
|
+
@session_key = nil
|
22
|
+
@packets = PacketList.new
|
23
|
+
@socket = socket
|
24
|
+
@mutex = Mutex.new
|
25
|
+
@server = server
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
# Opens the underlying socket
|
30
|
+
def open
|
31
|
+
self.session_key = nil
|
32
|
+
self.disconnecting = false
|
33
|
+
self.socket = TCPSocket.open(@server.host, @server.port)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Closes the socket
|
37
|
+
def disconnect
|
38
|
+
self.session_key = nil
|
39
|
+
self.disconnecting = true
|
40
|
+
socket.close
|
41
|
+
end
|
42
|
+
|
43
|
+
# Determine if the connection is disconnected. This is determined
|
44
|
+
# by the socket being opened or close.
|
45
|
+
#
|
46
|
+
# @return [Bool]
|
47
|
+
def disconnected?
|
48
|
+
socket.closed?
|
49
|
+
end
|
50
|
+
|
51
|
+
# Sets the connections session key. Used for crypto
|
52
|
+
def session_key=(v)
|
53
|
+
@mutex.synchronize { @session_key = v }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Send a Message to the Steam server. If we are in an encrypted
|
57
|
+
# session the Packet body is encrypted before it is sent.
|
58
|
+
#
|
59
|
+
# @see Protocol::Message
|
60
|
+
# @see Networking::Packet
|
61
|
+
#
|
62
|
+
# @param msg [Networking::Message] the message to send
|
63
|
+
def send_msg(msg)
|
64
|
+
data = msg.encode
|
65
|
+
data = Crypto.encrypt(data, session_key) if session_key
|
66
|
+
|
67
|
+
packet = Packet.new(data)
|
68
|
+
write_socket(packet.encode)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Yields each Packet from the socket to the calling block.
|
72
|
+
#
|
73
|
+
# Starts a thread for reading from the socket into a queue. And another
|
74
|
+
# thread to consume that queue. Packets that are consumed from the queue
|
75
|
+
# will be yielded to the block
|
76
|
+
#
|
77
|
+
# @see Networking::Packet
|
78
|
+
def each_packet(&block)
|
79
|
+
start_read_thread
|
80
|
+
start_listen_thread(&block)
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# @api private
|
86
|
+
def start_listen_thread
|
87
|
+
@listen_thread = Thread.new do
|
88
|
+
until disconnected?
|
89
|
+
packet = @packets.pop
|
90
|
+
yield packet if packet
|
91
|
+
end
|
92
|
+
end
|
93
|
+
@listen_thread.abort_on_exception = true
|
94
|
+
end
|
95
|
+
|
96
|
+
# @api private
|
97
|
+
def start_read_thread
|
98
|
+
@read_thread = Thread.new do
|
99
|
+
until disconnected?
|
100
|
+
packet = recv_packet_from_socet
|
101
|
+
@packets.add(packet) if packet
|
102
|
+
end
|
103
|
+
end
|
104
|
+
@read_thread.abort_on_exception = true
|
105
|
+
end
|
106
|
+
|
107
|
+
# @api private
|
108
|
+
def recv_packet_from_socet
|
109
|
+
packet_size = read_packet_header
|
110
|
+
read_packet(packet_size) if packet_size
|
111
|
+
end
|
112
|
+
|
113
|
+
# @api private
|
114
|
+
def read_packet_header
|
115
|
+
data = read_socket(8)
|
116
|
+
return nil if data.nil?
|
117
|
+
|
118
|
+
header = ByteReader.new(StringIO.new(data))
|
119
|
+
|
120
|
+
size = header.signed_int32
|
121
|
+
magic = header.string(4)
|
122
|
+
raise "invalid packet: size=#{size}, magic=#{magic}" unless
|
123
|
+
size.nonzero? && magic == Packet::TCP_MAGIC
|
124
|
+
size
|
125
|
+
end
|
126
|
+
|
127
|
+
# @api private
|
128
|
+
def read_packet(packet_size)
|
129
|
+
data = read_socket(packet_size)
|
130
|
+
data = Crypto.decrypt(data, session_key) if session_key
|
131
|
+
Packet.new(data)
|
132
|
+
end
|
133
|
+
|
134
|
+
# @api private
|
135
|
+
def write_socket(data)
|
136
|
+
socket.write(data)
|
137
|
+
rescue IOError => e
|
138
|
+
# If we are disconnecting, the socket is allowed to be closed
|
139
|
+
return 0 if disconnecting
|
140
|
+
raise e
|
141
|
+
end
|
142
|
+
|
143
|
+
# @api private
|
144
|
+
def read_socket(len)
|
145
|
+
socket.read(len)
|
146
|
+
rescue IOError => e
|
147
|
+
# If we are disconnecting, the socket is allowed to be closed
|
148
|
+
return nil if disconnecting
|
149
|
+
raise e
|
150
|
+
end
|
151
|
+
|
152
|
+
# @api private
|
153
|
+
def socket
|
154
|
+
@mutex.synchronize { @socket }
|
155
|
+
end
|
156
|
+
|
157
|
+
# @api private
|
158
|
+
def socket=(socket)
|
159
|
+
@mutex.synchronize { @socket = socket }
|
160
|
+
end
|
161
|
+
|
162
|
+
# @api private
|
163
|
+
def disconnecting
|
164
|
+
@mutex.synchronize { @disconnecting }
|
165
|
+
end
|
166
|
+
|
167
|
+
# @api private
|
168
|
+
def disconnecting=(v)
|
169
|
+
@mutex.synchronize { @disconnecting = v }
|
170
|
+
end
|
171
|
+
|
172
|
+
# @api private
|
173
|
+
def session_key
|
174
|
+
@mutex.synchronize { @session_key }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Steam
|
3
|
+
module Networking
|
4
|
+
# An object representing a packet from the connection.
|
5
|
+
#
|
6
|
+
# The packets come in the form
|
7
|
+
#
|
8
|
+
# LENGTH MAGIC BODY
|
9
|
+
#
|
10
|
+
# To create a packet only the body needs to be supplied. The body should
|
11
|
+
# never contain the MAGIC or LENGTH
|
12
|
+
class Packet
|
13
|
+
# Valve's TCP Packet identifier
|
14
|
+
TCP_MAGIC = 'VT01'
|
15
|
+
|
16
|
+
# The raw bytes of the TCP packet
|
17
|
+
attr_reader :body
|
18
|
+
|
19
|
+
# The EMsg type derived from the Packet body
|
20
|
+
attr_reader :msg_type
|
21
|
+
alias emsg msg_type
|
22
|
+
|
23
|
+
# Instantiates a Packet object
|
24
|
+
#
|
25
|
+
# @param body [String]
|
26
|
+
def initialize(body)
|
27
|
+
raise 'packet must have raw tcp body' if body.nil? || body.empty?
|
28
|
+
|
29
|
+
@body = body
|
30
|
+
@io = ByteReader.new(StringIO.new(body))
|
31
|
+
@iden = ByteReader.new(StringIO.new(body)).unsigned_int32
|
32
|
+
@msg_type = @iden & ~Message::PROTO_MASK
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
# Encode a Packet to a byte string
|
37
|
+
#
|
38
|
+
# @return [String] byte string representation of the Packet
|
39
|
+
def encode
|
40
|
+
stream = ByteWriter.new
|
41
|
+
stream.write_unsigned_int32(body.length)
|
42
|
+
stream.write_string(TCP_MAGIC)
|
43
|
+
stream.write_string(body)
|
44
|
+
stream.string
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns true if the Packet contains other packets
|
48
|
+
#
|
49
|
+
# @return [Bool]
|
50
|
+
def multi?
|
51
|
+
@msg_type == EMsg::MULTI
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns true if the Packet is Protobuf backed
|
55
|
+
#
|
56
|
+
# @return [Bool]
|
57
|
+
def proto?
|
58
|
+
(@iden & Message::PROTO_MASK) == Message::PROTO_MASK
|
59
|
+
end
|
60
|
+
|
61
|
+
# Converts the Packet into a Message object.
|
62
|
+
#
|
63
|
+
# @param msg [Message] The message to decode from the packet
|
64
|
+
# @return [Message] the resulting message
|
65
|
+
def as_message(msg)
|
66
|
+
cm = if proto?
|
67
|
+
ProtobufMessage.new(MsgHdrProtoBuf.new, msg, @msg_type)
|
68
|
+
else
|
69
|
+
ClientMessage.new(MsgHdr.new, msg, @msg_type)
|
70
|
+
end
|
71
|
+
|
72
|
+
cm.decode(@io)
|
73
|
+
cm
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Steam
|
3
|
+
module Networking
|
4
|
+
# Holds a list of Packet objects in a non-blocking thread safe queue
|
5
|
+
class PacketList
|
6
|
+
def initialize
|
7
|
+
@packets = Thread::Queue.new
|
8
|
+
end
|
9
|
+
|
10
|
+
# Adds a Packet to the internal queue
|
11
|
+
#
|
12
|
+
# @param packet [Packet] the Packet to add
|
13
|
+
def add(packet)
|
14
|
+
@packets.push(packet)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Removes the next message
|
18
|
+
#
|
19
|
+
# @return [Packet] The Packet object
|
20
|
+
def pop
|
21
|
+
@packets.pop(true)
|
22
|
+
rescue ThreadError
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Steam
|
3
|
+
# Namespace for any Networking concerns.
|
4
|
+
#
|
5
|
+
# @see Networking::Connection
|
6
|
+
# @see Networking::Packet
|
7
|
+
# @see Networking::PacketList
|
8
|
+
module Networking
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'steam/networking/packet_list'
|
13
|
+
require 'steam/networking/connection'
|
14
|
+
require 'steam/networking/packet'
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Steam
|
3
|
+
# Allows "plugins" via gems. The plugins are just gems that react to client
|
4
|
+
# events an maybe expose an additional api. For example a CSGO plugin would
|
5
|
+
# be respoinsible for strictly CSGO related things and could have syntatic
|
6
|
+
# candy for things such as logging the player on, and handling the connection
|
7
|
+
# flow of CSGO related connection events. It could also provide the abililty
|
8
|
+
# to send specific GC messages like the requesting of match info.
|
9
|
+
#
|
10
|
+
# The plugins are refered to by name, ie: csgo
|
11
|
+
#
|
12
|
+
# The CSGO plugin would be a gem that is named `steam-csgo` and has the
|
13
|
+
# following struture.
|
14
|
+
#
|
15
|
+
# -steam-csgo
|
16
|
+
# - lib
|
17
|
+
# - steam
|
18
|
+
# - csgo
|
19
|
+
# - base.rb
|
20
|
+
# - csgo.rb
|
21
|
+
# - ... other gem things ...
|
22
|
+
#
|
23
|
+
# The fastpeek/steam-csgo gem is a CSGO plugin for references
|
24
|
+
class Plugins
|
25
|
+
include Enumerable
|
26
|
+
|
27
|
+
attr_reader :client, :loaded_plugins
|
28
|
+
|
29
|
+
# Instantiate a new Plugins engine
|
30
|
+
def initialize(client)
|
31
|
+
@client = client
|
32
|
+
@mutex = Mutex.new
|
33
|
+
@plugins = []
|
34
|
+
@loaded_plugins = []
|
35
|
+
@loaded = false
|
36
|
+
end
|
37
|
+
|
38
|
+
# Determine if the Client has a plugin loaded
|
39
|
+
#
|
40
|
+
# @param plugin [Symbol]
|
41
|
+
# @return [Bool]
|
42
|
+
def loaded?(plugin)
|
43
|
+
@loaded_plugins.include?(plugin.to_sym)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Add a plugin to the internal list. Plugins are not loaded until
|
47
|
+
# #load is called.
|
48
|
+
def plugin(plugin)
|
49
|
+
@plugins << plugin.to_sym
|
50
|
+
@plugins
|
51
|
+
end
|
52
|
+
|
53
|
+
# Load the plugins. Attempt to require each plugin. If the require call
|
54
|
+
# fails an error message is printed.
|
55
|
+
#
|
56
|
+
# If the client has already loaded the plugins, they are not loaded again.
|
57
|
+
#
|
58
|
+
# @return [Integer] the number of plugins it loaded
|
59
|
+
def load
|
60
|
+
return true if @loaded
|
61
|
+
@loaded_plugins = require_plugins
|
62
|
+
Steam.logger.debug("Loaded plugins: #{loaded_plugins}")
|
63
|
+
setup_plugin_helpers(loaded_plugins)
|
64
|
+
@loaded = true
|
65
|
+
@loaded_plugins.count >= 1
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# @api private
|
71
|
+
def setup_plugin_helpers(loaded_plugins)
|
72
|
+
loaded_plugins.each { |plugin| define_plugin_ivar(plugin) }
|
73
|
+
end
|
74
|
+
|
75
|
+
# @api private
|
76
|
+
def define_plugin_ivar(plugin)
|
77
|
+
client.class.class_eval do
|
78
|
+
define_method plugin do
|
79
|
+
var = instance_variable_get("@#{plugin}")
|
80
|
+
|
81
|
+
unless var
|
82
|
+
val = "Steam::#{plugin.capitalize}::Base".constantize.new(self)
|
83
|
+
var = instance_variable_set("@#{plugin}", val)
|
84
|
+
end
|
85
|
+
var
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# @api private
|
91
|
+
def require_plugins
|
92
|
+
required_plugins = @plugins.map do |plugin|
|
93
|
+
begin
|
94
|
+
require "steam/#{plugin}"
|
95
|
+
plugin
|
96
|
+
rescue LoadError
|
97
|
+
msg = "Failed to load #{plugin} plugin. Is it in your Gemfile?"
|
98
|
+
Steamd.logger.error(msg)
|
99
|
+
nil
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
required_plugins.compact
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Steam
|
3
|
+
module Protocol
|
4
|
+
# Represents a Message received or sent to the Steam
|
5
|
+
# network.
|
6
|
+
#
|
7
|
+
# Client messages typically don't have session or steam meta
|
8
|
+
# data associated with them.
|
9
|
+
class ClientMessage
|
10
|
+
include Message
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Steam
|
3
|
+
module Protocol
|
4
|
+
# Represents a GC Message received or sent to the Steam
|
5
|
+
# network via the Game Coordinator.
|
6
|
+
class GcProtobufMessage < ProtobufMessage
|
7
|
+
# Convert the internal gc message into the message we care about
|
8
|
+
#
|
9
|
+
# @param klass [Message] the type of message
|
10
|
+
def as(klass)
|
11
|
+
klass = klass.class unless klass.is_a?(Class)
|
12
|
+
io = StringIO.new(body.payload)
|
13
|
+
@header.decode_from(io)
|
14
|
+
klass.decode(io.read)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Steam
|
3
|
+
module Protocol
|
4
|
+
# The base Steam message. Each message contains a header, body and emsg
|
5
|
+
# attribute.
|
6
|
+
#
|
7
|
+
# The #emsg corresponds to the constants EMsg in the SteamLanguage
|
8
|
+
#
|
9
|
+
# @see ClientMessage
|
10
|
+
# @see ProtobufMessage
|
11
|
+
module Message
|
12
|
+
# Valve's mask for determining if a packet is Protobuf backed
|
13
|
+
PROTO_MASK = 0x80000000
|
14
|
+
|
15
|
+
# Each packet has a header. The header is defined by the Steam Language.
|
16
|
+
# A header can be a MsgHdr, or a MsgHdrProtoBuf
|
17
|
+
#
|
18
|
+
# @see MsgHdrProtoBuf
|
19
|
+
# @see MsgHdr
|
20
|
+
attr_reader :header
|
21
|
+
|
22
|
+
# Each packet has a body, this is the Message being held in the packet.
|
23
|
+
# @see Message
|
24
|
+
attr_reader :body
|
25
|
+
|
26
|
+
# Each packet has an optional byte string payload
|
27
|
+
attr_reader :payload
|
28
|
+
|
29
|
+
# The Message type
|
30
|
+
#
|
31
|
+
# @see EMsg
|
32
|
+
attr_reader :emsg
|
33
|
+
|
34
|
+
# Instantiate a Message object
|
35
|
+
#
|
36
|
+
# @param header [MsgHdr, MsgHdrProtoBuf]
|
37
|
+
# @param body
|
38
|
+
# @param emsg
|
39
|
+
def initialize(header, body, emsg)
|
40
|
+
@header = header
|
41
|
+
@body = body
|
42
|
+
@header.msg = emsg
|
43
|
+
@payload = ByteWriter.new
|
44
|
+
@emsg = emsg
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# By default a Message is not Protobuf backed
|
49
|
+
def proto?
|
50
|
+
false
|
51
|
+
end
|
52
|
+
|
53
|
+
# The steam id associated with the message
|
54
|
+
#
|
55
|
+
# @param _sid [String] the Steam id
|
56
|
+
def steam_id=(_sid)
|
57
|
+
raise NotImplementedError
|
58
|
+
end
|
59
|
+
|
60
|
+
# The session id associated with the message
|
61
|
+
#
|
62
|
+
# @param _sid [String] the Session id
|
63
|
+
def session_id=(_sid)
|
64
|
+
raise NotImplementedError
|
65
|
+
end
|
66
|
+
|
67
|
+
# Decode a Packet from an io object
|
68
|
+
#
|
69
|
+
# @param io [#read] The IO stream
|
70
|
+
def decode(io)
|
71
|
+
@header.decode_from(io)
|
72
|
+
|
73
|
+
if @body.respond_to?(:decode_from)
|
74
|
+
# Steam language
|
75
|
+
@body.decode_from(io)
|
76
|
+
else
|
77
|
+
# Proto
|
78
|
+
@body = @body.class.decode(io.read)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Encode a Packet from to a string
|
83
|
+
#
|
84
|
+
# @see Packet
|
85
|
+
# @return [String] The byte representation of the Packet
|
86
|
+
def encode
|
87
|
+
io = StringIO.new
|
88
|
+
io.set_encoding('BINARY')
|
89
|
+
|
90
|
+
@header.encode_to(io)
|
91
|
+
body = @body.encode
|
92
|
+
io.write(body)
|
93
|
+
io.write(@payload.string)
|
94
|
+
|
95
|
+
io.string
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Steam
|
3
|
+
module Protocol
|
4
|
+
# Represents a Protobuf message. They have a protobuf header, and body.
|
5
|
+
# Their emsg is masked with the PROTO_MASK flag
|
6
|
+
class ProtobufMessage
|
7
|
+
include Message
|
8
|
+
|
9
|
+
# A Protobuf Message contains a steam id and a session id. It also uses
|
10
|
+
# the PROTO_MASK on the EMsg.
|
11
|
+
def initialize(header, body, emsg)
|
12
|
+
super(header, body, emsg | PROTO_MASK)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the masked protobuf emsg
|
16
|
+
def emsg
|
17
|
+
@emsg & ~PROTO_MASK
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get the steam_id associated with this message
|
21
|
+
#
|
22
|
+
# @return [Integer]
|
23
|
+
def steam_id
|
24
|
+
@header.proto.steamid
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Get the steam_id associated with this message
|
29
|
+
#
|
30
|
+
# @return [Integer]
|
31
|
+
def session_id
|
32
|
+
@header.proto.client_sessionid
|
33
|
+
end
|
34
|
+
|
35
|
+
# The steam id associated with the message
|
36
|
+
#
|
37
|
+
# @param sid [String] the Steam id
|
38
|
+
def steam_id=(sid)
|
39
|
+
@header.proto.steamid = sid
|
40
|
+
end
|
41
|
+
|
42
|
+
# The session id associated with the message
|
43
|
+
#
|
44
|
+
# @param sid [String] the Session id
|
45
|
+
def session_id=(sid)
|
46
|
+
@header.proto.client_sessionid = sid
|
47
|
+
end
|
48
|
+
|
49
|
+
def proto?
|
50
|
+
true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Steam
|
3
|
+
# Encapsulates the Steam protocol
|
4
|
+
module Protocol
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'steam/protocol/message'
|
9
|
+
require 'steam/protocol/client_message'
|
10
|
+
require 'steam/protocol/protobuf_message'
|
11
|
+
require 'steam/protocol/gc_message'
|
12
|
+
require 'steam/protocol/gc_protobuf_message'
|
13
|
+
|
14
|
+
# @todo remove these
|
15
|
+
Steam::ClientMessage = Steam::Protocol::ClientMessage
|
16
|
+
Steam::ProtobufMessage = Steam::Protocol::ProtobufMessage
|
17
|
+
Steam::GcMessage = Steam::Protocol::GcMessage
|
18
|
+
Steam::GcProtobufMessage = Steam::Protocol::GcProtobufMessage
|
19
|
+
Steam::Message = Steam::Protocol::Message
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Steam
|
3
|
+
# Represents a Sentry file on disk
|
4
|
+
#
|
5
|
+
# @todo this will overwrite any existing file, namespace
|
6
|
+
# to username?
|
7
|
+
class SentryFile
|
8
|
+
# Writes the digest to the sentry file
|
9
|
+
#
|
10
|
+
# @example Write the SentryFile
|
11
|
+
# file = SentryFile.new
|
12
|
+
# file.write('sha1digest')
|
13
|
+
#
|
14
|
+
# @param digest [String]
|
15
|
+
def write(digest)
|
16
|
+
File.open(path, 'wb') do |file|
|
17
|
+
file.write(digest)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Read the digest from the sentry file. Returns
|
22
|
+
# nil if the Sentry file does not exist.
|
23
|
+
#
|
24
|
+
# @example Read the SentryFile
|
25
|
+
# file = SentryFile.new
|
26
|
+
# file.read # => 'somedigest'
|
27
|
+
#
|
28
|
+
# @return [String,nil]
|
29
|
+
def read
|
30
|
+
return nil unless File.exist?(path)
|
31
|
+
|
32
|
+
File.open(path, 'rb', &:read)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# @api private
|
38
|
+
def path
|
39
|
+
File.expand_path('~/.steam-sentry')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|