sc2 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in sc2.gemspec
4
+ gemspec
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'bindata'
3
+ require 'bzip2-ruby'
4
+ Dir['./wrappers/*.rb'].each { |f| require f }
5
+ Dir['./magick/*.rb'].each { |f| require f }
6
+
7
+ module Sc2
8
+ class Replay
9
+ def initialize rep_file
10
+ @reader = Reader.new rep_file
11
+ @replay = Hash[:details, Parser.parse(@reader.read_file('replay.details')),
12
+ :initData, InitData.read(@reader.read_file('replay.initData')),
13
+ :userData, Parser.parse(@reader.user_hdr.user_data),
14
+ :gameVersion, '']
15
+ @replay[:gameVersion] = { :major => @replay[:userData][1][1], :minor => @replay[:userData][1][2], :patch => @replay[:userData[1][3]], :build => @replay[:userData][1][4] }
16
+ @replay[:players] = p_players
17
+ end
18
+
19
+ def map
20
+ @replay[:details][1]
21
+ end
22
+
23
+ def winner
24
+ @replay[:players].each { |player| return player if player.win? }
25
+ end
26
+
27
+ def loser
28
+ @replay[:players].each { |player| return player if !player.win? }
29
+ end
30
+
31
+ def players
32
+ @replay[:players]
33
+ end
34
+
35
+ private
36
+ def attributes
37
+ return @attributes if defined? @attributes
38
+ data = @reader.read_file "replay.attributes.events"
39
+ data.slice! 0, (@replay[:gameVersion][:build] < 17326 ? 4 : 5)
40
+ @attributes = []
41
+ data.slice!(0, 4).unpack("V")[0].times { @attributes << Attribute.read(data.slice!(0, 13)) }
42
+ @attributes
43
+ end
44
+
45
+ def p_players
46
+ return @players if defined? @players
47
+ player_id = 0
48
+ @players = @replay[:details][0].map { |player| Player.new ({ :name => player[0], :outcome => player[8], :id => player_id += 1, :attrs => attributes.map { |attr| { :attr_id => attr[:id], :value => attr[:val] } if attr[:player] == player_id } }) }
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,45 @@
1
+ module Sc2
2
+ class Hasher
3
+ def initialize
4
+ set_enc_tbl
5
+ end
6
+
7
+ def hash_for hash_type, s
8
+ hash_type = [:table_offset, :hash_a, :hash_b, :table].index hash_type
9
+ seed1, seed2 = 0x7FED7FED, 0xEEEEEEEE
10
+ s.upcase.each_byte do |c|
11
+ value = @encryption_table[(hash_type << 8) + c]
12
+ seed1 = (value ^ (seed1 + seed2)) & 0xFFFFFFFF
13
+ seed2 = (c + seed1 + seed2 + (seed2 << 5) + 3) & 0xFFFFFFFF
14
+ end
15
+ seed1
16
+ end
17
+
18
+ def decrypt data, seed1
19
+ seed2 = 0xEEEEEEEE
20
+ data.unpack('V*').map do |value|
21
+ seed2 = (seed2 + @encryption_table[0x400 + (seed1 & 0xFF)]) & 0xFFFFFFFF
22
+ value = (value ^ (seed1 + seed2)) & 0xFFFFFFFF
23
+ seed1 = (((~seed1 << 0x15) + 0x11111111) | (seed1 >> 0x0B)) & 0xFFFFFFFF
24
+ seed2 = (value + seed2 + (seed2 << 5) + 3) & 0xFFFFFFFF
25
+ value
26
+ end.pack('V*')
27
+ end
28
+
29
+ private
30
+ def set_enc_tbl
31
+ seed = 0x00100001
32
+ @encryption_table = {}
33
+ (0..255).each do |i|
34
+ index = i
35
+ (0..4).each do |j|
36
+ seed = (seed * 125 + 3) % 0x2AAAAB
37
+ tmp1 = (seed & 0xFFFF) << 0x10
38
+ seed = (seed * 125 + 3) % 0x2AAAAB
39
+ tmp2 = (seed & 0xFFFF)
40
+ @encryption_table[i + j * 0x100] = (tmp1 | tmp2)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,42 @@
1
+ module Sc2
2
+ class Parser
3
+ def self.parse data
4
+ self.parse_recur String.new data
5
+ end
6
+
7
+ def self.parse_recur data
8
+ case data.slice!(0).bytes.next
9
+ when 2
10
+ data.slice! 0, vlf(data)
11
+ when 4
12
+ data.slice! 0, 2
13
+ (0...vlf(data)).map {|i| parse_recur data }
14
+ when 5
15
+ Hash.[]((0...vlf(data)).map do |i|
16
+ [vlf(data), parse_recur(data)]
17
+ end)
18
+ when 6
19
+ data.slice! 0
20
+ when 7
21
+ data.slice!(0, 4).unpack("V")[0]
22
+ when 9
23
+ vlf data
24
+ else
25
+ nil
26
+ end
27
+ end
28
+
29
+ def self.vlf data
30
+ ret, shift = 0, 0
31
+ loop do
32
+ char = data.slice!(0)
33
+ return nil unless char
34
+ byte = char.bytes.next
35
+ ret += (byte & 0x7F) << (7 * shift)
36
+ break if byte & 0x80 == 0
37
+ shift += 1
38
+ end
39
+ (ret >> 1) * ((ret & 0x1) == 0 ? 1 : -1)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,60 @@
1
+ module Sc2
2
+ class Reader
3
+ attr_reader :user_hdr
4
+ def initialize rep_file
5
+ @rep_file = rep_file
6
+ set_hdrs
7
+ set_tbls
8
+ end
9
+
10
+ def read_file file
11
+ hasher = Hasher.new
12
+ hash_a = hasher.hash_for :hash_a, file
13
+ hash_b = hasher.hash_for :hash_b, file
14
+ hash_entry = @hash_tbl.find { |h| [h.hash_a, h.hash_b] == [hash_a, hash_b] }
15
+ block_entry = @block_tbl[hash_entry.block_index]
16
+ @rep_file.seek @user_hdr.archive_header_offset + block_entry.block_offset
17
+ file_data = @rep_file.read block_entry.archived_size
18
+ if block_entry.single_unit?
19
+ file_data = Bzip2.uncompress file_data[1, file_data.length] if block_entry.compressed? && file_data.bytes.next == 16
20
+ return file_data
21
+ end
22
+ sector_size = 512 << @archive_hdr.sector_size_shift
23
+ sectors = block_entry.size / sector_size + 1
24
+ sectors += 1 if block_entry.has_checksums
25
+ positions = file_data[0, 4 * (sectors + 1)].unpack "V#{sectors + 1}"
26
+ sectors = []
27
+ positions.each_with_index do |pos, i|
28
+ break if i + 1 == positions.length
29
+ sector = file_data[pos, positions[i + 1] - pos]
30
+ sector = Bzip2.uncompress sector if block_entry.compressed? && block_entry.size > block_entry.archived_size && sector.bytes.next == 16
31
+ sectors << sector
32
+ end
33
+ sectors.join ''
34
+ end
35
+
36
+ private
37
+ def set_hdrs
38
+ @user_hdr = UserHeader.read @rep_file
39
+ @rep_file.seek @user_hdr.archive_header_offset
40
+ @archive_hdr = ArchiveHeader.read @rep_file
41
+ end
42
+
43
+ def set_tbls
44
+ @hash_tbl = read_tbl 'hash'
45
+ @block_tbl = read_tbl 'block'
46
+ end
47
+
48
+ def read_tbl type
49
+ tbl_offset = @archive_hdr[type + '_table_offset']
50
+ @rep_file.seek @user_hdr.archive_header_offset + tbl_offset
51
+ tbl_entries = @archive_hdr[type + '_table_entries']
52
+ hasher = Hasher.new
53
+ key = hasher.hash_for :table, "(#{type} table)"
54
+ data = @rep_file.read tbl_entries * 16
55
+ data = hasher.decrypt data, key
56
+ layout = type == 'hash' ? HashTableEntry : BlockTableEntry
57
+ (0...tbl_entries).map { |i| layout.read(data[i * 16, 16]) }
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,3 @@
1
+ module Sc2
2
+ VERSION = "0.0.4"
3
+ end
@@ -0,0 +1,18 @@
1
+ module Sc2
2
+ class ArchiveHeader < BinData::Record
3
+ endian :little
4
+ string :archive_magic, :length => 4
5
+ int32 :header_size
6
+ int32 :archive_size
7
+ int16 :format_version
8
+ int8 :sector_size_shift
9
+ int8
10
+ int32 :hash_table_offset
11
+ int32 :block_table_offset
12
+ int32 :hash_table_entries
13
+ int32 :block_table_entries
14
+ int64 :extended_block_table_offset
15
+ int16 :hash_table_offset_high
16
+ int16 :block_table_offset_high
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ module Sc2
2
+ class Attribute < BinData::Record
3
+ endian :little
4
+ string :header, :length => 4
5
+ uint32 :id
6
+ uint8 :player
7
+ string :val, :length => 4
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ module Sc2
2
+ class BlockTableEntry < BinData::Record
3
+ endian :little
4
+ int32 :block_offset
5
+ int32 :archived_size
6
+ int32 :file_size
7
+ uint32 :flags
8
+
9
+ def file?
10
+ (flags & 0x80000000) != 0
11
+ end
12
+
13
+ def compressed?
14
+ (flags & 0x00000200) != 0
15
+ end
16
+
17
+ def encrypted?
18
+ (flags & 0x00010000) != 0
19
+ end
20
+
21
+ def single_unit?
22
+ (flags & 0x01000000) != 0
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ module Sc2
2
+ class HashTableEntry < BinData::Record
3
+ endian :little
4
+ uint32 :hash_a
5
+ uint32 :hash_b
6
+ int16 :language
7
+ int8 :platform
8
+ int8
9
+ int32 :block_index
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ module Sc2
2
+ class InitData < BinData::Record
3
+ uint8 :num_players
4
+ array :players, :initial_length => :num_players do
5
+ uint8 :player_name_length
6
+ string :player_name, :length => :player_name_length
7
+ skip :length => 5
8
+ end
9
+ string :unknown_24, :length => 24
10
+ uint8 :account_length
11
+ string :account, :length => :account_length
12
+ rest :rest
13
+ end
14
+ end
@@ -0,0 +1,34 @@
1
+ module Sc2
2
+ class Player
3
+ OUTCOMES = [:unknown, :win, :loss]
4
+ ATTRIBUTES = { :player_race => { "RAND" => :random, "Terr" => 'terran', "Prot" => 'protoss', "Zerg" => 'zerg' } }
5
+ def initialize params
6
+ @params = params
7
+ @params[:outcome] = OUTCOMES[@params[:outcome]]
8
+ @params[:attrs].compact!
9
+ @params[:attrs].each { |attr| attr[:value].reverse! }
10
+ end
11
+
12
+ def race
13
+ @params[:attrs].each do |attr|
14
+ return ATTRIBUTES[:player_race][attr[:value]] if attr[:attr_id] == 3001
15
+ end
16
+ end
17
+
18
+ def id
19
+ @params[:id]
20
+ end
21
+
22
+ def name
23
+ @params[:name]
24
+ end
25
+
26
+ def win?
27
+ @params[:outcome] == :win
28
+ end
29
+
30
+ def loss?
31
+ @params[:outcome] == :loss
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,10 @@
1
+ module Sc2
2
+ class UserHeader < BinData::Record
3
+ endian :little
4
+ string :user_magic, :length => 4
5
+ uint32 :user_data_max_length
6
+ uint32 :archive_header_offset
7
+ uint32 :user_data_length
8
+ string :user_data, :length => :user_data_length
9
+ end
10
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "sc2"
6
+ s.version = "0.0.5"
7
+ s.authors = ["vasechika"]
8
+ s.email = ["second.pilot@gmail.com"]
9
+ s.homepage = "https://github.com/vasechika/sc2"
10
+ s.summary = "SC 2"
11
+ s.description = "Gem for parsing Starcraft 2 replays"
12
+
13
+ s.rubyforge_project = "sc2"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ # specify any dependencies here; for example:
21
+ # s.add_runtime_dependency "rest-client"
22
+ s.add_development_dependency "rspec"
23
+ s.add_dependency "bindata"
24
+ s.add_dependency "bzip2-ruby"
25
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sc2
3
+ version: !ruby/object:Gem::Version
4
+ hash: 21
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 5
10
+ version: 0.0.5
11
+ platform: ruby
12
+ authors:
13
+ - vasechika
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-10-16 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :development
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: bindata
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: bzip2-ruby
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ type: :runtime
61
+ version_requirements: *id003
62
+ description: Gem for parsing Starcraft 2 replays
63
+ email:
64
+ - second.pilot@gmail.com
65
+ executables: []
66
+
67
+ extensions: []
68
+
69
+ extra_rdoc_files: []
70
+
71
+ files:
72
+ - .gitignore
73
+ - Gemfile
74
+ - Rakefile
75
+ - lib/sc2.rb
76
+ - lib/sc2/magick/hasher.rb
77
+ - lib/sc2/magick/parser.rb
78
+ - lib/sc2/magick/reader.rb
79
+ - lib/sc2/version.rb
80
+ - lib/sc2/wrappers/archive_header.rb
81
+ - lib/sc2/wrappers/attribute.rb
82
+ - lib/sc2/wrappers/block_table_entry.rb
83
+ - lib/sc2/wrappers/hash_table_entry.rb
84
+ - lib/sc2/wrappers/init_data.rb
85
+ - lib/sc2/wrappers/player.rb
86
+ - lib/sc2/wrappers/user_header.rb
87
+ - sc2.gemspec
88
+ homepage: https://github.com/vasechika/sc2
89
+ licenses: []
90
+
91
+ post_install_message:
92
+ rdoc_options: []
93
+
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ hash: 3
102
+ segments:
103
+ - 0
104
+ version: "0"
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ hash: 3
111
+ segments:
112
+ - 0
113
+ version: "0"
114
+ requirements: []
115
+
116
+ rubyforge_project: sc2
117
+ rubygems_version: 1.8.11
118
+ signing_key:
119
+ specification_version: 3
120
+ summary: SC 2
121
+ test_files: []
122
+