overlook-csgo 0.2.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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +2 -0
  3. data/.gitignore +5 -0
  4. data/.rspec +2 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +3 -0
  7. data/Gemfile.lock +115 -0
  8. data/README.md +77 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +7 -0
  11. data/bin/setup +7 -0
  12. data/lib/overlook.rb +20 -0
  13. data/lib/overlook/bit_buffer.rb +52 -0
  14. data/lib/overlook/bit_reader.rb +64 -0
  15. data/lib/overlook/byte_reader.rb +58 -0
  16. data/lib/overlook/csgo.rb +9 -0
  17. data/lib/overlook/csgo/demo.rb +33 -0
  18. data/lib/overlook/csgo/demo/base.rb +20 -0
  19. data/lib/overlook/csgo/demo/command.rb +67 -0
  20. data/lib/overlook/csgo/demo/command_factory.rb +15 -0
  21. data/lib/overlook/csgo/demo/encrypted_data_handler.rb +16 -0
  22. data/lib/overlook/csgo/demo/game_event_list_message_handler.rb +19 -0
  23. data/lib/overlook/csgo/demo/game_event_message_handler.rb +35 -0
  24. data/lib/overlook/csgo/demo/header.rb +45 -0
  25. data/lib/overlook/csgo/demo/key_reader.rb +18 -0
  26. data/lib/overlook/csgo/demo/packet.rb +16 -0
  27. data/lib/overlook/csgo/demo/packet_factory.rb +22 -0
  28. data/lib/overlook/csgo/demo/packet_handler.rb +50 -0
  29. data/lib/overlook/csgo/demo/parser.rb +66 -0
  30. data/lib/overlook/csgo/demo/user_message_message_handler.rb +43 -0
  31. data/lib/overlook/csgo/match_info_decoder.rb +92 -0
  32. data/lib/overlook/csgo/sharecode_decoder.rb +83 -0
  33. data/lib/overlook/version.rb +3 -0
  34. data/lib/proto/.gitkeep +0 -0
  35. data/lib/proto/compiled/cstrike15_gcmessages.pb.rb +1382 -0
  36. data/lib/proto/compiled/cstrike15_usermessages.pb.rb +702 -0
  37. data/lib/proto/compiled/descriptor.pb.rb +280 -0
  38. data/lib/proto/compiled/netmessages.pb.rb +687 -0
  39. data/lib/proto/compiled/steammessages.pb.rb +798 -0
  40. data/overlook-csgo.gemspec +36 -0
  41. metadata +209 -0
@@ -0,0 +1,9 @@
1
+ require 'overlook/csgo/sharecode_decoder'
2
+ require 'overlook/csgo/match_info_decoder'
3
+
4
+ require 'overlook/csgo/demo'
5
+
6
+ module Overlook
7
+ module Csgo
8
+ end
9
+ end
@@ -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,15 @@
1
+ module Overlook
2
+ module Csgo
3
+ module Demo
4
+ class CommandFactory
5
+ def self.from_io(reader)
6
+ cmd = reader.byte
7
+ tick = reader.signed_int32
8
+ slot = reader.byte
9
+
10
+ Command.new(cmd, tick, slot)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ module Overlook
2
+ module Csgo
3
+ module Demo
4
+ class EncryptedDataHandler
5
+ def initialize(parser)
6
+ @parser = parser
7
+ end
8
+
9
+ def handle(message)
10
+ # nop
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -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,16 @@
1
+
2
+ module Overlook
3
+ module Csgo
4
+ module Demo
5
+ class Packet
6
+ attr_accessor :size, :data, :command_info
7
+
8
+ def initialize(size, data, command_info)
9
+ @size = size
10
+ @data = data
11
+ @command_info = command_info
12
+ end
13
+ end
14
+ end
15
+ end
16
+ 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