tassadar 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +57 -0
- data/Rakefile +22 -0
- data/lib/tassadar.rb +7 -0
- data/lib/tassadar/mpq.rb +49 -0
- data/lib/tassadar/mpq/archive_header.rb +31 -0
- data/lib/tassadar/mpq/archive_size.rb +17 -0
- data/lib/tassadar/mpq/block_encryptor.rb +105 -0
- data/lib/tassadar/mpq/block_table.rb +36 -0
- data/lib/tassadar/mpq/crypt_buf.rb +16 -0
- data/lib/tassadar/mpq/file_data.rb +84 -0
- data/lib/tassadar/mpq/hash_table.rb +52 -0
- data/lib/tassadar/mpq/sector.rb +25 -0
- data/lib/tassadar/sc2/attributes.rb +23 -0
- data/lib/tassadar/sc2/details.rb +7 -0
- data/lib/tassadar/sc2/game.rb +27 -0
- data/lib/tassadar/sc2/player.rb +22 -0
- data/lib/tassadar/sc2/replay.rb +23 -0
- data/lib/tassadar/sc2/reverse_string.rb +15 -0
- data/lib/tassadar/sc2/serialized_data.rb +86 -0
- data/lib/tassadar/version.rb +3 -0
- data/spec/mpq/archive_header_spec.rb +72 -0
- data/spec/mpq/block_table_spec.rb +41 -0
- data/spec/mpq_spec.rb +32 -0
- data/spec/replays/Delta Quadrant.SC2Replay +0 -0
- data/spec/replays/eu_replay.SC2Replay +0 -0
- data/spec/replays/random.sc2replay +0 -0
- data/spec/sc2/game_spec.rb +31 -0
- data/spec/sc2/player_spec.rb +54 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/spec_watchr.rb +77 -0
- data/tassadar.gemspec +24 -0
- metadata +122 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color --format nested
|
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use --create 1.9.2@tassadar
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2011-2012 Agora Games
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# Tassadar
|
2
|
+
|
3
|
+
Starcraft 2 replay parser written in pure-Ruby.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```
|
10
|
+
gem 'tassadar'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
```
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
```
|
22
|
+
$ gem install tassadar
|
23
|
+
```
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
Create a replay object:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
replay = Tassadar::SC2::Replay.new(path_to_replay)
|
31
|
+
```
|
32
|
+
|
33
|
+
All of the important information is in the game object:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
replay.game
|
37
|
+
=> #<Tassadar::SC2::Game:0x007f9e41e31408 @winner=#<Tassadar::SC2::Player:0x007f9e41e31728 @name="redgar", @id=2569192, @won=true, @color={:alpha=>255, :red=>0, :green=>66, :blue=>255}, @chosen_race="Zerg", @actual_race="Zerg", @handicap=100>, @time=2011-07-05 17:01:08 -0500, @map="Delta Quadrant">
|
38
|
+
```
|
39
|
+
|
40
|
+
Or the player objects:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
replay.players.first
|
44
|
+
=> #<Tassadar::SC2::Player:0x007f9e41e31a48 @name="guitsaru", @id=1918894, @won=false, @color={:alpha=>255, :red=>180, :green=>20, :blue=>30}, @chosen_race="Terran", @actual_race="Terran", @handicap=100>
|
45
|
+
```
|
46
|
+
|
47
|
+
## Contributing
|
48
|
+
|
49
|
+
1. Fork it
|
50
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
51
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
52
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
53
|
+
5. Create new Pull Request
|
54
|
+
|
55
|
+
## Copyright
|
56
|
+
|
57
|
+
Copyright (c) 2012 Agora Games. See LICENSE.txt for further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require 'rake'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
desc "Open an irb session preloaded with this library"
|
7
|
+
task :console do
|
8
|
+
sh "bundle exec irb -rubygems -I lib -r tassadar.rb"
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Automatically run specs when files change."
|
12
|
+
task :"spec:watchr" do
|
13
|
+
sh "watchr spec/spec_watchr.rb"
|
14
|
+
end
|
15
|
+
|
16
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
17
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
18
|
+
spec.rspec_opts = ['--backtrace']
|
19
|
+
# spec.ruby_opts = ['-w']
|
20
|
+
end
|
21
|
+
|
22
|
+
task :default => :spec
|
data/lib/tassadar.rb
ADDED
data/lib/tassadar/mpq.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'bindata'
|
2
|
+
|
3
|
+
require 'tassadar/mpq/archive_size'
|
4
|
+
require 'tassadar/mpq/archive_header'
|
5
|
+
require 'tassadar/mpq/sector'
|
6
|
+
require 'tassadar/mpq/file_data'
|
7
|
+
require 'tassadar/mpq/block_table'
|
8
|
+
require 'tassadar/mpq/hash_table'
|
9
|
+
require 'tassadar/mpq/block_encryptor'
|
10
|
+
|
11
|
+
module Tassadar
|
12
|
+
module MPQ
|
13
|
+
class MPQ < BinData::Record
|
14
|
+
endian :little
|
15
|
+
|
16
|
+
string :magic, :length => 3, :check_value => "MPQ"
|
17
|
+
int8 :magic_4, :check_value => 27
|
18
|
+
int32 :user_data_size
|
19
|
+
int32 :archive_header_offset
|
20
|
+
string :user_data, :read_length => :user_data_size
|
21
|
+
|
22
|
+
archive_header :archive_header, :adjust_offset => lambda { archive_header_offset }
|
23
|
+
encrypted_block_table :block_table, :entries => lambda { archive_header.block_table_entries },
|
24
|
+
:adjust_offset => lambda { archive_header_offset + archive_header.block_table_offset }
|
25
|
+
encrypted_hash_table :hash_table, :entries => lambda { archive_header.hash_table_entries },
|
26
|
+
:adjust_offset => lambda { archive_header_offset + archive_header.hash_table_offset },
|
27
|
+
:compressed => lambda { archive_header.block_table_offset != archive_header.hash_table_offset + archive_header.hash_table_entries * 16 }
|
28
|
+
file_data_array :file_data, :blocks => lambda { block_table.blocks },
|
29
|
+
:sector_size_shift => lambda { archive_header.sector_size_shift },
|
30
|
+
:archive_header_offset => :archive_header_offset
|
31
|
+
|
32
|
+
def files
|
33
|
+
@files ||= read_file('(listfile)').split
|
34
|
+
end
|
35
|
+
|
36
|
+
def read_file(filename)
|
37
|
+
get_hash_table_entry(filename).decompressed_data
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_hash_table_entry(filename)
|
41
|
+
hash_a = BlockEncryptor.hash_string(filename, 0x100)
|
42
|
+
hash_b = BlockEncryptor.hash_string(filename, 0x200)
|
43
|
+
|
44
|
+
block = self.block_table.blocks[self.hash_table.hashes.select {|hash| hash.file_path_hash_a == hash_a && hash.file_path_hash_b == hash_b}.first.file_block_index]
|
45
|
+
self.file_data.select {|f| f.block_offset == block.block_offset}.first
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Tassadar
|
2
|
+
module MPQ
|
3
|
+
class ArchiveHeader < BinData::Record
|
4
|
+
endian :little
|
5
|
+
|
6
|
+
string :magic, :length => 3, :check_value => "MPQ"
|
7
|
+
uint8 :magic_4, :check_value => 26
|
8
|
+
|
9
|
+
uint32 :header_size, :check_value => 44
|
10
|
+
|
11
|
+
# archive_size actually here, but not used and is computed from later data
|
12
|
+
skip :length => 4
|
13
|
+
|
14
|
+
uint16 :format_version
|
15
|
+
uint8 :sector_size_shift, :check_value => 3
|
16
|
+
skip :length => 1
|
17
|
+
uint32 :hash_table_offset
|
18
|
+
uint32 :block_table_offset
|
19
|
+
uint32 :hash_table_entries
|
20
|
+
uint32 :block_table_entries
|
21
|
+
uint64 :extended_block_table_offset
|
22
|
+
uint16 :hash_table_offset_high
|
23
|
+
uint16 :block_table_offset_high
|
24
|
+
|
25
|
+
archive_size :archive_size, :hash_table_offset => :hash_table_offset,
|
26
|
+
:hash_table_entries => :hash_table_entries,
|
27
|
+
:block_table_offset => :block_table_offset,
|
28
|
+
:block_table_entries => :block_table_entries
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Tassadar
|
2
|
+
module MPQ
|
3
|
+
class ArchiveSize < BinData::Primitive
|
4
|
+
endian :little
|
5
|
+
|
6
|
+
def get
|
7
|
+
[eval_parameter(:hash_table_offset) + (eval_parameter(:hash_table_entries) * 16),
|
8
|
+
eval_parameter(:block_table_offset) + (eval_parameter(:block_table_entries) * 16)].max
|
9
|
+
end
|
10
|
+
|
11
|
+
def set(v)
|
12
|
+
[eval_parameter(:hash_table_offset) + (eval_parameter(:hash_table_entries) * 16),
|
13
|
+
eval_parameter(:block_table_offset) + (eval_parameter(:block_table_entries) * 16)].max
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Tassadar
|
2
|
+
module MPQ
|
3
|
+
class BlockEncryptor
|
4
|
+
attr_accessor :key, :offset, :buffer, :size
|
5
|
+
|
6
|
+
def initialize(key, offset, buffer, size)
|
7
|
+
@key = key
|
8
|
+
@offset = offset
|
9
|
+
@buffer = buffer
|
10
|
+
@size = size
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.hash_string(key, offset)
|
14
|
+
BlockEncryptor.new(key, offset, nil, nil).hash_string
|
15
|
+
end
|
16
|
+
|
17
|
+
def decrypt
|
18
|
+
seed1 = hash_string
|
19
|
+
seed2 = 0xEEEEEEEE
|
20
|
+
result = ""
|
21
|
+
size = @size
|
22
|
+
|
23
|
+
while size >= 4
|
24
|
+
seed2 += encryption_table[0x400 + (seed1 & 0xFF)]
|
25
|
+
seed2 &= 0xFFFFFFFF
|
26
|
+
|
27
|
+
value = buffer.readbytes(4).unpack("V").first
|
28
|
+
value = (value ^ (seed1 + seed2)) & 0xFFFFFFFF
|
29
|
+
|
30
|
+
result += [value].pack("V")
|
31
|
+
|
32
|
+
seed1 = ((~seed1 << 0x15) + 0x11111111) | (seed1 >> 0x0B)
|
33
|
+
seed1 &= 0xFFFFFFFF
|
34
|
+
seed2 = value + seed2 + (seed2 << 5) + 3 & 0xFFFFFFFF
|
35
|
+
size = size - 4
|
36
|
+
end
|
37
|
+
|
38
|
+
result
|
39
|
+
end
|
40
|
+
|
41
|
+
def encrypt
|
42
|
+
seed1 = hash_string
|
43
|
+
seed2 = 0xEEEEEEEE
|
44
|
+
encrypted_block = []
|
45
|
+
|
46
|
+
while @size >= 4
|
47
|
+
seed2 += encryption_table[0x400 + (seed1 & 0xFF)]
|
48
|
+
seed2 &= 0xFFFFFFFF
|
49
|
+
|
50
|
+
value = buffer.readbytes(4).unpack("V").first
|
51
|
+
value = (value ^ (seed1 + seed2)) & 0xFFFFFFFF
|
52
|
+
|
53
|
+
encrypted_block << value
|
54
|
+
|
55
|
+
seed = ((~seed1 << 0x15) + 0x11111111) | (seed1 >> 0x0B)
|
56
|
+
seed2 = value + seed2 + (seed2 << 5) + 3 & 0xFFFFFFFF
|
57
|
+
|
58
|
+
@size = @size - 4
|
59
|
+
end
|
60
|
+
|
61
|
+
encrypted_block.pack("V*")
|
62
|
+
end
|
63
|
+
|
64
|
+
def hash_string
|
65
|
+
seed1 = 0x7FED7FED
|
66
|
+
seed2 = 0xEEEEEEEE
|
67
|
+
|
68
|
+
@key.upcase.each_byte do |char|
|
69
|
+
value = encryption_table[@offset + char]
|
70
|
+
seed1 = (value ^ (seed1 + seed2)) & 0xFFFFFFFF
|
71
|
+
seed2 = char + seed1 + seed2 + (seed2 << 5) + 3 & 0xFFFFFFFF
|
72
|
+
end
|
73
|
+
|
74
|
+
seed1
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
def encryption_table
|
79
|
+
@encryption_table ||= begin
|
80
|
+
crypt_buf = []
|
81
|
+
seed = 0x00100001
|
82
|
+
|
83
|
+
0.upto(0x100 - 1) do |index1|
|
84
|
+
index2 = index1
|
85
|
+
|
86
|
+
0.upto(4) do |i|
|
87
|
+
seed = (seed * 125 + 3) % 0x2AAAAB
|
88
|
+
temp1 = (seed & 0xFFFF) << 0x10
|
89
|
+
|
90
|
+
seed = (seed * 125 + 3) % 0x2AAAAB
|
91
|
+
temp2 = (seed & 0xFFFF)
|
92
|
+
|
93
|
+
crypt_buf[index2] = (temp1 | temp2)
|
94
|
+
|
95
|
+
index2 = index2 + 0x100
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
crypt_buf
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Tassadar
|
2
|
+
module MPQ
|
3
|
+
class BlockTable < BinData::Record
|
4
|
+
endian :little
|
5
|
+
|
6
|
+
array :blocks, :read_until => :eof do
|
7
|
+
uint32 :block_offset
|
8
|
+
uint32 :block_size
|
9
|
+
uint32 :file_size
|
10
|
+
uint32 :flags
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class EncryptedBlockTable < BinData::BasePrimitive
|
15
|
+
mandatory_parameters :entries
|
16
|
+
|
17
|
+
def read_and_return_value(io)
|
18
|
+
value = BlockEncryptor.new("(block table)", 0x300, io, eval_parameter(:entries) * 16).decrypt
|
19
|
+
BlockTable.read(value)
|
20
|
+
end
|
21
|
+
|
22
|
+
def value_to_binary_string(value)
|
23
|
+
packed_string = ""
|
24
|
+
value.blocks.each do |block|
|
25
|
+
packed_string += [block.block_offset, block.block_size, block.file_size, block.flags].pack("VVVV")
|
26
|
+
end
|
27
|
+
|
28
|
+
BlockEncryptor.new("(block table)", 0x300, BinData::IO.new(packed_string), eval_parameter(:entries) * 16).encrypt
|
29
|
+
end
|
30
|
+
|
31
|
+
def sensible_default
|
32
|
+
[0,0,0,0].pack("VVVV")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'bzip2'
|
3
|
+
|
4
|
+
module Tassadar
|
5
|
+
module MPQ
|
6
|
+
class FileData < BinData::Record
|
7
|
+
MPQ_FILE_ENCRYPTED = 0x00010000
|
8
|
+
MPQ_FILE_EXISTS = 0x80000000
|
9
|
+
MPQ_SINGLE_UNIT = 0x01000000
|
10
|
+
MPQ_COMPRESSED = 0x00000200
|
11
|
+
|
12
|
+
attr_accessor :block_offset
|
13
|
+
|
14
|
+
endian :little
|
15
|
+
|
16
|
+
string :data, :read_length => lambda { block.block_size }
|
17
|
+
|
18
|
+
def decompressed_data
|
19
|
+
result = nil
|
20
|
+
block = eval_parameter(:block)
|
21
|
+
|
22
|
+
if (block.flags & MPQ_FILE_EXISTS) > 0
|
23
|
+
result = self.data
|
24
|
+
end
|
25
|
+
|
26
|
+
if (block.flags & MPQ_FILE_ENCRYPTED) > 0
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
if (block.flags & MPQ_SINGLE_UNIT) > 0
|
31
|
+
if block.flags & MPQ_COMPRESSED && block.file_size > block.block_size
|
32
|
+
result = decompress(self.data)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
end
|
36
|
+
|
37
|
+
result
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def decompress(data)
|
42
|
+
compression_type = data.bytes.first
|
43
|
+
case compression_type
|
44
|
+
when 0
|
45
|
+
data
|
46
|
+
when 2
|
47
|
+
Zlib::Deflate.deflate(data[1,data.size - 1])
|
48
|
+
when 16
|
49
|
+
Bzip2.uncompress(data[1,data.size - 1])
|
50
|
+
else
|
51
|
+
raise NotImplementedError
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class FileDataArray < BinData::BasePrimitive
|
57
|
+
mandatory_parameters :blocks, :sector_size_shift
|
58
|
+
|
59
|
+
def read_and_return_value(io)
|
60
|
+
result = []
|
61
|
+
sector_size = 512 * (2 ** eval_parameter(:sector_size_shift))
|
62
|
+
|
63
|
+
eval_parameter(:blocks).each do |block|
|
64
|
+
num_sectors = block.flags & 0x01000000 ? 1 : (block["block_size"] / sector_size)
|
65
|
+
file = FileData.new(:adjust_offset => block.block_offset + eval_parameter(:archive_header_offset),
|
66
|
+
:block => block).read(io)
|
67
|
+
file.block_offset = block.block_offset
|
68
|
+
|
69
|
+
result << file
|
70
|
+
end
|
71
|
+
|
72
|
+
result
|
73
|
+
end
|
74
|
+
|
75
|
+
def value_to_binary_string(value)
|
76
|
+
value.pack("V*")
|
77
|
+
end
|
78
|
+
|
79
|
+
def sensible_default
|
80
|
+
''
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Tassadar
|
2
|
+
module MPQ
|
3
|
+
class FileTime < BinData::Record
|
4
|
+
endian :little
|
5
|
+
|
6
|
+
uint32 :low_date_time
|
7
|
+
uint32 :high_date_time
|
8
|
+
end
|
9
|
+
|
10
|
+
class Md5 < BinData::Record
|
11
|
+
endian :little
|
12
|
+
|
13
|
+
string :md5_hash, :length => 16
|
14
|
+
end
|
15
|
+
|
16
|
+
class HashTable < BinData::Record
|
17
|
+
endian :little
|
18
|
+
|
19
|
+
array :hashes, :read_until => :eof do
|
20
|
+
uint32 :file_path_hash_a
|
21
|
+
uint32 :file_path_hash_b
|
22
|
+
uint16 :language
|
23
|
+
uint8 :platform
|
24
|
+
skip :length => 1
|
25
|
+
uint32 :file_block_index
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class EncryptedHashTable < BinData::BasePrimitive
|
30
|
+
mandatory_parameters :entries
|
31
|
+
|
32
|
+
def read_and_return_value(io)
|
33
|
+
value = BlockEncryptor.new("(hash table)", 0x300, io, eval_parameter(:entries) * 16).decrypt
|
34
|
+
HashTable.read(value)
|
35
|
+
end
|
36
|
+
|
37
|
+
def value_to_binary_string(value)
|
38
|
+
packed_string = ""
|
39
|
+
value.hashes.each do |hash|
|
40
|
+
packed_string += [hash.file_path_hash_a, hash.file_path_hash_b, hash.language, hash.platform, 0, hash.file_block_index].pack("VVvCCV")
|
41
|
+
end
|
42
|
+
|
43
|
+
BlockEncryptor.new("(hash table)", 0x300, value, eval_parameter(:entries) * 16).encrypt
|
44
|
+
end
|
45
|
+
|
46
|
+
def sensible_default
|
47
|
+
[0,0,0,0,0,0].pack("VVvCCV")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Tassadar
|
2
|
+
module MPQ
|
3
|
+
class Sector < BinData::Record
|
4
|
+
endian :little
|
5
|
+
|
6
|
+
uint8 :compression_mask
|
7
|
+
end
|
8
|
+
|
9
|
+
class SectorArray < BinData::BasePrimitive
|
10
|
+
mandatory_parameters :sector_offsets, :sector_size
|
11
|
+
|
12
|
+
def read_and_return_value(io)
|
13
|
+
result = []
|
14
|
+
file_data_offset = parent.offset
|
15
|
+
|
16
|
+
eval_parameter(:sector_offsets).each do |offset|
|
17
|
+
raise offset.inspect
|
18
|
+
result << Sector.new(:adjust_offset => offset + file_data_offset).read(io)
|
19
|
+
end
|
20
|
+
|
21
|
+
result
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# https://github.com/GraylinKim/sc2reader/wiki/replay.attributes.events
|
2
|
+
|
3
|
+
module Tassadar
|
4
|
+
module SC2
|
5
|
+
class Attribute < BinData::Record
|
6
|
+
endian :little
|
7
|
+
|
8
|
+
string :header, :read_length => 4, :check_value => "\xE7\x03\x00\x00"
|
9
|
+
uint32 :id
|
10
|
+
uint8 :player_number
|
11
|
+
reverse_string :attribute_value, :read_length => 4
|
12
|
+
end
|
13
|
+
|
14
|
+
class Attributes < BinData::Record
|
15
|
+
endian :little
|
16
|
+
|
17
|
+
skip :length => 5
|
18
|
+
uint32 :num_attributes
|
19
|
+
|
20
|
+
array :attributes, :type => :attribute, :initial_length => :num_attributes
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Tassadar
|
2
|
+
module SC2
|
3
|
+
class Game
|
4
|
+
attr_accessor :map, :time, :winner, :speed, :type, :category
|
5
|
+
|
6
|
+
def initialize(replay)
|
7
|
+
@winner = replay.players.select {|p| p.won}.first
|
8
|
+
@time = convert_windows_to_ruby_date_time(replay.details.data[5], replay.details.data[6])
|
9
|
+
@map = replay.details.data[1]
|
10
|
+
@type = replay.attributes.attributes.select {|a| a.id == 2001}.first.attribute_value
|
11
|
+
|
12
|
+
speeds = {"Fasr" => "Faster", "Slow" => "Slow", "Fast" => "Fast", "Norm" => "Normal", "Slor" => "Slower"}
|
13
|
+
@speed = speeds[replay.attributes.attributes.select {|a| a.id == 3000}.first.attribute_value]
|
14
|
+
|
15
|
+
categories = {"Amm" => "Ladder", "Priv" => "Private", "Pub" => "Public"}
|
16
|
+
@category = categories[replay.attributes.attributes.select {|a| a.id == 3009}.first.attribute_value]
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def convert_windows_to_ruby_date_time(time, zone)
|
21
|
+
unix_time = (time - 116444735995904000) / (10 ** 7)
|
22
|
+
|
23
|
+
@time = Time.at(unix_time)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Tassadar
|
2
|
+
module SC2
|
3
|
+
class Player
|
4
|
+
attr_accessor :name, :id, :won, :color, :chosen_race, :actual_race, :handicap
|
5
|
+
|
6
|
+
def initialize(details_hash, attributes)
|
7
|
+
@name = details_hash[0]
|
8
|
+
@id = details_hash[1][4]
|
9
|
+
@won = [false, true, false][details_hash[8]]
|
10
|
+
@color = {:alpha => details_hash[3][0], :red => details_hash[3][1], :green => details_hash[3][2], :blue => details_hash[3][3]}
|
11
|
+
races = {"Terr" => "Terran", "Prot" => "Protoss", "Zerg" => "Zerg", "RAND" => "Random"}
|
12
|
+
@chosen_race = races[attributes.select {|a| a.id == 0x0BB9 && a.player_number == details_hash[7] + 1}.first.attribute_value]
|
13
|
+
@actual_race = details_hash[2]
|
14
|
+
@handicap = details_hash[6]
|
15
|
+
end
|
16
|
+
|
17
|
+
def winner?
|
18
|
+
@won
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'tassadar/sc2/reverse_string'
|
2
|
+
require 'tassadar/sc2/serialized_data'
|
3
|
+
require 'tassadar/sc2/attributes'
|
4
|
+
require 'tassadar/sc2/details'
|
5
|
+
require 'tassadar/sc2/player'
|
6
|
+
require 'tassadar/sc2/game'
|
7
|
+
|
8
|
+
module Tassadar
|
9
|
+
module SC2
|
10
|
+
class Replay
|
11
|
+
attr_accessor :mpq, :attributes, :details, :players, :game
|
12
|
+
|
13
|
+
def initialize(filename)
|
14
|
+
@mpq = MPQ::MPQ.read(File.read(filename))
|
15
|
+
@attributes = Attributes.read(@mpq.read_file("replay.attributes.events"))
|
16
|
+
@details = Details.read(@mpq.read_file("replay.details"))
|
17
|
+
|
18
|
+
@players = @details.data[0].map {|h| Player.new(h, @attributes.attributes)}
|
19
|
+
@game = Game.new(self)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Tassadar
|
2
|
+
module SC2
|
3
|
+
class ReverseString < BinData::String
|
4
|
+
mandatory_parameters :read_length
|
5
|
+
|
6
|
+
def read_and_return_value(io)
|
7
|
+
super.reverse.gsub("\x00", '')
|
8
|
+
end
|
9
|
+
|
10
|
+
def value_to_binary_string(value)
|
11
|
+
clamp_to_length(val.reverse)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Tassadar
|
2
|
+
module SC2
|
3
|
+
class SerializedData < BinData::BasePrimitive
|
4
|
+
def read_and_return_value(io)
|
5
|
+
key = io.readbytes(1).unpack("C").first
|
6
|
+
|
7
|
+
case key
|
8
|
+
when 2
|
9
|
+
read_byte_string(io)
|
10
|
+
when 4
|
11
|
+
read_array(io)
|
12
|
+
when 5
|
13
|
+
read_kvo(io)
|
14
|
+
when 6
|
15
|
+
read_small_int(io)
|
16
|
+
when 7
|
17
|
+
read_big_int(io)
|
18
|
+
when 9
|
19
|
+
read_vlf_int(io)
|
20
|
+
else
|
21
|
+
puts "No parser for key: #{key}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def value_to_binary_string(value)
|
26
|
+
value.pack("V")
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def read_byte_string(io)
|
31
|
+
num_bytes = io.readbytes(1).unpack("C").first >> 1
|
32
|
+
|
33
|
+
io.readbytes(num_bytes).unpack("A#{num_bytes}").first.force_encoding('UTF-8')
|
34
|
+
end
|
35
|
+
|
36
|
+
def read_small_int(io)
|
37
|
+
io.readbytes(1).unpack("C").first
|
38
|
+
end
|
39
|
+
|
40
|
+
def read_big_int(io)
|
41
|
+
io.readbytes(4).unpack("v").first
|
42
|
+
end
|
43
|
+
|
44
|
+
def read_vlf_int(io)
|
45
|
+
byte = io.readbytes(1).unpack("C").first
|
46
|
+
value = (byte & 0x7F)
|
47
|
+
shift = 1
|
48
|
+
|
49
|
+
while byte & 0x80 > 0
|
50
|
+
byte = io.readbytes(1).unpack("C").first
|
51
|
+
value += (byte & 0x7F) << (7 * shift)
|
52
|
+
shift += 1
|
53
|
+
end
|
54
|
+
|
55
|
+
(value & 1) == 1 ? -(value >> 1) : (value >> 1)
|
56
|
+
end
|
57
|
+
|
58
|
+
def read_array(io)
|
59
|
+
result = []
|
60
|
+
2.times { io.readbytes(1).unpack("C").first }
|
61
|
+
|
62
|
+
num_elements = io.readbytes(1).unpack("C").first >> 1
|
63
|
+
|
64
|
+
num_elements.times do
|
65
|
+
result << read_and_return_value(io)
|
66
|
+
end
|
67
|
+
|
68
|
+
result
|
69
|
+
end
|
70
|
+
|
71
|
+
def read_kvo(io)
|
72
|
+
result = {}
|
73
|
+
num_pairs = io.readbytes(1).unpack("C").first >> 1
|
74
|
+
|
75
|
+
num_pairs.times do
|
76
|
+
key = io.readbytes(1).unpack("C").first >> 1
|
77
|
+
value = read_and_return_value(io)
|
78
|
+
|
79
|
+
result[key] = value
|
80
|
+
end
|
81
|
+
|
82
|
+
result
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Tassadar::MPQ::ArchiveHeader do
|
4
|
+
before(:each) do
|
5
|
+
@archive_header = Tassadar::MPQ::ArchiveHeader.read("MPQ\x1A,\x00\x00\x00P5\x00\x00\x01\x00\x03\x00\xB03\x00\x00\xB04\x00\x00\x10\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "require a valid magic string" do
|
9
|
+
it "should accept a valid magic string" do
|
10
|
+
expect { Tassadar::MPQ::ArchiveHeader.read("MPQ\x1A,\x00\x00\x00P5\x00\x00\x01\x00\x03\x00\xB03\x00\x00\xB04\x00\x00\x10\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") }.to_not raise_error
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should not accept an invalid magic string" do
|
14
|
+
expect { Tassadar::MPQ.ArchiveHeader.read("MPQ\x1A,\x00\x00\x00P5\x00\x00\x01\x00\x03\x00\xB03\x00\x00\xB04\x00\x00\x10\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") }.to raise_error
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should read the magic header" do
|
19
|
+
@archive_header.magic.should == "MPQ"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should read magic 4" do
|
23
|
+
@archive_header.magic_4.should == 26
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should read the header size" do
|
27
|
+
@archive_header.header_size.should == 44
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should read the archive size" do
|
31
|
+
@archive_header.archive_size.should == 13648
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should read the format version" do
|
35
|
+
@archive_header.format_version.should == 1
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should read the sector size shift" do
|
39
|
+
@archive_header.sector_size_shift.should == 3
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should read the hash table offset" do
|
43
|
+
@archive_header.hash_table_offset.should == 13232
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should read the block table offset" do
|
47
|
+
@archive_header.block_table_offset.should == 13488
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should read the hash table entries" do
|
51
|
+
@archive_header.hash_table_entries.should > 0
|
52
|
+
@archive_header.hash_table_entries.should < 2 ** 20
|
53
|
+
Math.log2(@archive_header.hash_table_entries.to_i).floor.should == Math.log2(@archive_header.hash_table_entries.to_i).ceil
|
54
|
+
@archive_header.hash_table_entries.should == 16
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should read the block table entries" do
|
58
|
+
@archive_header.block_table_entries.should == 10
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should read the extended_block_table_offset" do
|
62
|
+
@archive_header.extended_block_table_offset.should == 0
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should read the hash table offset high" do
|
66
|
+
@archive_header.hash_table_offset_high.should == 0
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should read the block table offset high" do
|
70
|
+
@archive_header.block_table_offset_high.should == 0
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Tassadar::MPQ::ArchiveHeader do
|
4
|
+
before(:each) do
|
5
|
+
@mpq = Tassadar::MPQ::MPQ.read(File.read("spec/replays/Delta\ Quadrant.SC2Replay"))
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should have some blocks" do
|
9
|
+
@mpq.block_table.blocks.size.should == 10
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should have a valid block table entry" do
|
13
|
+
block = @mpq.block_table.blocks.first
|
14
|
+
block.block_offset.should == 0x0000002C
|
15
|
+
block.block_size.should == 448
|
16
|
+
block.file_size.should == 448
|
17
|
+
block.flags.should == 0x81000200
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should have another valid block table entry" do
|
21
|
+
block = @mpq.block_table.blocks[1]
|
22
|
+
block.block_offset.should == 0x000001EC
|
23
|
+
block.block_size.should == 652
|
24
|
+
block.file_size.should == 1216
|
25
|
+
block.flags.should == 0x81000200
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# MPQ archive block table
|
30
|
+
# -----------------------------------
|
31
|
+
# Offset ArchSize RealSize Flags
|
32
|
+
# 0000002C 448 448 81000200
|
33
|
+
# 000001EC 652 1216 81000200
|
34
|
+
# 00000478 9984 19453 81000200
|
35
|
+
# 00002B78 113 149 81000200
|
36
|
+
# 00002BE9 96 96 81000200
|
37
|
+
# 00002C49 578 760 81000200
|
38
|
+
# 00002E8B 682 1112 81000200
|
39
|
+
# 00003135 254 581 81000200
|
40
|
+
# 00003233 120 164 81000200
|
41
|
+
# 000032AB 261 288 81000200
|
data/spec/mpq_spec.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Tassadar::MPQ::MPQ do
|
4
|
+
before(:each) do
|
5
|
+
@mpq = Tassadar::MPQ::MPQ.read(File.read("spec/replays/Delta\ Quadrant.SC2Replay"))
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should have a valid magic string" do
|
9
|
+
@mpq.magic.should == "MPQ"
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should read the user data size" do
|
13
|
+
@mpq.user_data_size.should == 512
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should have block_table entries" do
|
17
|
+
@mpq.block_table.blocks.size.should == 10
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should have hash_table entries" do
|
21
|
+
@mpq.hash_table.hashes.size.should == 16
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should have files" do
|
25
|
+
@mpq.file_data.size.should > 1
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should have a list of files" do
|
29
|
+
@mpq.files.size.should == 8
|
30
|
+
@mpq.files.should include("replay.attributes.events")
|
31
|
+
end
|
32
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Tassadar::SC2::Game do
|
4
|
+
before(:each) do
|
5
|
+
@replay = Tassadar::SC2::Replay.new("spec/replays/Delta\ Quadrant.SC2Replay")
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should set the winner" do
|
9
|
+
@replay.game.winner.name.should == "redgar"
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should set the map" do
|
13
|
+
@replay.game.map.should == "Delta Quadrant"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should set the time" do
|
17
|
+
@replay.game.time.should == Time.new(2011, 07, 05, 17, 01, 8, "-05:00")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should set the speed" do
|
21
|
+
@replay.game.speed.should == "Faster"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should set the game type" do
|
25
|
+
@replay.game.type.should == "1v1"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should set the category" do
|
29
|
+
@replay.game.category.should == "Ladder"
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Tassadar::SC2::Player do
|
5
|
+
context 'NA Sc2 Replay' do
|
6
|
+
before(:each) do
|
7
|
+
@replay = Tassadar::SC2::Replay.new("spec/replays/Delta\ Quadrant.SC2Replay")
|
8
|
+
@player = @replay.players.first
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should set the name" do
|
12
|
+
@player.name.should == "guitsaru"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should set the id" do
|
16
|
+
@player.id.should == 1918894
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should tell if the player won" do
|
20
|
+
@player.should_not be_winner
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should have a color" do
|
24
|
+
@player.color.should == {:alpha => 255, :red => 180, :green => 20, :blue => 30}
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should have a chosen race" do
|
28
|
+
@player.chosen_race.should == "Terran"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should have random as the chosen race if random" do
|
32
|
+
replay = Tassadar::SC2::Replay.new("spec/replays/random.sc2replay")
|
33
|
+
replay.players.last.chosen_race.should == "Random"
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should have an actual race" do
|
37
|
+
@player.actual_race.should == "Terran"
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should have a handicap" do
|
41
|
+
@player.handicap.should == 100
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'EU SC2 Replay' do
|
46
|
+
let(:replay) { Tassadar::SC2::Replay.new('spec/replays/eu_replay.SC2Replay') }
|
47
|
+
subject { replay.players.last }
|
48
|
+
|
49
|
+
it 'encodes the name in UTF-8' do
|
50
|
+
subject.name.encoding.to_s.should == 'UTF-8'
|
51
|
+
subject.name.should == 'MǂStephano'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/spec_watchr.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
ENV["WATCHR"] = "1"
|
2
|
+
system 'clear'
|
3
|
+
|
4
|
+
$spec_cmd = "bundle exec rspec --tty --color --format nested -d"
|
5
|
+
|
6
|
+
def run(cmd)
|
7
|
+
puts(cmd)
|
8
|
+
if system("which growlnotify > /dev/null")
|
9
|
+
run_with_notifier :growl, cmd
|
10
|
+
elsif system("which notify-send > /dev/null")
|
11
|
+
run_with_notifier :libnotify, cmd
|
12
|
+
else
|
13
|
+
`#{cmd}`
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def run_with_notifier(notifier, cmd)
|
18
|
+
pass = system(cmd)
|
19
|
+
if pass
|
20
|
+
image = File.join(File.dirname(__FILE__), 'support', 'rails_ok.png')
|
21
|
+
message = "Success!"
|
22
|
+
else
|
23
|
+
image = File.join(File.dirname(__FILE__), 'support', 'rails_fail.png')
|
24
|
+
message = "Failure!"
|
25
|
+
end
|
26
|
+
|
27
|
+
case notifier
|
28
|
+
when :growl
|
29
|
+
`growlnotify -n "StarLeagues Specs" -m "StarLeagues Specs" --image #{image} #{message}`
|
30
|
+
when :libnotify
|
31
|
+
`notify-send --icon #{image} "StarLeagues Specs" #{message}`
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def run_spec(file)
|
36
|
+
system('clear')
|
37
|
+
result = run "#{$spec_cmd} #{file}"
|
38
|
+
result.split("\n").last rescue nil
|
39
|
+
puts result
|
40
|
+
end
|
41
|
+
|
42
|
+
def run_all_specs
|
43
|
+
system('clear')
|
44
|
+
result = run "#{$spec_cmd} spec/"
|
45
|
+
puts result
|
46
|
+
end
|
47
|
+
|
48
|
+
def related_specs(path)
|
49
|
+
Dir['spec/**/*.rb'].select { |file| file =~ /#{File.basename(path).split(".").first}_spec.rb/ }
|
50
|
+
end
|
51
|
+
|
52
|
+
watch('.*') { run_all_specs }
|
53
|
+
|
54
|
+
# Ctrl-\
|
55
|
+
Signal.trap 'QUIT' do
|
56
|
+
puts " --- Running all specs ---\n\n"
|
57
|
+
run_all_specs
|
58
|
+
end
|
59
|
+
|
60
|
+
@interrupted = false
|
61
|
+
|
62
|
+
# Ctrl-C
|
63
|
+
Signal.trap 'INT' do
|
64
|
+
if @interrupted then
|
65
|
+
@wants_to_quit = true
|
66
|
+
abort("\n")
|
67
|
+
else
|
68
|
+
puts "Interrupt a second time to quit"
|
69
|
+
@interrupted = true
|
70
|
+
Kernel.sleep 1.5
|
71
|
+
# raise Interrupt, nil # let the run loop catch it
|
72
|
+
run_all_specs
|
73
|
+
@interrupted = false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
puts "Watching..."
|
data/tassadar.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "tassadar/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "tassadar"
|
7
|
+
s.version = Tassadar::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Matt Pruitt", "Andrew Nordman"]
|
10
|
+
s.email = ["mpruitt@agoragames.com", "anordman@agoragames.com"]
|
11
|
+
s.homepage = "https://github.com/agoragames/tassadar"
|
12
|
+
s.summary = %q{Pure ruby MPQ and SC2 Replay parser}
|
13
|
+
s.description = %q{Pure ruby MPQ and SC2 Replay parser}
|
14
|
+
|
15
|
+
s.rubyforge_project = "tassadar"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency("bindata")
|
23
|
+
s.add_dependency("bzip2-ruby")
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tassadar
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Matt Pruitt
|
9
|
+
- Andrew Nordman
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-05-04 00:00:00.000000000Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: bindata
|
17
|
+
requirement: &70239924932500 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *70239924932500
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: bzip2-ruby
|
28
|
+
requirement: &70239924854920 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *70239924854920
|
37
|
+
description: Pure ruby MPQ and SC2 Replay parser
|
38
|
+
email:
|
39
|
+
- mpruitt@agoragames.com
|
40
|
+
- anordman@agoragames.com
|
41
|
+
executables: []
|
42
|
+
extensions: []
|
43
|
+
extra_rdoc_files: []
|
44
|
+
files:
|
45
|
+
- .gitignore
|
46
|
+
- .rspec
|
47
|
+
- .rvmrc
|
48
|
+
- CHANGELOG.md
|
49
|
+
- Gemfile
|
50
|
+
- LICENSE.txt
|
51
|
+
- README.md
|
52
|
+
- Rakefile
|
53
|
+
- lib/tassadar.rb
|
54
|
+
- lib/tassadar/mpq.rb
|
55
|
+
- lib/tassadar/mpq/archive_header.rb
|
56
|
+
- lib/tassadar/mpq/archive_size.rb
|
57
|
+
- lib/tassadar/mpq/block_encryptor.rb
|
58
|
+
- lib/tassadar/mpq/block_table.rb
|
59
|
+
- lib/tassadar/mpq/crypt_buf.rb
|
60
|
+
- lib/tassadar/mpq/file_data.rb
|
61
|
+
- lib/tassadar/mpq/hash_table.rb
|
62
|
+
- lib/tassadar/mpq/sector.rb
|
63
|
+
- lib/tassadar/sc2/attributes.rb
|
64
|
+
- lib/tassadar/sc2/details.rb
|
65
|
+
- lib/tassadar/sc2/game.rb
|
66
|
+
- lib/tassadar/sc2/player.rb
|
67
|
+
- lib/tassadar/sc2/replay.rb
|
68
|
+
- lib/tassadar/sc2/reverse_string.rb
|
69
|
+
- lib/tassadar/sc2/serialized_data.rb
|
70
|
+
- lib/tassadar/version.rb
|
71
|
+
- spec/mpq/archive_header_spec.rb
|
72
|
+
- spec/mpq/block_table_spec.rb
|
73
|
+
- spec/mpq_spec.rb
|
74
|
+
- spec/replays/Delta Quadrant.SC2Replay
|
75
|
+
- spec/replays/eu_replay.SC2Replay
|
76
|
+
- spec/replays/random.sc2replay
|
77
|
+
- spec/sc2/game_spec.rb
|
78
|
+
- spec/sc2/player_spec.rb
|
79
|
+
- spec/spec_helper.rb
|
80
|
+
- spec/spec_watchr.rb
|
81
|
+
- tassadar.gemspec
|
82
|
+
homepage: https://github.com/agoragames/tassadar
|
83
|
+
licenses: []
|
84
|
+
post_install_message:
|
85
|
+
rdoc_options: []
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
hash: -1060130835485239362
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ! '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
segments:
|
104
|
+
- 0
|
105
|
+
hash: -1060130835485239362
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project: tassadar
|
108
|
+
rubygems_version: 1.8.10
|
109
|
+
signing_key:
|
110
|
+
specification_version: 3
|
111
|
+
summary: Pure ruby MPQ and SC2 Replay parser
|
112
|
+
test_files:
|
113
|
+
- spec/mpq/archive_header_spec.rb
|
114
|
+
- spec/mpq/block_table_spec.rb
|
115
|
+
- spec/mpq_spec.rb
|
116
|
+
- spec/replays/Delta Quadrant.SC2Replay
|
117
|
+
- spec/replays/eu_replay.SC2Replay
|
118
|
+
- spec/replays/random.sc2replay
|
119
|
+
- spec/sc2/game_spec.rb
|
120
|
+
- spec/sc2/player_spec.rb
|
121
|
+
- spec/spec_helper.rb
|
122
|
+
- spec/spec_watchr.rb
|