overlook-csgo 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitattributes +2 -0
- data/.gitignore +5 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +115 -0
- data/README.md +77 -0
- data/Rakefile +6 -0
- data/bin/console +7 -0
- data/bin/setup +7 -0
- data/lib/overlook.rb +20 -0
- data/lib/overlook/bit_buffer.rb +52 -0
- data/lib/overlook/bit_reader.rb +64 -0
- data/lib/overlook/byte_reader.rb +58 -0
- data/lib/overlook/csgo.rb +9 -0
- data/lib/overlook/csgo/demo.rb +33 -0
- data/lib/overlook/csgo/demo/base.rb +20 -0
- data/lib/overlook/csgo/demo/command.rb +67 -0
- data/lib/overlook/csgo/demo/command_factory.rb +15 -0
- data/lib/overlook/csgo/demo/encrypted_data_handler.rb +16 -0
- data/lib/overlook/csgo/demo/game_event_list_message_handler.rb +19 -0
- data/lib/overlook/csgo/demo/game_event_message_handler.rb +35 -0
- data/lib/overlook/csgo/demo/header.rb +45 -0
- data/lib/overlook/csgo/demo/key_reader.rb +18 -0
- data/lib/overlook/csgo/demo/packet.rb +16 -0
- data/lib/overlook/csgo/demo/packet_factory.rb +22 -0
- data/lib/overlook/csgo/demo/packet_handler.rb +50 -0
- data/lib/overlook/csgo/demo/parser.rb +66 -0
- data/lib/overlook/csgo/demo/user_message_message_handler.rb +43 -0
- data/lib/overlook/csgo/match_info_decoder.rb +92 -0
- data/lib/overlook/csgo/sharecode_decoder.rb +83 -0
- data/lib/overlook/version.rb +3 -0
- data/lib/proto/.gitkeep +0 -0
- data/lib/proto/compiled/cstrike15_gcmessages.pb.rb +1382 -0
- data/lib/proto/compiled/cstrike15_usermessages.pb.rb +702 -0
- data/lib/proto/compiled/descriptor.pb.rb +280 -0
- data/lib/proto/compiled/netmessages.pb.rb +687 -0
- data/lib/proto/compiled/steammessages.pb.rb +798 -0
- data/overlook-csgo.gemspec +36 -0
- metadata +209 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'overlook/csgo/demo/base'
|
2
|
+
|
3
|
+
require 'overlook/csgo/demo/packet'
|
4
|
+
require 'overlook/csgo/demo/command'
|
5
|
+
require 'overlook/csgo/demo/header'
|
6
|
+
|
7
|
+
require 'overlook/csgo/demo/parser'
|
8
|
+
|
9
|
+
require 'overlook/csgo/demo/key_reader'
|
10
|
+
|
11
|
+
require 'overlook/csgo/demo/packet_handler'
|
12
|
+
|
13
|
+
require 'overlook/csgo/demo/user_message_message_handler'
|
14
|
+
require 'overlook/csgo/demo/game_event_list_message_handler'
|
15
|
+
require 'overlook/csgo/demo/game_event_message_handler'
|
16
|
+
require 'overlook/csgo/demo/encrypted_data_handler'
|
17
|
+
|
18
|
+
require 'overlook/csgo/demo/command_factory'
|
19
|
+
require 'overlook/csgo/demo/packet_factory'
|
20
|
+
|
21
|
+
module Overlook
|
22
|
+
module Csgo
|
23
|
+
module Demo
|
24
|
+
class << self
|
25
|
+
def open(io)
|
26
|
+
Base.new(io)
|
27
|
+
end
|
28
|
+
|
29
|
+
alias :new :open
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
module Overlook
|
3
|
+
module Csgo
|
4
|
+
module Demo
|
5
|
+
class Base
|
6
|
+
def initialize(io)
|
7
|
+
@parser = Parser.new(io)
|
8
|
+
end
|
9
|
+
|
10
|
+
def subscribe(subscriber)
|
11
|
+
@parser.subscribe(subscriber)
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse
|
15
|
+
@parser.parse
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Overlook
|
4
|
+
module Csgo
|
5
|
+
module Demo
|
6
|
+
class Command
|
7
|
+
|
8
|
+
module TYPE
|
9
|
+
SIGNON = 1
|
10
|
+
PACKET = 2
|
11
|
+
SYNCTICK = 3
|
12
|
+
CONSOLECMD = 4
|
13
|
+
USERCMD = 5
|
14
|
+
DATATABLES = 6
|
15
|
+
STOP = 7
|
16
|
+
CUSTOMDATA = 8
|
17
|
+
STRINGTABLES = 9
|
18
|
+
LASTCMD = STRINGTABLES
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_accessor :type, :tick, :slot
|
22
|
+
|
23
|
+
def initialize(type, tick, slot)
|
24
|
+
@type = type
|
25
|
+
@tick = tick
|
26
|
+
@slot = slot
|
27
|
+
@tick = 0 unless packet?
|
28
|
+
end
|
29
|
+
|
30
|
+
def type
|
31
|
+
case @type
|
32
|
+
when Command::TYPE::PACKET, Command::TYPE::SIGNON
|
33
|
+
:packet
|
34
|
+
when Command::TYPE::CUSTOMDATA, Command::TYPE::STRINGTABLES, Command::TYPE::DATATABLES
|
35
|
+
:skipable
|
36
|
+
when Command::TYPE::SYNCTICK
|
37
|
+
:sync
|
38
|
+
when Command::TYPE::STOP
|
39
|
+
:stop
|
40
|
+
else
|
41
|
+
:unknown
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def unknown?
|
46
|
+
type == :stop
|
47
|
+
end
|
48
|
+
|
49
|
+
def stop?
|
50
|
+
type == :stop
|
51
|
+
end
|
52
|
+
|
53
|
+
def sync?
|
54
|
+
type == :sync
|
55
|
+
end
|
56
|
+
|
57
|
+
def packet?
|
58
|
+
type == :packet
|
59
|
+
end
|
60
|
+
|
61
|
+
def skipable?
|
62
|
+
type == :skipable
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Overlook
|
2
|
+
module Csgo
|
3
|
+
module Demo
|
4
|
+
class GameEventListMessageHandler
|
5
|
+
def initialize(parser)
|
6
|
+
@parser = parser
|
7
|
+
end
|
8
|
+
|
9
|
+
def handle(message)
|
10
|
+
game_event_list_message = CSVCMsg_GameEventList.decode(message)
|
11
|
+
|
12
|
+
game_event_list_message.descriptors.each do |descriptor|
|
13
|
+
@parser.descriptors[descriptor.eventid] ||= descriptor
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Overlook
|
2
|
+
module Csgo
|
3
|
+
module Demo
|
4
|
+
class GameEventMessageHandler
|
5
|
+
def initialize(parser)
|
6
|
+
@parser = parser
|
7
|
+
end
|
8
|
+
|
9
|
+
def handle(message)
|
10
|
+
game_event_message = CSVCMsg_GameEvent.decode(message)
|
11
|
+
descriptor = @parser.descriptors[game_event_message.eventid]
|
12
|
+
event_keys = game_event_message.keys || []
|
13
|
+
|
14
|
+
keys = descriptor.keys
|
15
|
+
key_reader = KeyReader.new
|
16
|
+
attributes = event_keys.each_with_index.inject({}) do |memo, (key, index)|
|
17
|
+
memo[keys[index].name] = key_reader.read(key)
|
18
|
+
memo
|
19
|
+
end
|
20
|
+
|
21
|
+
case descriptor.name
|
22
|
+
when 'player_connect', 'player_connect_full'
|
23
|
+
# @parser.emit ...
|
24
|
+
when 'player_hurt'
|
25
|
+
when 'weapon_fire'
|
26
|
+
when 'bomb_beginplant', 'bomb_abortplant', 'bomb_planted', 'bomb_defused', 'bomb_exploded'
|
27
|
+
when 'round_end'
|
28
|
+
when 'begin_new_match'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
|
2
|
+
module Overlook
|
3
|
+
module Csgo
|
4
|
+
module Demo
|
5
|
+
class Header
|
6
|
+
MAX_STRING_LENGTH = 260.freeze
|
7
|
+
|
8
|
+
attr_accessor :map_name
|
9
|
+
|
10
|
+
def initialize(map_name)
|
11
|
+
raise ArgumentError 'map_name can\'t be nil' if map_name.nil?
|
12
|
+
|
13
|
+
@map_name = map_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_hash
|
17
|
+
{ map_name: map_name }
|
18
|
+
end
|
19
|
+
|
20
|
+
def map_name
|
21
|
+
@map_name.split("\x00").first
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.from_io(reader)
|
25
|
+
stamp = reader.read(8)
|
26
|
+
demo_protocol = reader.signed_int32
|
27
|
+
network_protocol = reader.signed_int32
|
28
|
+
server_name = reader.string(MAX_STRING_LENGTH)
|
29
|
+
client_name = reader.string(MAX_STRING_LENGTH)
|
30
|
+
map_name = reader.string(MAX_STRING_LENGTH)
|
31
|
+
game_directory = reader.string(MAX_STRING_LENGTH)
|
32
|
+
playtime = reader.float
|
33
|
+
ticks = reader.signed_int32
|
34
|
+
frames = reader.signed_int32
|
35
|
+
signon_length = reader.signed_int32
|
36
|
+
|
37
|
+
raise InvalidDemo, "#{self.class.name} only supports HL2DEMO, got #{stamp}" if stamp !~ /hl2demo/i
|
38
|
+
raise InvalidDemo, "#{self.class.name} only supports Valve demo files." if server_name !~ /valve/i
|
39
|
+
|
40
|
+
new(map_name)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Overlook
|
2
|
+
module Csgo
|
3
|
+
module Demo
|
4
|
+
class KeyReader
|
5
|
+
def read(key)
|
6
|
+
{
|
7
|
+
1 => key.val_string,
|
8
|
+
2 => key.val_float,
|
9
|
+
3 => key.val_long,
|
10
|
+
4 => key.val_short,
|
11
|
+
5 => key.val_byte,
|
12
|
+
6 => key.val_bool
|
13
|
+
}[key.type]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Overlook
|
2
|
+
module Csgo
|
3
|
+
module Demo
|
4
|
+
# Allows creation of Packet objects
|
5
|
+
class PacketFactory
|
6
|
+
COMMAND_INFO_SIZE = 160.freeze
|
7
|
+
|
8
|
+
# Creates a packet from an io stream.
|
9
|
+
#
|
10
|
+
# @return [Packet] The created packet object
|
11
|
+
def self.from_io(io)
|
12
|
+
command_info = io.read(COMMAND_INFO_SIZE)
|
13
|
+
size = io.signed_int32
|
14
|
+
data = StringIO.new(io.read(size))
|
15
|
+
|
16
|
+
Packet.new(size,
|
17
|
+
data, command_info)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Overlook
|
2
|
+
module Csgo
|
3
|
+
module Demo
|
4
|
+
# Determines which handler to use to read a packet and yields that
|
5
|
+
# handler along with the message to the block
|
6
|
+
class PacketHandler
|
7
|
+
module MessageTypes
|
8
|
+
USER_MESSAGE_MESSAGE_ID = 23
|
9
|
+
GAME_EVENT_MESSAGE_ID = 25
|
10
|
+
GAME_EVENT_LIST_MESSAGE_ID = 30
|
11
|
+
ENCRYPTED_DATA_ID = 35
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :parser
|
15
|
+
|
16
|
+
def initialize(_parser)
|
17
|
+
@parser = _parser
|
18
|
+
end
|
19
|
+
|
20
|
+
def handle(packet)
|
21
|
+
reader = BitReader.new(packet.data)
|
22
|
+
|
23
|
+
loop do
|
24
|
+
message_type = reader.var_int32
|
25
|
+
message_size = reader.var_int32
|
26
|
+
|
27
|
+
if handler = handlers[message_type]
|
28
|
+
handler.handle(reader.bytes(message_size))
|
29
|
+
else
|
30
|
+
reader.skip(message_size)
|
31
|
+
end
|
32
|
+
|
33
|
+
break if reader.eof?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def handlers
|
40
|
+
{
|
41
|
+
MessageTypes::USER_MESSAGE_MESSAGE_ID => UserMessageMessageHandler.new(parser),
|
42
|
+
MessageTypes::GAME_EVENT_LIST_MESSAGE_ID => GameEventListMessageHandler.new(parser),
|
43
|
+
MessageTypes::GAME_EVENT_MESSAGE_ID => GameEventMessageHandler.new(parser),
|
44
|
+
MessageTypes::ENCRYPTED_DATA_ID => EncryptedDataHandler.new(parser),
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'wisper'
|
2
|
+
|
3
|
+
module Overlook
|
4
|
+
module Csgo
|
5
|
+
module Demo
|
6
|
+
class Parser
|
7
|
+
include Wisper::Publisher
|
8
|
+
|
9
|
+
Exception = Class.new(StandardError)
|
10
|
+
InvalidDemo = Class.new(Exception)
|
11
|
+
|
12
|
+
attr_reader :reader
|
13
|
+
attr_accessor :descriptors
|
14
|
+
|
15
|
+
def initialize(io)
|
16
|
+
@reader = ByteReader.new(io)
|
17
|
+
@header = nil
|
18
|
+
@descriptors = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse
|
22
|
+
header
|
23
|
+
packets
|
24
|
+
done
|
25
|
+
end
|
26
|
+
|
27
|
+
def emit(event, data = {})
|
28
|
+
broadcast(event.to_sym, data)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def header
|
34
|
+
emit(:header,
|
35
|
+
Header.from_io(reader).to_hash)
|
36
|
+
end
|
37
|
+
|
38
|
+
def done
|
39
|
+
emit(:done)
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
def packets
|
44
|
+
packet_handler = PacketHandler.new(self)
|
45
|
+
|
46
|
+
loop do
|
47
|
+
command = CommandFactory.from_io(reader)
|
48
|
+
|
49
|
+
next if command.sync?
|
50
|
+
break if command.stop?
|
51
|
+
raise "Unknown Command: #{command.type}" if command.unknown?
|
52
|
+
|
53
|
+
if command.packet?
|
54
|
+
packet = PacketFactory.from_io(reader)
|
55
|
+
packet_handler.handle(packet)
|
56
|
+
elsif command.skipable?
|
57
|
+
reader.read(reader.signed_int32)
|
58
|
+
else
|
59
|
+
raise "Unknown Command #{command.type}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|