nwn-lib 0.3.6 → 0.4.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/BINARIES +40 -45
- data/CHANGELOG +4 -0
- data/CHEATSHEET +7 -73
- data/COPYING +1 -1
- data/DATA_STRUCTURES +50 -0
- data/README +27 -37
- data/Rakefile +7 -5
- data/SCRIPTING +44 -0
- data/SETTINGS +80 -0
- data/TYPE_VALUE_INFERRING +93 -0
- data/bin/nwn-dsl +28 -0
- data/bin/nwn-gff +192 -0
- data/bin/nwn-irb +51 -0
- data/data/gff-common-nwn1.yaml +982 -0
- data/lib/nwn/all.rb +7 -0
- data/lib/nwn/gff.rb +47 -861
- data/lib/nwn/gff/api.rb +88 -0
- data/lib/nwn/gff/cexolocstr.rb +28 -0
- data/lib/nwn/gff/field.rb +105 -0
- data/lib/nwn/gff/list.rb +2 -0
- data/lib/nwn/gff/reader.rb +220 -0
- data/lib/nwn/gff/struct.rb +34 -0
- data/lib/nwn/gff/writer.rb +201 -0
- data/lib/nwn/helpers.rb +1 -30
- data/lib/nwn/infer.rb +125 -0
- data/lib/nwn/kivinen.rb +55 -0
- data/lib/nwn/scripting.rb +129 -0
- data/lib/nwn/settings.rb +7 -0
- data/lib/nwn/twoda.rb +105 -7
- data/lib/nwn/yaml.rb +276 -5
- data/scripts/clean_locstrs.rb +46 -0
- data/scripts/extract_all_items.rb +19 -0
- data/scripts/fix_facings.rb +22 -0
- data/scripts/truncate_floats.rb +18 -0
- data/tools/migrate_03x_to_04x.sh +17 -0
- data/tools/verify.sh +35 -0
- metadata +36 -8
- data/bin/nwn-gff-import +0 -69
- data/bin/nwn-gff-irb +0 -62
- data/bin/nwn-gff-print +0 -133
data/lib/nwn/all.rb
ADDED
data/lib/nwn/gff.rb
CHANGED
@@ -15,7 +15,7 @@ module NWN
|
|
15
15
|
# not exist.
|
16
16
|
class GffPathInvalidError < RuntimeError; end
|
17
17
|
|
18
|
-
# This hash lists all possible NWN::Gff::
|
18
|
+
# This hash lists all possible NWN::Gff::Field types.
|
19
19
|
Types = {
|
20
20
|
0 => :byte,
|
21
21
|
1 => :char,
|
@@ -56,879 +56,65 @@ module NWN
|
|
56
56
|
:double => 'd',
|
57
57
|
}.freeze
|
58
58
|
|
59
|
-
#
|
60
|
-
|
61
|
-
#
|
62
|
-
# [+s+] The gff object to yield pairs for; can be one of NWN::Gff::Gff, NWN::Gff::Struct, Array (for lists), or NWN::Gff::Element.
|
63
|
-
# [+prefix+] Supply a prefix to add to the output.
|
64
|
-
# [+types_too+] Yield type definitions as well (gffprint.pl -t).
|
65
|
-
# [+add_prefix+] Add a prefix <tt>(unknown type)</tt> of no type information can be derived from the input.
|
66
|
-
# [+file_type+] File type override. If non-null, add a global struct header with the given file type (useful for passing to gffencode.pl)
|
67
|
-
# [+struct_id+] Provide a struct_id override (if printing a struct).
|
68
|
-
def self.kivinen_format s, prefix = "/", types_too = false, add_prefix = true, file_type = nil, struct_id = nil, &block
|
69
|
-
if s.is_a?(NWN::Gff::Gff)
|
70
|
-
if types_too
|
71
|
-
yield("/", "")
|
72
|
-
yield("/ ____file_type", file_type.nil? ? s.type : file_type)
|
73
|
-
yield("/ ____file_version", s.version)
|
74
|
-
end
|
75
|
-
s = NWN::Gff::Element.new("", :struct, s.root_struct)
|
76
|
-
elsif file_type != nil
|
77
|
-
yield("/", "")
|
78
|
-
yield("/ ____file_type", file_type)
|
79
|
-
yield("/ ____file_version", "V3.2")
|
80
|
-
end
|
81
|
-
|
82
|
-
if s.is_a?(Array)
|
83
|
-
v = NWN::Gff::Element.new(add_prefix ? "(unlabeled list)" : "", :list, s)
|
84
|
-
end
|
85
|
-
|
86
|
-
if s.is_a?(NWN::Gff::Struct)
|
87
|
-
s = NWN::Gff::Element.new(add_prefix ? "(unlabeled struct)" : "", :struct, s)
|
88
|
-
end
|
89
|
-
|
90
|
-
if s.is_a?(String)
|
91
|
-
yield("(unlabeled string)" + prefix, s)
|
92
|
-
return
|
93
|
-
end
|
94
|
-
|
95
|
-
case s.type
|
96
|
-
when :struct
|
97
|
-
yield(prefix + " ____struct_type", struct_id.nil? ? s.value.struct_id : struct_id) if types_too
|
98
|
-
s.value.sort.each {|k,v|
|
99
|
-
kivinen_format v, prefix + s.label + (s.label == "" ? "" : "/"), types_too do |l,v|
|
100
|
-
yield(l, v)
|
101
|
-
end
|
102
|
-
}
|
103
|
-
|
104
|
-
when :cexolocstr
|
105
|
-
|
106
|
-
s.value.each {|kk,vv|
|
107
|
-
yield(prefix + s.label + "/" + kk.to_s, vv.gsub(/([\000-\037\177-\377%])/) {|v| "%" + v.unpack("H2")[0] })
|
108
|
-
}
|
109
|
-
yield(prefix + s.label + ". ____string_ref", s.str_ref)
|
110
|
-
|
111
|
-
when :list
|
112
|
-
s.value.each_with_index {|vv, idx|
|
113
|
-
if types_too
|
114
|
-
yield(prefix + s.label + "[#{idx}]/", prefix + s.label + "[#{idx}]")
|
115
|
-
yield(prefix + s.label + "[#{idx}]/" + " ____struct_type", 0)
|
116
|
-
end
|
117
|
-
vv.each {|kkk, vvv|
|
118
|
-
kivinen_format vvv, prefix + s.label + "[#{idx}]/", types_too do |l,v|
|
119
|
-
yield(l,v)
|
120
|
-
end
|
121
|
-
}
|
122
|
-
}
|
123
|
-
when :cexostr
|
124
|
-
yield(prefix + s.label, s.value.gsub(/([\000-\037\177-\377%])/) {|v| "%" + v.unpack("H2")[0] })
|
125
|
-
else
|
126
|
-
yield(prefix + s.label, s.value)
|
127
|
-
end
|
128
|
-
|
129
|
-
if types_too && s.label != ""
|
130
|
-
yield(prefix + s.label + ". ____type", Types.index(s.type).to_s)
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
end
|
136
|
-
|
137
|
-
# A GFF object encapsulates a whole GFF identity, with a type,
|
138
|
-
# version, and a root structure.
|
139
|
-
# This object also provides easy accessors for labels and values.
|
140
|
-
class NWN::Gff::Gff
|
141
|
-
include NWN::Gff
|
59
|
+
# These field types can never be inlined in YAML.
|
60
|
+
YAMLNonInlineableFields = [:struct, :list, :cexolocstr]
|
142
61
|
|
143
|
-
|
144
|
-
attr_accessor :version
|
145
|
-
|
146
|
-
def to_yaml_properties
|
147
|
-
[ '@type', '@version', '@hash' ]
|
148
|
-
end
|
62
|
+
FileFormats = [:gff, :yaml, :kivinen, :marshal]
|
149
63
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
@version = version
|
159
|
-
end
|
160
|
-
|
161
|
-
# Return the root struct of this GFF.
|
162
|
-
def root_struct
|
163
|
-
@hash
|
164
|
-
end
|
165
|
-
|
166
|
-
def to_yaml( opts = {} )
|
167
|
-
YAML::quick_emit( self, opts ) do |out|
|
168
|
-
out.map( taguri, to_yaml_style ) do |map|
|
169
|
-
to_yaml_properties.each do |m|
|
170
|
-
map.add( m[1..-1], instance_variable_get( m ) )
|
171
|
-
end
|
172
|
-
end
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
# A simple accessor that can be used to
|
177
|
-
# retrieve or set properties in the struct, delimited by slashes.
|
178
|
-
#
|
179
|
-
# Will raise a GffPathInvalidError if the given path cannot be found,
|
180
|
-
# and GffTypeError if some type fails to validate.
|
181
|
-
#
|
182
|
-
# Examples (with +gff+ assumed to be a item):
|
183
|
-
# gff['/Tag']
|
184
|
-
# will retrieve the Tag of the given object
|
185
|
-
# gff['/Tag'] = 'Test'
|
186
|
-
# Set the Tag to 'Test'
|
187
|
-
# gff['/PropertiesList']
|
188
|
-
# will retrieve an array of Gff::Elements
|
189
|
-
# gff['/PropertiesList[1]']
|
190
|
-
# will yield element 2 in the list
|
191
|
-
# gff['/'] = NWN::Gff::Element.new('Property', :byte, 14)
|
192
|
-
# will add a new property at the root struct with the name of 'Property', or
|
193
|
-
# overwrite an existing one with the same label.
|
194
|
-
# gff['/PropertiesList[0]'] = 'Test'
|
195
|
-
# This will raise an error (obviously)
|
196
|
-
def get_or_set k, new_value = nil, new_type = nil, new_label = nil, newstr_ref = nil
|
197
|
-
h = self.root_struct
|
198
|
-
path = []
|
199
|
-
value_path = [h]
|
200
|
-
current_value = nil
|
201
|
-
|
202
|
-
k.split('/').each {|v|
|
203
|
-
next if v == ""
|
204
|
-
path << v
|
205
|
-
|
206
|
-
if current_value.is_a?(Gff::Element) && current_value.type == :list # && v =~ /\[(\d+)\]$/
|
207
|
-
current_value = current_value.value[$1.to_i]
|
208
|
-
end
|
209
|
-
|
210
|
-
if h.is_a?(Gff::Element)
|
211
|
-
case h.type
|
212
|
-
when :cexolocstr
|
213
|
-
current_value = h.value.languages[v.to_i] || ''
|
214
|
-
|
215
|
-
when :list
|
216
|
-
raise GffPathInvalidError, "List-selector access not implemented yet."
|
217
|
-
|
218
|
-
else
|
219
|
-
raise GffPathInvalidError,
|
220
|
-
"Tried to access sub-label of a non-complex field: /#{path.join('/')}"
|
221
|
-
|
222
|
-
end
|
223
|
-
elsif h.is_a?(Gff::Struct)
|
224
|
-
|
225
|
-
if v =~ /^(.+?)\[(\d+)\]$/
|
226
|
-
current_value = h[$1.to_s]
|
227
|
-
if current_value.is_a?(Gff::Element) && !current_value.type == :list
|
228
|
-
raise GffPathInvalidError, "Tried to access list-index of a non-list at /#{path.join('/')}"
|
229
|
-
end
|
230
|
-
current_value = current_value.value[$2.to_i]
|
231
|
-
else
|
232
|
-
current_value = h[v]
|
233
|
-
end
|
234
|
-
else
|
235
|
-
raise GffPathInvalidError, "Unknown sub-field type #{h.class.to_s} at /#{path.join('/')}"
|
236
|
-
end
|
237
|
-
|
238
|
-
value_path << current_value
|
239
|
-
h = current_value
|
240
|
-
|
241
|
-
raise GffPathInvalidError,
|
242
|
-
"Cannot find path: /#{path.join('/')}" if current_value.nil? && !new_value.is_a?(Gff::Element)
|
64
|
+
FileFormatGuesses = {
|
65
|
+
/^ut[cdeimpstw]$/ => :gff,
|
66
|
+
/^(git|are|gic)$/ => :gff,
|
67
|
+
/^(mod|ifo|fac|ssf|dlg)$/ => :gff,
|
68
|
+
/^(bic)$/ => :gff,
|
69
|
+
/^ya?ml$/ => :yaml,
|
70
|
+
/^marshal$/ => :marshal,
|
71
|
+
/^k(ivinen)?$/ => :kivinen,
|
243
72
|
}
|
244
73
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
else
|
249
|
-
raise GffPathInvalidError, "Do not operate on the root struct unless through adding items."
|
250
|
-
end
|
74
|
+
def self.guess_file_format(filename)
|
75
|
+
extension = File.extname(filename)[1..-1]
|
76
|
+
FileFormatGuesses[FileFormatGuesses.keys.select {|key| extension =~ key}[0]]
|
251
77
|
end
|
252
78
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
value_path[-2].delete(current_value.label)
|
264
|
-
current_value.label = new_label
|
265
|
-
value_path[-2][new_label] = current_value
|
266
|
-
end
|
267
|
-
|
268
|
-
if !new_type.nil?
|
269
|
-
# Set a new datatype
|
270
|
-
raise GffTypeError, "Cannot set a type on a non-element." unless current_value.is_a?(Gff::Element)
|
271
|
-
test = current_value.dup
|
272
|
-
test.type = new_type
|
273
|
-
test.validate
|
274
|
-
|
275
|
-
current_value.type = new_type
|
276
|
-
|
277
|
-
end
|
278
|
-
|
279
|
-
if !newstr_ref.nil?
|
280
|
-
# Set a new str_ref
|
281
|
-
raise GffTypeError, "specified path is not a CExoStr" unless current_value.is_a?(Gff::CExoString)
|
282
|
-
current_value.str_ref = new_str_ref.to_i
|
283
|
-
end
|
284
|
-
|
285
|
-
if !new_value.nil?
|
286
|
-
|
287
|
-
case current_value
|
288
|
-
when Gff::Element
|
289
|
-
test = current_value.dup
|
290
|
-
test.value = new_value
|
291
|
-
test.validate
|
292
|
-
current_value.value = new_value
|
293
|
-
|
294
|
-
when String #means: cexolocstr assignment
|
295
|
-
if value_path[-2].is_a?(Gff::Element) && value_path[-2].type == :cexolocstr
|
296
|
-
value_path[-2].value[path[-1].to_i] = new_value
|
297
|
-
else
|
298
|
-
raise GffPathInvalidError, "Dont know how to set #{new_value.class} on #{path.inspect}."
|
299
|
-
end
|
300
|
-
else
|
301
|
-
raise GffPathInvalidError, "Don't know what to do with #{current_value.class} -> #{new_value.class} at /#{path.join('/')}"
|
302
|
-
end
|
303
|
-
|
304
|
-
end
|
305
|
-
end
|
306
|
-
|
307
|
-
old_value
|
308
|
-
end
|
309
|
-
|
310
|
-
# A alias for get_or_set(key).
|
311
|
-
def [] k
|
312
|
-
get_or_set k
|
313
|
-
end
|
314
|
-
|
315
|
-
# A alias for get_or_set(key, value).
|
316
|
-
def []= k, v
|
317
|
-
get_or_set k, v
|
318
|
-
end
|
319
|
-
|
320
|
-
end
|
321
|
-
|
322
|
-
# A Element wraps a GFF label->value pair,
|
323
|
-
# provides a +.type+ and, optionally,
|
324
|
-
# a +.str_ref+ for CExoLocStrings.
|
325
|
-
#
|
326
|
-
# Fields:
|
327
|
-
# [+label+] The label of this element, for reference.
|
328
|
-
# [+type+] The type of this element. (See NWN::Gff)
|
329
|
-
# [+value+] The value of this element.
|
330
|
-
class NWN::Gff::Element
|
331
|
-
NonInline = [:struct, :list, :cexolocstr]
|
332
|
-
|
333
|
-
attr_accessor :label, :type, :value, :str_ref
|
334
|
-
|
335
|
-
def to_yaml_properties
|
336
|
-
[ '@label', '@type', '@value', '@str_ref' ]
|
337
|
-
end
|
338
|
-
|
339
|
-
def initialize label = nil, type = nil, value = nil
|
340
|
-
@label, @type, @value = label, type, value
|
341
|
-
end
|
342
|
-
|
343
|
-
def to_yaml( opts = {} )
|
344
|
-
YAML::quick_emit( self, opts ) do |out|
|
345
|
-
out.map( taguri, to_yaml_style ) do |map|
|
346
|
-
map.style = :inline unless NonInline.index(self.type)
|
347
|
-
to_yaml_properties.sort.each do |m|
|
348
|
-
map.add( m[1..-1], instance_variable_get( m ) ) unless instance_variable_get( m ).nil?
|
349
|
-
end
|
350
|
-
end
|
351
|
-
end
|
352
|
-
end
|
353
|
-
|
354
|
-
def validate path_prefix = "/"
|
355
|
-
raise NWN::Gff::GffTypeError, "#{path_prefix}#{self.label}: New value #{self.value} is not compatible with the current type #{self.type}" unless
|
356
|
-
self.class.valid_for?(self.value, self.type)
|
357
|
-
end
|
358
|
-
|
359
|
-
# 0 => :byte,
|
360
|
-
# 1 => :char,
|
361
|
-
# 2 => :word,
|
362
|
-
# 3 => :short,
|
363
|
-
# 4 => :dword,
|
364
|
-
# 5 => :int,
|
365
|
-
# 6 => :dword64,
|
366
|
-
# 7 => :int64,
|
367
|
-
# 8 => :float,
|
368
|
-
# 9 => :double,
|
369
|
-
# 10 => :cexostr,
|
370
|
-
# 11 => :resref,
|
371
|
-
# 12 => :cexolocstr,
|
372
|
-
# 13 => :void,
|
373
|
-
# 14 => :struct,
|
374
|
-
# 15 => :list,
|
375
|
-
|
376
|
-
# Validate if +value+ is within bounds of +type+.
|
377
|
-
def self.valid_for? value, type
|
378
|
-
case type
|
379
|
-
when :char, :byte
|
380
|
-
value.is_a?(Fixnum)
|
381
|
-
when :short, :word
|
382
|
-
value.is_a?(Fixnum)
|
383
|
-
when :int, :dword
|
384
|
-
value.is_a?(Fixnum)
|
385
|
-
when :int64, :dword64
|
386
|
-
value.is_a?(Fixnum)
|
387
|
-
when :float, :double
|
388
|
-
value.is_a?(Float)
|
389
|
-
when :resref
|
390
|
-
value.is_a?(String) && (1..16).member?(value.size)
|
391
|
-
when :cexostr
|
392
|
-
value.is_a?(String)
|
393
|
-
when :cexolocstr
|
394
|
-
value.is_a?(Array)
|
395
|
-
when :struct, :list
|
396
|
-
value.is_a?(Array)
|
397
|
-
when :void
|
398
|
-
true
|
399
|
-
else
|
400
|
-
false
|
401
|
-
end
|
402
|
-
end
|
403
|
-
|
404
|
-
end
|
405
|
-
|
406
|
-
# A Gff::Struct is a hash of label->Element pairs,
|
407
|
-
# with an added +.struct_id+.
|
408
|
-
class NWN::Gff::Struct
|
409
|
-
attr_accessor :struct_id
|
410
|
-
attr_accessor :hash
|
411
|
-
|
412
|
-
def to_yaml_properties
|
413
|
-
[ '@struct_id', '@hash' ]
|
414
|
-
end
|
415
|
-
|
416
|
-
def to_yaml( opts = {} )
|
417
|
-
YAML::quick_emit( nil, opts ) do |out|
|
418
|
-
out.map( taguri, to_yaml_style ) do |map|
|
419
|
-
# Inline certain structs that are small enough.
|
420
|
-
map.style = :inline if hash.size <= 1 &&
|
421
|
-
hash.values.select {|x|
|
422
|
-
NWN::Gff::Element::NonInline.index(x.type)
|
423
|
-
}.size == 0
|
424
|
-
|
425
|
-
to_yaml_properties.each do |m|
|
426
|
-
map.add( m[1..-1], instance_variable_get( m ) ) # unless instance_variable_get( m ).nil?
|
427
|
-
end
|
428
|
-
end
|
429
|
-
end
|
430
|
-
end
|
431
|
-
|
432
|
-
def initialize
|
433
|
-
@struct_id = 0
|
434
|
-
@hash = {}
|
435
|
-
super
|
436
|
-
end
|
437
|
-
|
438
|
-
def method_missing meth, *a, &block
|
439
|
-
@hash.method(meth).call(*a, &block)
|
440
|
-
end
|
441
|
-
|
442
|
-
# Sets a NWN::Gff::Element on this struct.
|
443
|
-
# Overwrites existing labels.
|
444
|
-
|
445
|
-
# args can be the following:
|
446
|
-
# * A single NWN::Gff::Element
|
447
|
-
# * A single NWN::Gff::Struct, in which case it will be merged into this one
|
448
|
-
# * label, type
|
449
|
-
# * label, type, value
|
450
|
-
def set *args
|
451
|
-
if args.size == 1
|
452
|
-
arg = args[0]
|
453
|
-
case arg
|
454
|
-
when NWN::Gff::Struct
|
455
|
-
@hash.merge!(addme)
|
456
|
-
|
457
|
-
when NWN::Gff::Element
|
458
|
-
@hash[arg.label] = arg
|
79
|
+
def self.read(io, format)
|
80
|
+
return case format
|
81
|
+
when :gff
|
82
|
+
NWN::Gff::Reader.read(io)
|
83
|
+
when :yaml
|
84
|
+
YAML.load(io)
|
85
|
+
when :marshal
|
86
|
+
Marshal.load(io)
|
87
|
+
when :kivinen
|
88
|
+
raise NotImplementedError, "Reading kivinen-style data is not supported."
|
459
89
|
else
|
460
|
-
raise
|
90
|
+
raise NotImplementedError, "Don't know how to read #{format}."
|
461
91
|
end
|
462
|
-
|
463
|
-
elsif args.size == 2 || args.size == 3
|
464
|
-
element = NWN::Gff::Element.new(*args)
|
465
|
-
|
466
|
-
@hash[element.label] = element
|
467
|
-
else
|
468
|
-
raise ArgumentError, "Cannot determine how to handle #{args.inspect}."
|
469
92
|
end
|
470
|
-
end
|
471
|
-
|
472
|
-
end
|
473
|
-
|
474
|
-
# A CExoLocString is a localised CExoString.
|
475
|
-
# It contains pairs of language => text pairs,
|
476
|
-
# where language is a language_id as specified in the GFF
|
477
|
-
# documentation pdf.
|
478
|
-
class NWN::Gff::CExoLocString
|
479
|
-
attr_reader :languages
|
480
|
-
def initialize
|
481
|
-
@languages = {}
|
482
|
-
end
|
483
|
-
|
484
|
-
# Retrieve the text for a given language.
|
485
|
-
# Returns "" if no text is set for the given
|
486
|
-
# language.
|
487
|
-
def [] language_id
|
488
|
-
@languages[language_id.to_i] || ""
|
489
|
-
end
|
490
|
-
|
491
|
-
# Sets a new language text.
|
492
|
-
def []= language_id, text
|
493
|
-
@languages[language_id.to_i] = text
|
494
|
-
end
|
495
93
|
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
@languages.compact.reject {|k,v| "" == v}
|
508
|
-
end
|
509
|
-
end
|
510
|
-
|
511
|
-
# A class that parses binary GFF bytes into ruby-friendly data structures.
|
512
|
-
class NWN::Gff::Reader
|
513
|
-
include NWN::Gff
|
514
|
-
|
515
|
-
attr_reader :hash
|
516
|
-
attr_reader :gff
|
517
|
-
|
518
|
-
# This is a hash containing the following options:
|
519
|
-
# [+float_rounding+]
|
520
|
-
# Round floating numbers to this many righthand positions.
|
521
|
-
# Defaults to nil (for no rounding). This can be set to prevent
|
522
|
-
# minor toolset fiddlings from showing up in diffs.
|
523
|
-
# Note that this is somewhat experimental and may introduce
|
524
|
-
# accumulating rounding errors over periods of time.
|
525
|
-
# Suggested values:
|
526
|
-
# *.git: 4
|
527
|
-
attr_accessor :options
|
528
|
-
|
529
|
-
# Create a new Reader with the given +bytes+ and immediately parse it.
|
530
|
-
# This is not needed usually; use Reader.read instead.
|
531
|
-
def initialize bytes, options = {}
|
532
|
-
@bytes = bytes
|
533
|
-
@options = {
|
534
|
-
:float_rounding => nil,
|
535
|
-
}.merge(options)
|
536
|
-
|
537
|
-
read_all
|
538
|
-
end
|
539
|
-
|
540
|
-
# Reads +bytes+ as gff data and returns a NWN::Gff:Gff object.
|
541
|
-
def self.read bytes, options = {}
|
542
|
-
self.new(bytes, options).gff
|
543
|
-
end
|
544
|
-
|
545
|
-
private
|
546
|
-
|
547
|
-
def read_all
|
548
|
-
type, version,
|
549
|
-
struct_offset, struct_count,
|
550
|
-
field_offset, field_count,
|
551
|
-
label_offset, label_count,
|
552
|
-
field_data_offset, field_data_count,
|
553
|
-
field_indices_offset, field_indices_count,
|
554
|
-
list_indices_offset, list_indices_count =
|
555
|
-
@bytes.unpack("a4a4 VV VV VV VV VV VV")
|
556
|
-
|
557
|
-
raise GffError, "Unknown version #{@version}; not a gff?" unless
|
558
|
-
version == "V3.2"
|
559
|
-
|
560
|
-
raise GffError, "struct offset at wrong place, not a gff?" unless
|
561
|
-
struct_offset == 56
|
562
|
-
|
563
|
-
struct_len = struct_count * 12
|
564
|
-
field_len = field_count * 16
|
565
|
-
label_len = label_count * 16
|
566
|
-
|
567
|
-
@structs = @bytes[struct_offset, struct_len].unpack("V*")
|
568
|
-
@fields = @bytes[field_offset, field_len].unpack("V*")
|
569
|
-
@labels = @bytes[label_offset, label_len].unpack("A16" * label_count)
|
570
|
-
@field_data = @bytes[field_data_offset, field_data_count]
|
571
|
-
@field_indices = @bytes[field_indices_offset, field_indices_count].unpack("V*")
|
572
|
-
@list_indices = @bytes[list_indices_offset, list_indices_count].unpack("V*")
|
573
|
-
# puts "FieldDataOffset = #{field_data_offset}, Count = #{field_data_count}"
|
574
|
-
@hash = read_struct 0
|
575
|
-
@gff = Gff.new(@hash, type, version)
|
576
|
-
end
|
577
|
-
|
578
|
-
# This iterates through a struct and reads all fields into a hash, which it returns.
|
579
|
-
def read_struct index
|
580
|
-
struct = Gff::Struct.new
|
581
|
-
|
582
|
-
type = @structs[index * 3]
|
583
|
-
data_or_offset = @structs[index * 3 + 1]
|
584
|
-
count = @structs[index * 3 + 2]
|
585
|
-
|
586
|
-
raise GffError, "struct index #{index} outside of struct_array" if
|
587
|
-
index * 3 + 3 > @structs.size + 1
|
588
|
-
|
589
|
-
if count == 1
|
590
|
-
lbl, vl = * read_field(data_or_offset)
|
591
|
-
struct[lbl] = vl
|
592
|
-
else
|
593
|
-
if count > 0
|
594
|
-
raise GffError, "struct index not divisable by 4" if
|
595
|
-
data_or_offset % 4 != 0
|
596
|
-
data_or_offset /= 4
|
597
|
-
for i in data_or_offset...(data_or_offset+count)
|
598
|
-
lbl, vl = * read_field(@field_indices[i])
|
599
|
-
struct[lbl] = vl
|
600
|
-
end
|
601
|
-
end
|
602
|
-
end
|
603
|
-
|
604
|
-
struct.struct_id = type
|
605
|
-
struct
|
606
|
-
end
|
607
|
-
|
608
|
-
# Reads the field at +index+ and returns [label_name, Gff::Element]
|
609
|
-
def read_field index
|
610
|
-
gff = {}
|
611
|
-
|
612
|
-
field = Element.new
|
613
|
-
|
614
|
-
index *= 3
|
615
|
-
type = @fields[index]
|
616
|
-
label_index = @fields[index + 1]
|
617
|
-
data_or_offset = @fields[index + 2]
|
618
|
-
# puts "Reading field #{index}"
|
619
|
-
# puts "Label_index = #{label_index}, label = #{@labels[label_index]}"
|
620
|
-
# puts "type = #{type}, data_or_offset = #{data_or_offset}"
|
621
|
-
raise GffError, "Label index #{label_index} outside of label array" if
|
622
|
-
label_index > @labels.size
|
623
|
-
|
624
|
-
label = @labels[label_index]
|
625
|
-
|
626
|
-
raise GffError, "Unknown field type #{type}." unless Types[type]
|
627
|
-
type = Types[type]
|
628
|
-
|
629
|
-
raise GffError, "Field '#{label}' (type: #{type} )data offset #{data_or_offset} outside of field data block (#{@field_data.size})" if
|
630
|
-
ComplexTypes.index(type) && data_or_offset > @field_data.size
|
631
|
-
|
632
|
-
value = case type
|
633
|
-
when :byte, :char
|
634
|
-
data_or_offset & 0xff
|
635
|
-
|
636
|
-
when :word
|
637
|
-
data_or_offset & 0xffff
|
638
|
-
|
639
|
-
when :short
|
640
|
-
[(data_or_offset & 0xffff)].pack("S").unpack("s")[0]
|
641
|
-
|
642
|
-
when :dword
|
643
|
-
data_or_offset
|
644
|
-
|
645
|
-
when :int
|
646
|
-
[data_or_offset].pack("I").unpack("i")[0]
|
647
|
-
|
648
|
-
when :float
|
649
|
-
vsx = [data_or_offset].pack("V").unpack("f")[0]
|
650
|
-
@options[:float_rounding] ? ("%.#{@options[:float_rounding]}f" % vsx).to_f : vsx
|
651
|
-
|
652
|
-
when :dword64
|
653
|
-
len = 8
|
654
|
-
v1, v2 = @field_data[data_or_offset, len].unpack("II")
|
655
|
-
v1 * (2**32) + v2
|
656
|
-
|
657
|
-
when :int64
|
658
|
-
len = 8
|
659
|
-
@field_data[data_or_offset, len].unpack("q")[0]
|
660
|
-
|
661
|
-
when :double
|
662
|
-
len = 8
|
663
|
-
@field_data[data_or_offset, len].unpack("d")[0]
|
664
|
-
|
665
|
-
when :cexostr
|
666
|
-
len = @field_data[data_or_offset, 4].unpack("V")[0]
|
667
|
-
@field_data[data_or_offset + 4, len]
|
668
|
-
|
669
|
-
when :resref
|
670
|
-
len = @field_data[data_or_offset, 1].unpack("C")[0]
|
671
|
-
@field_data[data_or_offset + 1, len]
|
672
|
-
|
673
|
-
when :cexolocstr
|
674
|
-
exostr = Gff::CExoLocString.new
|
675
|
-
total_size, str_ref, str_count =
|
676
|
-
@field_data[data_or_offset, 12].unpack("VVV")
|
677
|
-
all = @field_data[data_or_offset + 12, total_size]
|
678
|
-
field.str_ref = str_ref
|
679
|
-
|
680
|
-
str_count.times {
|
681
|
-
id, len = all.unpack("VV")
|
682
|
-
str = all[8, len].unpack("a*")[0]
|
683
|
-
all = all[(8 + len)..-1]
|
684
|
-
exostr[id] = str
|
685
|
-
}
|
686
|
-
len = total_size + 4
|
687
|
-
exostr
|
688
|
-
|
689
|
-
when :void
|
690
|
-
len = @field_data[data_or_offset, 4].unpack("V")[0]
|
691
|
-
@field_data[data_or_offset + 4, len].unpack("H*")[0]
|
692
|
-
|
693
|
-
when :struct
|
694
|
-
read_struct data_or_offset
|
695
|
-
|
696
|
-
when :list
|
697
|
-
list = []
|
698
|
-
|
699
|
-
raise GffError, "List index not divisable by 4" unless
|
700
|
-
data_or_offset % 4 == 0
|
701
|
-
|
702
|
-
data_or_offset /= 4
|
703
|
-
|
704
|
-
raise GffError, "List index outside list indices" if
|
705
|
-
data_or_offset > @list_indices.size
|
706
|
-
|
707
|
-
count = @list_indices[data_or_offset]
|
708
|
-
|
709
|
-
raise GffError, "List index overflow the list indices array" if
|
710
|
-
data_or_offset + count > @list_indices.size
|
711
|
-
|
712
|
-
data_or_offset += 1
|
713
|
-
|
714
|
-
for i in data_or_offset...(data_or_offset + count)
|
715
|
-
list << read_struct(@list_indices[i])
|
716
|
-
end
|
717
|
-
|
718
|
-
list
|
719
|
-
|
720
|
-
end
|
721
|
-
|
722
|
-
raise GffError, "Field data overflows from the field data block area\
|
723
|
-
offset = #{data_or_offset + len}, len = #{@field_data.size}" if
|
724
|
-
len && data_or_offset + len > @field_data.size
|
725
|
-
|
726
|
-
field.label = label
|
727
|
-
field.type = type
|
728
|
-
field.value = value
|
729
|
-
|
730
|
-
[label, field] #::Gff::Element.new(type,label,value)
|
731
|
-
end
|
732
|
-
end
|
733
|
-
|
734
|
-
|
735
|
-
class NWN::Gff::Writer
|
736
|
-
include NWN::Gff
|
737
|
-
|
738
|
-
attr_reader :bytes
|
739
|
-
|
740
|
-
def initialize(gff)
|
741
|
-
@gff = gff
|
742
|
-
|
743
|
-
@structs = []
|
744
|
-
@fields = []
|
745
|
-
@labels = []
|
746
|
-
@field_indices = []
|
747
|
-
@list_indices = []
|
748
|
-
@field_data = ""
|
749
|
-
|
750
|
-
write_all
|
751
|
-
end
|
752
|
-
|
753
|
-
# Takes a NWN::Gff::Gff object and dumps it as raw bytes,
|
754
|
-
# including the header.
|
755
|
-
def self.dump(gff)
|
756
|
-
self.new(gff).bytes
|
757
|
-
end
|
758
|
-
|
759
|
-
private
|
760
|
-
|
761
|
-
def get_label_id_for_label str
|
762
|
-
@labels << str unless @labels.index(str)
|
763
|
-
@labels.index(str)
|
764
|
-
end
|
765
|
-
|
766
|
-
def add_data_field type, label, content
|
767
|
-
label_id = get_label_id_for_label label
|
768
|
-
@fields.push Types.index(type), label_id, content
|
769
|
-
(@fields.size - 1) / 3
|
770
|
-
end
|
771
|
-
|
772
|
-
def write_all
|
773
|
-
data = []
|
774
|
-
write_struct @gff.root_struct
|
775
|
-
|
776
|
-
c_offset = 0
|
777
|
-
data << [
|
778
|
-
@gff.type,
|
779
|
-
@gff.version,
|
780
|
-
|
781
|
-
# Offset of Struct array as bytes from the beginning of the file
|
782
|
-
c_offset += 56,
|
783
|
-
# Number of elements in Struct array
|
784
|
-
@structs.size / 3,
|
785
|
-
|
786
|
-
# Offset of Field array as bytes from the beginning of the file
|
787
|
-
fields_start = c_offset += @structs.size / 3 * 12,
|
788
|
-
# Number of elements in Field array
|
789
|
-
@fields.size / 3,
|
790
|
-
|
791
|
-
# Offset of Label array as bytes from the beginning of the file
|
792
|
-
c_offset += @fields.size / 3 * 12,
|
793
|
-
# Number of elements in Label array
|
794
|
-
@labels.size,
|
795
|
-
|
796
|
-
# Offset of Field Data as bytes from the beginning of the file
|
797
|
-
c_offset += @labels.size * 16,
|
798
|
-
# Number of bytes in Field Data block
|
799
|
-
@field_data.size,
|
800
|
-
|
801
|
-
# Offset of Field Indices array as bytes from the beginning of the file
|
802
|
-
c_offset += @field_data.size,
|
803
|
-
# Number of bytes in Field Indices array
|
804
|
-
@field_indices.size * 4,
|
805
|
-
|
806
|
-
# Offset of List Indices array as bytes from the beginning of the file
|
807
|
-
c_offset += @field_indices.size * 4,
|
808
|
-
# Number of bytes in List Indices array
|
809
|
-
@list_indices.size * 4
|
810
|
-
|
811
|
-
].pack("a4a4 VV VV VV VV VV VV")
|
812
|
-
|
813
|
-
data << @structs.pack("V*")
|
814
|
-
data << @fields.pack("V*")
|
815
|
-
data << @labels.pack("a16" * @labels.size)
|
816
|
-
data << @field_data
|
817
|
-
data << @field_indices.pack("V*")
|
818
|
-
data << @list_indices.pack("V*")
|
819
|
-
|
820
|
-
@bytes = data.join("")
|
821
|
-
end
|
822
|
-
|
823
|
-
def write_struct struct
|
824
|
-
raise GffError, "struct invalid: #{struct.inspect}" unless struct.is_a?(Gff::Struct)
|
825
|
-
raise GffError, "struct_id missing from struct" unless struct.struct_id
|
826
|
-
|
827
|
-
# This holds all field label ids this struct has as a member
|
828
|
-
fields_of_this_struct = []
|
829
|
-
|
830
|
-
# This will hold the index of this struct
|
831
|
-
index = @structs.size / 3
|
832
|
-
|
833
|
-
@structs.push struct.struct_id, 0, 0
|
834
|
-
|
835
|
-
struct.sort.each {|k,v|
|
836
|
-
raise GffError, "Empty label." if !k || k == ""
|
837
|
-
|
838
|
-
case v.type
|
839
|
-
# simple data types
|
840
|
-
when :byte, :char, :word, :short, :dword, :int, :float
|
841
|
-
format = Formats[v.type]
|
842
|
-
fields_of_this_struct << add_data_field(v.type, k, [v.value].pack(format).unpack("V")[0])
|
843
|
-
|
844
|
-
# complex data types
|
845
|
-
when :dword64, :int64, :double, :void
|
846
|
-
fields_of_this_struct << add_data_field(v.type, k, @field_data.size)
|
847
|
-
format = Formats[v.type]
|
848
|
-
@field_data << case v.type
|
849
|
-
when :dword64
|
850
|
-
[
|
851
|
-
( v.value / (2**32) ) & 0xffffffff,
|
852
|
-
v.value % (2**32)
|
853
|
-
].pack("II")
|
854
|
-
when :void
|
855
|
-
[ v.value.size / 2, v.value ].pack("VH*")
|
856
|
-
else
|
857
|
-
[v.value].pack(format)
|
94
|
+
def self.write(io, format, data)
|
95
|
+
case format
|
96
|
+
when :gff
|
97
|
+
io.print NWN::Gff::Writer.dump(data)
|
98
|
+
when :yaml
|
99
|
+
io.puts data.to_yaml
|
100
|
+
when :marshal
|
101
|
+
io.print Marshal.dump(data)
|
102
|
+
when :kivinen
|
103
|
+
data.kivinen_format $options[:types], nil, nil do |l,v|
|
104
|
+
io.puts "%s:\t%s" % [l, v]
|
858
105
|
end
|
859
|
-
|
860
|
-
when :struct
|
861
|
-
raise GffError, "type = struct, but value not a hash" unless
|
862
|
-
v.value.is_a?(Gff::Struct)
|
863
|
-
|
864
|
-
fields_of_this_struct << add_data_field(v.type, k, write_struct(v.value))
|
865
|
-
|
866
|
-
when :list
|
867
|
-
raise GffError, "type = list, but value not an array" unless
|
868
|
-
v.value.is_a?(Array)
|
869
|
-
|
870
|
-
fields_of_this_struct << add_data_field(v.type, k, 4 * @list_indices.size)
|
871
|
-
|
872
|
-
count = v.value.size
|
873
|
-
tmp = @list_indices.size
|
874
|
-
@list_indices << count
|
875
|
-
count.times {
|
876
|
-
@list_indices << 0
|
877
|
-
}
|
878
|
-
|
879
|
-
v.value.each_with_index do |kk, idx|
|
880
|
-
vv = write_struct(kk)
|
881
|
-
@list_indices[ idx + tmp + 1 ] = vv
|
882
|
-
end
|
883
|
-
|
884
|
-
when :resref
|
885
|
-
fields_of_this_struct << add_data_field(v.type, k, @field_data.size)
|
886
|
-
@field_data << [v.value.size, v.value].pack("Ca*")
|
887
|
-
|
888
|
-
when :cexostr
|
889
|
-
fields_of_this_struct << add_data_field(v.type, k, @field_data.size)
|
890
|
-
@field_data << [v.value.size, v.value].pack("Va*")
|
891
|
-
|
892
|
-
when :cexolocstr
|
893
|
-
raise GffError, "type = cexolocstr, but value not a exolocstr" unless
|
894
|
-
v.value.is_a?(NWN::Gff::CExoLocString)
|
895
|
-
|
896
|
-
fields_of_this_struct << add_data_field(v.type, k, @field_data.size)
|
897
|
-
|
898
|
-
# total size (4), str_ref (4), str_count (4)
|
899
|
-
total_size = 8
|
900
|
-
v.value.each {|kk,vv|
|
901
|
-
total_size += vv.size + 8
|
902
|
-
}
|
903
|
-
@field_data << [
|
904
|
-
total_size,
|
905
|
-
v.str_ref,
|
906
|
-
v.value.size
|
907
|
-
].pack("VVV")
|
908
|
-
|
909
|
-
v.value.each {|k,v|
|
910
|
-
@field_data << [k, v.size, v].pack("VVa*")
|
911
|
-
}
|
912
|
-
|
913
106
|
else
|
914
|
-
raise
|
107
|
+
raise NotImplementedError, "Don't know how to write data-format #{format.inspect}"
|
915
108
|
end
|
916
|
-
}
|
917
|
-
|
918
|
-
# id/type, data_or_offset, nr_of_fields
|
919
|
-
@structs[3 * (index) + 2] = fields_of_this_struct.size
|
920
|
-
|
921
|
-
if fields_of_this_struct.size < 1
|
922
|
-
elsif fields_of_this_struct.size == 1
|
923
|
-
@structs[3 * (index) + 1] = fields_of_this_struct[0]
|
924
|
-
else
|
925
|
-
# Offset into field_indices starting where are number of nr_of_fields
|
926
|
-
# dwords as indexes into @fields
|
927
|
-
@structs[3 * (index) + 1] = 4 * (@field_indices.size)
|
928
|
-
@field_indices.push *fields_of_this_struct
|
929
109
|
end
|
930
|
-
|
931
|
-
index
|
932
110
|
end
|
933
|
-
|
934
111
|
end
|
112
|
+
|
113
|
+
require 'nwn/gff/struct'
|
114
|
+
require 'nwn/gff/field'
|
115
|
+
require 'nwn/gff/list'
|
116
|
+
require 'nwn/gff/cexolocstr'
|
117
|
+
require 'nwn/gff/reader'
|
118
|
+
require 'nwn/gff/writer'
|
119
|
+
require 'nwn/gff/api'
|
120
|
+
require 'nwn/infer'
|