mpq 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|