mpq 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/VERSION +1 -1
- data/lib/jsonish.rb +75 -0
- data/lib/replay_file.rb +205 -0
- data/mpq.gemspec +78 -0
- data/test/test_jsonish.rb +40 -0
- data/test/test_replay_file.rb +59 -0
- metadata +10 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/jsonish.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# MPQ files store some data in a serialized format that's strikingly similar to
|
2
|
+
# JSON, so I've called it **JSONish**. It's a fairly simple format, documented
|
3
|
+
# at http://github.com/GraylinKim/sc2reader/wiki/serialized.data and at
|
4
|
+
# http://teamliquid.net/forum/viewmessage.php?topic_id=117260¤tpage=3#45.
|
5
|
+
module MPQ
|
6
|
+
module JSONish
|
7
|
+
|
8
|
+
# `parse` is the public API here, the following two methods are simply
|
9
|
+
# helpers.
|
10
|
+
def self.parse data
|
11
|
+
self.parse_recur String.new data
|
12
|
+
end
|
13
|
+
|
14
|
+
# JSONish consists of strings, arrays, maps, and integers. The first byte
|
15
|
+
# of each of these indicates which is about to follow.
|
16
|
+
def self.parse_recur data
|
17
|
+
case data.slice!(0).bytes.next
|
18
|
+
|
19
|
+
# `02` indicates a string. The next byte is a variable-length integer
|
20
|
+
# (see below) indicating the string's length, and the remaining bytes are
|
21
|
+
# the string itself.
|
22
|
+
when 2
|
23
|
+
data.slice! 0, vlf(data)
|
24
|
+
|
25
|
+
# `04` is an array, a list of values. Each value indicates its type, so
|
26
|
+
# this is largely just a recursive process.
|
27
|
+
when 4
|
28
|
+
data.slice! 0, 2
|
29
|
+
(0...vlf(data)).map {|i| parse_recur data }
|
30
|
+
|
31
|
+
# `05` starts a map, also known as a Hash or an object literal. It maps
|
32
|
+
# keys to values. In JSONish, keys are always variable-length integers,
|
33
|
+
# while values can be anything.
|
34
|
+
when 5
|
35
|
+
Hash.[]((0...vlf(data)).map do |i|
|
36
|
+
[vlf(data), parse_recur(data)]
|
37
|
+
end)
|
38
|
+
|
39
|
+
# `06` is a single-byte integer.
|
40
|
+
when 6
|
41
|
+
data.slice! 0
|
42
|
+
|
43
|
+
# `07` is a four-byte integer in little-endian format.
|
44
|
+
when 7
|
45
|
+
data.slice!(0, 4).unpack("V")[0]
|
46
|
+
|
47
|
+
# `09` is a standalone (i.e. not a key or length) variable-length integer.
|
48
|
+
when 9
|
49
|
+
vlf data
|
50
|
+
|
51
|
+
# If there are other types in JSONish, we don't know about them.
|
52
|
+
else
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# A variable-length integer is a concise serialization of an
|
58
|
+
# arbitrary-precision integer. Each byte (except the last) sets the high bit
|
59
|
+
# to `1` to indicate that the next byte is included in the integer. Seven
|
60
|
+
# bits of each byte, plus all eight bits of the final byte, make up the
|
61
|
+
# final number in little-endian format.
|
62
|
+
def self.vlf data
|
63
|
+
ret, shift = 0, 0
|
64
|
+
loop do
|
65
|
+
char = data.slice!(0)
|
66
|
+
return nil unless char
|
67
|
+
byte = char.bytes.next
|
68
|
+
ret += (byte & 0x7F) << (7 * shift)
|
69
|
+
break if byte & 0x80 == 0
|
70
|
+
shift += 1
|
71
|
+
end
|
72
|
+
(ret >> 1) * ((ret & 0x1) == 0 ? 1 : -1)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/replay_file.rb
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
# StarCraft 2 replay files are MPQ archives, with some data serialized as what
|
2
|
+
# I'm calling *JSONish*. We also use `bindata` as a DSL for some of the file
|
3
|
+
# contents.
|
4
|
+
require 'bindata'
|
5
|
+
require 'mpq'
|
6
|
+
require 'jsonish'
|
7
|
+
|
8
|
+
module MPQ
|
9
|
+
class SC2ReplayFile < Archive
|
10
|
+
|
11
|
+
# Game length is given as the number of frames.
|
12
|
+
def game_length
|
13
|
+
@game_length ||= user_data[3] / FRAMES_PER_SECOND
|
14
|
+
end
|
15
|
+
|
16
|
+
# These are the numbers you see in the bottom left of the game's menu. Use
|
17
|
+
# the `build` to change features, etc. based on game version. Use the rest
|
18
|
+
# when presenting a version to a person.
|
19
|
+
def game_version
|
20
|
+
@game_version ||= {
|
21
|
+
:major => user_data[1][1],
|
22
|
+
:minor => user_data[1][2],
|
23
|
+
:patch => user_data[1][3],
|
24
|
+
:build => user_data[1][4]
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Player information is spread among a couple of files: the
|
29
|
+
# `replay.details` file and the `replay.attributes.events` file. Here we
|
30
|
+
# combine the information contained in each.
|
31
|
+
def players
|
32
|
+
return @players if defined? @players
|
33
|
+
@players = details[0].map do |player|
|
34
|
+
{ :name => player[0],
|
35
|
+
|
36
|
+
# This could probably be 'unknown' in some circumstances I haven't
|
37
|
+
# yet checked.
|
38
|
+
:outcome => OUTCOMES[player[8]]
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
# Unlike the `replay.initData` file, this method of determining race is
|
43
|
+
# the same across all localizations.
|
44
|
+
attributes.each do |attr|
|
45
|
+
case attr.id.to_i
|
46
|
+
when 0x01f4
|
47
|
+
@players[attr.player - 1][:type] = ATTRIBUTES[:player_type][attr.sval]
|
48
|
+
when 0x0bb9
|
49
|
+
@players[attr.player - 1][:race] = ATTRIBUTES[:player_race][attr.sval]
|
50
|
+
when 0x0bba
|
51
|
+
@players[attr.player - 1][:color] =
|
52
|
+
ATTRIBUTES[:player_color][attr.sval]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
@players
|
56
|
+
end
|
57
|
+
|
58
|
+
# The localized map name. Should probably translate it.
|
59
|
+
def map_name
|
60
|
+
details[1]
|
61
|
+
end
|
62
|
+
|
63
|
+
# The start date of the game, probably off by a time zone or ten.
|
64
|
+
def start_date
|
65
|
+
=begin
|
66
|
+
FIXME: parse this as UTC (possibly requires use of time zone offset
|
67
|
+
present in details).
|
68
|
+
=end
|
69
|
+
Time.at((details[5] - 116444735995904000) / 1e7)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Two-uppercase-character abbreviation, like `NA`.
|
73
|
+
def realm
|
74
|
+
initdata.rest.split('s2ma')[1][2, 2]
|
75
|
+
end
|
76
|
+
|
77
|
+
# Each of these getters are for information contained in the
|
78
|
+
# `replay.attributes.events` file, but their exact position cannot be
|
79
|
+
# assumed from file to file, so we might as well extract them all when
|
80
|
+
# asked for any one of them.
|
81
|
+
%w[game_type game_speed category].each do |lazy_getter|
|
82
|
+
class_eval <<-EVAL
|
83
|
+
def #{lazy_getter}
|
84
|
+
parse_global_attributes unless defined? @#{lazy_getter}
|
85
|
+
@#{lazy_getter}
|
86
|
+
end
|
87
|
+
EVAL
|
88
|
+
end
|
89
|
+
|
90
|
+
# Wrappers for deserializing some JSONish.
|
91
|
+
private
|
92
|
+
def user_data
|
93
|
+
@user_data ||= JSONish.parse @user_header.user_data
|
94
|
+
end
|
95
|
+
|
96
|
+
def details
|
97
|
+
@details ||= JSONish.parse read_file "replay.details"
|
98
|
+
end
|
99
|
+
|
100
|
+
# `replay.initData` has some useful information, but almost all of it can
|
101
|
+
# be more reliably obtained elsewhere. All that's really useful here is the
|
102
|
+
# realm the game was played in.
|
103
|
+
def initdata
|
104
|
+
@initdata ||= InitData.read read_file "replay.initData"
|
105
|
+
end
|
106
|
+
|
107
|
+
class InitData < BinData::Record
|
108
|
+
uint8 :num_players
|
109
|
+
array :players, :initial_length => :num_players do
|
110
|
+
uint8 :player_name_length
|
111
|
+
string :player_name, :length => :player_name_length
|
112
|
+
skip :length => 5
|
113
|
+
end
|
114
|
+
string :unknown_24, :length => 24
|
115
|
+
uint8 :account_length
|
116
|
+
string :account, :length => :account_length
|
117
|
+
rest :rest
|
118
|
+
end
|
119
|
+
|
120
|
+
# `replay.attributes.events` has plenty of handy information. Here we
|
121
|
+
# simply deserialize all the attributes, taking into account a format
|
122
|
+
# change that took place in build 17326, for later processing.
|
123
|
+
def attributes
|
124
|
+
return @attributes if defined? @attributes
|
125
|
+
data = read_file "replay.attributes.events"
|
126
|
+
data.slice! 0, (game_version[:build] < 17326 ? 4 : 5)
|
127
|
+
@attributes = []
|
128
|
+
data.slice!(0, 4).unpack("V")[0].times do
|
129
|
+
@attributes << Attribute.read(data.slice!(0, 13))
|
130
|
+
end
|
131
|
+
@attributes
|
132
|
+
end
|
133
|
+
|
134
|
+
class Attribute < BinData::Record
|
135
|
+
endian :little
|
136
|
+
|
137
|
+
string :header, :length => 4
|
138
|
+
uint32 :id
|
139
|
+
uint8 :player
|
140
|
+
string :val, :length => 4
|
141
|
+
|
142
|
+
def sval
|
143
|
+
val.reverse
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Several pieces of information come from `replay.attributes.events`, and
|
148
|
+
# finding one of them is about as hard as finding all of them, so we just
|
149
|
+
# find all of them here when asked.
|
150
|
+
def parse_global_attributes
|
151
|
+
attributes.each do |attr|
|
152
|
+
case attr.id.to_i
|
153
|
+
when 0x07d1
|
154
|
+
@game_type = attr.sval
|
155
|
+
@game_type = @game_type == 'Cust' ? :custom : @game_type[1, 3].to_sym
|
156
|
+
when 0x0bb8
|
157
|
+
@game_speed = ATTRIBUTES[:game_speed][attr.sval]
|
158
|
+
when 0x0bc1
|
159
|
+
@category = ATTRIBUTES[:category][attr.sval]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Some translations for various values in replays.
|
167
|
+
FRAMES_PER_SECOND = 16
|
168
|
+
OUTCOMES = [:unknown, :win, :loss]
|
169
|
+
ATTRIBUTES = {
|
170
|
+
:player_type => {"Humn" => :human, "Comp" => :computer},
|
171
|
+
:player_race => {
|
172
|
+
"RAND" => :random, "Terr" => :terran, "Prot" => :protoss, "Zerg" => :zerg
|
173
|
+
},
|
174
|
+
:player_color => {
|
175
|
+
"tc01" => :red,
|
176
|
+
"tc02" => :blue,
|
177
|
+
"tc03" => :teal,
|
178
|
+
"tc04" => :purple,
|
179
|
+
"tc05" => :yellow,
|
180
|
+
"tc06" => :orange,
|
181
|
+
"tc07" => :green,
|
182
|
+
"tc08" => :light_pink,
|
183
|
+
"tc09" => :violet,
|
184
|
+
"tc10" => :light_grey,
|
185
|
+
"tc11" => :dark_green,
|
186
|
+
"tc12" => :brown,
|
187
|
+
"tc13" => :light_green,
|
188
|
+
"tc14" => :dark_grey,
|
189
|
+
"tc15" => :pink
|
190
|
+
},
|
191
|
+
:game_speed => {
|
192
|
+
"Slor" => :slower,
|
193
|
+
"Slow" => :slow,
|
194
|
+
"Norm" => :normal,
|
195
|
+
"Fast" => :fast,
|
196
|
+
"Fasr" => :faster
|
197
|
+
},
|
198
|
+
:category => {
|
199
|
+
"Priv" => :private,
|
200
|
+
|
201
|
+
# `Amm` is assumed to stand for "automatic matchmaker".
|
202
|
+
"Amm" => :ladder,
|
203
|
+
"Pub" => :public
|
204
|
+
}
|
205
|
+
}
|
data/mpq.gemspec
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{mpq}
|
8
|
+
s.version = "0.2.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Nolan Waite"]
|
12
|
+
s.date = %q{2011-06-16}
|
13
|
+
s.description = %q{Read files and metadata from MPQ archives}
|
14
|
+
s.email = %q{nolan@nolanw.ca}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
"Gemfile",
|
21
|
+
"Gemfile.lock",
|
22
|
+
"LICENSE.txt",
|
23
|
+
"README.md",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/jsonish.rb",
|
27
|
+
"lib/mpq.rb",
|
28
|
+
"lib/replay_file.rb",
|
29
|
+
"mpq.gemspec",
|
30
|
+
"test/helper.rb",
|
31
|
+
"test/some.SC2Replay",
|
32
|
+
"test/test_jsonish.rb",
|
33
|
+
"test/test_mpq.rb",
|
34
|
+
"test/test_replay_file.rb"
|
35
|
+
]
|
36
|
+
s.homepage = %q{http://github.com/nolanw/mpq}
|
37
|
+
s.licenses = ["WTFPL"]
|
38
|
+
s.require_paths = ["lib"]
|
39
|
+
s.rubygems_version = %q{1.7.2}
|
40
|
+
s.summary = %q{Read files and metadata from MPQ archives}
|
41
|
+
s.test_files = [
|
42
|
+
"test/helper.rb",
|
43
|
+
"test/test_jsonish.rb",
|
44
|
+
"test/test_mpq.rb",
|
45
|
+
"test/test_replay_file.rb"
|
46
|
+
]
|
47
|
+
|
48
|
+
if s.respond_to? :specification_version then
|
49
|
+
s.specification_version = 3
|
50
|
+
|
51
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
52
|
+
s.add_runtime_dependency(%q<bindata>, ["~> 1.3.1"])
|
53
|
+
s.add_runtime_dependency(%q<bzip2-ruby>, ["~> 0.2.7"])
|
54
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
55
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
56
|
+
s.add_development_dependency(%q<rocco>, ["~> 0.6"])
|
57
|
+
s.add_runtime_dependency(%q<bindata>, [">= 1.3.1"])
|
58
|
+
s.add_development_dependency(%q<rocco>, [">= 0.6"])
|
59
|
+
else
|
60
|
+
s.add_dependency(%q<bindata>, ["~> 1.3.1"])
|
61
|
+
s.add_dependency(%q<bzip2-ruby>, ["~> 0.2.7"])
|
62
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
63
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
64
|
+
s.add_dependency(%q<rocco>, ["~> 0.6"])
|
65
|
+
s.add_dependency(%q<bindata>, [">= 1.3.1"])
|
66
|
+
s.add_dependency(%q<rocco>, [">= 0.6"])
|
67
|
+
end
|
68
|
+
else
|
69
|
+
s.add_dependency(%q<bindata>, ["~> 1.3.1"])
|
70
|
+
s.add_dependency(%q<bzip2-ruby>, ["~> 0.2.7"])
|
71
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
72
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
73
|
+
s.add_dependency(%q<rocco>, ["~> 0.6"])
|
74
|
+
s.add_dependency(%q<bindata>, [">= 1.3.1"])
|
75
|
+
s.add_dependency(%q<rocco>, [">= 0.6"])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
$:.unshift File.expand_path(File.join File.dirname(__FILE__), '..', 'src')
|
2
|
+
require "jsonish"
|
3
|
+
require "test/unit"
|
4
|
+
|
5
|
+
class TestJSONish < Test::Unit::TestCase
|
6
|
+
def test_vlf
|
7
|
+
assert_equal 0, MPQ::JSONish.vlf("\x00")
|
8
|
+
assert_equal 0, MPQ::JSONish.vlf("\x01")
|
9
|
+
assert_equal 1, MPQ::JSONish.vlf("\x02")
|
10
|
+
assert_equal -1, MPQ::JSONish.vlf("\x03")
|
11
|
+
assert_equal 2, MPQ::JSONish.vlf("\x04")
|
12
|
+
assert_equal 63, MPQ::JSONish.vlf("\x7e")
|
13
|
+
assert_equal -63, MPQ::JSONish.vlf("\x7f")
|
14
|
+
assert_equal nil, MPQ::JSONish.vlf("\x80")
|
15
|
+
assert_equal 0, MPQ::JSONish.vlf("\x80\x00")
|
16
|
+
assert_equal 64, MPQ::JSONish.vlf("\x80\x01")
|
17
|
+
assert_equal -64, MPQ::JSONish.vlf("\x81\x01")
|
18
|
+
assert_equal 65, MPQ::JSONish.vlf("\x82\x01")
|
19
|
+
assert_equal 128, MPQ::JSONish.vlf("\x80\x02")
|
20
|
+
assert_equal -128, MPQ::JSONish.vlf("\x81\x02")
|
21
|
+
assert_equal 18092, MPQ::JSONish.vlf("\xd8\x9a\x02")
|
22
|
+
assert_equal 18317, MPQ::JSONish.vlf("\x9a\x9e\x02")
|
23
|
+
assert_equal 16777216, MPQ::JSONish.vlf("\x80\x80\x80\x10")
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_parse
|
27
|
+
assert_equal "hi", MPQ::JSONish.parse("\x02\x04\x68\x69")
|
28
|
+
assert_equal "Pille", MPQ::JSONish.parse("\x02\x0a\x50\x69\x6c\x6c\x65")
|
29
|
+
assert_equal(["Pille", "\x2a", "\xa6", "\x8d"],
|
30
|
+
MPQ::JSONish.parse("\x04\x00\x01\x08\x02\x0A\x50\x69\x6C\x6C\x65" +
|
31
|
+
"\x06\x2A\x06\xA6\x06\x8D"))
|
32
|
+
assert_equal({0 => "hi"},
|
33
|
+
MPQ::JSONish.parse("\x05\x02\x00\x02\x04\x68\x69"))
|
34
|
+
assert_equal({0 => "hi", 1 => "hi"},
|
35
|
+
MPQ::JSONish.parse("\x05\x04\x00\x02\x04\x68\x69\x02\x02\x04\x68\x69"))
|
36
|
+
assert_equal({0 => 1, 1 => 2, 4 => 3},
|
37
|
+
MPQ::JSONish.parse("\x05\x06\x00\x09\x02\x02\x09\x04\x08\x09\x06"))
|
38
|
+
assert_equal 76, MPQ::JSONish.parse("\x06\x4C").ord
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'replay_file'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
class TestReplayFile < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
@file = File.new File.join(File.dirname(__FILE__), "some.SC2Replay")
|
8
|
+
@replay = MPQ::SC2ReplayFile.new @file
|
9
|
+
end
|
10
|
+
|
11
|
+
def teardown
|
12
|
+
@file.close
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_game_version
|
16
|
+
assert_equal 1, @replay.game_version[:major]
|
17
|
+
assert_equal 3, @replay.game_version[:minor]
|
18
|
+
assert_equal 18317, @replay.game_version[:build]
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_game_length
|
22
|
+
assert_equal 1260, @replay.game_length
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_players
|
26
|
+
names = @replay.players.map{|p| p[:name]}
|
27
|
+
assert names.include? "ESCGoOdy"
|
28
|
+
goody = @replay.players[names.index "ESCGoOdy"]
|
29
|
+
assert_equal :terran, goody[:race]
|
30
|
+
assert_equal :purple, goody[:color]
|
31
|
+
assert_equal :human, goody[:type]
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_map_name
|
35
|
+
assert_equal "The Shattered Temple", @replay.map_name
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_start_date
|
39
|
+
# FIXME: should be parsed as UTC, and this test should reflect that.
|
40
|
+
assert (Time.parse("2011-04-24 10:09:18 -0600") -
|
41
|
+
@replay.start_date).abs < 1
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_realm
|
45
|
+
assert_equal "EU", @replay.realm
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_game_type
|
49
|
+
assert_equal :"1v1", @replay.game_type
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_game_speed
|
53
|
+
assert_equal :faster, @replay.game_speed
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_category
|
57
|
+
assert_equal :private, @replay.category
|
58
|
+
end
|
59
|
+
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: mpq
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.2.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Nolan Waite
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-
|
13
|
+
date: 2011-06-16 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: bindata
|
@@ -105,10 +105,15 @@ files:
|
|
105
105
|
- README.md
|
106
106
|
- Rakefile
|
107
107
|
- VERSION
|
108
|
+
- lib/jsonish.rb
|
108
109
|
- lib/mpq.rb
|
110
|
+
- lib/replay_file.rb
|
111
|
+
- mpq.gemspec
|
109
112
|
- test/helper.rb
|
110
113
|
- test/some.SC2Replay
|
114
|
+
- test/test_jsonish.rb
|
111
115
|
- test/test_mpq.rb
|
116
|
+
- test/test_replay_file.rb
|
112
117
|
homepage: http://github.com/nolanw/mpq
|
113
118
|
licenses:
|
114
119
|
- WTFPL
|
@@ -122,7 +127,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
122
127
|
requirements:
|
123
128
|
- - ">="
|
124
129
|
- !ruby/object:Gem::Version
|
125
|
-
hash: -
|
130
|
+
hash: -1950555829179380990
|
126
131
|
segments:
|
127
132
|
- 0
|
128
133
|
version: "0"
|
@@ -141,4 +146,6 @@ specification_version: 3
|
|
141
146
|
summary: Read files and metadata from MPQ archives
|
142
147
|
test_files:
|
143
148
|
- test/helper.rb
|
149
|
+
- test/test_jsonish.rb
|
144
150
|
- test/test_mpq.rb
|
151
|
+
- test/test_replay_file.rb
|