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.
@@ -0,0 +1,7 @@
1
+ require 'nwn/twoda'
2
+ require 'nwn/gff'
3
+ require 'nwn/yaml'
4
+ require 'nwn/kivinen'
5
+ require 'nwn/scripting'
6
+ require 'nwn/helpers'
7
+ require 'nwn/settings'
@@ -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::Element types.
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
- # Parses +s+ as an arbitary GFF object and yields for each field found,
60
- # with the proper prefix.
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
- attr_accessor :type
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
- # Create a new GFF object from the given +struct+.
151
- # This is normally not needed unless you are creating
152
- # GFF objects entirely from hand.
153
- #
154
- # See NWN::Gff::Reader.
155
- def initialize struct, type, version = "V3.2"
156
- @hash = struct
157
- @type = type
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
- if path.size == 0
246
- if new_value.is_a?(Gff::Element)
247
- value_path << h
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
- old_value = current_value.nil? ? nil : current_value.dup
254
-
255
- if new_value.is_a?(Gff::Element)
256
- new_value.validate
257
- value_path[-2].delete(current_value)
258
- value_path[-2][new_value.label] = new_value
259
- else
260
-
261
- if !new_label.nil?
262
- # Set a new label
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 ArgumentError, "Cannot determine how to handle #{arg.class.to_s}."
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
- def size
497
- @languages.size
498
- end
499
-
500
- def each
501
- @languages.each {|k,v|
502
- yield k, v
503
- }
504
- end
505
-
506
- def compact
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 GffError, "Unknown data type: #{v.type}"
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'