nbtfile 0.0.1
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/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +140 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/nbtfile.rb +517 -0
- data/samples/bigtest.nbt +0 -0
- data/samples/chunk0.nbt +0 -0
- data/samples/test.nbt +0 -0
- data/spec/nbtfile_spec.rb +279 -0
- data/spec/roundtrip_spec.rb +33 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +28 -0
- metadata +80 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 MenTaLguY
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
= nbtfile
|
2
|
+
|
3
|
+
NBTFile is a low-level library for reading and writing
|
4
|
+
NBT-format files, as used by the popular game Minecraft.
|
5
|
+
|
6
|
+
== Official Specification
|
7
|
+
|
8
|
+
The official (if somewhat confusing) specification for
|
9
|
+
the NBT file format may be found at
|
10
|
+
http://www.minecraft.net/docs/NBT.txt
|
11
|
+
|
12
|
+
== Data Model
|
13
|
+
|
14
|
+
The NBT data model has ten different data types:
|
15
|
+
|
16
|
+
- 8-bit signed integers (Tag_Byte)
|
17
|
+
- 16-bit signed integers (Tag_Short)
|
18
|
+
- 32-bit signed integers (Tag_Int)
|
19
|
+
- 64-bit signed integers (Tag_Long)
|
20
|
+
- 32-bit floating-point numbers (Tag_Float)
|
21
|
+
- 64-bit floating-point numbers (Tag_Double)
|
22
|
+
- UTF-8 strings (Tag_String)
|
23
|
+
- raw byte strings (Tag_Byte_Array)
|
24
|
+
- homogenous lists (Tag_List)
|
25
|
+
- compound structures (Tag_Compound)
|
26
|
+
|
27
|
+
Compound structures (Tag_Compound) are unordered
|
28
|
+
collections where each item ("tag") has a name associated
|
29
|
+
with it. Compound structures are heterogenous; the
|
30
|
+
elements of a compound structure may be any mixture of
|
31
|
+
types.
|
32
|
+
|
33
|
+
Lists (Tag_List) are ordered collections of unnamed items.
|
34
|
+
Lists are homogenous; every element of a particular list
|
35
|
+
must have the same type. Note that all lists have the
|
36
|
+
same type (Tag_List) regardless of the type of their
|
37
|
+
elements.
|
38
|
+
|
39
|
+
Items of an eleventh "type" (Tag_End) serve to terminate
|
40
|
+
compound structures and lists (of any type). The wording
|
41
|
+
of the official specification could permit lists of
|
42
|
+
Tag_End, but this is not supported in practice.
|
43
|
+
|
44
|
+
The top level of an NBT file must be a single-element
|
45
|
+
compound structure.
|
46
|
+
|
47
|
+
== Syntax
|
48
|
+
|
49
|
+
NBT files are gzip-compressed; the structure of the
|
50
|
+
uncompressed data can be described by the following
|
51
|
+
ABNF (see RFC 5234).
|
52
|
+
|
53
|
+
nbt-data = tag-compound
|
54
|
+
|
55
|
+
tag-compound = TAG-COMPOUND name compound-body
|
56
|
+
|
57
|
+
compound-body = *tag TAG-END
|
58
|
+
|
59
|
+
tag = TAG-BYTE name byte /
|
60
|
+
TAG-SHORT name short /
|
61
|
+
TAG-INT name int /
|
62
|
+
TAG-LONG name long /
|
63
|
+
TAG-FLOAT name float /
|
64
|
+
TAG-DOUBLE name double /
|
65
|
+
TAG-STRING name string /
|
66
|
+
TAG-BYTE-ARRAY name byte-array /
|
67
|
+
TAG-LIST name list /
|
68
|
+
tag-compound
|
69
|
+
|
70
|
+
name = string
|
71
|
+
|
72
|
+
list = TAG-BYTE list-length *byte /
|
73
|
+
TAG-SHORT list-length *short /
|
74
|
+
TAG-INT list-length *int /
|
75
|
+
TAG-LONG list-length *long /
|
76
|
+
TAG-FLOAT list-length *float /
|
77
|
+
TAG-DOUBLE list-length *double /
|
78
|
+
TAG-STRING list-length *string /
|
79
|
+
TAG-BYTE-ARRAY list-length *byte-array /
|
80
|
+
TAG-LIST list-length *list /
|
81
|
+
TAG-COMPOUND list-length *compound-body
|
82
|
+
|
83
|
+
list-length = int
|
84
|
+
|
85
|
+
; see RFC 3629 for definition of UTF8-octets
|
86
|
+
string = string-length UTF8-octets
|
87
|
+
|
88
|
+
string-length = short
|
89
|
+
|
90
|
+
byte-array = byte-array-length *byte
|
91
|
+
|
92
|
+
byte-array-length = int
|
93
|
+
|
94
|
+
byte = OCTET ; 8-bit signed integer
|
95
|
+
|
96
|
+
short = 2OCTET ; 16-bit signed integer, big-endian
|
97
|
+
|
98
|
+
int = 4OCTET ; 32-bit signed integer, big-endian
|
99
|
+
|
100
|
+
long = 8OCTET ; 64-bit signed integer, big-endian
|
101
|
+
|
102
|
+
float = 4OCTET ; 32-bit float, big-endian, IEEE 754-2008
|
103
|
+
|
104
|
+
double = 8OCTET ; 64-bit float, big-endian, IEEE 754-2008
|
105
|
+
|
106
|
+
TAG-END = %x00
|
107
|
+
TAG-BYTE = %x01
|
108
|
+
TAG-SHORT = %x02
|
109
|
+
TAG-INT = %x03
|
110
|
+
TAG-LONG = %x04
|
111
|
+
TAG-FLOAT = %x05
|
112
|
+
TAG-DOUBLE = %x06
|
113
|
+
TAG-BYTE-ARRAY = %x07
|
114
|
+
TAG-STRING = %x08
|
115
|
+
TAG-LIST = %x09
|
116
|
+
TAG-COMPOUND = %x0a
|
117
|
+
|
118
|
+
== Interface
|
119
|
+
|
120
|
+
== Low-level API
|
121
|
+
|
122
|
+
=== NBTFile.tokenize
|
123
|
+
|
124
|
+
== High-level API
|
125
|
+
|
126
|
+
=== NBTFile.load
|
127
|
+
|
128
|
+
== Note on Patches/Pull Requests
|
129
|
+
|
130
|
+
* Fork the project.
|
131
|
+
* Make your feature addition or bug fix.
|
132
|
+
* Add tests for it. This is important so I don't break it in a
|
133
|
+
future version unintentionally.
|
134
|
+
* Commit, do not mess with rakefile, version, or history.
|
135
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
136
|
+
* Send me a pull request. Bonus points for topic branches.
|
137
|
+
|
138
|
+
== Copyright
|
139
|
+
|
140
|
+
Copyright (c) 2010 MenTaLguY. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "nbtfile"
|
8
|
+
gem.summary = %Q{nbtfile provides a low-level API for reading and writing files using Minecraft's NBT serialization format}
|
9
|
+
gem.description = %Q{Library for reading and writing NBT files (as used by Minecraft).}
|
10
|
+
gem.email = "mental@rydia.net"
|
11
|
+
gem.homepage = "http://github.com/mental/nbtfile"
|
12
|
+
gem.authors = ["MenTaLguY"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'spec/rake/spectask'
|
22
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
23
|
+
spec.libs << 'lib' << 'spec'
|
24
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
25
|
+
end
|
26
|
+
|
27
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
28
|
+
spec.libs << 'lib' << 'spec'
|
29
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
30
|
+
spec.rcov = true
|
31
|
+
end
|
32
|
+
|
33
|
+
task :spec => :check_dependencies
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
|
37
|
+
require 'rake/rdoctask'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
40
|
+
|
41
|
+
rdoc.rdoc_dir = 'rdoc'
|
42
|
+
rdoc.title = "nbtfile #{version}"
|
43
|
+
rdoc.rdoc_files.include('README*')
|
44
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/lib/nbtfile.rb
ADDED
@@ -0,0 +1,517 @@
|
|
1
|
+
# nbtfile
|
2
|
+
#
|
3
|
+
# Copyright (c) 2010 MenTaLguY
|
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.
|
23
|
+
|
24
|
+
require 'zlib'
|
25
|
+
require 'stringio'
|
26
|
+
|
27
|
+
class String
|
28
|
+
begin
|
29
|
+
alias_method :_nbtfile_getbyte, :getbyte
|
30
|
+
rescue NameError
|
31
|
+
alias_method :_nbtfile_getbyte, :[]
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
alias_method :_nbtfile_force_encoding, :force_encoding
|
36
|
+
rescue NameError
|
37
|
+
def _nbtfile_force_encoding(encoding)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module NBTFile
|
43
|
+
|
44
|
+
TOKEN_CLASSES_BY_INDEX = []
|
45
|
+
TOKEN_INDICES_BY_CLASS = {}
|
46
|
+
|
47
|
+
BaseToken = Struct.new :name, :value
|
48
|
+
|
49
|
+
module Tokens
|
50
|
+
tag_names = %w(End Byte Short Int Long Float Double
|
51
|
+
Byte_Array String List Compound)
|
52
|
+
tag_names.each_with_index do |tag_name, index|
|
53
|
+
tag_name = "TAG_#{tag_name}"
|
54
|
+
token_class = Class.new(BaseToken)
|
55
|
+
|
56
|
+
const_set tag_name, token_class
|
57
|
+
|
58
|
+
TOKEN_CLASSES_BY_INDEX[index] = token_class
|
59
|
+
TOKEN_INDICES_BY_CLASS[token_class] = index
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module CommonMethods
|
64
|
+
def sign_bit(n_bytes)
|
65
|
+
1 << ((n_bytes << 3) - 1)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
module ReadMethods
|
70
|
+
include Tokens
|
71
|
+
include CommonMethods
|
72
|
+
|
73
|
+
def read_raw(io, n_bytes)
|
74
|
+
data = io.read(n_bytes)
|
75
|
+
raise EOFError unless data and data.length == n_bytes
|
76
|
+
data
|
77
|
+
end
|
78
|
+
|
79
|
+
def read_integer(io, n_bytes)
|
80
|
+
raw_value = read_raw(io, n_bytes)
|
81
|
+
value = (0...n_bytes).reduce(0) do |accum, n|
|
82
|
+
(accum << 8) | raw_value._nbtfile_getbyte(n)
|
83
|
+
end
|
84
|
+
value -= ((value & sign_bit(n_bytes)) << 1)
|
85
|
+
value
|
86
|
+
end
|
87
|
+
|
88
|
+
def read_byte(io)
|
89
|
+
read_integer(io, 1)
|
90
|
+
end
|
91
|
+
|
92
|
+
def read_short(io)
|
93
|
+
read_integer(io, 2)
|
94
|
+
end
|
95
|
+
|
96
|
+
def read_int(io)
|
97
|
+
read_integer(io, 4)
|
98
|
+
end
|
99
|
+
|
100
|
+
def read_long(io)
|
101
|
+
read_integer(io, 8)
|
102
|
+
end
|
103
|
+
|
104
|
+
def read_float(io)
|
105
|
+
read_raw(io, 4).unpack("g").first
|
106
|
+
end
|
107
|
+
|
108
|
+
def read_double(io)
|
109
|
+
read_raw(io, 8).unpack("G").first
|
110
|
+
end
|
111
|
+
|
112
|
+
def read_string(io)
|
113
|
+
length = read_short(io)
|
114
|
+
string = read_raw(io, length)
|
115
|
+
string._nbtfile_force_encoding("UTF-8")
|
116
|
+
string
|
117
|
+
end
|
118
|
+
|
119
|
+
def read_byte_array(io)
|
120
|
+
length = read_int(io)
|
121
|
+
read_raw(io, length)
|
122
|
+
end
|
123
|
+
|
124
|
+
def read_list_header(io)
|
125
|
+
list_type = read_type(io)
|
126
|
+
list_length = read_int(io)
|
127
|
+
[list_type, list_length]
|
128
|
+
end
|
129
|
+
|
130
|
+
def read_type(io)
|
131
|
+
byte = read_byte(io)
|
132
|
+
begin
|
133
|
+
TOKEN_CLASSES_BY_INDEX.fetch(byte)
|
134
|
+
rescue IndexError
|
135
|
+
raise RuntimeError, "Unexpected tag ordinal #{byte}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def read_value(io, type, name, state, cont)
|
140
|
+
next_state = state
|
141
|
+
|
142
|
+
case
|
143
|
+
when type == TAG_End
|
144
|
+
next_state = cont
|
145
|
+
value = nil
|
146
|
+
when type == TAG_Byte
|
147
|
+
value = read_byte(io)
|
148
|
+
when type == TAG_Short
|
149
|
+
value = read_short(io)
|
150
|
+
when type == TAG_Int
|
151
|
+
value = read_int(io)
|
152
|
+
when type == TAG_Long
|
153
|
+
value = read_long(io)
|
154
|
+
when type == TAG_Float
|
155
|
+
value = read_float(io)
|
156
|
+
when type == TAG_Double
|
157
|
+
value = read_double(io)
|
158
|
+
when type == TAG_Byte_Array
|
159
|
+
value = read_byte_array(io)
|
160
|
+
when type == TAG_String
|
161
|
+
value = read_string(io)
|
162
|
+
when type == TAG_List
|
163
|
+
list_type, list_length = read_list_header(io)
|
164
|
+
next_state = ListReaderState.new(state, list_type, list_length)
|
165
|
+
value = list_type
|
166
|
+
when type == TAG_Compound
|
167
|
+
next_state = CompoundReaderState.new(state)
|
168
|
+
end
|
169
|
+
|
170
|
+
[next_state, type[name, value]]
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
class TopReaderState
|
175
|
+
include ReadMethods
|
176
|
+
include Tokens
|
177
|
+
|
178
|
+
def get_token(io)
|
179
|
+
type = read_type(io)
|
180
|
+
raise RuntimeError, "expected TAG_Compound" unless type == TAG_Compound
|
181
|
+
name = read_string(io)
|
182
|
+
end_state = EndReaderState.new()
|
183
|
+
next_state = CompoundReaderState.new(end_state)
|
184
|
+
[next_state, type[name, nil]]
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class CompoundReaderState
|
189
|
+
include ReadMethods
|
190
|
+
include Tokens
|
191
|
+
|
192
|
+
def initialize(cont)
|
193
|
+
@cont = cont
|
194
|
+
end
|
195
|
+
|
196
|
+
def get_token(io)
|
197
|
+
type = read_type(io)
|
198
|
+
|
199
|
+
if type != TAG_End
|
200
|
+
name = read_string(io)
|
201
|
+
else
|
202
|
+
name = ""
|
203
|
+
end
|
204
|
+
|
205
|
+
read_value(io, type, name, self, @cont)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
class ListReaderState
|
210
|
+
include ReadMethods
|
211
|
+
include Tokens
|
212
|
+
|
213
|
+
def initialize(cont, type, length)
|
214
|
+
@cont = cont
|
215
|
+
@length = length
|
216
|
+
@offset = 0
|
217
|
+
@type = type
|
218
|
+
end
|
219
|
+
|
220
|
+
def get_token(io)
|
221
|
+
if @offset < @length
|
222
|
+
type = @type
|
223
|
+
else
|
224
|
+
type = TAG_End
|
225
|
+
end
|
226
|
+
|
227
|
+
index = @offset
|
228
|
+
@offset += 1
|
229
|
+
|
230
|
+
read_value(io, type, index, self, @cont)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
class EndReaderState
|
235
|
+
def get_token(io)
|
236
|
+
[self, nil]
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
class Reader
|
241
|
+
def initialize(io)
|
242
|
+
@gz = Zlib::GzipReader.new(io)
|
243
|
+
@state = TopReaderState.new()
|
244
|
+
end
|
245
|
+
|
246
|
+
def each_token
|
247
|
+
while token = get_token()
|
248
|
+
yield token
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def get_token
|
253
|
+
@state, token = @state.get_token(@gz)
|
254
|
+
token
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
module WriteMethods
|
259
|
+
include Tokens
|
260
|
+
include CommonMethods
|
261
|
+
|
262
|
+
def emit_integer(io, n_bytes, value)
|
263
|
+
value -= ((value & sign_bit(n_bytes)) << 1)
|
264
|
+
bytes = (1..n_bytes).map do |n|
|
265
|
+
byte = (value >> ((n_bytes - n) << 3) & 0xff)
|
266
|
+
end
|
267
|
+
io.write(bytes.pack("C*"))
|
268
|
+
end
|
269
|
+
|
270
|
+
def emit_byte(io, value)
|
271
|
+
emit_integer(io, 1, value)
|
272
|
+
end
|
273
|
+
|
274
|
+
def emit_short(io, value)
|
275
|
+
emit_integer(io, 2, value)
|
276
|
+
end
|
277
|
+
|
278
|
+
def emit_int(io, value)
|
279
|
+
emit_integer(io, 4, value)
|
280
|
+
end
|
281
|
+
|
282
|
+
def emit_long(io, value)
|
283
|
+
emit_integer(io, 8, value)
|
284
|
+
end
|
285
|
+
|
286
|
+
def emit_float(io, value)
|
287
|
+
io.write([value].pack("g"))
|
288
|
+
end
|
289
|
+
|
290
|
+
def emit_double(io, value)
|
291
|
+
io.write([value].pack("G"))
|
292
|
+
end
|
293
|
+
|
294
|
+
def emit_byte_array(io, value)
|
295
|
+
emit_int(io, value.length)
|
296
|
+
io.write(value)
|
297
|
+
end
|
298
|
+
|
299
|
+
def emit_string(io, value)
|
300
|
+
emit_short(io, value.length)
|
301
|
+
io.write(value)
|
302
|
+
end
|
303
|
+
|
304
|
+
def emit_type(io, type)
|
305
|
+
emit_byte(io, TOKEN_INDICES_BY_CLASS[type])
|
306
|
+
end
|
307
|
+
|
308
|
+
def emit_list_header(io, type, count)
|
309
|
+
emit_type(io, type)
|
310
|
+
emit_int(io, count)
|
311
|
+
end
|
312
|
+
|
313
|
+
def emit_value(io, type, value, capturing, state, cont)
|
314
|
+
next_state = state
|
315
|
+
|
316
|
+
case
|
317
|
+
when type == TAG_Byte
|
318
|
+
emit_byte(io, value)
|
319
|
+
when type == TAG_Short
|
320
|
+
emit_short(io, value)
|
321
|
+
when type == TAG_Int
|
322
|
+
emit_int(io, value)
|
323
|
+
when type == TAG_Long
|
324
|
+
emit_long(io, value)
|
325
|
+
when type == TAG_Float
|
326
|
+
emit_float(io, value)
|
327
|
+
when type == TAG_Double
|
328
|
+
emit_double(io, value)
|
329
|
+
when type == TAG_Byte_Array
|
330
|
+
emit_byte_array(io, value)
|
331
|
+
when type == TAG_String
|
332
|
+
emit_string(io, value)
|
333
|
+
when type == TAG_Float
|
334
|
+
emit_float(io, value)
|
335
|
+
when type == TAG_Double
|
336
|
+
emit_double(io, value)
|
337
|
+
when type == TAG_List
|
338
|
+
next_state = ListWriterState.new(state, value, capturing)
|
339
|
+
when type == TAG_Compound
|
340
|
+
next_state = CompoundWriterState.new(state, capturing)
|
341
|
+
when type == TAG_End
|
342
|
+
next_state = cont
|
343
|
+
else
|
344
|
+
raise RuntimeError, "Unexpected token #{type}"
|
345
|
+
end
|
346
|
+
|
347
|
+
next_state
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
class TopWriterState
|
352
|
+
include WriteMethods
|
353
|
+
include Tokens
|
354
|
+
|
355
|
+
def emit_token(io, token)
|
356
|
+
case token
|
357
|
+
when TAG_Compound
|
358
|
+
emit_type(io, token.class)
|
359
|
+
emit_string(io, token.name)
|
360
|
+
end_state = EndWriterState.new()
|
361
|
+
next_state = CompoundWriterState.new(end_state, nil)
|
362
|
+
next_state
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
class CompoundWriterState
|
368
|
+
include WriteMethods
|
369
|
+
include Tokens
|
370
|
+
|
371
|
+
def initialize(cont, capturing)
|
372
|
+
@cont = cont
|
373
|
+
@capturing = capturing
|
374
|
+
end
|
375
|
+
|
376
|
+
def emit_token(io, token)
|
377
|
+
out = @capturing || io
|
378
|
+
|
379
|
+
type = token.class
|
380
|
+
|
381
|
+
emit_type(out, type)
|
382
|
+
emit_string(out, token.name) unless type == TAG_End
|
383
|
+
|
384
|
+
emit_value(out, type, token.value, @capturing, self, @cont)
|
385
|
+
end
|
386
|
+
|
387
|
+
def emit_item(io, value)
|
388
|
+
raise RuntimeError, "not in a list"
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
class ListWriterState
|
393
|
+
include WriteMethods
|
394
|
+
include Tokens
|
395
|
+
|
396
|
+
def initialize(cont, type, capturing)
|
397
|
+
@cont = cont
|
398
|
+
@type = type
|
399
|
+
@count = 0
|
400
|
+
@value = StringIO.new()
|
401
|
+
@capturing = capturing
|
402
|
+
end
|
403
|
+
|
404
|
+
def emit_token(io, token)
|
405
|
+
type = token.class
|
406
|
+
|
407
|
+
if type == TAG_End
|
408
|
+
out = @capturing || io
|
409
|
+
emit_list_header(out, @type, @count)
|
410
|
+
out.write(@value.string)
|
411
|
+
elsif type != @type
|
412
|
+
raise RuntimeError, "unexpected token #{token.class}, expected #{@type}"
|
413
|
+
end
|
414
|
+
|
415
|
+
_emit_item(io, type, token.value)
|
416
|
+
end
|
417
|
+
|
418
|
+
def emit_item(io, value)
|
419
|
+
_emit_item(io, @type, value)
|
420
|
+
end
|
421
|
+
|
422
|
+
def _emit_item(io, type, value)
|
423
|
+
@count += 1
|
424
|
+
emit_value(@value, type, value, @value, self, @cont)
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
class EndWriterState
|
429
|
+
def emit_token(io, token)
|
430
|
+
raise RuntimeError, "unexpected token #{token.class} after end"
|
431
|
+
end
|
432
|
+
|
433
|
+
def emit_item(io, value)
|
434
|
+
raise RuntimeError, "not in a list"
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
class Writer
|
439
|
+
include WriteMethods
|
440
|
+
|
441
|
+
def initialize(stream)
|
442
|
+
@gz = Zlib::GzipWriter.new(stream)
|
443
|
+
@state = TopWriterState.new()
|
444
|
+
end
|
445
|
+
|
446
|
+
def emit_token(token)
|
447
|
+
@state = @state.emit_token(@gz, token)
|
448
|
+
end
|
449
|
+
|
450
|
+
def emit_compound(name)
|
451
|
+
emit_token(TAG_Compound[name, nil])
|
452
|
+
begin
|
453
|
+
yield
|
454
|
+
ensure
|
455
|
+
emit_token(TAG_End[nil, nil])
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
def emit_list(name, type)
|
460
|
+
emit_token(TAG_List[name, type])
|
461
|
+
begin
|
462
|
+
yield
|
463
|
+
ensure
|
464
|
+
emit_token(TAG_End[nil, nil])
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
def emit_item(value)
|
469
|
+
@state = @state.emit_item(@gz, value)
|
470
|
+
end
|
471
|
+
|
472
|
+
def finish
|
473
|
+
@gz.close
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
def self.tokenize(io)
|
478
|
+
case io
|
479
|
+
when String
|
480
|
+
io = StringIO.new(io, "rb")
|
481
|
+
end
|
482
|
+
reader = Reader.new(io)
|
483
|
+
|
484
|
+
reader.each_token do |token|
|
485
|
+
yield token
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
def self.load(io)
|
490
|
+
root = {}
|
491
|
+
stack = [root]
|
492
|
+
|
493
|
+
self.tokenize(io) do |token|
|
494
|
+
case token
|
495
|
+
when Tokens::TAG_Compound
|
496
|
+
value = {}
|
497
|
+
when Tokens::TAG_List
|
498
|
+
value = []
|
499
|
+
when Tokens::TAG_End
|
500
|
+
stack.pop
|
501
|
+
next
|
502
|
+
else
|
503
|
+
value = token.value
|
504
|
+
end
|
505
|
+
|
506
|
+
stack.last[token.name] = value
|
507
|
+
|
508
|
+
case token
|
509
|
+
when Tokens::TAG_Compound, Tokens::TAG_List
|
510
|
+
stack.push value
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
root
|
515
|
+
end
|
516
|
+
|
517
|
+
end
|
data/samples/bigtest.nbt
ADDED
Binary file
|
data/samples/chunk0.nbt
ADDED
Binary file
|
data/samples/test.nbt
ADDED
Binary file
|
@@ -0,0 +1,279 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'enumerator'
|
3
|
+
require 'nbtfile'
|
4
|
+
require 'stringio'
|
5
|
+
require 'zlib'
|
6
|
+
|
7
|
+
shared_examples_for "readers and writers" do
|
8
|
+
Tokens = NBTFile::Tokens unless defined? Tokens
|
9
|
+
|
10
|
+
def self.a_reader_or_writer(desc, serialized, tokens)
|
11
|
+
it desc do
|
12
|
+
check_reader_or_writer(serialized, tokens)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
a_reader_or_writer "should handle basic documents",
|
17
|
+
"\x0a\x00\x03foo" \
|
18
|
+
"\x00",
|
19
|
+
[Tokens::TAG_Compound["foo", nil],
|
20
|
+
Tokens::TAG_End["", nil]]
|
21
|
+
|
22
|
+
a_reader_or_writer "should treat integers as signed",
|
23
|
+
"\x0a\x00\x03foo" \
|
24
|
+
"\x03\x00\x03bar\xff\xff\xff\xfe" \
|
25
|
+
"\x00",
|
26
|
+
[Tokens::TAG_Compound["foo", nil],
|
27
|
+
Tokens::TAG_Int["bar", -2],
|
28
|
+
Tokens::TAG_End["", nil]]
|
29
|
+
|
30
|
+
a_reader_or_writer "should handle integer fields",
|
31
|
+
"\x0a\x00\x03foo" \
|
32
|
+
"\x03\x00\x03bar\x01\x02\x03\x04" \
|
33
|
+
"\x00",
|
34
|
+
[Tokens::TAG_Compound["foo", nil],
|
35
|
+
Tokens::TAG_Int["bar", 0x01020304],
|
36
|
+
Tokens::TAG_End["", nil]]
|
37
|
+
|
38
|
+
a_reader_or_writer "should handle short fields",
|
39
|
+
"\x0a\x00\x03foo" \
|
40
|
+
"\x02\x00\x03bar\x4e\x5a" \
|
41
|
+
"\x00",
|
42
|
+
[Tokens::TAG_Compound["foo", nil],
|
43
|
+
Tokens::TAG_Short["bar", 0x4e5a],
|
44
|
+
Tokens::TAG_End["", nil]]
|
45
|
+
|
46
|
+
a_reader_or_writer "should handle byte fields",
|
47
|
+
"\x0a\x00\x03foo" \
|
48
|
+
"\x01\x00\x03bar\x4e" \
|
49
|
+
"\x00",
|
50
|
+
[Tokens::TAG_Compound["foo", nil],
|
51
|
+
Tokens::TAG_Byte["bar", 0x4e],
|
52
|
+
Tokens::TAG_End["", nil]]
|
53
|
+
|
54
|
+
a_reader_or_writer "should handle string fields",
|
55
|
+
"\x0a\x00\x03foo" \
|
56
|
+
"\x08\x00\x03bar\x00\x04hoge" \
|
57
|
+
"\x00",
|
58
|
+
[Tokens::TAG_Compound["foo", nil],
|
59
|
+
Tokens::TAG_String["bar", "hoge"],
|
60
|
+
Tokens::TAG_End["", nil]]
|
61
|
+
|
62
|
+
a_reader_or_writer "should handle byte array fields",
|
63
|
+
"\x0a\x00\x03foo" \
|
64
|
+
"\x07\x00\x03bar\x00\x00\x00\x05\x01\x02\x03\x04\x05" \
|
65
|
+
"\x00",
|
66
|
+
[Tokens::TAG_Compound["foo", nil],
|
67
|
+
Tokens::TAG_Byte_Array["bar", "\x01\x02\x03\x04\x05"],
|
68
|
+
Tokens::TAG_End["", nil]]
|
69
|
+
|
70
|
+
a_reader_or_writer "should handle long fields",
|
71
|
+
"\x0a\x00\x03foo" \
|
72
|
+
"\x04\x00\x03bar\x01\x02\x03\x04\x05\x06\x07\x08" \
|
73
|
+
"\x00",
|
74
|
+
[Tokens::TAG_Compound["foo", nil],
|
75
|
+
Tokens::TAG_Long["bar", 0x0102030405060708],
|
76
|
+
Tokens::TAG_End["", nil]]
|
77
|
+
|
78
|
+
a_reader_or_writer "should handle float fields",
|
79
|
+
"\x0a\x00\x03foo" \
|
80
|
+
"\x05\x00\x03bar\x3f\xa0\x00\x00" \
|
81
|
+
"\x00",
|
82
|
+
[Tokens::TAG_Compound["foo", nil],
|
83
|
+
Tokens::TAG_Float["bar", "\x3f\xa0\x00\x00".unpack("g").first],
|
84
|
+
Tokens::TAG_End["", nil]]
|
85
|
+
|
86
|
+
a_reader_or_writer "should handle double fields",
|
87
|
+
"\x0a\x00\x03foo" \
|
88
|
+
"\x06\x00\x03bar\x3f\xf4\x00\x00\x00\x00\x00\x00" \
|
89
|
+
"\x00",
|
90
|
+
[Tokens::TAG_Compound["foo", nil],
|
91
|
+
Tokens::TAG_Double["bar", "\x3f\xf4\x00\x00\x00\x00\x00\x00".unpack("G").first],
|
92
|
+
Tokens::TAG_End["", nil]]
|
93
|
+
|
94
|
+
a_reader_or_writer "should handle nested compound fields",
|
95
|
+
"\x0a\x00\x03foo" \
|
96
|
+
"\x0a\x00\x03bar" \
|
97
|
+
"\x01\x00\x04hoge\x4e" \
|
98
|
+
"\x00" \
|
99
|
+
"\x00",
|
100
|
+
[Tokens::TAG_Compound["foo", nil],
|
101
|
+
Tokens::TAG_Compound["bar", nil],
|
102
|
+
Tokens::TAG_Byte["hoge", 0x4e],
|
103
|
+
Tokens::TAG_End["", nil],
|
104
|
+
Tokens::TAG_End["", nil]]
|
105
|
+
|
106
|
+
simple_list_types = [
|
107
|
+
["bytes", Tokens::TAG_Byte, 0x01, lambda { |ns| ns.pack("C*") }],
|
108
|
+
["shorts", Tokens::TAG_Short, 0x02, lambda { |ns| ns.pack("n*") }],
|
109
|
+
["ints", Tokens::TAG_Int, 0x03, lambda { |ns| ns.pack("N*") }],
|
110
|
+
["longs", Tokens::TAG_Long, 0x04, lambda { |ns| ns.map { |n| [n].pack("x4N") }.join("") }],
|
111
|
+
["floats", Tokens::TAG_Float, 0x05, lambda { |ns| ns.pack("g*") }],
|
112
|
+
["doubles", Tokens::TAG_Double, 0x06, lambda { |ns| ns.pack("G*") }]
|
113
|
+
]
|
114
|
+
|
115
|
+
for label, type, token, pack in simple_list_types
|
116
|
+
values = [9, 5]
|
117
|
+
a_reader_or_writer "should handle lists of #{label}",
|
118
|
+
"\x0a\x00\x03foo" \
|
119
|
+
"\x09\x00\x03bar#{[token].pack("C")}\x00\x00\x00\x02" \
|
120
|
+
"#{pack.call(values)}" \
|
121
|
+
"\x00",
|
122
|
+
[Tokens::TAG_Compound["foo", nil],
|
123
|
+
Tokens::TAG_List["bar", type],
|
124
|
+
type[0, values[0]],
|
125
|
+
type[1, values[1]],
|
126
|
+
Tokens::TAG_End[2, nil],
|
127
|
+
Tokens::TAG_End["", nil]]
|
128
|
+
end
|
129
|
+
|
130
|
+
a_reader_or_writer "should handle nested lists",
|
131
|
+
"\x0a\x00\x03foo" \
|
132
|
+
"\x09\x00\x03bar\x09\x00\x00\x00\x01" \
|
133
|
+
"\x01\x00\x00\x00\x01" \
|
134
|
+
"\x4a" \
|
135
|
+
"\x00",
|
136
|
+
[Tokens::TAG_Compound["foo", nil],
|
137
|
+
Tokens::TAG_List["bar", Tokens::TAG_List],
|
138
|
+
Tokens::TAG_List[0, Tokens::TAG_Byte],
|
139
|
+
Tokens::TAG_Byte[0, 0x4a],
|
140
|
+
Tokens::TAG_End[1, nil],
|
141
|
+
Tokens::TAG_End[1, nil],
|
142
|
+
Tokens::TAG_End["", nil]]
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "NBTFile::tokenize" do
|
146
|
+
include ZlibHelpers
|
147
|
+
|
148
|
+
it_should_behave_like "readers and writers"
|
149
|
+
|
150
|
+
def check_reader_or_writer(input, tokens)
|
151
|
+
io = make_zipped_stream(input)
|
152
|
+
actual_tokens = []
|
153
|
+
NBTFile.tokenize(io) do |token|
|
154
|
+
actual_tokens << token
|
155
|
+
end
|
156
|
+
actual_tokens.should == tokens
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe "NBTFile::load" do
|
161
|
+
include ZlibHelpers
|
162
|
+
|
163
|
+
def self.nbtfile_load(description, tokens, result)
|
164
|
+
it description do
|
165
|
+
io = StringIO.new
|
166
|
+
writer = NBTFile::Writer.new(io)
|
167
|
+
for token in tokens
|
168
|
+
writer.emit_token(token)
|
169
|
+
end
|
170
|
+
writer.finish
|
171
|
+
actual_result = NBTFile.load(StringIO.new(io.string))
|
172
|
+
actual_result.should == result
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
nbtfile_load "should generate a top-level hash",
|
177
|
+
[Tokens::TAG_Compound["foo", nil],
|
178
|
+
Tokens::TAG_Byte["a", 19],
|
179
|
+
Tokens::TAG_Byte["b", 23],
|
180
|
+
Tokens::TAG_End[nil, nil]],
|
181
|
+
{"foo" => {"a" => 19, "b" => 23}}
|
182
|
+
|
183
|
+
nbtfile_load "should map compound structures to hashes",
|
184
|
+
[Tokens::TAG_Compound["foo", nil],
|
185
|
+
Tokens::TAG_Compound["bar", nil],
|
186
|
+
Tokens::TAG_Byte["a", 123],
|
187
|
+
Tokens::TAG_Byte["b", 56],
|
188
|
+
Tokens::TAG_End[nil, nil],
|
189
|
+
Tokens::TAG_End[nil, nil]],
|
190
|
+
{"foo" => {"bar" => {"a" => 123, "b" => 56}}}
|
191
|
+
|
192
|
+
nbtfile_load "should map lists to arrays",
|
193
|
+
[Tokens::TAG_Compound["foo", nil],
|
194
|
+
Tokens::TAG_List["bar", Tokens::TAG_Byte],
|
195
|
+
Tokens::TAG_Byte[0, 32],
|
196
|
+
Tokens::TAG_Byte[1, 45],
|
197
|
+
Tokens::TAG_End[2, nil],
|
198
|
+
Tokens::TAG_End["", nil]],
|
199
|
+
{"foo" => {"bar" => [32, 45]}}
|
200
|
+
end
|
201
|
+
|
202
|
+
describe NBTFile::Reader do
|
203
|
+
include ZlibHelpers
|
204
|
+
|
205
|
+
it_should_behave_like "readers and writers"
|
206
|
+
|
207
|
+
def check_reader_or_writer(input, tokens)
|
208
|
+
io = make_zipped_stream(input)
|
209
|
+
reader = NBTFile::Reader.new(io)
|
210
|
+
actual_tokens = []
|
211
|
+
reader.each_token do |token|
|
212
|
+
actual_tokens << token
|
213
|
+
end
|
214
|
+
actual_tokens.should == tokens
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
describe NBTFile::Writer do
|
219
|
+
include ZlibHelpers
|
220
|
+
|
221
|
+
it_should_behave_like "readers and writers"
|
222
|
+
|
223
|
+
def check_reader_or_writer(output, tokens)
|
224
|
+
stream = StringIO.new()
|
225
|
+
writer = NBTFile::Writer.new(stream)
|
226
|
+
begin
|
227
|
+
for token in tokens
|
228
|
+
writer.emit_token(token)
|
229
|
+
end
|
230
|
+
ensure
|
231
|
+
writer.finish
|
232
|
+
end
|
233
|
+
actual_output = unzip_string(stream.string)
|
234
|
+
actual_output.should == output
|
235
|
+
end
|
236
|
+
|
237
|
+
it "should support shorthand for emitting lists" do
|
238
|
+
output = StringIO.new()
|
239
|
+
writer = NBTFile::Writer.new(output)
|
240
|
+
begin
|
241
|
+
writer.emit_token(Tokens::TAG_Compound["test", nil])
|
242
|
+
writer.emit_list("foo", Tokens::TAG_Byte) do
|
243
|
+
writer.emit_item(12)
|
244
|
+
writer.emit_item(43)
|
245
|
+
end
|
246
|
+
writer.emit_token(Tokens::TAG_End[nil, nil])
|
247
|
+
ensure
|
248
|
+
writer.finish
|
249
|
+
end
|
250
|
+
|
251
|
+
actual_output = unzip_string(output.string)
|
252
|
+
actual_output.should == "\x0a\x00\x04test" \
|
253
|
+
"\x09\x00\x03foo\x01\x00\x00\x00\x02" \
|
254
|
+
"\x0c\x2b" \
|
255
|
+
"\x00"
|
256
|
+
end
|
257
|
+
|
258
|
+
it "should support shorthand for emitting compound structures" do
|
259
|
+
output = StringIO.new()
|
260
|
+
writer = NBTFile::Writer.new(output)
|
261
|
+
begin
|
262
|
+
writer.emit_token(Tokens::TAG_Compound["test", nil])
|
263
|
+
writer.emit_compound("xyz") do
|
264
|
+
writer.emit_token(Tokens::TAG_Byte["foo", 0x08])
|
265
|
+
writer.emit_token(Tokens::TAG_Byte["bar", 0x02])
|
266
|
+
end
|
267
|
+
writer.emit_token(Tokens::TAG_End[nil, nil])
|
268
|
+
ensure
|
269
|
+
writer.finish
|
270
|
+
end
|
271
|
+
actual_output = unzip_string(output.string)
|
272
|
+
actual_output.should == "\x0a\x00\x04test" \
|
273
|
+
"\x0a\x00\x03xyz" \
|
274
|
+
"\x01\x00\x03foo\x08" \
|
275
|
+
"\x01\x00\x03bar\x02" \
|
276
|
+
"\x00" \
|
277
|
+
"\x00"
|
278
|
+
end
|
279
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'nbtfile'
|
3
|
+
require 'stringio'
|
4
|
+
require 'zlib'
|
5
|
+
|
6
|
+
|
7
|
+
describe NBTFile do
|
8
|
+
include ZlibHelpers
|
9
|
+
|
10
|
+
sample_pattern = File.join(File.dirname(__FILE__), '..', 'samples', '*.nbt')
|
11
|
+
|
12
|
+
for file in Dir.glob(sample_pattern)
|
13
|
+
it "should roundtrip #{File.basename(file)}" do
|
14
|
+
input = StringIO.new(File.read(file))
|
15
|
+
output = StringIO.new()
|
16
|
+
|
17
|
+
reader = NBTFile::Reader.new(input)
|
18
|
+
writer = NBTFile::Writer.new(output)
|
19
|
+
begin
|
20
|
+
reader.each_token do |token|
|
21
|
+
writer.emit_token(token)
|
22
|
+
end
|
23
|
+
ensure
|
24
|
+
writer.finish
|
25
|
+
end
|
26
|
+
|
27
|
+
input_bytes = unzip_string(input.string)
|
28
|
+
output_bytes = unzip_string(output.string)
|
29
|
+
|
30
|
+
output_bytes.should == input_bytes
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'nbtfile'
|
4
|
+
require 'spec'
|
5
|
+
require 'spec/autorun'
|
6
|
+
|
7
|
+
Spec::Runner.configure do |config|
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
module ZlibHelpers
|
12
|
+
|
13
|
+
def make_zipped_stream(data)
|
14
|
+
gz = Zlib::GzipWriter.new(StringIO.new())
|
15
|
+
gz << data
|
16
|
+
string = gz.close.string
|
17
|
+
StringIO.new(string, "rb")
|
18
|
+
end
|
19
|
+
|
20
|
+
def unzip_string(string)
|
21
|
+
gz = Zlib::GzipReader.new(StringIO.new(string))
|
22
|
+
begin
|
23
|
+
gz.read
|
24
|
+
ensure
|
25
|
+
gz.close
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nbtfile
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- MenTaLguY
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-10-27 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.2.9
|
24
|
+
version:
|
25
|
+
description: Library for reading and writing NBT files (as used by Minecraft).
|
26
|
+
email: mental@rydia.net
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- LICENSE
|
33
|
+
- README.rdoc
|
34
|
+
files:
|
35
|
+
- .document
|
36
|
+
- .gitignore
|
37
|
+
- LICENSE
|
38
|
+
- README.rdoc
|
39
|
+
- Rakefile
|
40
|
+
- VERSION
|
41
|
+
- lib/nbtfile.rb
|
42
|
+
- samples/bigtest.nbt
|
43
|
+
- samples/chunk0.nbt
|
44
|
+
- samples/test.nbt
|
45
|
+
- spec/nbtfile_spec.rb
|
46
|
+
- spec/roundtrip_spec.rb
|
47
|
+
- spec/spec.opts
|
48
|
+
- spec/spec_helper.rb
|
49
|
+
has_rdoc: true
|
50
|
+
homepage: http://github.com/mental/nbtfile
|
51
|
+
licenses: []
|
52
|
+
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options:
|
55
|
+
- --charset=UTF-8
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
version:
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
version:
|
70
|
+
requirements: []
|
71
|
+
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 1.3.5
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: nbtfile provides a low-level API for reading and writing files using Minecraft's NBT serialization format
|
77
|
+
test_files:
|
78
|
+
- spec/nbtfile_spec.rb
|
79
|
+
- spec/roundtrip_spec.rb
|
80
|
+
- spec/spec_helper.rb
|