nwn-lib 0.4.5 → 0.4.6
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 +1 -1
- data/CHANGELOG +39 -1
- data/CHEATSHEET +20 -6
- data/README +29 -12
- data/Rakefile +3 -3
- data/SETTINGS +3 -69
- data/bin/nwn-dsl +2 -7
- data/lib/nwn/all.rb +1 -0
- data/lib/nwn/erf.rb +19 -192
- data/lib/nwn/gff.rb +0 -1
- data/lib/nwn/gff/field.rb +53 -14
- data/lib/nwn/gff/reader.rb +2 -2
- data/lib/nwn/gff/struct.rb +27 -9
- data/lib/nwn/res.rb +275 -0
- data/lib/nwn/settings.rb +0 -4
- data/lib/nwn/tlk.rb +1 -1
- data/lib/nwn/twoda.rb +36 -17
- data/lib/nwn/yaml.rb +13 -198
- data/spec/cexolocstr_spec.rb +15 -0
- data/spec/erf_spec.rb +87 -0
- data/spec/field_spec.rb +21 -0
- data/spec/gff_spec.rb +22 -0
- data/spec/res_spec.rb +59 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/struct_spec.rb +61 -0
- data/spec/tlk_spec.rb +47 -0
- data/spec/twoda_spec.rb +146 -0
- data/spec/wellformed_gff.binary +0 -0
- metadata +13 -8
- data/DATA_STRUCTURES +0 -50
- data/TYPE_VALUE_INFERRING +0 -93
- data/data/gff-common-nwn1.yaml +0 -982
- data/lib/nwn/infer.rb +0 -125
data/lib/nwn/gff.rb
CHANGED
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
|
-
|
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 :
|
90
|
-
value.is_a?(
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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) && (
|
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
|
-
|
106
|
-
value.is_a?(Array)
|
144
|
+
|
107
145
|
when :void
|
108
|
-
|
146
|
+
value.is_a?(String)
|
147
|
+
|
109
148
|
else
|
110
149
|
false
|
111
150
|
end
|
data/lib/nwn/gff/reader.rb
CHANGED
@@ -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?}
|
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
|
data/lib/nwn/gff/struct.rb
CHANGED
@@ -3,16 +3,21 @@
|
|
3
3
|
module NWN::Gff::Struct
|
4
4
|
DEFAULT_DATA_VERSION = "V3.2"
|
5
5
|
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
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
|
-
|
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
|