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/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
|