sc2 0.0.5

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.
@@ -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
+