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/gff/api.rb
ADDED
@@ -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
|
data/lib/nwn/gff/list.rb
ADDED
@@ -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
|