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,88 @@
1
+
2
+ module NWN::Gff::Field
3
+ #:stopdoc:
4
+ # Used by NWN::Gff::Struct#by_flat_path
5
+ def each_by_flat_path &block
6
+ case field_type
7
+ when :cexolocstr
8
+ yield("", self)
9
+ field_value.sort.each {|lid, str|
10
+ yield("/" + lid.to_s, str)
11
+ }
12
+
13
+ when :struct
14
+ yield("", self)
15
+ field_value.each_by_flat_path {|v, x|
16
+ yield(v, x)
17
+ }
18
+
19
+ when :list
20
+ yield("", self)
21
+ field_value.each_with_index {|item, index|
22
+ yield("[" + index.to_s + "]", item)
23
+ item.each_by_flat_path("/") {|v, x|
24
+ yield("[" + index.to_s + "]" + v, x)
25
+ }
26
+ }
27
+
28
+ else
29
+ yield("", self)
30
+ end
31
+ end
32
+ #:startdoc:
33
+ end
34
+
35
+ module NWN::Gff::Struct
36
+
37
+ # Iterates this struct, yielding flat, absolute
38
+ # pathes and the Gff::Field for each element found.
39
+
40
+ # Example:
41
+ # "/AddCost" => {"type"=>:dword, ..}
42
+ def each_by_flat_path prefix = "/", &block
43
+ sort.each {|label, field|
44
+ field.each_by_flat_path do |ll, lv|
45
+ yield(prefix + label + ll, lv)
46
+ end
47
+ }
48
+ end
49
+
50
+ # Retrieve an object from within the given tree.
51
+ # Path is a slash-separated destination, given as
52
+ # a string
53
+ #
54
+ # Prefixed/postfixed slashes are optional.
55
+ #
56
+ # Examples:
57
+ # /
58
+ # /AddCost
59
+ # /PropertiesList/
60
+ # /PropertiesList[0]/CostValue
61
+ def by_path path
62
+ struct = self
63
+ current_path = ""
64
+ path = path.split('/').map {|v| v.strip }.reject {|v| v.empty?}.join('/')
65
+
66
+ path.split('/').each {|v|
67
+ if v =~ /^(.+?)\[(\d+)\]$/
68
+ v, index = $1, $2
69
+ end
70
+
71
+ struct = struct[v]
72
+ if index
73
+ struct.field_type == :list or raise NWN::Gff::GffPathInvalidError,
74
+ "Specified a list offset for a non-list item: #{v}[#{index}]."
75
+
76
+ struct = struct.field_value[index.to_i]
77
+ end
78
+
79
+ raise NWN::Gff::GffPathInvalidError,
80
+ "Cannot find a path to /#{path} (at: /#{current_path})." unless struct
81
+
82
+ current_path += v
83
+ }
84
+
85
+ struct
86
+ end
87
+
88
+ end
@@ -0,0 +1,28 @@
1
+ module NWN::Gff::Field
2
+ def has_str_ref?
3
+ false
4
+ end
5
+ end
6
+
7
+ module NWN::Gff::Cexolocstr
8
+ DEFAULT_STR_REF = 0xffffffff
9
+
10
+ def str_ref
11
+ self['str_ref'] || DEFAULT_STR_REF
12
+ end
13
+ def str_ref= s
14
+ self['str_ref'] = s.to_i
15
+ end
16
+ def has_str_ref?
17
+ str_ref != DEFAULT_STR_REF
18
+ end
19
+ end
20
+
21
+ module NWN::Gff::CexolocstrValue
22
+ # Removes all nil and empty strings.
23
+ def compact!
24
+ self.each {|lid,str|
25
+ self.delete(lid) if str.nil? || str.empty?
26
+ }
27
+ end
28
+ end
@@ -0,0 +1,105 @@
1
+ # A Field wraps a GFF label->value pair, providing:
2
+ # * +.field_type+ describing the field type (e.g. :int)
3
+ # * +.field_value+ holding the value of this Field
4
+
5
+ # and, if loaded by Gff::Reader or through YAML:
6
+ # * +.field_label+ holding the label
7
+ # * +.parent+ holding the struct this Field is child of.
8
+ #
9
+ # Note that it is ADVISED to use the provided accessors,
10
+ # since they do some structure-keeping in the background.
11
+ # If you do NOT want it to do that, use hash-notation for access:
12
+ #
13
+ # field['value'], field['type'], field['str_ref'], field['label']
14
+ module NWN::Gff::Field
15
+ # The parent struct.
16
+ # This is set internally by Gff::Reader on load.
17
+ attr_accessor :parent
18
+
19
+ def field_type
20
+ self['type']
21
+ end
22
+ alias :t :field_type
23
+
24
+ def field_type= t
25
+ self['type'] = t
26
+ end
27
+ alias :t= :field_type=
28
+
29
+ def field_value
30
+ self['value']
31
+ end
32
+ alias :v :field_value
33
+
34
+ def field_value= v
35
+ Field.valid_for?(v, field_type) or raise ArgumentError,
36
+ "Given field_value is not valid for type #{field_type.inspect}."
37
+
38
+ self['value'] = v
39
+ end
40
+ alias :v= :field_value=
41
+
42
+ def field_label
43
+ self['label']
44
+ end
45
+ alias :l :field_label
46
+
47
+ def field_label= l
48
+ self['label']= l
49
+ end
50
+ alias :l= :field_label=
51
+
52
+ # Returns the path to this field, including all parents structs.
53
+ # For example: UTI/PropertiesList/CostTable
54
+ def path
55
+ raise NWN::Gff::GffError, "field not bound to a parent" unless @parent
56
+ parent_path = @parent.path
57
+ parent_path + "/" + field_label
58
+ end
59
+
60
+ # This extends this field object and its' value with the
61
+ # appropriate meta classes, depending on field_type.
62
+ def extend_meta_classes
63
+ return if field_type == :struct
64
+
65
+ field_klass_name = field_type.to_s.capitalize
66
+ field_klass = NWN::Gff.const_defined?(field_klass_name) ?
67
+ NWN::Gff.const_get(field_klass_name) : nil
68
+ field_value_klass = NWN::Gff.const_defined?(field_klass_name + 'Value') ?
69
+ NWN::Gff.const_get(field_klass_name + 'Value') : nil
70
+
71
+ self.extend(field_klass) unless field_klass.nil? ||
72
+ self.is_a?(field_klass)
73
+
74
+ field_value.extend(field_value_klass) unless field_value_klass.nil? ||
75
+ field_value.is_a?(field_value_klass)
76
+ end
77
+
78
+ # Validate if +value+ is within bounds of +type+.
79
+ def self.valid_for? value, type
80
+ case type
81
+ when :char, :byte
82
+ value.is_a?(Fixnum)
83
+ when :short, :word
84
+ value.is_a?(Fixnum)
85
+ when :int, :dword
86
+ value.is_a?(Fixnum)
87
+ when :int64, :dword64
88
+ value.is_a?(Fixnum)
89
+ when :float, :double
90
+ value.is_a?(Float)
91
+ when :resref
92
+ value.is_a?(String) && (1..16).member?(value.size)
93
+ when :cexostr
94
+ value.is_a?(String)
95
+ when :cexolocstr
96
+ value.is_a?(Array)
97
+ when :struct, :list
98
+ value.is_a?(Array)
99
+ when :void
100
+ true
101
+ else
102
+ false
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,2 @@
1
+ module NWN::Gff::List
2
+ end
@@ -0,0 +1,220 @@
1
+ # A class that parses binary GFF bytes into ruby-friendly data structures.
2
+ class NWN::Gff::Reader
3
+ include NWN::Gff
4
+
5
+ attr_reader :root_struct
6
+
7
+ # Create a new Reader with the given +bytes+ and immediately parse it.
8
+ # This is not needed usually; use Reader.read instead.
9
+ def initialize bytes
10
+ @bytes = bytes
11
+
12
+ read_all
13
+ end
14
+
15
+ # Reads +bytes+ as gff data and returns a NWN::Gff:Gff object.
16
+ def self.read bytes
17
+ self.new(bytes).root_struct
18
+ end
19
+
20
+ private
21
+
22
+ def read_all
23
+ type, version,
24
+ struct_offset, struct_count,
25
+ field_offset, field_count,
26
+ label_offset, label_count,
27
+ field_data_offset, field_data_count,
28
+ field_indices_offset, field_indices_count,
29
+ list_indices_offset, list_indices_count =
30
+ @bytes.unpack("a4a4 VV VV VV VV VV VV")
31
+
32
+ raise GffError, "Unknown version #{@version}; not a gff?" unless
33
+ version == "V3.2"
34
+
35
+ raise GffError, "struct offset at wrong place, not a gff?" unless
36
+ struct_offset == 56
37
+
38
+ struct_len = struct_count * 12
39
+ field_len = field_count * 16
40
+ label_len = label_count * 16
41
+
42
+ @structs = @bytes[struct_offset, struct_len].unpack("V*")
43
+ @fields = @bytes[field_offset, field_len].unpack("V*")
44
+ @labels = @bytes[label_offset, label_len].unpack("A16" * label_count)
45
+ @field_data = @bytes[field_data_offset, field_data_count]
46
+ @field_indices = @bytes[field_indices_offset, field_indices_count].unpack("V*")
47
+ @list_indices = @bytes[list_indices_offset, list_indices_count].unpack("V*")
48
+ @root_struct = read_struct 0, type.strip, version
49
+ end
50
+
51
+ # This iterates through a struct and reads all fields into a hash, which it returns.
52
+ def read_struct index, file_type = nil, file_version = nil
53
+ struct = {}.taint
54
+ struct.extend(NWN::Gff::Struct)
55
+
56
+ type = @structs[index * 3]
57
+ data_or_offset = @structs[index * 3 + 1]
58
+ count = @structs[index * 3 + 2]
59
+
60
+ raise GffError, "struct index #{index} outside of struct_array" if
61
+ index * 3 + 3 > @structs.size + 1
62
+
63
+ struct.struct_id = type
64
+ struct.data_type = file_type
65
+ struct.data_version = file_version
66
+
67
+ if count == 1
68
+ lbl, vl = * read_field(data_or_offset, struct)
69
+ struct[lbl] = vl
70
+ else
71
+ if count > 0
72
+ raise GffError, "struct index not divisable by 4" if
73
+ data_or_offset % 4 != 0
74
+ data_or_offset /= 4
75
+ for i in data_or_offset...(data_or_offset+count)
76
+ lbl, vl = * read_field(@field_indices[i], struct)
77
+ struct[lbl] = vl
78
+ end
79
+ end
80
+ end
81
+
82
+ struct
83
+ end
84
+
85
+ # Reads the field at +index+ and returns [label_name, Gff::Field]
86
+ def read_field index, parent_of
87
+ gff = {}
88
+
89
+ field = {}.taint
90
+ field.extend(NWN::Gff::Field)
91
+
92
+ index *= 3
93
+ type = @fields[index]
94
+ label_index = @fields[index + 1]
95
+ data_or_offset = @fields[index + 2]
96
+
97
+ raise GffError, "Label index #{label_index} outside of label array" if
98
+ label_index > @labels.size
99
+
100
+ label = @labels[label_index]
101
+
102
+ raise GffError, "Unknown field type #{type}." unless Types[type]
103
+ type = Types[type]
104
+
105
+ raise GffError, "Field '#{label}' (type: #{type} )data offset #{data_or_offset} outside of field data block (#{@field_data.size})" if
106
+ ComplexTypes.index(type) && data_or_offset > @field_data.size
107
+
108
+ field['type'] = type
109
+ field['label'] = label
110
+ field.parent = parent_of
111
+
112
+ value = case type
113
+ when :byte, :char
114
+ data_or_offset & 0xff
115
+
116
+ when :word
117
+ data_or_offset & 0xffff
118
+
119
+ when :short
120
+ [(data_or_offset & 0xffff)].pack("S").unpack("s")[0]
121
+
122
+ when :dword
123
+ data_or_offset
124
+
125
+ when :int
126
+ [data_or_offset].pack("I").unpack("i")[0]
127
+
128
+ when :float
129
+ [data_or_offset].pack("V").unpack("f")[0]
130
+
131
+ when :dword64
132
+ len = 8
133
+ v1, v2 = @field_data[data_or_offset, len].unpack("II")
134
+ v1 * (2**32) + v2
135
+
136
+ when :int64
137
+ len = 8
138
+ @field_data[data_or_offset, len].unpack("q")[0]
139
+
140
+ when :double
141
+ len = 8
142
+ @field_data[data_or_offset, len].unpack("d")[0]
143
+
144
+ when :cexostr
145
+ len = @field_data[data_or_offset, 4].unpack("V")[0]
146
+ @field_data[data_or_offset + 4, len]
147
+
148
+ when :resref
149
+ len = @field_data[data_or_offset, 1].unpack("C")[0]
150
+ @field_data[data_or_offset + 1, len]
151
+
152
+ when :cexolocstr
153
+ exostr = {}
154
+
155
+ total_size, str_ref, str_count =
156
+ @field_data[data_or_offset, 12].unpack("VVV")
157
+ all = @field_data[data_or_offset + 12, total_size]
158
+ field.extend(NWN::Gff::Cexolocstr)
159
+ field.str_ref = str_ref
160
+
161
+ str_count.times {
162
+ id, len = all.unpack("VV")
163
+ str = all[8, len].unpack("a*")[0]
164
+ all = all[(8 + len)..-1]
165
+ exostr[id] = str
166
+ }
167
+ len = total_size + 4
168
+ # Filter out empty strings.
169
+ exostr.reject! {|k,v| v.nil? || v.empty?} if
170
+ ENV['NWN_LIB_FILTER_EMPTY_EXOLOCSTR']
171
+ exostr.taint
172
+
173
+ when :void
174
+ len = @field_data[data_or_offset, 4].unpack("V")[0]
175
+ @field_data[data_or_offset + 4, len].unpack("H*")[0]
176
+
177
+ when :struct
178
+ read_struct data_or_offset, field.path, field.parent.data_version
179
+
180
+ when :list
181
+ list = []
182
+
183
+ raise GffError, "List index not divisable by 4" unless
184
+ data_or_offset % 4 == 0
185
+
186
+ data_or_offset /= 4
187
+
188
+ raise GffError, "List index outside list indices" if
189
+ data_or_offset > @list_indices.size
190
+
191
+ count = @list_indices[data_or_offset]
192
+
193
+ raise GffError, "List index overflow the list indices array" if
194
+ data_or_offset + count > @list_indices.size
195
+
196
+ data_or_offset += 1
197
+
198
+ for i in data_or_offset...(data_or_offset + count)
199
+ list << read_struct(@list_indices[i], field.path, field.parent.data_version)
200
+ end
201
+
202
+ list.taint
203
+
204
+ end
205
+
206
+ raise GffError, "Field data overflows from the field data block area\
207
+ offset = #{data_or_offset + len}, len = #{@field_data.size}" if
208
+ len && data_or_offset + len > @field_data.size
209
+
210
+ [value].compact.flatten.each {|iv|
211
+ iv.element = field if iv.respond_to?('element=')
212
+ }
213
+ field['value'] = value.taint
214
+
215
+ # We extend all fields and field_values with matching classes.
216
+ field.extend_meta_classes
217
+
218
+ [label, field]
219
+ end
220
+ end
@@ -0,0 +1,34 @@
1
+ # A Gff::Struct is a hash of label->Element pairs with some
2
+ # meta-information in local variables.
3
+ module NWN::Gff::Struct
4
+ DEFAULT_DATA_VERSION = "V3.2"
5
+
6
+ # The file-type this struct represents.
7
+ # This is usually the file extension for root structs,
8
+ # and nil for sub-structs.
9
+ attr_accessor :data_type
10
+
11
+ # The file version. Usually "V3.2" for root structs,
12
+ # and nil for sub-structs.
13
+ attr_accessor :data_version
14
+
15
+ # GFF struct type
16
+ attr_accessor :struct_id
17
+
18
+ # The field this struct is value of.
19
+ # It is most likely a Field of :list, or
20
+ # :nil if it is the root struct.
21
+ attr_accessor :element
22
+
23
+ # Returns the path to this struct (which is usually __data_type)
24
+ def path
25
+ @data_type.to_s
26
+ end
27
+
28
+ # Dump this struct as GFF binary data.
29
+ #
30
+ # Optionally specify data_type and data_version
31
+ def to_gff data_type = nil
32
+ NWN::Gff::Writer.dump(self, data_type)
33
+ end
34
+ end