warchat 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/Rakefile +2 -0
- data/lib/warchat/byte_string.rb +8 -0
- data/lib/warchat/chat/chat_response.rb +20 -0
- data/lib/warchat/chat/client.rb +98 -0
- data/lib/warchat/chat/message.rb +35 -0
- data/lib/warchat/chat/presence.rb +22 -0
- data/lib/warchat/network/binary_reader.rb +46 -0
- data/lib/warchat/network/binary_writer.rb +86 -0
- data/lib/warchat/network/connection.rb +85 -0
- data/lib/warchat/network/request.rb +60 -0
- data/lib/warchat/network/response.rb +25 -0
- data/lib/warchat/network/session.rb +85 -0
- data/lib/warchat/srp/client.rb +138 -0
- data/lib/warchat/timer.rb +45 -0
- data/lib/warchat/version.rb +3 -0
- data/lib/warchat.rb +7 -0
- data/warchat.gemspec +24 -0
- metadata +115 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Warchat module Chat
|
2
|
+
module ChatResponse
|
3
|
+
def presence?
|
4
|
+
chat_type == 'wow_presence'
|
5
|
+
end
|
6
|
+
|
7
|
+
def ack?
|
8
|
+
chat_type == 'message_ack'
|
9
|
+
end
|
10
|
+
|
11
|
+
def message?
|
12
|
+
chat_type == 'wow_message'
|
13
|
+
end
|
14
|
+
|
15
|
+
def chat_type
|
16
|
+
self['chatType']
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Warchat
|
2
|
+
module Chat
|
3
|
+
class Warchat::Chat::Client
|
4
|
+
attr_accessor :on_message,:on_presence,:on_logout,:on_fail,:on_establish
|
5
|
+
attr_accessor :on_message_afk,:on_message_dnd,:on_message_guild_chat,:on_message_motd,:on_message_officer_chat,:on_message_whisper,:on_chat_logout
|
6
|
+
attr_accessor :character_name,:character_realm
|
7
|
+
|
8
|
+
attr_reader :session
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@session = Warchat::Network::Session.new
|
12
|
+
|
13
|
+
[:receive,:establish,:error].each do |m|
|
14
|
+
session.send("on_#{m}=".to_sym, method("session_#{m}".to_sym))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def start username, password
|
19
|
+
self.session.start(username,password)
|
20
|
+
end
|
21
|
+
|
22
|
+
def session_error response
|
23
|
+
on_fail.andand.call(response["body"]) if response.target == "/chat-login"
|
24
|
+
end
|
25
|
+
|
26
|
+
def session_establish response
|
27
|
+
puts 'Session Established'
|
28
|
+
on_establish.andand.call(response)
|
29
|
+
end
|
30
|
+
|
31
|
+
def session_receive response
|
32
|
+
send(response.target.gsub('/','').underscore.to_sym,response)
|
33
|
+
end
|
34
|
+
|
35
|
+
def login
|
36
|
+
request = Warchat::Network::Request.new("/chat-login",:options=>{:mature_filter=>'false'},:n=>character_name,:r=>character_realm)
|
37
|
+
session.send_request(request)
|
38
|
+
@timer = Warchat::Timer.new(120,&method(:keep_alive))
|
39
|
+
end
|
40
|
+
|
41
|
+
def logout
|
42
|
+
request = Warchat::Network::Request.new('/chat-logout',:chatSessionId=>@chat_session_id)
|
43
|
+
end
|
44
|
+
|
45
|
+
def chat_logout response
|
46
|
+
puts 'Logged out of chat'
|
47
|
+
@timer.andand.stop
|
48
|
+
on_chat_logout.andand.call response
|
49
|
+
session.close
|
50
|
+
end
|
51
|
+
|
52
|
+
def chat_login response
|
53
|
+
puts "Logged into chat"
|
54
|
+
@chat_session_id = response["chatSessionId"]
|
55
|
+
end
|
56
|
+
|
57
|
+
def chat response
|
58
|
+
response.extend(ChatResponse)
|
59
|
+
if response.ack?
|
60
|
+
puts("chat ack")
|
61
|
+
elsif response.message?
|
62
|
+
message = Message.new(response)
|
63
|
+
on_message.andand.call(message)
|
64
|
+
send("on_message_#{message.type}".to_sym).andand.call(message)
|
65
|
+
elsif response.presence?
|
66
|
+
puts
|
67
|
+
on_presence.andand.call(Presence.new(response))
|
68
|
+
else
|
69
|
+
puts "unhandled chat type: #{response.chat_type}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def close
|
74
|
+
request = Warchat::Network::Request.new("/chat-logout",:chatSessionId=>@chat_session_id)
|
75
|
+
session.send_request(request)
|
76
|
+
end
|
77
|
+
|
78
|
+
def keep_alive
|
79
|
+
puts "Sending 'keep-alive'"
|
80
|
+
|
81
|
+
request = Warchat::Network::Request("/ah-mail")
|
82
|
+
request["r"] = realm
|
83
|
+
request["cn"] = name
|
84
|
+
session.send_request(request)
|
85
|
+
end
|
86
|
+
|
87
|
+
def message(msg, chat_type = Message.CHAT_MSG_TYPE_GUILD_CHAT)
|
88
|
+
request = Warchat::Network::Request.new("/chat-guild",:type=>chat_type,:body=>msg,:chatSessionId=>@chat_session_id)
|
89
|
+
session.send_request(request)
|
90
|
+
end
|
91
|
+
|
92
|
+
def whisper(msg, char_id)
|
93
|
+
request = Warchat::Network::Request.new("/chat-whisper",:to=>char_id,:body=>msg,:chatSessionId=>@chat_session_id)
|
94
|
+
session.send_request(request)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Warchat
|
2
|
+
module Chat
|
3
|
+
class Message
|
4
|
+
CHAT_ID_TYPE_CHARACTER = "character"
|
5
|
+
CHAT_ID_TYPE_GUILD = "guild"
|
6
|
+
CHAT_ID_TYPE_GUILD_MEMBER = "guild_member"
|
7
|
+
|
8
|
+
CHAT_MSG_TYPE_AFK = "afk"
|
9
|
+
CHAT_MSG_TYPE_DND = "dnd"
|
10
|
+
CHAT_MSG_TYPE_GUILD_CHAT = "guild_chat"
|
11
|
+
CHAT_MSG_TYPE_GUILD_MOTD = "motd"
|
12
|
+
CHAT_MSG_TYPE_OFFICER_CHAT = "officer_chat"
|
13
|
+
CHAT_MSG_TYPE_WHISPER = "whisper"
|
14
|
+
|
15
|
+
attr_reader :type,:body,:from_type,:character_id,:from
|
16
|
+
|
17
|
+
def initialize response
|
18
|
+
@type = response["messageType"]
|
19
|
+
@body = response['body']
|
20
|
+
@from = response["from"]
|
21
|
+
@from_type = from["chatIdType"]
|
22
|
+
|
23
|
+
@character_id = from["characterId"]
|
24
|
+
end
|
25
|
+
|
26
|
+
def character_name
|
27
|
+
@character_id.andand.split(':')[-2]
|
28
|
+
end
|
29
|
+
|
30
|
+
def realm_id
|
31
|
+
@character_id.andand.split(':').last
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Warchat
|
2
|
+
module Chat
|
3
|
+
class Presence
|
4
|
+
attr_reader :name,:character
|
5
|
+
|
6
|
+
def initialize response
|
7
|
+
@type = response["presenceType"];
|
8
|
+
@character = response["character"];
|
9
|
+
@name = character["n"];
|
10
|
+
end
|
11
|
+
|
12
|
+
def offline?
|
13
|
+
@type.andand.include? 'offline'
|
14
|
+
end
|
15
|
+
|
16
|
+
def type
|
17
|
+
return 'unknown' unless @type
|
18
|
+
@type.split('_')[1..-1].join '_'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Warchat
|
2
|
+
module Network
|
3
|
+
class BinaryReader
|
4
|
+
def initialize socket
|
5
|
+
@socket = socket
|
6
|
+
end
|
7
|
+
|
8
|
+
TYPES = [:hash,:array,:int_32,:string,:string,:boolean,:int_64]
|
9
|
+
def parse_next *args
|
10
|
+
send TYPES[byte.unpack('C').first-1]
|
11
|
+
end
|
12
|
+
|
13
|
+
def substream l
|
14
|
+
@socket.read l
|
15
|
+
end
|
16
|
+
|
17
|
+
def byte
|
18
|
+
substream 1
|
19
|
+
end
|
20
|
+
|
21
|
+
def string
|
22
|
+
substream(int_32)
|
23
|
+
end
|
24
|
+
|
25
|
+
def array
|
26
|
+
(1..(int_32)).map &method(:parse_next)
|
27
|
+
end
|
28
|
+
|
29
|
+
def hash
|
30
|
+
Hash[*(1..(int_32)).map do
|
31
|
+
[string,parse_next]
|
32
|
+
end.flatten(1)]
|
33
|
+
end
|
34
|
+
|
35
|
+
{16=>'n',32=>'N',64=>'L_'}.each do |size,directive|
|
36
|
+
define_method "int_#{size}".to_sym do
|
37
|
+
substream(size/8).unpack(directive).first
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def boolean
|
42
|
+
byte == "\001"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Warchat
|
2
|
+
module Network
|
3
|
+
class BinaryWriter
|
4
|
+
|
5
|
+
attr_reader :stream
|
6
|
+
|
7
|
+
def initialize stream
|
8
|
+
@stream = stream
|
9
|
+
end
|
10
|
+
|
11
|
+
def write obj
|
12
|
+
m = (obj.class.ancestors.map do |c|
|
13
|
+
"write_#{File.basename c.name.underscore}".to_sym
|
14
|
+
end.find(&method(:respond_to?)) or :write_string)
|
15
|
+
send(m,obj)
|
16
|
+
end
|
17
|
+
|
18
|
+
def write_string obj
|
19
|
+
byte 5
|
20
|
+
string obj.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
def write_integer obj
|
24
|
+
if obj < 2**32
|
25
|
+
byte 3
|
26
|
+
int_32 obj
|
27
|
+
else
|
28
|
+
byte 7
|
29
|
+
int_64 obj
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def write_hash obj
|
34
|
+
byte 1
|
35
|
+
int_32 obj.size
|
36
|
+
obj.each do |k,v|
|
37
|
+
string(k.to_s)
|
38
|
+
write(v)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def write_array obj
|
43
|
+
byte 2
|
44
|
+
int_32 obj.size
|
45
|
+
obj.each do |v|
|
46
|
+
write(v)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def write_byte_string obj
|
51
|
+
byte 4
|
52
|
+
int_32 obj.length
|
53
|
+
bytes obj
|
54
|
+
end
|
55
|
+
|
56
|
+
def write_bool obj
|
57
|
+
byte 6
|
58
|
+
byte(obj ? 1 : 0)
|
59
|
+
end
|
60
|
+
alias_method :write_true_class,:write_bool
|
61
|
+
alias_method :write_false_class,:write_bool
|
62
|
+
|
63
|
+
|
64
|
+
{16=>'n',32=>'N',64=>'L_'}.each do |size,directive|
|
65
|
+
define_method "int_#{size}".to_sym do |obj|
|
66
|
+
@stream.print [obj].pack(directive)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def string obj
|
71
|
+
obj = obj.to_s
|
72
|
+
int_32 obj.length
|
73
|
+
@stream.print obj
|
74
|
+
end
|
75
|
+
|
76
|
+
def byte obj
|
77
|
+
obj = (obj.is_a? Integer and [obj].pack('C') or obj.to_s[0..0])
|
78
|
+
@stream.print obj
|
79
|
+
end
|
80
|
+
|
81
|
+
def bytes obj
|
82
|
+
@stream.print obj.to_s
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module Warchat
|
5
|
+
module Network
|
6
|
+
class Connection
|
7
|
+
|
8
|
+
attr_accessor :host,:port,:on_send,:on_receive,:on_close
|
9
|
+
|
10
|
+
def initialize *args
|
11
|
+
options = args.pop if args.last.is_a? Hash
|
12
|
+
|
13
|
+
self.host = (args.shift or "m.us.wowarmory.com")
|
14
|
+
self.port = (args.shift or 8780)
|
15
|
+
@closed = true
|
16
|
+
@queue = []
|
17
|
+
@mutex = Mutex.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def start
|
21
|
+
close
|
22
|
+
|
23
|
+
@closed = false;
|
24
|
+
@socket = TCPSocket.open(host,port)
|
25
|
+
@socket.sync = false
|
26
|
+
@request_thread = Thread.new &method(:handle_requests)
|
27
|
+
@response_thread = Thread.new &method(:handle_responses)
|
28
|
+
@request_thread['name'] = 'Request Thead'
|
29
|
+
@response_thread['name'] = 'Response Thread'
|
30
|
+
end
|
31
|
+
|
32
|
+
def close reason = ""
|
33
|
+
return if is_closed?
|
34
|
+
|
35
|
+
on_close.andand.call(reason)
|
36
|
+
|
37
|
+
@mutex.synchronize do
|
38
|
+
@closed = true
|
39
|
+
@socket.close
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def is_closed?
|
44
|
+
@closed or @socket.nil?
|
45
|
+
end
|
46
|
+
|
47
|
+
def send_request request
|
48
|
+
@mutex.synchronize do
|
49
|
+
@queue << request
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def handle_responses
|
54
|
+
until is_closed?
|
55
|
+
response = Response.new(@socket)
|
56
|
+
on_receive.andand.call(response) unless is_closed?
|
57
|
+
sleep 0.01
|
58
|
+
end
|
59
|
+
rescue Exception => e
|
60
|
+
puts e.message
|
61
|
+
puts e.backtrace unless e.is_a? IOError
|
62
|
+
end
|
63
|
+
|
64
|
+
def handle_requests
|
65
|
+
until is_closed?
|
66
|
+
@mutex.synchronize do
|
67
|
+
until @queue.empty?
|
68
|
+
|
69
|
+
request = @queue.shift
|
70
|
+
unless is_closed?
|
71
|
+
request.stream @socket
|
72
|
+
@socket.flush
|
73
|
+
on_send.andand.call(request)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
sleep 0.01
|
78
|
+
end
|
79
|
+
rescue Exception => e
|
80
|
+
puts e.message
|
81
|
+
puts e.backtrace
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Warchat
|
2
|
+
module Network
|
3
|
+
class Request < Hash
|
4
|
+
class StringSocket
|
5
|
+
def initialize
|
6
|
+
@stream = ""
|
7
|
+
end
|
8
|
+
|
9
|
+
def print obj
|
10
|
+
@stream << obj.to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def length
|
14
|
+
@stream.length
|
15
|
+
end
|
16
|
+
|
17
|
+
def value
|
18
|
+
@stream
|
19
|
+
end
|
20
|
+
end
|
21
|
+
attr_reader :id,:target
|
22
|
+
|
23
|
+
def initialize target,*args
|
24
|
+
@target = target
|
25
|
+
@@request_count ||= -1
|
26
|
+
@@request_count += 1
|
27
|
+
@id = @@request_count
|
28
|
+
merge! args.shift if args.first.is_a? Hash
|
29
|
+
super *args
|
30
|
+
end
|
31
|
+
|
32
|
+
def stream socket
|
33
|
+
writer = BinaryWriter.new socket
|
34
|
+
writer.string(target)
|
35
|
+
writer.int_32(@id)
|
36
|
+
each do |k,v|
|
37
|
+
if [Hash,Array,Warchat::ByteString].none? &v.method(:is_a?)
|
38
|
+
writer.byte 5
|
39
|
+
writer.string k
|
40
|
+
writer.string v.to_s
|
41
|
+
else
|
42
|
+
writer.byte 4
|
43
|
+
writer.string k
|
44
|
+
tmp_socket = StringSocket.new
|
45
|
+
tmp = BinaryWriter.new(tmp_socket)
|
46
|
+
tmp.write v
|
47
|
+
writer.int_32(tmp_socket.length)
|
48
|
+
writer.bytes(tmp_socket.value)
|
49
|
+
end
|
50
|
+
writer.byte 0xFF
|
51
|
+
end
|
52
|
+
writer.byte 0xFF
|
53
|
+
end
|
54
|
+
|
55
|
+
def inspect
|
56
|
+
"<#{self.class.name} id:#{id.inspect} target:#{target.inspect} #{super}>"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Warchat
|
2
|
+
module Network
|
3
|
+
class Response < Hash
|
4
|
+
attr_accessor :length,:status,:target,:id
|
5
|
+
def initialize socket,*args
|
6
|
+
super *args
|
7
|
+
reader = BinaryReader.new socket
|
8
|
+
self.length = reader.int_32
|
9
|
+
self.status = reader.int_16
|
10
|
+
self.target = reader.string
|
11
|
+
self.id = reader.int_32
|
12
|
+
|
13
|
+
merge! reader.parse_next
|
14
|
+
end
|
15
|
+
|
16
|
+
def ok?
|
17
|
+
status == 200
|
18
|
+
end
|
19
|
+
|
20
|
+
def inspect
|
21
|
+
"<#{self.class.name} id:#{id.inspect} target:#{target.inspect} status:#{status.inspect} #{super}>"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Warchat
|
2
|
+
module Network
|
3
|
+
class Session
|
4
|
+
|
5
|
+
attr_accessor :on_close,:on_receive,:on_establish,:on_error
|
6
|
+
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@connection = Warchat::Network::Connection.new
|
10
|
+
|
11
|
+
@connection.on_close = method(:connection_close)
|
12
|
+
@connection.on_receive = method(:connection_receive)
|
13
|
+
end
|
14
|
+
|
15
|
+
def start account_name, password
|
16
|
+
@account_name = account_name
|
17
|
+
@password = password
|
18
|
+
|
19
|
+
@connection.start
|
20
|
+
|
21
|
+
@srp = Warchat::Srp::Client.new
|
22
|
+
|
23
|
+
send_request Request.new('/authenticate1',
|
24
|
+
:screenRes => "PHONE_HIGH",
|
25
|
+
:device => "iPhone",
|
26
|
+
:deviceSystemVersion => "4.2.1",
|
27
|
+
:deviceModel => "iPhone3,1",
|
28
|
+
:appV => "3.0.0",
|
29
|
+
:deviceTime => Time.now.to_i,
|
30
|
+
:deviceTimeZoneId => "America/New_York",
|
31
|
+
:clientA => @srp.a_bytes,
|
32
|
+
:appId => "Armory",
|
33
|
+
:deviceId => '50862581c5dc46072050d51886cbae3149b3473c',
|
34
|
+
:emailAddress => account_name,
|
35
|
+
:deviceTimeZone => "-14400000",
|
36
|
+
:locale => "en_US"
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
def established?
|
41
|
+
@established
|
42
|
+
end
|
43
|
+
|
44
|
+
def close reason=''
|
45
|
+
@connection.andand.close reason
|
46
|
+
end
|
47
|
+
|
48
|
+
def send_request request
|
49
|
+
@connection.andand.send_request(request)
|
50
|
+
end
|
51
|
+
|
52
|
+
def stage_1 response
|
53
|
+
proof = @srp.auth1_proof(response["user"], @password[0..15].upcase, response["salt"], response["B"])
|
54
|
+
puts("sending client proof: 0x#{@srp.unpack_int(proof).to_s(16)} length: #{proof.length}")
|
55
|
+
send_request(Request.new("/authenticate2",:clientProof=>proof))
|
56
|
+
end
|
57
|
+
|
58
|
+
def stage_2 response
|
59
|
+
puts("sending client proof2")
|
60
|
+
send_request(Request.new("/authenticate2",:clientProof=>Warchat::ByteString.new("\000")))
|
61
|
+
end
|
62
|
+
|
63
|
+
def stage_3 response
|
64
|
+
@established = true
|
65
|
+
on_establish.andand.call(response)
|
66
|
+
end
|
67
|
+
|
68
|
+
def connection_receive response
|
69
|
+
puts response.inspect
|
70
|
+
m = "stage_#{response['stage']}".to_sym
|
71
|
+
if response.ok?
|
72
|
+
respond_to? m and send(m,response) or on_receive.andand.call(response)
|
73
|
+
else
|
74
|
+
error = response["body"]
|
75
|
+
puts("error: " + error)
|
76
|
+
close(error) if respond_to? m
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def connection_close reason
|
81
|
+
on_close.andand.call(reason)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
module Warchat
|
5
|
+
module Srp
|
6
|
+
class Client
|
7
|
+
G = 2
|
8
|
+
|
9
|
+
MODULUS_SIZE = 128
|
10
|
+
MODULUS = 0x86a7f6deeb306ce519770fe37d556f29944132554ded0bd68205e27f3231fef5a10108238a3150c59caf7b0b6478691c13a6acf5e1b5adafd4a943d4a21a142b800e8a55f8bfbac700eb77a7235ee5a609e350ea9fc19f10d921c2fa832e4461b7125d38d254a0be873dfc27858acb3f8b9f258461e4373bc3a6c2a9634324ab
|
11
|
+
|
12
|
+
SALT_SIZE = 32
|
13
|
+
HASH_SIZE = 32
|
14
|
+
SESSION_KEY_SIZE = HASH_SIZE * 2
|
15
|
+
|
16
|
+
attr_reader :b,:b_bytes
|
17
|
+
|
18
|
+
def modpow(a, n, m)
|
19
|
+
r = 1
|
20
|
+
while true
|
21
|
+
r = r * a % m if n[0] == 1
|
22
|
+
n >>= 1
|
23
|
+
return r if n == 0
|
24
|
+
a = a * a % m
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def pack_int int
|
29
|
+
Warchat::ByteString.new [int.to_s(16).reverse].pack('h*')
|
30
|
+
end
|
31
|
+
|
32
|
+
def unpack_int str
|
33
|
+
str.unpack('h*').first.reverse.hex
|
34
|
+
end
|
35
|
+
|
36
|
+
def digest_int s
|
37
|
+
unpack_int(Digest::SHA2.digest(s))
|
38
|
+
end
|
39
|
+
|
40
|
+
def adjust_size str,length
|
41
|
+
str[0..(length-1)].ljust(length,"\000")
|
42
|
+
end
|
43
|
+
|
44
|
+
def random_crypt
|
45
|
+
unpack_int(OpenSSL::Random.random_bytes(MODULUS_SIZE*2)) % MODULUS
|
46
|
+
end
|
47
|
+
|
48
|
+
def hN_xor_hG
|
49
|
+
# xor H(N) and H(G)
|
50
|
+
return @hN_xor_hG if @hN_xor_hG
|
51
|
+
hN = Digest::SHA2.digest(pack_int(MODULUS))
|
52
|
+
hG = Digest::SHA2.digest(pack_int(G))
|
53
|
+
|
54
|
+
@hN_xor_hG = "\000" * HASH_SIZE
|
55
|
+
|
56
|
+
HASH_SIZE.times do |i| @hN_xor_hG[i] = (hN[i] ^ hG[i]) end
|
57
|
+
|
58
|
+
@hN_xor_hG
|
59
|
+
end
|
60
|
+
|
61
|
+
def a
|
62
|
+
@a ||= random_crypt
|
63
|
+
end
|
64
|
+
|
65
|
+
def a_bytes
|
66
|
+
@a_bytes ||= adjust_size(pack_int(modpow(G,a,MODULUS)),MODULUS_SIZE)
|
67
|
+
end
|
68
|
+
|
69
|
+
def k
|
70
|
+
@k ||= digest_int(pack_int(MODULUS)+pack_int(G))
|
71
|
+
end
|
72
|
+
|
73
|
+
def u
|
74
|
+
# H(A | B)
|
75
|
+
@u ||= digest_int(a_bytes+b_bytes)
|
76
|
+
end
|
77
|
+
|
78
|
+
def x
|
79
|
+
# H(salt | H(userHash | : | sessionPassword))
|
80
|
+
@x ||= digest_int(@salt+Digest::SHA2.digest(@user+":"+@password))
|
81
|
+
end
|
82
|
+
|
83
|
+
def s
|
84
|
+
# (B - k * G^x) ^ (a + u * x)
|
85
|
+
return modpow(b - k * modpow(G, x, MODULUS), a + u * x, MODULUS)
|
86
|
+
end
|
87
|
+
|
88
|
+
def s_bytes
|
89
|
+
@s_bytes ||= adjust_size(pack_int(s),(MODULUS_SIZE))
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def auth1_proof user, password, salt, b_bytes
|
94
|
+
@b = unpack_int b_bytes
|
95
|
+
@b_bytes = adjust_size(b_bytes,MODULUS_SIZE)
|
96
|
+
@salt = adjust_size(salt,SALT_SIZE)
|
97
|
+
@user = user
|
98
|
+
@password = password
|
99
|
+
|
100
|
+
# hash this to generate client proof, H(H(N) xor H(G) | H(userHash) | salt | A | B | K)
|
101
|
+
Warchat::ByteString.new Digest::SHA2.digest(hN_xor_hG+Digest::SHA2.digest(@user)+salt+a_bytes+@b_bytes+session_key)
|
102
|
+
end
|
103
|
+
|
104
|
+
def session_key
|
105
|
+
return @session_key if @session_key
|
106
|
+
@session_key = "\000" * SESSION_KEY_SIZE
|
107
|
+
|
108
|
+
l = s_bytes.length
|
109
|
+
offset = (l.odd? and 1 or 0)
|
110
|
+
l -= offset
|
111
|
+
|
112
|
+
l = [l/2,MODULUS_SIZE].min
|
113
|
+
|
114
|
+
temp = ''
|
115
|
+
l.times do |i|
|
116
|
+
temp << s_bytes[i*2+offset]
|
117
|
+
end
|
118
|
+
|
119
|
+
hash = Digest::SHA2.digest(temp)
|
120
|
+
HASH_SIZE.times do |i|
|
121
|
+
@session_key[i*2] = hash[i]
|
122
|
+
end
|
123
|
+
|
124
|
+
temp = ''
|
125
|
+
l.times do |i|
|
126
|
+
temp << s_bytes[i*2+offset+1]
|
127
|
+
end
|
128
|
+
|
129
|
+
hash = Digest::SHA2.digest(temp)
|
130
|
+
HASH_SIZE.times do |i|
|
131
|
+
@session_key[i*2+1] = hash[i]
|
132
|
+
end
|
133
|
+
|
134
|
+
@session_key
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'monitor'
|
2
|
+
|
3
|
+
module Warchat class Timer
|
4
|
+
def initialize(interval, &handler)
|
5
|
+
|
6
|
+
raise ArgumentError, "Illegal interval" if interval < 0
|
7
|
+
@interval = interval
|
8
|
+
extend MonitorMixin
|
9
|
+
@run = true
|
10
|
+
@th = Thread.new do
|
11
|
+
while run?
|
12
|
+
do_sleep and handler.call rescue nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
@th['name'] = 'Timer'
|
16
|
+
end
|
17
|
+
|
18
|
+
def stop
|
19
|
+
synchronize do
|
20
|
+
@run = false
|
21
|
+
end
|
22
|
+
sleeping? and @th.kill or @th.join
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def sleeping?
|
28
|
+
synchronize do @sleeping end
|
29
|
+
end
|
30
|
+
|
31
|
+
def do_sleep
|
32
|
+
synchronize do @sleeping = true end
|
33
|
+
sleep(@interval)
|
34
|
+
synchronize do @sleeping = false end
|
35
|
+
rescue
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def run?
|
40
|
+
synchronize do
|
41
|
+
@run
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/warchat.rb
ADDED
data/warchat.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "warchat/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "warchat"
|
7
|
+
s.version = Warchat::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Zachary Gavin"]
|
10
|
+
s.email = ["zgavin@gmail.com"]
|
11
|
+
s.homepage = "http://www.github.com/zgavin/warchat"
|
12
|
+
s.summary = %q{A simple interface to World of Warcraft Remote Guild Chat based off Eike Siewertsen's C# implementation}
|
13
|
+
s.description = %q{A simple interface to World of Warcraft Remote Guild Chat in Ruby. Supports whispers, guild chat, officer chat, and presence notifications. Many thanks to Eike Siewertsen (https://github.com/fry) for his work deciphering the protocol. }
|
14
|
+
|
15
|
+
s.rubyforge_project = "warchat"
|
16
|
+
|
17
|
+
s.add_dependency('activesupport','>= 3.0.0')
|
18
|
+
s.add_dependency('andand')
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: warchat
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Zachary Gavin
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-05-16 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: activesupport
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 7
|
30
|
+
segments:
|
31
|
+
- 3
|
32
|
+
- 0
|
33
|
+
- 0
|
34
|
+
version: 3.0.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: andand
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
type: :runtime
|
50
|
+
version_requirements: *id002
|
51
|
+
description: "A simple interface to World of Warcraft Remote Guild Chat in Ruby. Supports whispers, guild chat, officer chat, and presence notifications. Many thanks to Eike Siewertsen (https://github.com/fry) for his work deciphering the protocol. "
|
52
|
+
email:
|
53
|
+
- zgavin@gmail.com
|
54
|
+
executables: []
|
55
|
+
|
56
|
+
extensions: []
|
57
|
+
|
58
|
+
extra_rdoc_files: []
|
59
|
+
|
60
|
+
files:
|
61
|
+
- .gitignore
|
62
|
+
- Gemfile
|
63
|
+
- Rakefile
|
64
|
+
- lib/warchat.rb
|
65
|
+
- lib/warchat/byte_string.rb
|
66
|
+
- lib/warchat/chat/chat_response.rb
|
67
|
+
- lib/warchat/chat/client.rb
|
68
|
+
- lib/warchat/chat/message.rb
|
69
|
+
- lib/warchat/chat/presence.rb
|
70
|
+
- lib/warchat/network/binary_reader.rb
|
71
|
+
- lib/warchat/network/binary_writer.rb
|
72
|
+
- lib/warchat/network/connection.rb
|
73
|
+
- lib/warchat/network/request.rb
|
74
|
+
- lib/warchat/network/response.rb
|
75
|
+
- lib/warchat/network/session.rb
|
76
|
+
- lib/warchat/srp/client.rb
|
77
|
+
- lib/warchat/timer.rb
|
78
|
+
- lib/warchat/version.rb
|
79
|
+
- warchat.gemspec
|
80
|
+
has_rdoc: true
|
81
|
+
homepage: http://www.github.com/zgavin/warchat
|
82
|
+
licenses: []
|
83
|
+
|
84
|
+
post_install_message:
|
85
|
+
rdoc_options: []
|
86
|
+
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
hash: 3
|
95
|
+
segments:
|
96
|
+
- 0
|
97
|
+
version: "0"
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
hash: 3
|
104
|
+
segments:
|
105
|
+
- 0
|
106
|
+
version: "0"
|
107
|
+
requirements: []
|
108
|
+
|
109
|
+
rubyforge_project: warchat
|
110
|
+
rubygems_version: 1.5.0
|
111
|
+
signing_key:
|
112
|
+
specification_version: 3
|
113
|
+
summary: A simple interface to World of Warcraft Remote Guild Chat based off Eike Siewertsen's C# implementation
|
114
|
+
test_files: []
|
115
|
+
|