tassadar 0.0.2
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.
- 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
|