nwn-lib 0.4.5 → 0.4.6

Sign up to get free protection for your applications and to get access to all the features.
data/lib/nwn/gff.rb CHANGED
@@ -117,4 +117,3 @@ require 'nwn/gff/cexolocstr'
117
117
  require 'nwn/gff/reader'
118
118
  require 'nwn/gff/writer'
119
119
  require 'nwn/gff/api'
120
- require 'nwn/infer'
data/lib/nwn/gff/field.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # A Field wraps a GFF label->value pair, providing:
2
2
  # * +.field_type+ describing the field type (e.g. :int)
3
3
  # * +.field_value+ holding the value of this Field
4
-
4
+ #
5
5
  # and, if loaded by Gff::Reader or through YAML:
6
6
  # * +.field_label+ holding the label
7
7
  # * +.parent+ holding the struct this Field is child of.
@@ -21,6 +21,7 @@ module NWN::Gff::Field
21
21
  s = {}.extend(self)
22
22
  s['label'], s['type'], s['value'] = label, type, value
23
23
  s.extend_meta_classes
24
+ s.validate
24
25
  s
25
26
  end
26
27
 
@@ -62,7 +63,12 @@ module NWN::Gff::Field
62
63
  def path
63
64
  raise NWN::Gff::GffError, "field not bound to a parent" unless @parent
64
65
  parent_path = @parent.path
65
- parent_path + "/" + field_label
66
+ if @parent.element && @parent.element.field_type == :list
67
+ idx = @parent.element.field_value.index(@parent)
68
+ parent_path + "[#{idx}]/" + field_label
69
+ else
70
+ parent_path + "/" + field_label
71
+ end
66
72
  end
67
73
 
68
74
  # This extends this field object and its' value with the
@@ -83,29 +89,62 @@ module NWN::Gff::Field
83
89
  field_value.is_a?(field_value_klass)
84
90
  end
85
91
 
92
+ # Validate if this field value is within the bounds of the set type.
93
+ def valid?
94
+ NWN::Gff::Field.valid_for? self.v, self.t
95
+ end
96
+
97
+ # Validate this field, and raise an Excpetion if not valid.
98
+ def validate
99
+ valid? or raise NWN::Gff::GffError,
100
+ "#{self.path rescue $!.to_s + '/' + self.l}: " +
101
+ "value '#{self.v.inspect}' not valid for type '#{self.t.inspect}'"
102
+ end
103
+
86
104
  # Validate if +value+ is within bounds of +type+.
87
105
  def self.valid_for? value, type
88
106
  case type
89
- when :char, :byte
90
- value.is_a?(Fixnum)
91
- when :short, :word
92
- value.is_a?(Fixnum)
93
- when :int, :dword
94
- value.is_a?(Fixnum)
95
- when :int64, :dword64
96
- value.is_a?(Fixnum)
107
+ when :byte, :char
108
+ value.is_a?(Integer) && value >= 0 && value <= 255
109
+
110
+ when :short
111
+ value.is_a?(Integer) && value >= -0x8000 && value <= 0x7fff
112
+ when :word
113
+ value.is_a?(Integer) && value >= 0 && value <= 0xffff
114
+
115
+ when :int
116
+ value.is_a?(Integer) && value >= -0x80000000 && value <= 0x7fffffff
117
+ when :dword
118
+ value.is_a?(Integer) && value >= 0 && value <= 0xffffffff
119
+
120
+ when :int64
121
+ value.is_a?(Integer) && value >= -0x800000000000 && value <= 0x7fffffffffff
122
+ when :dword64
123
+ value.is_a?(Integer) && value >= 0 && value <= 0xffffffffffff
124
+
97
125
  when :float, :double
98
126
  value.is_a?(Float)
127
+
99
128
  when :resref
100
- value.is_a?(String) && (1..16).member?(value.size)
129
+ value.is_a?(String) && (0..16).member?(value.size)
130
+
101
131
  when :cexostr
102
132
  value.is_a?(String)
133
+
103
134
  when :cexolocstr
135
+ value.is_a?(Hash) &&
136
+ value.keys.reject {|x| x.is_a?(Fixnum) && x >= 0 }.size == 0 &&
137
+ value.values.reject {|x| x.is_a?(String) }.size == 0
138
+
139
+ when :struct
140
+ value.is_a?(Hash)
141
+
142
+ when :list
104
143
  value.is_a?(Array)
105
- when :struct, :list
106
- value.is_a?(Array)
144
+
107
145
  when :void
108
- true
146
+ value.is_a?(String)
147
+
109
148
  else
110
149
  false
111
150
  end
@@ -166,8 +166,7 @@ class NWN::Gff::Reader
166
166
  }
167
167
  len = total_size + 4
168
168
  # Filter out empty strings.
169
- exostr.reject! {|k,v| v.nil? || v.empty?} if
170
- ENV['NWN_LIB_FILTER_EMPTY_EXOLOCSTR']
169
+ exostr.reject! {|k,v| v.nil? || v.empty?}
171
170
  exostr.taint
172
171
 
173
172
  when :void
@@ -214,6 +213,7 @@ class NWN::Gff::Reader
214
213
 
215
214
  # We extend all fields and field_values with matching classes.
216
215
  field.extend_meta_classes
216
+ field.validate
217
217
 
218
218
  [label, field]
219
219
  end
@@ -3,16 +3,21 @@
3
3
  module NWN::Gff::Struct
4
4
  DEFAULT_DATA_VERSION = "V3.2"
5
5
 
6
- # The file-type this struct represents.
7
- # This is usually the file extension for root structs,
8
- # and nil for sub-structs.
6
+ # Each Gff::Struct has a data_type, which describes the type of data the struct contains.
7
+ # For top-level structs, this equals the data type written to the GFF file ("UTI",
8
+ # for example); for sub structures, this is usually the top-level data type + the
9
+ # field label ("UTI/PropertiesList", for example).
10
+ #
11
+ # This is set for completeness' sake, but is not required to save the struct.
12
+ # Scripts could use this, for example, to reliably re-attach a Item within
13
+ # /ItemList/ somewhere else, or export it as .uti.
9
14
  attr_accessor :data_type
10
15
 
11
16
  # The file version. Usually "V3.2" for root structs,
12
17
  # and nil for sub-structs.
13
18
  attr_accessor :data_version
14
19
 
15
- # GFF struct type
20
+ # GFF struct type. The default is 0xffffffff.
16
21
  attr_accessor :struct_id
17
22
 
18
23
  # The field this struct is value of.
@@ -43,11 +48,15 @@ module NWN::Gff::Struct
43
48
  # Create a new struct.
44
49
  # Usually, you can leave out data_type and data_version for non-root structs,
45
50
  # because that will be guess-inherited based on the existing associations.
51
+ #
52
+ # You can pass a block to this method, which will receive the newly-created
53
+ # Struct as the only argument.
46
54
  def self.new struct_id = 0xffffffff, data_type = nil, data_version = nil
47
55
  s = {}.extend(self)
48
56
  s.struct_id = struct_id
49
57
  s.data_type = data_type
50
58
  s.data_version = data_version
59
+ yield(s) if block_given?
51
60
  s
52
61
  end
53
62
 
@@ -58,17 +67,26 @@ module NWN::Gff::Struct
58
67
  # some_struct.add_field 'ID', :byte, 5
59
68
  # is equivalent to:
60
69
  # some_struct.add_byte 'ID', 5
70
+ #
71
+ # You can pass a block to this method, which will receive the newly-created
72
+ # Field as an argument.
73
+ #
74
+ # This allows for code like this:
75
+ # Gff::Struct.new(0) do |s|
76
+ # s.add_byte "Byte", 5
77
+ # s.add_list "Some_List", [] do |l|
78
+ # l.v << Gff::Struct.new ...
79
+ # ..
80
+ # end
81
+ # end
61
82
  def add_field label, type, value, &block
62
83
  self[label] = NWN::Gff::Field.new(label, type, value)
63
84
  self[label].parent = self
64
- if block_given?
65
- yield(self[label])
66
- end
85
+ yield(self[label]) if block_given?
67
86
  self[label]
68
87
  end
69
88
 
70
- #:nodoc:
71
- def method_missing meth, *av, &block
89
+ def method_missing meth, *av, &block # :nodoc:
72
90
  if meth.to_s =~ /^add_(.+)$/
73
91
  if NWN::Gff::Types.index($1.to_sym)
74
92
  av.size == 2 or super
data/lib/nwn/res.rb ADDED
@@ -0,0 +1,275 @@
1
+ module NWN
2
+ module Resources
3
+
4
+ # This is a generic index to a resource.
5
+ class ContentObject
6
+ attr_accessor :resref
7
+ attr_accessor :res_type
8
+ attr_accessor :io
9
+ attr_accessor :offset
10
+ attr_accessor :size_override
11
+
12
+ # Create a new index to +filename+, optionally specifying +io+.
13
+ def self.new_from filename, io = nil
14
+ filename = File.expand_path(filename)
15
+ base = File.basename(filename).split(".")[0..-2].join(".").downcase
16
+ ext = File.extname(filename)[1..-1].downcase
17
+ res_type = NWN::Resources::Extensions[ext] or raise ArgumentError,
18
+ "Not a valid extension: #{ext.inspect} (while packing #{filename})"
19
+
20
+ ContentObject.new(base, res_type, io || filename, 0, io.size || File.stat(filename).size)
21
+ end
22
+
23
+ def initialize resref, res_type, io = nil, offset = nil, size = nil
24
+ @resref, @res_type = resref.downcase, res_type
25
+ @io, @offset = io, offset
26
+ @size_override = size
27
+
28
+ raise ArgumentError, "Invalid object passed: responds_to :read, want @offset, but does not respond_to :seek" if
29
+ @io.respond_to?(:read) && @offset && @offset != 0 && !@io.respond_to?(:seek)
30
+ end
31
+
32
+ # Get the size in bytes of this object.
33
+ def size
34
+ @size_override || (@io.is_a?(IO) ? @io.stat.size : File.stat(@io).size)
35
+ end
36
+
37
+ # Get the contents of this object. This is a costly operation, loading
38
+ # the whole buffer. If you want fine-grained access, use ContentObject#io
39
+ # and do it yourself, observing ContentObject#offset and ContentObject#size.
40
+ def get
41
+ if @io.respond_to?(:read)
42
+ @io.seek(@offset) if @offset
43
+ d = @io.read(self.size)
44
+ raise IOError,
45
+ "not enough data available while reading #{self.filename}" if
46
+ d.size != self.size
47
+ d
48
+ else
49
+ IO.read(@io)
50
+ end
51
+ end
52
+
53
+ # Get the canonical filename of this object.
54
+ def filename
55
+ @resref + "." + self.extension
56
+ end
57
+
58
+ # Get the extension of this object.
59
+ def extension
60
+ NWN::Resources::Extensions.index(@res_type)
61
+ end
62
+ end
63
+
64
+ # Wraps n ContentObjects; a baseclass for erf/key encapsulation.
65
+ class Container
66
+ attr_reader :content
67
+
68
+ def initialize
69
+ @content = []
70
+ end
71
+
72
+ def has?(filename)
73
+ base = File.basename(filename)
74
+ @content.each {|f|
75
+ return true if f.filename.downcase == base.downcase
76
+ }
77
+ return false
78
+ end
79
+
80
+ # Add a content object giving a +filename+ and a optional
81
+ # +io+.
82
+ def add_file filename, io = nil
83
+ @content << ContentObject.new_from(filename, io)
84
+ end
85
+
86
+ # Add a content object giving the ContentObject
87
+ def add o
88
+ @content << o
89
+ end
90
+
91
+ # Returns a list of filenames
92
+ def filenames
93
+ @content.map {|x| x.filename }
94
+ end
95
+
96
+ # Get the ContentObject pointing to the given filename.
97
+ # Raises ENOENT if not mapped.
98
+ def get_content_object filename
99
+ ret = @content.select {|x| filename.downcase == x.filename }
100
+ raise Errno::ENOENT,
101
+ "No ContentObject with the given filename found." if
102
+ ret.size == 0
103
+ ret[0]
104
+ end
105
+
106
+ # Get the contents of the given filename.
107
+ # Raises ENOENT if not mapped.
108
+ def get filename
109
+ get_content_object(filename.downcase).get
110
+ end
111
+ end
112
+
113
+ # A Container that directly wraps a directory (e.g. override/).
114
+ # Does not update on changes - caches the directory entries on initialize.
115
+ class DirectoryContainer < Container
116
+ def initialize path
117
+ super()
118
+ @path = path
119
+ Dir[path + "/*.*"].each {|x|
120
+ add_file x
121
+ }
122
+ end
123
+ end
124
+
125
+ # The resource manager, providing ordered access to Container objects.
126
+ class Manager
127
+ def initialize
128
+ @path = []
129
+ @_content_cache = nil
130
+ end
131
+
132
+ def add_container c
133
+ @path << c
134
+ end
135
+
136
+ # Get the ContentObject pointing to the given filename.
137
+ # Raises ENOENT if not mapped.
138
+ def get_content_object filename
139
+ @path.reverse.each {|con|
140
+ con.has?(filename) or next
141
+ return con.get_content_object(filename)
142
+ }
143
+ raise Errno::ENOENT, "No ContentObject with the given filename found."
144
+ end
145
+
146
+ # Get the contents of the given filename.
147
+ # Raises ENOENT if not mapped.
148
+ def get filename
149
+ get_content_object(filename).get
150
+ end
151
+
152
+ # Get a list of filenames contained inside.
153
+ def content
154
+ @_content_cache ||= @path.inject([]) {|a, x|
155
+ a |= x.filenames
156
+ }
157
+ end
158
+ end
159
+
160
+ Extensions = {
161
+ 'res' => 0,
162
+ 'bmp' => 1,
163
+ 'mve' => 2,
164
+ 'tga' => 3,
165
+ 'wav' => 4,
166
+ 'wfx' => 5,
167
+ 'plt' => 6,
168
+ 'ini' => 7,
169
+ 'mp3' => 8,
170
+ 'mpg' => 9,
171
+ 'txt' => 10,
172
+ 'plh' => 2000,
173
+ 'tex' => 2001,
174
+ 'mdl' => 2002,
175
+ 'thg' => 2003,
176
+ 'fnt' => 2005,
177
+ 'lua' => 2007,
178
+ 'slt' => 2008,
179
+ 'nss' => 2009,
180
+ 'ncs' => 2010,
181
+ 'mod' => 2011,
182
+ 'are' => 2012,
183
+ 'set' => 2013,
184
+ 'ifo' => 2014,
185
+ 'bic' => 2015,
186
+ 'wok' => 2016,
187
+ '2da' => 2017,
188
+ 'tlk' => 2018,
189
+ 'txi' => 2022,
190
+ 'git' => 2023,
191
+ 'bti' => 2024,
192
+ 'uti' => 2025,
193
+ 'btc' => 2026,
194
+ 'utc' => 2027,
195
+ 'dlg' => 2029,
196
+ 'itp' => 2030,
197
+ 'btt' => 2031,
198
+ 'utt' => 2032,
199
+ 'dds' => 2033,
200
+ 'bts' => 2034,
201
+ 'uts' => 2035,
202
+ 'ltr' => 2036,
203
+ 'gff' => 2037,
204
+ 'fac' => 2038,
205
+ 'bte' => 2039,
206
+ 'ute' => 2040,
207
+ 'btd' => 2041,
208
+ 'utd' => 2042,
209
+ 'btp' => 2043,
210
+ 'utp' => 2044,
211
+ 'dft' => 2045,
212
+ 'gic' => 2046,
213
+ 'gui' => 2047,
214
+ 'css' => 2048,
215
+ 'ccs' => 2049,
216
+ 'btm' => 2050,
217
+ 'utm' => 2051,
218
+ 'dwk' => 2052,
219
+ 'pwk' => 2053,
220
+ 'btg' => 2054,
221
+ 'utg' => 2055,
222
+ 'jrl' => 2056,
223
+ 'sav' => 2057,
224
+ 'utw' => 2058,
225
+ '4pc' => 2059,
226
+ 'ssf' => 2060,
227
+ 'hak' => 2061,
228
+ 'nwm' => 2062,
229
+ 'bik' => 2063,
230
+ 'ndb' => 2064,
231
+ 'ptm' => 2065,
232
+ 'ptt' => 2066,
233
+ 'bak' => 2067,
234
+ 'osc' => 3000,
235
+ 'usc' => 3001,
236
+ 'trn' => 3002,
237
+ 'utr' => 3003,
238
+ 'uen' => 3004,
239
+ 'ult' => 3005,
240
+ 'sef' => 3006,
241
+ 'pfx' => 3007,
242
+ 'cam' => 3008,
243
+ 'lfx' => 3009,
244
+ 'bfx' => 3010,
245
+ 'upe' => 3011,
246
+ 'ros' => 3012,
247
+ 'rst' => 3013,
248
+ 'ifx' => 3014,
249
+ 'pfb' => 3015,
250
+ 'zip' => 3016,
251
+ 'wmp' => 3017,
252
+ 'bbx' => 3018,
253
+ 'tfx' => 3019,
254
+ 'wlk' => 3020,
255
+ 'xml' => 3021,
256
+ 'scc' => 3022,
257
+ 'ptx' => 3033,
258
+ 'ltx' => 3034,
259
+ 'trx' => 3035,
260
+ 'mdb' => 4000,
261
+ 'mda' => 4001,
262
+ 'spt' => 4002,
263
+ 'gr2' => 4003,
264
+ 'fxa' => 4004,
265
+ 'fxe' => 4005,
266
+ 'jpg' => 4007,
267
+ 'pwc' => 4008,
268
+ 'ids' => 9996,
269
+ 'erf' => 9997,
270
+ 'bif' => 9998,
271
+ 'key' => 9999,
272
+ }.freeze
273
+
274
+ end
275
+ end