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/crypto.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'openssl'
|
3
|
+
require 'zlib'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
module Steam
|
7
|
+
# Handles Steam AES key encryption and decrptyion of raw packet data.
|
8
|
+
class Crypto
|
9
|
+
# The Steam public key
|
10
|
+
KEY = 'MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDf7BrWLBBmLBc1OhSwfFkRf53'\
|
11
|
+
'T2Ct64+AVzRkeRuh7h3SiGEYxqQMUeYKO6UWiSRKpI2hzic9pobFhRr3Bvr/WARvY'\
|
12
|
+
'gdTckPv+T1JzZsuVcNfFjrocejN1oWI0Rrtgt4Bo+hOneoo3S57G9F1fOpn5nsQ66'\
|
13
|
+
'WOiu4gZKODnFMBCiQIBEQ=='
|
14
|
+
|
15
|
+
# Encrypt a given string with a given key, get the encrypted data back.
|
16
|
+
#
|
17
|
+
# @param data [String] the data to encrypt
|
18
|
+
# @param key [String] the key to encrypt the data with
|
19
|
+
def self.encrypt(data, key)
|
20
|
+
new.encrypt(StringIO.new(data), key).string
|
21
|
+
end
|
22
|
+
|
23
|
+
# Decrypt a given string with a given key, get the decrypted data back.
|
24
|
+
#
|
25
|
+
# @param data [String] the data to decrypt
|
26
|
+
# @param key [String] the key to decrypt the data with
|
27
|
+
def self.decrypt(data, key)
|
28
|
+
new.decrypt(StringIO.new(data), key).string
|
29
|
+
end
|
30
|
+
|
31
|
+
# Generates a tuple representing the session key, both plain text and
|
32
|
+
# encrypted
|
33
|
+
#
|
34
|
+
# @example Generating a key
|
35
|
+
# crypto = Cyrpto.new
|
36
|
+
# crypted_key, key = crypto.generate_key
|
37
|
+
#
|
38
|
+
# @return [Array] the crypted_key, plain_key
|
39
|
+
def generate_key
|
40
|
+
key = OpenSSL::PKey::RSA.new(Base64.strict_decode64(KEY))
|
41
|
+
plain = Random.new.bytes(32)
|
42
|
+
padding = OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
|
43
|
+
|
44
|
+
[key.public_encrypt(plain, padding), plain]
|
45
|
+
end
|
46
|
+
|
47
|
+
# Encrypts the given IO stream using the given key
|
48
|
+
#
|
49
|
+
# @example Encrypting data
|
50
|
+
# crypto = Crypto.new
|
51
|
+
# crypto.encrypt('data', 'key')
|
52
|
+
#
|
53
|
+
# @param io [#read] The IO object to encrypt
|
54
|
+
# @param key [String] The key
|
55
|
+
# @return [:read] an IO object represeting the encrypted data
|
56
|
+
def encrypt(io, key)
|
57
|
+
iv = Random.new.bytes(16)
|
58
|
+
|
59
|
+
iv_cipher = iv_cipher(key, :encrypt)
|
60
|
+
cipher = data_cipher(key, iv, :encrypt)
|
61
|
+
|
62
|
+
StringIO.new(iv_cipher.update(iv) + iv_cipher.final +
|
63
|
+
cipher.update(io.read) + cipher.final)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Decrypts the given IO stream using the given key
|
67
|
+
#
|
68
|
+
# @example Encrypting data
|
69
|
+
# crypto = Crypto.new
|
70
|
+
# crypto.decrypt(StringIO.new('data'), 'key')
|
71
|
+
#
|
72
|
+
# @param io [#read] The IO object to encrypt
|
73
|
+
# @param key [String] The key
|
74
|
+
# @return [:read] an IO object represeting the decrypted data
|
75
|
+
def decrypt(io, key)
|
76
|
+
iv_cipher = iv_cipher(key, :decrypt)
|
77
|
+
crypted_iv = io.read(16)
|
78
|
+
iv = iv_cipher.update(crypted_iv) + iv_cipher.final
|
79
|
+
|
80
|
+
cipher = data_cipher(key, iv, :decrypt)
|
81
|
+
|
82
|
+
StringIO.new(cipher.update(io.read) + cipher.final)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Get the 32 bit crc for the given key
|
86
|
+
#
|
87
|
+
# @param key [String] The key
|
88
|
+
# @return [Integer] the crc
|
89
|
+
def session_key_crc(key)
|
90
|
+
Zlib.crc32(key)
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
# @api private
|
96
|
+
def data_cipher(key, iv, type)
|
97
|
+
cipher = OpenSSL::Cipher::AES.new(256, :CBC)
|
98
|
+
cipher.send(type)
|
99
|
+
cipher.key = key
|
100
|
+
cipher.iv = iv
|
101
|
+
cipher
|
102
|
+
end
|
103
|
+
|
104
|
+
# @api private
|
105
|
+
def iv_cipher(key, type)
|
106
|
+
iv_cipher = OpenSSL::Cipher::AES.new(256, :ECB)
|
107
|
+
iv_cipher.send(type)
|
108
|
+
iv_cipher.key = key
|
109
|
+
iv_cipher.padding = 0
|
110
|
+
iv_cipher
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Steam
|
3
|
+
# Utility to provide EMsg convenience. Allows looking up
|
4
|
+
# EMsg name by the EMsg integer value.
|
5
|
+
#
|
6
|
+
# EMsgUtil.new(123).name # => 'SOME_EMSG'
|
7
|
+
class EMsgUtil
|
8
|
+
# Instantiate the object
|
9
|
+
#
|
10
|
+
# @param emsg [Integer] the integer emsg value
|
11
|
+
def initialize(emsg)
|
12
|
+
@emsg = emsg
|
13
|
+
end
|
14
|
+
|
15
|
+
# Return the name of the EMsg
|
16
|
+
#
|
17
|
+
# @return [String]
|
18
|
+
def name
|
19
|
+
emsg.to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# @api private
|
25
|
+
def emsg
|
26
|
+
EMsg.constants.select { |x| EMsg.const_get(x) == @emsg }.first
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Steam
|
3
|
+
module Handler
|
4
|
+
# Steam Auth handler. Handles channel encryption and decryption.
|
5
|
+
#
|
6
|
+
# @example Using the handler inside Handler::Collection
|
7
|
+
# collection = Handler::Collection.new
|
8
|
+
# collection << Handler::Auth.new
|
9
|
+
# collection.handle(packet)
|
10
|
+
#
|
11
|
+
# @example Using the handler stand alone
|
12
|
+
# handler = Handler::Auth.new
|
13
|
+
# handler.handle_packet(packet)
|
14
|
+
class Auth
|
15
|
+
include Handler::Base
|
16
|
+
|
17
|
+
# Respond the Channel encrypt requests and results
|
18
|
+
handles EMsg::CHANNEL_ENCRYPT_REQUEST, EMsg::CHANNEL_ENCRYPT_RESULT
|
19
|
+
|
20
|
+
# Override the constructor to set our key to nil. This key will be
|
21
|
+
# used if the encryption is a success to decode and encode all future
|
22
|
+
# packet data.
|
23
|
+
def initialize(*)
|
24
|
+
super
|
25
|
+
@key = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
# Handles the given packet. This handler is only concerned about the
|
29
|
+
# channel encryption request and result.
|
30
|
+
#
|
31
|
+
# @param packet [Networking::Packet] the packet to handle
|
32
|
+
def handle(packet)
|
33
|
+
case packet.msg_type
|
34
|
+
when EMsg::CHANNEL_ENCRYPT_REQUEST
|
35
|
+
handle_channel_encrypt_request(packet)
|
36
|
+
when EMsg::CHANNEL_ENCRYPT_RESULT
|
37
|
+
handle_channel_encrypt_result(packet)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Decodes the packet as a MsgChannelEncryptResult. If the result is
|
44
|
+
# a success the client's connection session key is set.
|
45
|
+
#
|
46
|
+
# @param packet [Networking::Packet]
|
47
|
+
def handle_channel_encrypt_result(packet)
|
48
|
+
msg = packet.as_message(MsgChannelEncryptResult.new)
|
49
|
+
raise 'encrypt result failed' unless msg.body.result == EResult::OK
|
50
|
+
|
51
|
+
# Set the session key
|
52
|
+
client.session_key = @key
|
53
|
+
client.ready
|
54
|
+
Steam.logger.debug('Client channel encrypted')
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
# Decodes the packet as a MsgChannelEncryptResponse. A key is generated
|
59
|
+
# and sent to the steam servers. If the encryption request is a success
|
60
|
+
# the connection's session key is set in the handling of the result
|
61
|
+
#
|
62
|
+
# @param _packet [Networking::Packet]
|
63
|
+
def handle_channel_encrypt_request(_packet)
|
64
|
+
encrypt = Crypto.new
|
65
|
+
crypted_key, key = encrypt.generate_key
|
66
|
+
|
67
|
+
msg = ClientMessage.new(MsgHdr.new,
|
68
|
+
MsgChannelEncryptResponse.new,
|
69
|
+
EMsg::CHANNEL_ENCRYPT_RESPONSE)
|
70
|
+
msg.payload.write(crypted_key)
|
71
|
+
msg.payload.write_int64(encrypt.session_key_crc(crypted_key))
|
72
|
+
|
73
|
+
send_msg(msg)
|
74
|
+
@key = key
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Steam
|
3
|
+
module Handler
|
4
|
+
# Defines the base Handler
|
5
|
+
#
|
6
|
+
# @example Creating a handler
|
7
|
+
# class MyHandler
|
8
|
+
# include HandlerBase
|
9
|
+
#
|
10
|
+
# handles :CLIENT_LOG_OFF
|
11
|
+
#
|
12
|
+
# def handle(packet)
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
module Base
|
16
|
+
# Inject class methods into the hosting class
|
17
|
+
def self.included(base)
|
18
|
+
base.send(:extend, ClassMethods)
|
19
|
+
end
|
20
|
+
|
21
|
+
# The Client object
|
22
|
+
#
|
23
|
+
# @see Client
|
24
|
+
attr_reader :client
|
25
|
+
|
26
|
+
# Instantiate a Handler with a Client
|
27
|
+
#
|
28
|
+
# @example Creating a Handler
|
29
|
+
# class MyHandler
|
30
|
+
# include Handler::Base
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# handler = MyHandler.new(Client.new)
|
34
|
+
def initialize(client)
|
35
|
+
@client = client
|
36
|
+
end
|
37
|
+
|
38
|
+
# Handle the incoming packet
|
39
|
+
#
|
40
|
+
# @param _packet [Networking::Packet] the packet to handle
|
41
|
+
def handle(_packet)
|
42
|
+
raise NotImplementedError
|
43
|
+
end
|
44
|
+
|
45
|
+
# Determines if this handler can handle the given message type. It
|
46
|
+
# compares the symbols given via .handles to known EMsg constants.
|
47
|
+
#
|
48
|
+
# @param msg [Integer]
|
49
|
+
# @return [Bool]
|
50
|
+
def handles?(msg)
|
51
|
+
self.class.handled_messages.any? { |m| msg == m }
|
52
|
+
end
|
53
|
+
|
54
|
+
# Send a message to the client
|
55
|
+
#
|
56
|
+
# @param msg [Message] The Message to send to steam
|
57
|
+
# @return [Bool]
|
58
|
+
def send_msg(msg)
|
59
|
+
@client.send_msg(msg)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Inject methods into the hosting class
|
63
|
+
module ClassMethods
|
64
|
+
# The list of EMsgs that this handler knows
|
65
|
+
# how to handle
|
66
|
+
def handled_messages
|
67
|
+
@handled_messages ||= []
|
68
|
+
end
|
69
|
+
|
70
|
+
# Specify with EMsgs this Handler cares about
|
71
|
+
def handles(*args)
|
72
|
+
handled_messages << args.dup.flatten
|
73
|
+
handled_messages.flatten!
|
74
|
+
handled_messages.uniq!
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Steam
|
3
|
+
module Handler
|
4
|
+
# Holds a collection of Handler objects
|
5
|
+
#
|
6
|
+
# @example Create a collection of Handler objects
|
7
|
+
# collection = Handler::Collection.new
|
8
|
+
# collection << MyHandler.new(client)
|
9
|
+
class Collection
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
# Creates an empty list of handlers
|
13
|
+
def initialize
|
14
|
+
@handlers = []
|
15
|
+
end
|
16
|
+
|
17
|
+
# Handle a packet. If a handler is found that cares about this packet,
|
18
|
+
# the packet object is passed to the handler.
|
19
|
+
#
|
20
|
+
# @param packet [Networking::Packet] the packet to handle
|
21
|
+
def handle(packet)
|
22
|
+
handler = find_handler(packet.msg_type)
|
23
|
+
|
24
|
+
if handler.nil?
|
25
|
+
Steam.logger.debug("No hander found for: #{EMsgUtil.new(packet.emsg).name}")
|
26
|
+
return false
|
27
|
+
end
|
28
|
+
|
29
|
+
handler.handle(packet)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Add a handler to the collection
|
33
|
+
#
|
34
|
+
# @param handlers [Array<Handler::Base>] the handler to add
|
35
|
+
def add(*handlers)
|
36
|
+
handlers.each do |handler|
|
37
|
+
@handlers << handler
|
38
|
+
Steam.logger.debug("Added handler #{handler.class.name}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Iterate through each Handler. Allows the collection to act as an
|
43
|
+
# Enumerable
|
44
|
+
def each(&block)
|
45
|
+
@handlers.each(&block)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# @api private
|
51
|
+
# :nocov:
|
52
|
+
def find_handler(msg)
|
53
|
+
message_handlers = @handlers.select do |handler|
|
54
|
+
handler.handles?(msg)
|
55
|
+
end
|
56
|
+
message_handlers.first
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
module Steam
|
5
|
+
module Handler
|
6
|
+
# Game coordinator handler
|
7
|
+
class GameCoordinator
|
8
|
+
include Handler::Base
|
9
|
+
|
10
|
+
# Describes the EMsgs this Handler cares about
|
11
|
+
handles EMsg::CLIENT_FROM_GC
|
12
|
+
|
13
|
+
# Send a message to the Game Coordinator
|
14
|
+
#
|
15
|
+
# @note you must have asked and been given a GC session
|
16
|
+
# @todo remove hardcoded app id
|
17
|
+
#
|
18
|
+
# @param msg [GcMessage] The message to send
|
19
|
+
# @return [Bool]
|
20
|
+
def send_msg(msg)
|
21
|
+
gcmsg = Steamclient::CMsgGCClient.new
|
22
|
+
gcmsg.appid = 730
|
23
|
+
gcmsg.msgtype = msg.header.msg
|
24
|
+
gcmsg.payload = msg.encode
|
25
|
+
|
26
|
+
header = MsgHdrProtoBuf.new
|
27
|
+
# header.proto.routing_appid = 730
|
28
|
+
gc = ProtobufMessage.new(header, gcmsg, EMsg::CLIENT_TO_GC)
|
29
|
+
|
30
|
+
client.send_msg(gc)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Handle a packet
|
34
|
+
#
|
35
|
+
# @param packet [Networking::Packet] the packet to handle
|
36
|
+
def handle(packet)
|
37
|
+
case packet.msg_type
|
38
|
+
when EMsg::CLIENT_FROM_GC
|
39
|
+
handle_gc_message(packet)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Tell the Steam servers that the Client is playing a game.
|
44
|
+
#
|
45
|
+
# @param game_id [Integer] the game to play
|
46
|
+
#
|
47
|
+
# @example Playing CS:GO
|
48
|
+
# game_coordinator = client.game_coordinator
|
49
|
+
# game_coordinator.play(730, "CS:GO")
|
50
|
+
def play(game_id)
|
51
|
+
game = Steamclient::CMsgClientGamesPlayed.new
|
52
|
+
|
53
|
+
played = Steamclient::CMsgClientGamesPlayed::GamePlayed.new
|
54
|
+
played.game_id = game_id
|
55
|
+
played.token = @client.connect_tokens.shift
|
56
|
+
|
57
|
+
game.games_played ||= [played]
|
58
|
+
|
59
|
+
msg = ProtobufMessage.new(MsgHdrProtoBuf.new, game,
|
60
|
+
EMsg::CLIENT_GAMES_PLAYED_WITH_DATA_BLOB)
|
61
|
+
client.send_msg(msg)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# @api private
|
67
|
+
def handle_gc_message(packet)
|
68
|
+
msg = packet.as_message(Steamclient::CMsgGCClient.new)
|
69
|
+
|
70
|
+
gcmsg = GcProtobufMessage.new(
|
71
|
+
MsgGCHdrProtoBuf.new,
|
72
|
+
msg.body,
|
73
|
+
msg.body.msgtype
|
74
|
+
)
|
75
|
+
|
76
|
+
client.gc_message(gcmsg)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Steam
|
3
|
+
module Handler
|
4
|
+
# Steam apps handler
|
5
|
+
class SteamApps
|
6
|
+
include Handler::Base
|
7
|
+
|
8
|
+
# Describes the EMsgs this Handler cares about
|
9
|
+
handles EMsg::CLIENT_GAME_CONNECT_TOKENS
|
10
|
+
|
11
|
+
# Handle a packet
|
12
|
+
#
|
13
|
+
# @param packet [Networking::Packet] the packet to handle
|
14
|
+
def handle(packet)
|
15
|
+
case packet.msg_type
|
16
|
+
when EMsg::CLIENT_GAME_CONNECT_TOKENS
|
17
|
+
handle_client_game_connect_tokens(packet)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Handles a packet that contains connect tokens. Connect tokens
|
24
|
+
# are required to tell the Steam servers you are playing a game.
|
25
|
+
#
|
26
|
+
# @param packet [Networking::Packet] the packet
|
27
|
+
def handle_client_game_connect_tokens(packet)
|
28
|
+
msg = packet.as_message(Steamclient::CMsgClientGameConnectTokens.new)
|
29
|
+
|
30
|
+
@client.update_connect_tokens(msg.body.tokens,
|
31
|
+
msg.body.max_tokens_to_keep)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Steam
|
4
|
+
module Handler
|
5
|
+
# Handles messages releated to a Steam user
|
6
|
+
#
|
7
|
+
# @example Logging in
|
8
|
+
# steam_user = client.steam_user
|
9
|
+
# steam_user.login(username, password, token, hash)
|
10
|
+
#
|
11
|
+
# @note You need to handle the LogonResponse to catch Steam Guard prompts
|
12
|
+
# and 2FA
|
13
|
+
class SteamUser
|
14
|
+
include Handler::Base
|
15
|
+
|
16
|
+
handles EMsg::CLIENT_LOG_ON_RESPONSE, EMsg::CLIENT_UPDATE_MACHINE_AUTH,
|
17
|
+
EMsg::CLIENT_LOGGED_OFF, EMsg::CLIENT_NEW_LOGIN_KEY
|
18
|
+
|
19
|
+
# rubocop:disable MethodLength
|
20
|
+
# rubocop:disable AbcSize
|
21
|
+
#
|
22
|
+
# Log a Client in
|
23
|
+
#
|
24
|
+
# @param username [String] the username
|
25
|
+
# @param password [String] the password
|
26
|
+
# @param token [String] the SteamGuard token or nil
|
27
|
+
# @param hash [String] the SteamGuard hash or nil
|
28
|
+
#
|
29
|
+
# @return True or false if the message was sent. You must handle the
|
30
|
+
# ClientLogonResponse to know the real logon result
|
31
|
+
def login(username, password, token = nil, hash = nil)
|
32
|
+
msg = ProtobufMessage.new(MsgHdrProtoBuf.new,
|
33
|
+
Steamclient::CMsgClientLogon.new,
|
34
|
+
EMsg::CLIENT_LOGON)
|
35
|
+
|
36
|
+
msg.body.auth_code = token if token
|
37
|
+
msg.body.eresult_sentryfile = EResult::FILE_NOT_FOUND
|
38
|
+
|
39
|
+
if hash
|
40
|
+
msg.body.sha_sentryfile = hash
|
41
|
+
msg.body.eresult_sentryfile = EResult::OK
|
42
|
+
end
|
43
|
+
|
44
|
+
ip = LocalIp.new.to_i ^ MsgClientLogon::OBFUSCATION_MASK
|
45
|
+
msg.body.obfustucated_private_ip = ip
|
46
|
+
msg.body.account_name = username
|
47
|
+
msg.body.password = password
|
48
|
+
msg.body.protocol_version = MsgClientLogon::CURRENT_PROTOCOL
|
49
|
+
msg.body.client_os_type = EOSType::WIN311
|
50
|
+
msg.body.client_package_version = 1771
|
51
|
+
|
52
|
+
send_msg(msg)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Change the Client's persona state.
|
56
|
+
#
|
57
|
+
# @example Setting a Client as "Online"
|
58
|
+
# steam_user = client.steam_user
|
59
|
+
# steam_user.change_persona_state(EPersonaState::ONLINE)
|
60
|
+
#
|
61
|
+
# @param state [EMsg::E_PERSONA_STATE] the new client state
|
62
|
+
# @return True if the message was sent, false otherwise
|
63
|
+
def change_persona_state(state)
|
64
|
+
client_status = Steamclient::CMsgClientChangeStatus.new
|
65
|
+
client_status.persona_state = state
|
66
|
+
|
67
|
+
client_change_status = ProtobufMessage.new(MsgHdrProtoBuf.new,
|
68
|
+
client_status,
|
69
|
+
EMsg::CLIENT_CHANGE_STATUS)
|
70
|
+
send_msg(client_change_status)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Change the Client's name
|
74
|
+
#
|
75
|
+
# @example Setting a Client as "Jimbo"
|
76
|
+
# steam_user = client.steam_user
|
77
|
+
# steam_user.change_persona_name("Jimbo")
|
78
|
+
#
|
79
|
+
# @param name [String] the new client name
|
80
|
+
# @return True if the message was sent, false otherwise
|
81
|
+
def change_persona_name(name)
|
82
|
+
client_status = Steamclient::CMsgClientChangeStatus.new
|
83
|
+
client_status.player_name = name
|
84
|
+
|
85
|
+
client_change_status = ProtobufMessage.new(MsgHdrProtoBuf.new,
|
86
|
+
client_status,
|
87
|
+
EMsg::CLIENT_CHANGE_STATUS)
|
88
|
+
send_msg(client_change_status)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Logs the Client off the Steam network
|
92
|
+
#
|
93
|
+
# @return [Bool]
|
94
|
+
def logoff
|
95
|
+
logoff = ProtobufMessage.new(MsgHdrProtoBuf.new,
|
96
|
+
Steamclient::CMsgClientLogOff.new,
|
97
|
+
EMsg::CLIENT_LOG_OFF)
|
98
|
+
send_msg(logoff)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Handles a given packet
|
102
|
+
#
|
103
|
+
# @param packet [Networking::Packet] the packet to handle
|
104
|
+
def handle(packet)
|
105
|
+
case packet.msg_type
|
106
|
+
when EMsg::CLIENT_LOG_ON_RESPONSE
|
107
|
+
handle_client_logon_response(packet)
|
108
|
+
when EMsg::CLIENT_UPDATE_MACHINE_AUTH
|
109
|
+
handle_machine_auth_update(packet)
|
110
|
+
when EMsg::CLIENT_NEW_LOGIN_KEY
|
111
|
+
handle_login_key(packet)
|
112
|
+
when EMsg::CLIENT_LOGGED_OFF
|
113
|
+
handle_client_logoff(packet)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
# @api private
|
120
|
+
def handle_client_logoff(packet)
|
121
|
+
msg = packet.as_message(Steamclient::CMsgClientLoggedOff.new)
|
122
|
+
raise NotImplementedError, msg.inspect.to_s
|
123
|
+
end
|
124
|
+
|
125
|
+
# @api private
|
126
|
+
def handle_login_key(packet)
|
127
|
+
msg = packet.as_message(Steamclient::CMsgClientNewLoginKey.new)
|
128
|
+
|
129
|
+
resp = Steamclient::CMsgClientNewLoginKeyAccepted.new
|
130
|
+
resp.unique_id = msg.body.unique_id
|
131
|
+
resp = ProtobufMessage.new(MsgHdrProtoBuf.new,
|
132
|
+
resp, EMsg::CLIENT_NEW_LOGIN_KEY_ACCEPTED)
|
133
|
+
send_msg(resp)
|
134
|
+
end
|
135
|
+
|
136
|
+
# @api private
|
137
|
+
def handle_machine_auth_update(packet)
|
138
|
+
msg = packet.as_message(Steamclient::CMsgClientUpdateMachineAuth.new)
|
139
|
+
digest = Digest::SHA1.digest(msg.body.bytes)
|
140
|
+
|
141
|
+
resp = Steamclient::CMsgClientUpdateMachineAuthResponse.new
|
142
|
+
resp.sha_file = digest
|
143
|
+
|
144
|
+
file = SentryFile.new
|
145
|
+
file.write(digest)
|
146
|
+
|
147
|
+
header = MsgHdrProtoBuf.new
|
148
|
+
header.proto.jobid_target = msg.header.proto.jobid_source
|
149
|
+
resp = ProtobufMessage.new(header, resp,
|
150
|
+
EMsg::CLIENT_UPDATE_MACHINE_AUTH_RESPONSE)
|
151
|
+
send_msg(resp)
|
152
|
+
end
|
153
|
+
|
154
|
+
# @api private
|
155
|
+
def handle_client_logon_response(packet)
|
156
|
+
resp = packet.as_message(Steamclient::CMsgClientLogonResponse.new)
|
157
|
+
|
158
|
+
body = resp.body
|
159
|
+
proto = resp.header.proto
|
160
|
+
if body.eresult == EResult::OK && proto.client_sessionid.positive?
|
161
|
+
client.create_session(proto.steamid,
|
162
|
+
proto.client_sessionid)
|
163
|
+
client.start_heartbeat(body.out_of_game_heartbeat_seconds)
|
164
|
+
end
|
165
|
+
|
166
|
+
client.on_logon(resp)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Steam
|
3
|
+
# Messsage handlers
|
4
|
+
#
|
5
|
+
# @see Handler::Base
|
6
|
+
module Handler
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'steam/handler/base'
|
11
|
+
require 'steam/handler/collection'
|
12
|
+
require 'steam/handler/auth'
|
13
|
+
require 'steam/handler/steam_user'
|
14
|
+
require 'steam/handler/steam_apps'
|
15
|
+
require 'steam/handler/game_coordinator'
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'socket'
|
3
|
+
require 'ipaddr'
|
4
|
+
|
5
|
+
module Steam
|
6
|
+
# Access to the Local IP
|
7
|
+
#
|
8
|
+
# @example Get Local IP
|
9
|
+
# ip = LocalIp.new
|
10
|
+
# ip.to_s # => '192.168.1.158'
|
11
|
+
# ip.to_i # => 3232235934
|
12
|
+
class LocalIp
|
13
|
+
def initialize
|
14
|
+
@addr = Socket.ip_address_list.detect(&:ipv4_private?)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Get the IP address as a string
|
18
|
+
#
|
19
|
+
# @return [String] the ip
|
20
|
+
def to_s
|
21
|
+
@addr.ip_address
|
22
|
+
end
|
23
|
+
alias address to_s
|
24
|
+
|
25
|
+
# Get the IP address as a int
|
26
|
+
#
|
27
|
+
# @return [Integer] the ip
|
28
|
+
def to_i
|
29
|
+
IPAddr.new(address).to_i
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|