warchat 0.0.1
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.
- 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
|
+
|