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/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/settings.rb
CHANGED
@@ -1,7 +1,3 @@
|
|
1
|
-
if ENV['NWN_LIB_INFER_DATA_FILE'] && ENV['NWN_LIB_INFER_DATA_FILE'] != ""
|
2
|
-
NWN::Gff.load_struct_defaults(ENV['NWN_LIB_INFER_DATA_FILE'])
|
3
|
-
end
|
4
|
-
|
5
1
|
if ENV['NWN_LIB_2DA_LOCATION'] && ENV['NWN_LIB_2DA_LOCATION'] != ""
|
6
2
|
NWN::TwoDA::Cache.setup ENV['NWN_LIB_2DA_LOCATION']
|
7
3
|
end
|
data/lib/nwn/tlk.rb
CHANGED
@@ -59,7 +59,7 @@ module NWN
|
|
59
59
|
|
60
60
|
return @cache[id] if @cache[id]
|
61
61
|
|
62
|
-
raise ArgumentError, "No such string ID: #{id.inspect}" if id
|
62
|
+
raise ArgumentError, "No such string ID: #{id.inspect}" if id > self.highest_id || id < 0
|
63
63
|
seek_to = HEADER_SIZE + (id) * DATA_ELEMENT_SIZE
|
64
64
|
@io.seek(seek_to)
|
65
65
|
data = @io.read(DATA_ELEMENT_SIZE)
|
data/lib/nwn/twoda.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'shellwords'
|
2
2
|
|
3
3
|
class Integer
|
4
|
+
# Returns the level that this amount experience resolves to.
|
5
|
+
# Depends on a set-up TwoDA::Cache, and reads from <tt>exptable</tt>.
|
4
6
|
def xp_to_level
|
5
7
|
NWN::TwoDA.get('exptable').rows.each {|row|
|
6
8
|
level, exp = row.Level.to_i, row.XP.to_i
|
@@ -9,6 +11,8 @@ class Integer
|
|
9
11
|
return nil
|
10
12
|
end
|
11
13
|
|
14
|
+
# Returns the amount of experience that this level resolves to.
|
15
|
+
# Depends on a set-up TwoDA::Cache, and reads from <tt>exptable</tt>.
|
12
16
|
def level_to_xp
|
13
17
|
NWN::TwoDA.get('exptable').by_col("XP", self - 1).to_i
|
14
18
|
end
|
@@ -97,11 +101,22 @@ module NWN
|
|
97
101
|
|
98
102
|
# Parses a string that represents a valid 2da definition.
|
99
103
|
# Replaces any content this table may already have.
|
104
|
+
# This will cope with all misformatting in the same way
|
105
|
+
# that NWN1 itself does. NWN2 employs slightly different
|
106
|
+
# parsing rules, and may or may not be compatible in the
|
107
|
+
# fringe cases.
|
108
|
+
#
|
109
|
+
# Will raise an ArgumentError if the given +bytes+ do
|
110
|
+
# not contain a valid 2DA header, or the file is so badly
|
111
|
+
# misshaped that it will not ever be parsed correctly by NWN1.
|
112
|
+
#
|
113
|
+
# Will complain about everything mismatching it finds
|
114
|
+
# on $stderr if $DEBUG is set (-d).
|
100
115
|
def parse bytes
|
101
116
|
magic, *data = *bytes.split(/\r?\n/).map {|v| v.strip }
|
102
117
|
|
103
|
-
raise ArgumentError, "Not valid 2da: No valid header found" if
|
104
|
-
magic
|
118
|
+
raise ArgumentError, "Not valid 2da: No valid header found (got: #{magic[0,20].inspect}..)" if
|
119
|
+
magic !~ /^2DA\s+V2.0$/
|
105
120
|
|
106
121
|
# strip all empty lines; they are regarded as comments
|
107
122
|
data.reject! {|ln| ln.strip == ""}
|
@@ -110,10 +125,7 @@ module NWN
|
|
110
125
|
|
111
126
|
header = Shellwords.shellwords(header.strip)
|
112
127
|
data.map! {|line|
|
113
|
-
|
114
|
-
# The last cell can be without double quotes even if it contains whitespace
|
115
|
-
r[header.size..-1] = r[header.size..-1].join(" ") if r.size > header.size
|
116
|
-
r
|
128
|
+
Shellwords.shellwords(line.strip)
|
117
129
|
}
|
118
130
|
|
119
131
|
new_row_data = []
|
@@ -121,24 +133,30 @@ module NWN
|
|
121
133
|
id_offset = 0
|
122
134
|
idx_offset = 0
|
123
135
|
data.each_with_index {|row, idx|
|
124
|
-
id = row.shift
|
136
|
+
id = row.shift
|
137
|
+
|
138
|
+
$stderr.puts "Warning: invalid ID in line #{idx}: #{id.inspect}" if $DEBUG && $id !~ /^\d+$/
|
125
139
|
|
126
|
-
|
140
|
+
id = id.to_i + id_offset
|
127
141
|
|
128
|
-
# Its an empty row -
|
142
|
+
# Its an empty row - NWN strictly numbers by counted lines - then so do we.
|
129
143
|
while id > idx + idx_offset
|
130
|
-
|
144
|
+
$stderr.puts "Warning: missing ID at #{id - id_offset}, fixing that for you." if $DEBUG
|
131
145
|
idx_offset += 1
|
132
146
|
end
|
133
147
|
|
134
148
|
# NWN automatically increments duplicate IDs - so do we.
|
135
|
-
while id
|
136
|
-
$stderr.puts "Warning: duplicate ID found at row #{idx} (id: #{id}); fixing that for you."
|
149
|
+
while id < idx + idx_offset
|
150
|
+
$stderr.puts "Warning: duplicate ID found at row #{idx} (id: #{id}); fixing that for you." if $DEBUG
|
137
151
|
id_offset += 1
|
138
152
|
id += 1
|
139
153
|
end
|
140
154
|
|
141
155
|
# NWN fills in missing columns with an empty value - so do we.
|
156
|
+
$stderr.puts "Warning: row #{id} (real: #{id - id_offset}) misses " +
|
157
|
+
"#{header.size - row.size} columns at the end, fixed" if
|
158
|
+
row.size < header.size if $DEBUG
|
159
|
+
|
142
160
|
row << "" while row.size < header.size
|
143
161
|
|
144
162
|
new_row_data << k_row = Row.new(row)
|
@@ -146,15 +164,16 @@ module NWN
|
|
146
164
|
|
147
165
|
k_row.map! {|cell|
|
148
166
|
cell = case cell
|
149
|
-
when nil; nil
|
167
|
+
when nil; raise "Bug in parser: nil-value for cell"
|
150
168
|
when "****"; ""
|
151
169
|
else cell
|
152
170
|
end
|
153
171
|
}
|
154
172
|
|
155
|
-
|
156
|
-
|
157
|
-
|
173
|
+
$stderr.puts "Warning: row #{idx} has too many cells (has #{k_row.size}, want <= #{header.size})" if
|
174
|
+
$DEBUG && k_row.size > header.size
|
175
|
+
|
176
|
+
k_row.pop while k_row.size > header.size
|
158
177
|
}
|
159
178
|
|
160
179
|
@columns = header
|
@@ -277,7 +296,7 @@ module NWN
|
|
277
296
|
when "2"
|
278
297
|
"\r"
|
279
298
|
when nil
|
280
|
-
@
|
299
|
+
@newline
|
281
300
|
end)
|
282
301
|
end
|
283
302
|
end
|
data/lib/nwn/yaml.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
# This file contains all YAML-specific loading and dumping code.
|
1
2
|
require 'yaml'
|
2
3
|
|
3
4
|
# See http://www.taguri.org/ for the exact meaning of this.
|
4
5
|
NWN::YAML_DOMAIN = "nwn-lib.elv.es,2008-12"
|
5
6
|
|
7
|
+
#:stopdoc:
|
6
8
|
class Array
|
7
9
|
attr_accessor :to_yaml_style
|
8
10
|
end
|
@@ -45,14 +47,10 @@ module NWN::Gff::Struct
|
|
45
47
|
}.size == 0
|
46
48
|
|
47
49
|
map.add('__' + 'data_type', @data_type) if @data_type
|
48
|
-
map.add('__' + 'data_version', @data_version) if @data_version &&
|
49
|
-
map.add('__' + 'struct_id', @struct_id) if @struct_id
|
50
|
+
map.add('__' + 'data_version', @data_version) if @data_version && @data_version != DEFAULT_DATA_VERSION
|
51
|
+
map.add('__' + 'struct_id', @struct_id) if @struct_id
|
50
52
|
|
51
|
-
|
52
|
-
# Dont emit fields that would result in their default values anyways.
|
53
|
-
ENV['NWN_LIB_CLEAR_KNOWN_VALUES'] && v.can_infer_type? &&
|
54
|
-
v.can_infer_str_ref? && v.can_infer_value?
|
55
|
-
}.sort.each {|k,v|
|
53
|
+
sort.each {|k,v|
|
56
54
|
map.add(k,v)
|
57
55
|
}
|
58
56
|
end
|
@@ -63,57 +61,12 @@ end
|
|
63
61
|
module NWN::Gff::Field
|
64
62
|
|
65
63
|
def to_yaml(opts = {})
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
calf = get_compact_as_list_field
|
74
|
-
case calf
|
75
|
-
when Array
|
76
|
-
style = NWN::Gff.get_struct_defaults_for(item.path, '__inline')
|
77
|
-
|
78
|
-
non_inferrable = (item.keys.reject {|x|
|
79
|
-
item[x].can_infer_type? && item[x].can_infer_value?
|
80
|
-
} - calf).sort
|
81
|
-
raise NWN::Gff::GffError, "cannot compact list struct at #{item.path}, would " +
|
82
|
-
"LOSE fields: #{non_inferrable.inspect}" if non_inferrable.size > 0
|
83
|
-
|
84
|
-
ar = calf.map {|ik| item[ik] || NWN::Gff.get_struct_default_value(item.path, ik) }
|
85
|
-
missing = ar.map {|x| x.nil? ? calf[ar.index(x)] : nil }.compact
|
86
|
-
|
87
|
-
raise NWN::Gff::GffError, "cannot compact list-struct at #{item.path}, does not " +
|
88
|
-
"have all compactable field values set or are inferrable (missing fields: #{missing.inspect})." if
|
89
|
-
missing.size > 0
|
90
|
-
|
91
|
-
ar.to_yaml_style = :inline if style
|
92
|
-
|
93
|
-
ar.unshift(item.struct_id) if struct_id_of_item == 'inline'
|
94
|
-
seq.add(ar)
|
95
|
-
else
|
96
|
-
isv = NWN::Gff.get_struct_defaults_for(self.path, '__inline')
|
97
|
-
seq.style = :inline if isv.nil? || isv === true
|
98
|
-
|
99
|
-
seq.add(item.struct_id) if struct_id_of_item == 'inline'
|
100
|
-
seq.add(item[calf])
|
101
|
-
end
|
102
|
-
}
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
elsif !ENV['NWN_LIB_DONT_COMPACT_FIELDS'] && can_compact_print?
|
107
|
-
field_value_as_compact.to_yaml(opts)
|
108
|
-
|
109
|
-
else
|
110
|
-
YAML::quick_emit(nil, opts) do |out|
|
111
|
-
out.map(taguri, to_yaml_style) do |map|
|
112
|
-
map.style = :inline unless NWN::Gff::YAMLNonInlineableFields.index(self['type'])
|
113
|
-
map.add('type', self['type']) unless can_infer_type?
|
114
|
-
map.add('str_ref', self['str_ref']) unless can_infer_str_ref?
|
115
|
-
map.add('value', self['value']) unless can_infer_value?
|
116
|
-
end
|
64
|
+
YAML::quick_emit(nil, opts) do |out|
|
65
|
+
out.map(taguri, to_yaml_style) do |map|
|
66
|
+
map.style = :inline unless NWN::Gff::YAMLNonInlineableFields.index(self['type'])
|
67
|
+
map.add('type', self['type'])
|
68
|
+
map.add('str_ref', self['str_ref']) if has_str_ref?
|
69
|
+
map.add('value', self['value'])
|
117
70
|
end
|
118
71
|
end
|
119
72
|
end
|
@@ -130,159 +83,21 @@ YAML.add_domain_type(NWN::YAML_DOMAIN,'struct') {|t,hash|
|
|
130
83
|
struct.data_version = hash.delete('__data_version')
|
131
84
|
struct.data_version ||= NWN::Gff::Struct::DEFAULT_DATA_VERSION
|
132
85
|
|
133
|
-
|
134
|
-
struct.struct_id = s_id.to_i
|
135
|
-
elsif struct.struct_id.nil?
|
136
|
-
raise NWN::Gff::GffError, "Cannot infer implicit struct_id for struct at #{struct.path}."
|
137
|
-
end
|
86
|
+
raise NWN::Gff::GffError, "no struct_id set for struct at #{struct.path}." if struct.struct_id.nil?
|
138
87
|
|
139
88
|
hash.each {|label,element|
|
140
89
|
label.freeze
|
141
|
-
element = case element
|
142
|
-
when Hash # already uncompacted or a compacted exolocstr
|
143
|
-
default_type = NWN::Gff.get_struct_default_type(struct.path, label)
|
144
|
-
raise NWN::Gff::GffError,
|
145
|
-
"Cannot parse compacted hash with no contents and no infer data at #{struct.path}/#{label}." if
|
146
|
-
element.size == 0 && default_type == nil
|
147
|
-
|
148
|
-
# It has only got numbers as key, we *assume* its a cexoloc.
|
149
|
-
# Thats okay, because type inferration will catch it later and bite us. Hopefully.
|
150
|
-
if element.keys.select {|x| x.to_s !~ /^(str_ref|\d+)$/}.size == 0
|
151
|
-
element = {
|
152
|
-
'type' => :cexolocstr,
|
153
|
-
'str_ref' => element.delete('str_ref') || NWN::Gff::Field::DEFAULT_STR_REF,
|
154
|
-
'value' => element,
|
155
|
-
}
|
156
|
-
end
|
157
|
-
|
158
|
-
element
|
159
|
-
|
160
|
-
when Array # compacted struct-list
|
161
|
-
element = {
|
162
|
-
'type' => :list,
|
163
|
-
'value' => element,
|
164
|
-
}
|
165
|
-
path = struct.data_type + "/" + label
|
166
|
-
unpack_struct_element = NWN::Gff.get_struct_defaults_for(path, '__compact')
|
167
|
-
if unpack_struct_element
|
168
|
-
# If it doesn't have unpack data, we need to assume its a compacted list itself.
|
169
|
-
# Sorry.
|
170
|
-
# Hope this wont bite anyone later on.
|
171
|
-
|
172
|
-
#raise NWN::Gff::GffError,
|
173
|
-
# "Cannot unpack compacted struct list at #{path}, no infer data available." unless
|
174
|
-
# unpack_struct_element
|
175
|
-
|
176
|
-
unpack_struct_element_struct_id = NWN::Gff.get_struct_defaults_for(path, "__struct_id")
|
177
|
-
|
178
|
-
unpack_struct_elements = [unpack_struct_element].flatten.freeze
|
179
|
-
|
180
|
-
unpack_struct_element_types = unpack_struct_elements.map {|unpack_struct_element|
|
181
|
-
raise NWN::Gff::GffError, "While unpacking #{path}: " +
|
182
|
-
"#{unpack_struct_element} is not a field-naime, dummy." unless
|
183
|
-
unpack_struct_element.is_a?(String)
|
184
|
-
|
185
|
-
unpack_struct_element_type =
|
186
|
-
NWN::Gff.get_struct_default_type(path, unpack_struct_element)
|
187
|
-
|
188
|
-
unpack_struct_element_type
|
189
|
-
}.freeze
|
190
|
-
|
191
|
-
last_struct_id = -1
|
192
|
-
element['value'].map! {|kv|
|
193
|
-
st = {}
|
194
|
-
st.extend(NWN::Gff::Struct)
|
195
|
-
st.struct_id = case unpack_struct_element_struct_id
|
196
|
-
when 'iterative'
|
197
|
-
last_struct_id += 1
|
198
|
-
when 'inline'
|
199
|
-
kv.shift
|
200
|
-
when Fixnum
|
201
|
-
unpack_struct_element_struct_id
|
202
|
-
else
|
203
|
-
kv['__struct_id'] || raise(NWN::Gff::GffError,
|
204
|
-
"dont know how to handle struct_id #{unpack_struct_element_struct_id} at #{label} = #{kv.inspect}")
|
205
|
-
end
|
206
|
-
st.data_type = path
|
207
|
-
|
208
|
-
unpack_struct_elements.each_with_index {|use, index|
|
209
|
-
case kv
|
210
|
-
when Hash
|
211
|
-
if !kv[use]
|
212
|
-
uset, usev = unpack_struct_element_types[index],
|
213
|
-
NWN::Gff.get_struct_default_value(path, use)
|
214
|
-
else
|
215
|
-
uset = kv[use]['type']
|
216
|
-
usev = kv[use]['value']
|
217
|
-
end
|
218
|
-
when Array
|
219
|
-
uset = unpack_struct_element_types[index]
|
220
|
-
usev = kv[index]
|
221
|
-
if usev.is_a?(Hash)
|
222
|
-
uset, usev = usev['type'], usev['value']
|
223
|
-
end
|
224
|
-
when String, Numeric, Symbol
|
225
|
-
uset = unpack_struct_element_types[index]
|
226
|
-
usev = kv
|
227
|
-
else
|
228
|
-
raise "Dont know how to unpack list-struct element: #{kv.inspect}"
|
229
|
-
end
|
230
|
-
|
231
|
-
raise NWN::Gff::GffError, "Cannot infer type of #{path}/#{use} (got: #{uset.inspect})" unless
|
232
|
-
uset && NWN::Gff::Types.index(uset)
|
233
|
-
raise NWN::Gff::GffError, "Cannot get or infer value of #{path}/#{use} (got: #{usev.inspect})" unless
|
234
|
-
usev && (usev.is_a?(String) || usev.is_a?(Numeric) || usev.is_a?(Symbol))
|
235
|
-
|
236
|
-
# Figure out the default value (which we'll need to re-add?) if
|
237
|
-
# it was filtered while outputting.
|
238
|
-
#kv[index] = NWN::Gff.get_struct_default_value(path, use) if !kv[index]
|
239
|
-
#raise NWN::Gff::GffError, "field present in compacted struct-list, and cant " +
|
240
|
-
# "infer default value at #{use}/#{index} (#{kv.inspect})" if !kv[index]
|
241
|
-
|
242
|
-
el = st[use] = {
|
243
|
-
'label' => use,
|
244
|
-
'type' => uset,
|
245
|
-
'value' => usev
|
246
|
-
}
|
247
|
-
el.extend(NWN::Gff::Field)
|
248
|
-
el.extend_meta_classes
|
249
|
-
el.parent = st
|
250
|
-
}
|
251
|
-
st
|
252
|
-
}
|
253
|
-
end
|
254
|
-
element
|
255
|
-
|
256
|
-
when Numeric, String # compacted scalar
|
257
|
-
{'value' => element}
|
258
|
-
|
259
|
-
else
|
260
|
-
fail "Don't know how to un-compact /#{label}: #{element.inspect}, klass #{element.class.to_s}"
|
261
|
-
end
|
262
90
|
|
263
91
|
element.extend(NWN::Gff::Field)
|
264
92
|
element.field_label = label
|
265
93
|
element.parent = struct
|
266
94
|
element.str_ref ||= NWN::Gff::Field::DEFAULT_STR_REF if element.respond_to?('str_ref=')
|
267
95
|
|
268
|
-
infer_field_type = NWN::Gff.get_struct_default_type(struct.path, element.field_label)
|
269
|
-
|
270
|
-
if element.field_type && infer_field_type && infer_field_type != element.field_type
|
271
|
-
raise NWN::Gff::GffError, "/#{label} has field_type #{element.field_type.inspect}, but infer data says #{infer_field_type.inspect}."
|
272
|
-
|
273
|
-
elsif element.field_type.nil? && infer_field_type.nil?
|
274
|
-
raise NWN::Gff::GffError, "Cannot infer implicit type for /#{label} while parsing struct-id #{struct.struct_id}."
|
275
|
-
|
276
|
-
elsif element.field_type.nil? && infer_field_type
|
277
|
-
element.field_type = infer_field_type
|
278
|
-
end
|
279
|
-
|
280
|
-
|
281
96
|
element.extend_meta_classes
|
97
|
+
element.validate
|
282
98
|
|
283
99
|
struct[label] = element.taint
|
284
100
|
}
|
285
101
|
|
286
|
-
NWN::Gff.__yaml_postparse nil, struct if struct.data_type
|
287
102
|
struct
|
288
103
|
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
describe "Gff::Cexolocstr" do
|
4
|
+
|
5
|
+
it "should compact nil and empty strings" do
|
6
|
+
str = Gff::Field.new('Test', :cexolocstr, {})
|
7
|
+
str.v[0] = "hi"
|
8
|
+
str.v[1] = nil
|
9
|
+
str.v[4] = ""
|
10
|
+
str.v[8] = "test"
|
11
|
+
str.v.compact!
|
12
|
+
str.v.should == {0=>"hi", 8=>"test"}
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
data/spec/erf_spec.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
WELLFORMED_ERF = ([
|
4
|
+
"HAK", "V1.0",
|
5
|
+
locstr_count = 1, locstr_size = 14,
|
6
|
+
entry_count = 3,
|
7
|
+
offset_to_locstr = 160,
|
8
|
+
offset_to_keys = offset_to_locstr + locstr_size,
|
9
|
+
offset_to_res = offset_to_locstr + locstr_size + entry_count * 24,
|
10
|
+
|
11
|
+
100, 126, # year, dayofyear
|
12
|
+
0xdeadbeef, "" #description strref, 116 bytes 0-padding
|
13
|
+
].pack("a4 a4 VV VV VV VV V a116") + [
|
14
|
+
0, 6, "abcdef" # one locstr
|
15
|
+
].pack("V V a*") + [
|
16
|
+
"resref", 0, 10, 0, # keylist: resref.txt, id = 0
|
17
|
+
"help", 1, 1, 0, # keylist: help.bmp, id = 1
|
18
|
+
"yegods", 2, 4, 0, # keylist: yegods.wav, id = 2
|
19
|
+
].pack("a16 V v v" * entry_count) + [
|
20
|
+
offset_to_res + entry_count * 8, 6, # offset, size
|
21
|
+
offset_to_res + entry_count * 8 + 6, 4, # offset, size
|
22
|
+
offset_to_res + entry_count * 8 + 6 + 4, 6, # offset, size
|
23
|
+
].pack("II" * entry_count) + [
|
24
|
+
"resref", "help", "yegods"
|
25
|
+
].pack("a* a* a*")).freeze
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
describe "Erf::Erf" do
|
30
|
+
|
31
|
+
def wellformed_verify binary, expect_locstr = true
|
32
|
+
t = nil
|
33
|
+
t = Erf::Erf.new(StringIO.new binary)
|
34
|
+
|
35
|
+
t.file_type.should == "HAK"
|
36
|
+
t.file_version.should == "V1.0"
|
37
|
+
if expect_locstr
|
38
|
+
t.localized_strings.should == {0 => "abcdef"}
|
39
|
+
else
|
40
|
+
t.localized_strings.should == {}
|
41
|
+
end
|
42
|
+
t.content.size.should == 3
|
43
|
+
t.year.should == 100
|
44
|
+
t.day_of_year.should == 126
|
45
|
+
t.description_str_ref.should == 0xdeadbeef
|
46
|
+
|
47
|
+
t.content[0].filename.should == "resref.txt"
|
48
|
+
t.content[1].filename.should == "help.bmp"
|
49
|
+
t.content[2].filename.should == "yegods.wav"
|
50
|
+
t.content[0].size.should == 6
|
51
|
+
t.content[1].size.should == 4
|
52
|
+
t.content[2].size.should == 6
|
53
|
+
t.content[0].get.should == "resref"
|
54
|
+
t.content[1].get.should == "help"
|
55
|
+
t.content[2].get.should == "yegods"
|
56
|
+
end
|
57
|
+
|
58
|
+
it "reads wellformed ERF containers" do
|
59
|
+
wellformed_verify WELLFORMED_ERF
|
60
|
+
end
|
61
|
+
|
62
|
+
it "reproduces correct ERF binary data" do
|
63
|
+
t = Erf::Erf.new(StringIO.new WELLFORMED_ERF)
|
64
|
+
io = StringIO.new
|
65
|
+
t.write_to(io)
|
66
|
+
io.seek(0)
|
67
|
+
proc {
|
68
|
+
wellformed_verify io.read
|
69
|
+
}.should_not raise_error IOError
|
70
|
+
end
|
71
|
+
|
72
|
+
it "does not read ERF with locstr_size = 0 and locstr_count > 0" do
|
73
|
+
b = WELLFORMED_ERF.dup
|
74
|
+
b[12,4] = [0].pack("V")
|
75
|
+
proc {
|
76
|
+
wellformed_verify b
|
77
|
+
}.should raise_error IOError
|
78
|
+
end
|
79
|
+
|
80
|
+
it "reads ERF with locstr_size > 0 and locstr_count = 0" do
|
81
|
+
b = WELLFORMED_ERF.dup
|
82
|
+
b[8,4] = [0].pack("V")
|
83
|
+
proc {
|
84
|
+
wellformed_verify b, false
|
85
|
+
}.should_not raise_error IOError
|
86
|
+
end
|
87
|
+
end
|