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/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
|