steamrb 0.1.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.
- 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
|