elder_scrolls_plugin 0.0.1
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.
- checksums.yaml +7 -0
- data/bin/esp_dump +110 -0
- data/lib/elder_scrolls_plugin.rb +914 -0
- data/lib/elder_scrolls_plugin/version.rb +5 -0
- data/spec/spec_helper.rb +100 -0
- metadata +105 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: df7d9a2c3af98a5aae409dfa910c13f7c2d02db2cc4ed0d5e5b11e3f85ac66af
|
4
|
+
data.tar.gz: a2e4c6e7870479a9eee159b9505a6c2e73ff1e1ee932b8a3984f86c8f8151f78
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5e78725ce4150b799f26e35f1eae10677d5f246ebbcfac2788b4d626ddedfaf9d629d9343eb766e2606895a6b7041589f8c0424d955b74b4c6a65a04319342a0
|
7
|
+
data.tar.gz: 9e2c4ceb94cba35a1451dcd6b7ddfa082c8df9ef9f7fc473dee6bb106748788def61a16547e3652fb7f361f67281992ead3b6259f7230af8f7dd09a2e2c3dbed
|
data/bin/esp_dump
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
require 'json'
|
4
|
+
require 'elder_scrolls_plugin'
|
5
|
+
require 'json-diff'
|
6
|
+
|
7
|
+
debug = false
|
8
|
+
read_only_tes4 = false
|
9
|
+
read_fields = false
|
10
|
+
output_unknown = false
|
11
|
+
output_masters = false
|
12
|
+
output_tree = false
|
13
|
+
output_json = false
|
14
|
+
output_form_ids = false
|
15
|
+
diff = false
|
16
|
+
OptionParser.new do |opts|
|
17
|
+
opts.banner = "Usage: #{$0} [options] files"
|
18
|
+
opts.on('-d', '--debug', 'Activate log debugs') do
|
19
|
+
debug = true
|
20
|
+
end
|
21
|
+
opts.on('-f', '--include-fields', 'Read the fields') do
|
22
|
+
read_fields = true
|
23
|
+
end
|
24
|
+
opts.on('-j', '--output-json', 'Output the tree of records as JSON') do
|
25
|
+
output_json = true
|
26
|
+
end
|
27
|
+
opts.on('-i', '--diff', 'Output a JSON of the differences between 2 esps. Requires 2 esps files to be given. Will display file2 - file1.') do
|
28
|
+
diff = true
|
29
|
+
end
|
30
|
+
opts.on('-m', '--output-masters', 'Output the masters list') do
|
31
|
+
output_masters = true
|
32
|
+
end
|
33
|
+
opts.on('-o', '--only-tes4', 'Read only the TES4 header') do
|
34
|
+
read_only_tes4 = true
|
35
|
+
end
|
36
|
+
opts.on('-r', '--output-form-ids', 'Output the absolute form IDs') do
|
37
|
+
output_form_ids = true
|
38
|
+
end
|
39
|
+
opts.on('-t', '--output-tree', 'Output the tree of records') do
|
40
|
+
output_tree = true
|
41
|
+
end
|
42
|
+
opts.on('-u', '--output-unknown', 'Output unknown chunks') do
|
43
|
+
output_unknown = true
|
44
|
+
end
|
45
|
+
end.parse!
|
46
|
+
files = ARGV.clone
|
47
|
+
raise 'Can\'t use --only-tes4 and --output-masters without --include-fields' if output_masters && read_only_tes4 && !read_fields
|
48
|
+
raise 'Need 2 files to be given when using --diff option' if diff && files.size != 2
|
49
|
+
raise '--diff can\'t be used with other --output-* options.' if diff && (output_json || output_masters || output_form_ids || output_tree || output_unknown)
|
50
|
+
@display_headers = files.size > 1 || [output_unknown, output_masters, output_tree, output_json, output_form_ids].select { |flag| flag }.size > 1
|
51
|
+
|
52
|
+
# Start a processing section with a message.
|
53
|
+
# If no headers need to be output, only execute the section's code.
|
54
|
+
#
|
55
|
+
# Parameters::
|
56
|
+
# * *title* (String): Section title
|
57
|
+
# * Proc: Section code called
|
58
|
+
def section(title)
|
59
|
+
puts "===== #{title} =====" if @display_headers
|
60
|
+
yield
|
61
|
+
puts if @display_headers
|
62
|
+
end
|
63
|
+
|
64
|
+
if diff
|
65
|
+
json1, json2 = files.map do |file|
|
66
|
+
ElderScrollsPlugin.new(
|
67
|
+
file,
|
68
|
+
debug: debug,
|
69
|
+
decode_only_tes4: read_only_tes4,
|
70
|
+
decode_fields: read_fields
|
71
|
+
).to_json
|
72
|
+
end
|
73
|
+
# Compute the diff of 2 jsons
|
74
|
+
puts JSON.pretty_generate(
|
75
|
+
JsonDiff.diff(json1, json2, include_was: true, moves: false).map do |json_diff|
|
76
|
+
readable_path = ''
|
77
|
+
json_cursor = json1
|
78
|
+
# puts json1
|
79
|
+
json_diff['path'].split('/')[1..-1].each do |token|
|
80
|
+
# puts "----- #{token}"
|
81
|
+
if token.scan(/\D/).empty?
|
82
|
+
# JSON should be an Array
|
83
|
+
json_cursor = json_cursor[token.to_i]
|
84
|
+
else
|
85
|
+
json_cursor = json_cursor[token.to_sym]
|
86
|
+
end
|
87
|
+
readable_path << "/#{json_cursor[:name]}" if json_cursor.is_a?(Hash) && json_cursor.key?(:name)
|
88
|
+
end
|
89
|
+
json_diff.merge(
|
90
|
+
'esp_path' => readable_path
|
91
|
+
)
|
92
|
+
end
|
93
|
+
)
|
94
|
+
else
|
95
|
+
files.each do |file|
|
96
|
+
esp = ElderScrollsPlugin.new(
|
97
|
+
file,
|
98
|
+
debug: debug,
|
99
|
+
decode_only_tes4: read_only_tes4,
|
100
|
+
ignore_unknown_chunks: output_unknown,
|
101
|
+
decode_fields: read_fields)
|
102
|
+
section("#{file} - Chunks tree") { esp.dump } if output_tree
|
103
|
+
section("#{file} - JSON") { puts JSON.pretty_generate(esp.to_json) } if output_json
|
104
|
+
section("#{file} - Masters") { esp.dump_masters } if output_masters
|
105
|
+
section("#{file} - Absolute Form IDs") { esp.dump_absolute_form_ids } if output_form_ids
|
106
|
+
section("#{file} - Unknown chunks") do
|
107
|
+
puts esp.unknown_chunks.map { |c| "#{c.name} [#{c.instance_variable_get(:@esp_info)[:type]}]" }.sort.uniq.join("\n")
|
108
|
+
end if output_unknown
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,914 @@
|
|
1
|
+
require 'riffola'
|
2
|
+
require 'base64'
|
3
|
+
require 'bindata'
|
4
|
+
|
5
|
+
class ElderScrollsPlugin
|
6
|
+
|
7
|
+
# Set the current esp being read (useful for BinData decoding types that depend on the esp)
|
8
|
+
#
|
9
|
+
# Parameters::
|
10
|
+
# * *esp* (ElderScrollsPlugin): The current esp
|
11
|
+
def self.current_esp=(esp)
|
12
|
+
@esp = esp
|
13
|
+
end
|
14
|
+
|
15
|
+
# Get the current esp being read (useful for BinData decoding types that depend on the esp)
|
16
|
+
#
|
17
|
+
# Result::
|
18
|
+
# * ElderScrollsPlugin: The current esp
|
19
|
+
def self.current_esp
|
20
|
+
@esp
|
21
|
+
end
|
22
|
+
|
23
|
+
# Hash< Chunk or nil, Array<Chunk> >: The chunks tree, with nil being the root node
|
24
|
+
attr_reader :chunks_tree
|
25
|
+
|
26
|
+
# Array<String>: Ordered list of masters
|
27
|
+
attr_reader :masters
|
28
|
+
|
29
|
+
# Array<Riffola::Chunk>: Unknown chunks encountered
|
30
|
+
attr_reader :unknown_chunks
|
31
|
+
|
32
|
+
KNOWN_GRUP_RECORDS_WITHOUT_FIELDS = %w(
|
33
|
+
NAVM
|
34
|
+
CELL
|
35
|
+
LAND
|
36
|
+
NPC_
|
37
|
+
)
|
38
|
+
|
39
|
+
KNOWN_GRUP_RECORDS_WITH_FIELDS = %w(
|
40
|
+
AACT
|
41
|
+
ACHR
|
42
|
+
ACTI
|
43
|
+
ADDN
|
44
|
+
ALCH
|
45
|
+
AMMO
|
46
|
+
ANIO
|
47
|
+
APPA
|
48
|
+
ARMA
|
49
|
+
ARMO
|
50
|
+
ARTO
|
51
|
+
ASPC
|
52
|
+
ASTP
|
53
|
+
AVIF
|
54
|
+
BOOK
|
55
|
+
BPTD
|
56
|
+
CAMS
|
57
|
+
CLAS
|
58
|
+
CLDC
|
59
|
+
CLFM
|
60
|
+
CLMT
|
61
|
+
COBJ
|
62
|
+
COLL
|
63
|
+
CONT
|
64
|
+
CPTH
|
65
|
+
CSTY
|
66
|
+
DEBR
|
67
|
+
DIAL
|
68
|
+
DLBR
|
69
|
+
DLVW
|
70
|
+
DOBJ
|
71
|
+
DOOR
|
72
|
+
DUAL
|
73
|
+
ECZN
|
74
|
+
EFSH
|
75
|
+
ENCH
|
76
|
+
EQUP
|
77
|
+
EXPL
|
78
|
+
EYES
|
79
|
+
FACT
|
80
|
+
FLOR
|
81
|
+
FLST
|
82
|
+
FSTP
|
83
|
+
FSTS
|
84
|
+
FURN
|
85
|
+
GLOB
|
86
|
+
GMST
|
87
|
+
GRAS
|
88
|
+
HAIR
|
89
|
+
HAZD
|
90
|
+
HDPT
|
91
|
+
IDLE
|
92
|
+
IDLM
|
93
|
+
IMAD
|
94
|
+
IMGS
|
95
|
+
INFO
|
96
|
+
INGR
|
97
|
+
IPCT
|
98
|
+
IPDS
|
99
|
+
KEYM
|
100
|
+
KYWD
|
101
|
+
LCRT
|
102
|
+
LCTN
|
103
|
+
LGTM
|
104
|
+
LIGH
|
105
|
+
LSCR
|
106
|
+
LTEX
|
107
|
+
LVLI
|
108
|
+
LVLN
|
109
|
+
LVSP
|
110
|
+
MATO
|
111
|
+
MATT
|
112
|
+
MESG
|
113
|
+
MGEF
|
114
|
+
MISC
|
115
|
+
MOVT
|
116
|
+
MSTT
|
117
|
+
MUSC
|
118
|
+
MUST
|
119
|
+
NAVI
|
120
|
+
OTFT
|
121
|
+
PACK
|
122
|
+
PERK
|
123
|
+
PROJ
|
124
|
+
PWAT
|
125
|
+
QUST
|
126
|
+
RACE
|
127
|
+
REFR
|
128
|
+
REGN
|
129
|
+
RELA
|
130
|
+
REVB
|
131
|
+
RFCT
|
132
|
+
RGDL
|
133
|
+
SCEN
|
134
|
+
SCOL
|
135
|
+
SCPT
|
136
|
+
SCRL
|
137
|
+
SHOU
|
138
|
+
SLGM
|
139
|
+
SMBN
|
140
|
+
SMEN
|
141
|
+
SMQN
|
142
|
+
SNCT
|
143
|
+
SNDR
|
144
|
+
SOPM
|
145
|
+
SOUN
|
146
|
+
SPEL
|
147
|
+
SPGD
|
148
|
+
STAT
|
149
|
+
TACT
|
150
|
+
TREE
|
151
|
+
TXST
|
152
|
+
VTYP
|
153
|
+
WATR
|
154
|
+
WEAP
|
155
|
+
WOOP
|
156
|
+
WRLD
|
157
|
+
WTHR
|
158
|
+
)
|
159
|
+
|
160
|
+
KNOWN_FIELDS = %w(
|
161
|
+
00TX
|
162
|
+
10TX
|
163
|
+
20TX
|
164
|
+
30TX
|
165
|
+
40TX
|
166
|
+
50TX
|
167
|
+
60TX
|
168
|
+
70TX
|
169
|
+
80TX
|
170
|
+
90TX
|
171
|
+
:0TX
|
172
|
+
;0TX
|
173
|
+
<0TX
|
174
|
+
=0TX
|
175
|
+
>0TX
|
176
|
+
?0TX
|
177
|
+
@0TX
|
178
|
+
A0TX
|
179
|
+
ACEC
|
180
|
+
ACEP
|
181
|
+
ACID
|
182
|
+
ACPR
|
183
|
+
ACSR
|
184
|
+
ACUN
|
185
|
+
AHCF
|
186
|
+
AHCM
|
187
|
+
ALCA
|
188
|
+
ALCL
|
189
|
+
ALCO
|
190
|
+
ALDN
|
191
|
+
ALEA
|
192
|
+
ALED
|
193
|
+
ALEQ
|
194
|
+
ALFA
|
195
|
+
ALFC
|
196
|
+
ALFD
|
197
|
+
ALFE
|
198
|
+
ALFI
|
199
|
+
ALFL
|
200
|
+
ALFR
|
201
|
+
ALID
|
202
|
+
ALLS
|
203
|
+
ALNA
|
204
|
+
ALNT
|
205
|
+
ALPC
|
206
|
+
ALRT
|
207
|
+
ALSP
|
208
|
+
ALST
|
209
|
+
ALUA
|
210
|
+
ANAM
|
211
|
+
ATKD
|
212
|
+
ATKE
|
213
|
+
AVSK
|
214
|
+
B0TX
|
215
|
+
BAMT
|
216
|
+
BIDS
|
217
|
+
BNAM
|
218
|
+
BOD2
|
219
|
+
BPND
|
220
|
+
BPNI
|
221
|
+
BPNN
|
222
|
+
BPNT
|
223
|
+
BPTN
|
224
|
+
C0TX
|
225
|
+
CIS1
|
226
|
+
CIS2
|
227
|
+
CITC
|
228
|
+
CNAM
|
229
|
+
CNTO
|
230
|
+
COCT
|
231
|
+
COED
|
232
|
+
CRDT
|
233
|
+
CRVA
|
234
|
+
CTDA
|
235
|
+
D0TX
|
236
|
+
DALC
|
237
|
+
DATA
|
238
|
+
DEMO
|
239
|
+
DESC
|
240
|
+
DEST
|
241
|
+
DEVA
|
242
|
+
DFTF
|
243
|
+
DFTM
|
244
|
+
DMAX
|
245
|
+
DMDL
|
246
|
+
DMDS
|
247
|
+
DMDT
|
248
|
+
DMIN
|
249
|
+
DNAM
|
250
|
+
DODT
|
251
|
+
DSTD
|
252
|
+
DSTF
|
253
|
+
E0TX
|
254
|
+
EAMT
|
255
|
+
ECOR
|
256
|
+
EDID
|
257
|
+
EFID
|
258
|
+
EFIT
|
259
|
+
EITM
|
260
|
+
ENAM
|
261
|
+
ENIT
|
262
|
+
EPF2
|
263
|
+
EPF3
|
264
|
+
EPFD
|
265
|
+
EPFT
|
266
|
+
ETYP
|
267
|
+
F0TX
|
268
|
+
FLMV
|
269
|
+
FLTR
|
270
|
+
FLTV
|
271
|
+
FNAM
|
272
|
+
FNPR
|
273
|
+
FTSF
|
274
|
+
FTSM
|
275
|
+
FULL
|
276
|
+
G0TX
|
277
|
+
GNAM
|
278
|
+
H0TX
|
279
|
+
HCLF
|
280
|
+
HEAD
|
281
|
+
HEDR
|
282
|
+
HNAM
|
283
|
+
HTID
|
284
|
+
ICO2
|
285
|
+
ICON
|
286
|
+
IDLA
|
287
|
+
IDLC
|
288
|
+
IDLF
|
289
|
+
IDLT
|
290
|
+
IMSP
|
291
|
+
INAM
|
292
|
+
INCC
|
293
|
+
INDX
|
294
|
+
INTV
|
295
|
+
ITXT
|
296
|
+
JNAM
|
297
|
+
K0TX
|
298
|
+
KNAM
|
299
|
+
KSIZ
|
300
|
+
KWDA
|
301
|
+
L0TX
|
302
|
+
LLCT
|
303
|
+
LNAM
|
304
|
+
LTMP
|
305
|
+
LVLD
|
306
|
+
LVLF
|
307
|
+
LVLG
|
308
|
+
LVLI
|
309
|
+
LVLO
|
310
|
+
MDOB
|
311
|
+
MHDT
|
312
|
+
MNAM
|
313
|
+
MO2S
|
314
|
+
MO2T
|
315
|
+
MO3S
|
316
|
+
MO3T
|
317
|
+
MO4S
|
318
|
+
MO4T
|
319
|
+
MO5T
|
320
|
+
MOD2
|
321
|
+
MOD3
|
322
|
+
MOD4
|
323
|
+
MOD5
|
324
|
+
MODL
|
325
|
+
MODS
|
326
|
+
MODT
|
327
|
+
MPAI
|
328
|
+
MPAV
|
329
|
+
MTNM
|
330
|
+
NAM0
|
331
|
+
NAM1
|
332
|
+
NAM2
|
333
|
+
NAM3
|
334
|
+
NAM4
|
335
|
+
NAM5
|
336
|
+
NAM7
|
337
|
+
NAM8
|
338
|
+
NAM9
|
339
|
+
LCEC
|
340
|
+
LCEP
|
341
|
+
LCID
|
342
|
+
LCPR
|
343
|
+
LCSR
|
344
|
+
LCUN
|
345
|
+
NAMA
|
346
|
+
NAME
|
347
|
+
NEXT
|
348
|
+
NNAM
|
349
|
+
NVER
|
350
|
+
NVMI
|
351
|
+
NVPP
|
352
|
+
OBND
|
353
|
+
OCOR
|
354
|
+
ONAM
|
355
|
+
PDTO
|
356
|
+
PFIG
|
357
|
+
PFPC
|
358
|
+
PHTN
|
359
|
+
PHWT
|
360
|
+
PKC2
|
361
|
+
PKCU
|
362
|
+
PKDT
|
363
|
+
PLDT
|
364
|
+
PLVD
|
365
|
+
PNAM
|
366
|
+
POBA
|
367
|
+
POCA
|
368
|
+
POEA
|
369
|
+
PRCB
|
370
|
+
PRKC
|
371
|
+
PRKE
|
372
|
+
PRKF
|
373
|
+
PSDT
|
374
|
+
PTDA
|
375
|
+
QNAM
|
376
|
+
QOBJ
|
377
|
+
QSDT
|
378
|
+
QSTA
|
379
|
+
QTGL
|
380
|
+
RCEC
|
381
|
+
RCLR
|
382
|
+
RCPR
|
383
|
+
RCSR
|
384
|
+
RCUN
|
385
|
+
RDAT
|
386
|
+
RDMO
|
387
|
+
RDSA
|
388
|
+
RDWT
|
389
|
+
RNAM
|
390
|
+
RNMV
|
391
|
+
RPLD
|
392
|
+
RPLI
|
393
|
+
RPRF
|
394
|
+
RPRM
|
395
|
+
SDSC
|
396
|
+
SLCP
|
397
|
+
SNAM
|
398
|
+
SNDD
|
399
|
+
SNMV
|
400
|
+
SOUL
|
401
|
+
SPCT
|
402
|
+
SPIT
|
403
|
+
SPLO
|
404
|
+
SPMV
|
405
|
+
SWMV
|
406
|
+
TCLT
|
407
|
+
TIFC
|
408
|
+
TINC
|
409
|
+
TIND
|
410
|
+
TINI
|
411
|
+
TINL
|
412
|
+
TINP
|
413
|
+
TINT
|
414
|
+
TINV
|
415
|
+
TIRS
|
416
|
+
TNAM
|
417
|
+
TPIC
|
418
|
+
TRDT
|
419
|
+
TWAT
|
420
|
+
TX00
|
421
|
+
TX01
|
422
|
+
TX02
|
423
|
+
TX03
|
424
|
+
TX04
|
425
|
+
TX05
|
426
|
+
TX07
|
427
|
+
UNAM
|
428
|
+
UNES
|
429
|
+
VENC
|
430
|
+
VEND
|
431
|
+
VENV
|
432
|
+
VMAD
|
433
|
+
VNAM
|
434
|
+
VTCK
|
435
|
+
WBDT
|
436
|
+
WCTR
|
437
|
+
WKMV
|
438
|
+
WNAM
|
439
|
+
XACT
|
440
|
+
XALP
|
441
|
+
XAPD
|
442
|
+
XAPR
|
443
|
+
XCNT
|
444
|
+
XEMI
|
445
|
+
XESP
|
446
|
+
XEZN
|
447
|
+
XIS2
|
448
|
+
XLCM
|
449
|
+
XLCN
|
450
|
+
XLIB
|
451
|
+
XLIG
|
452
|
+
XLKR
|
453
|
+
XLOC
|
454
|
+
XLRL
|
455
|
+
XLRM
|
456
|
+
XLRT
|
457
|
+
XLTW
|
458
|
+
XMBO
|
459
|
+
XMBR
|
460
|
+
XMRK
|
461
|
+
XNAM
|
462
|
+
XNDP
|
463
|
+
XOCP
|
464
|
+
XOWN
|
465
|
+
XPOD
|
466
|
+
XPPA
|
467
|
+
XPRD
|
468
|
+
XPRM
|
469
|
+
XRDS
|
470
|
+
XRGB
|
471
|
+
XRGD
|
472
|
+
XRMR
|
473
|
+
XSCL
|
474
|
+
XTEL
|
475
|
+
XTNM
|
476
|
+
XTRI
|
477
|
+
XWCN
|
478
|
+
XWCU
|
479
|
+
XWEM
|
480
|
+
XXXX
|
481
|
+
YNAM
|
482
|
+
ZNAM
|
483
|
+
)
|
484
|
+
|
485
|
+
class FormId < BinData::Primitive
|
486
|
+
uint32le :form_id
|
487
|
+
|
488
|
+
def get
|
489
|
+
"#{ElderScrollsPlugin.current_esp.absolute_form_id(sprintf('%08X', self.form_id))} - #{sprintf('%08X', self.form_id)}"
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
class Label < BinData::Primitive
|
494
|
+
string :label, read_length: 4
|
495
|
+
|
496
|
+
def get
|
497
|
+
self.label.ascii_only? ? self.label : "<#{Base64.encode64(self.label)}>"
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
class RoundedFloat < BinData::Primitive
|
502
|
+
float_le :float
|
503
|
+
def get
|
504
|
+
self.float.round(4)
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
class Door < BinData::Record
|
509
|
+
uint32le :unknown
|
510
|
+
form_id :door_ref
|
511
|
+
end
|
512
|
+
|
513
|
+
class Vertex < BinData::Record
|
514
|
+
rounded_float :x
|
515
|
+
rounded_float :y
|
516
|
+
rounded_float :z
|
517
|
+
end
|
518
|
+
|
519
|
+
class Triangle < BinData::Record
|
520
|
+
uint16le :vertex_index_0
|
521
|
+
uint16le :vertex_index_1
|
522
|
+
uint16le :vertex_index_2
|
523
|
+
end
|
524
|
+
|
525
|
+
class IslandNavMesh < BinData::Record
|
526
|
+
rounded_float :x_min
|
527
|
+
rounded_float :y_min
|
528
|
+
rounded_float :z_min
|
529
|
+
rounded_float :x_max
|
530
|
+
rounded_float :y_max
|
531
|
+
rounded_float :z_max
|
532
|
+
uint32le :triangles_count
|
533
|
+
array :triangles, type: :triangle, initial_length: :triangles_count
|
534
|
+
uint32le :vertices_count
|
535
|
+
array :vertices, type: :vertex, initial_length: :vertices_count
|
536
|
+
end
|
537
|
+
|
538
|
+
module Headers
|
539
|
+
class GRUP < BinData::Record
|
540
|
+
label :label
|
541
|
+
int32le :grup_type
|
542
|
+
uint16le :date
|
543
|
+
uint16le :unknown_1
|
544
|
+
uint16le :version
|
545
|
+
uint16le :unknown_2
|
546
|
+
end
|
547
|
+
class TES4 < BinData::Record
|
548
|
+
uint32le :flags
|
549
|
+
uint32le :id
|
550
|
+
uint32le :revision
|
551
|
+
uint16le :version
|
552
|
+
uint16le :unknown
|
553
|
+
end
|
554
|
+
class All < BinData::Record
|
555
|
+
uint32le :flags
|
556
|
+
form_id :id
|
557
|
+
uint32le :revision
|
558
|
+
uint16le :version
|
559
|
+
uint16le :unknown
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
module Data
|
564
|
+
module ACHR
|
565
|
+
class ACHR_DATA < BinData::Record
|
566
|
+
rounded_float :x_pos
|
567
|
+
rounded_float :y_pos
|
568
|
+
rounded_float :z_pos
|
569
|
+
rounded_float :x_rot
|
570
|
+
rounded_float :y_rot
|
571
|
+
rounded_float :z_rot
|
572
|
+
end
|
573
|
+
class ACHR_NAME < BinData::Record
|
574
|
+
form_id :base_npc
|
575
|
+
end
|
576
|
+
end
|
577
|
+
module NVMI
|
578
|
+
class NVMI_NAVI < BinData::Record
|
579
|
+
form_id :nav_mesh
|
580
|
+
uint32le :unknown
|
581
|
+
rounded_float :x
|
582
|
+
rounded_float :y
|
583
|
+
rounded_float :z
|
584
|
+
uint32le :preferred_merges_flag
|
585
|
+
uint32le :merged_to_count
|
586
|
+
array :merged_to, type: :form_id, initial_length: :merged_to_count
|
587
|
+
uint32le :preferred_merges_count
|
588
|
+
array :preferred_merges, type: :form_id, initial_length: :preferred_merges_count
|
589
|
+
uint32le :doors_count
|
590
|
+
array :doors, type: :door, initial_length: :doors_count
|
591
|
+
uint8 :is_island_mesh_flag
|
592
|
+
island_nav_mesh :island_nav_mesh, onlyif: :has_island_nav_mesh?
|
593
|
+
uint32le :location_marker
|
594
|
+
form_id :world_space
|
595
|
+
form_id :cell, onlyif: :world_space_is_skyrim?
|
596
|
+
form_id :grid_x, onlyif: :world_space_is_not_skyrim?
|
597
|
+
form_id :grid_y, onlyif: :world_space_is_not_skyrim?
|
598
|
+
|
599
|
+
def world_space_is_skyrim?
|
600
|
+
world_space.downcase == 'skyrim.esm/00003C'
|
601
|
+
end
|
602
|
+
def world_space_is_not_skyrim?
|
603
|
+
!world_space_is_skyrim?
|
604
|
+
end
|
605
|
+
def has_island_nav_mesh?
|
606
|
+
is_island_mesh_flag.nonzero?
|
607
|
+
end
|
608
|
+
end
|
609
|
+
end
|
610
|
+
module REFR
|
611
|
+
class REFR_DATA < BinData::Record
|
612
|
+
rounded_float :x_pos
|
613
|
+
rounded_float :y_pos
|
614
|
+
rounded_float :z_pos
|
615
|
+
rounded_float :x_rot
|
616
|
+
rounded_float :y_rot
|
617
|
+
rounded_float :z_rot
|
618
|
+
end
|
619
|
+
class REFR_NAME < BinData::Record
|
620
|
+
form_id :form_id
|
621
|
+
end
|
622
|
+
class REFR_XLIG < BinData::Record
|
623
|
+
rounded_float :fov
|
624
|
+
rounded_float :fade
|
625
|
+
rounded_float :end_distance
|
626
|
+
rounded_float :shadow_depth
|
627
|
+
int32le :unknown
|
628
|
+
end
|
629
|
+
class REFR_XLKR < BinData::Record
|
630
|
+
form_id :form_id_1
|
631
|
+
form_id :form_id_2
|
632
|
+
end
|
633
|
+
class REFR_XLRL < BinData::Record
|
634
|
+
form_id :form_id
|
635
|
+
end
|
636
|
+
class REFR_XNDP < BinData::Record
|
637
|
+
form_id :form_id
|
638
|
+
int32le :unknown
|
639
|
+
end
|
640
|
+
class REFR_XPRM < BinData::Record
|
641
|
+
rounded_float :x_bound
|
642
|
+
rounded_float :y_bound
|
643
|
+
rounded_float :z_bound
|
644
|
+
rounded_float :r
|
645
|
+
rounded_float :g
|
646
|
+
rounded_float :b
|
647
|
+
rounded_float :unknown_1
|
648
|
+
int32le :unknown_2
|
649
|
+
end
|
650
|
+
class REFR_XTEL < BinData::Record
|
651
|
+
form_id :door_form_id
|
652
|
+
rounded_float :x_pos
|
653
|
+
rounded_float :y_pos
|
654
|
+
rounded_float :z_pos
|
655
|
+
rounded_float :x_rot
|
656
|
+
rounded_float :y_rot
|
657
|
+
rounded_float :z_rot
|
658
|
+
int32le :alarm
|
659
|
+
end
|
660
|
+
end
|
661
|
+
end
|
662
|
+
|
663
|
+
# Constructor
|
664
|
+
#
|
665
|
+
# Parameters::
|
666
|
+
# * *file_name* (String): ESP file name
|
667
|
+
# * *decode_only_tes4* (Boolean): Do we decode only the TES4 header? [default: false]
|
668
|
+
# * *ignore_unknown_chunks* (Boolean): Do we ignore unknown chunks? [default: false]
|
669
|
+
# * *decode_fields* (Boolean): Do we decode fields content? [default: true]
|
670
|
+
# * *warnings* (Boolean): Do we activate warnings? [default: true]
|
671
|
+
# * *debug* (Boolean): Do we activate debugging logs? [default: false]
|
672
|
+
def initialize(file_name, decode_only_tes4: false, ignore_unknown_chunks: false, decode_fields: true, warnings: true, debug: false)
|
673
|
+
@file_name = file_name
|
674
|
+
@decode_only_tes4 = decode_only_tes4
|
675
|
+
@ignore_unknown_chunks = ignore_unknown_chunks
|
676
|
+
@decode_fields = decode_fields
|
677
|
+
@warnings = warnings
|
678
|
+
@debug = debug
|
679
|
+
# Get the list of masters
|
680
|
+
@masters = []
|
681
|
+
# Internal mapping of first 2 digits of a FormID to the corresponding master name
|
682
|
+
@master_ids = {}
|
683
|
+
# List of form ids being defined
|
684
|
+
@form_ids = []
|
685
|
+
# Unknown chunks encountered during decoding
|
686
|
+
@unknown_chunks = []
|
687
|
+
# Configure the current parser
|
688
|
+
ElderScrollsPlugin.current_esp = self
|
689
|
+
# Tree of chunks (nil for root)
|
690
|
+
# Hash< Chunk or nil, Array<Chunk> >
|
691
|
+
@chunks_tree = {}
|
692
|
+
chunks = Riffola.read(@file_name, chunks_format: {
|
693
|
+
'*' => { header_size: 8 },
|
694
|
+
'TES4' => { header_size: 16 },
|
695
|
+
'GRUP' => { data_size_correction: -24, header_size: 16 }
|
696
|
+
}, debug: @debug, warnings: @warnings) do |chunk|
|
697
|
+
# Decode the TES4 to get the masters
|
698
|
+
read_chunk(chunk) if chunk.name == 'TES4'
|
699
|
+
!decode_only_tes4 || chunk.name != 'TES4'
|
700
|
+
end
|
701
|
+
# We just finished parsing TES4, update the masters index
|
702
|
+
@master_ids.merge!(sprintf('%.2x', @master_ids.size) => File.basename(@file_name))
|
703
|
+
p @master_ids
|
704
|
+
@chunks_tree[nil] = chunks
|
705
|
+
unless decode_only_tes4
|
706
|
+
chunks.each do |chunk|
|
707
|
+
# Don't read TES4 twice, especially because we already have our master IDs parsed
|
708
|
+
read_chunk(chunk) unless chunk.name == 'TES4'
|
709
|
+
end
|
710
|
+
end
|
711
|
+
end
|
712
|
+
|
713
|
+
# Output a node of the chunks tree
|
714
|
+
#
|
715
|
+
# Parameters::
|
716
|
+
# * *chunk* (Riffola::Chunk or nil): The node to be dumped, or nil for root [default = nil]
|
717
|
+
# * *output_prefix* (String): Output prefix [default = '']
|
718
|
+
def dump(chunk = nil, output_prefix = '')
|
719
|
+
esp_info = chunk.nil? ? nil : chunk.instance_variable_get(:@esp_info)
|
720
|
+
sub_chunks = @chunks_tree[chunk]
|
721
|
+
puts "#{output_prefix}+- #{chunk.nil? ? 'ROOT' : "#{chunk.name}#{esp_info[:description].nil? ? '' : " - #{esp_info[:description]}"}"}#{sub_chunks.empty? ? '' : " (#{sub_chunks.size} sub-chunks)"}"
|
722
|
+
sub_chunks.each.with_index do |sub_chunk, idx_sub_chunk|
|
723
|
+
dump(sub_chunk, "#{output_prefix}#{idx_sub_chunk == sub_chunks.size - 1 ? ' ' : '|'} ")
|
724
|
+
end
|
725
|
+
end
|
726
|
+
|
727
|
+
# Dump masters
|
728
|
+
def dump_masters
|
729
|
+
@masters.each.with_index do |master, idx|
|
730
|
+
puts "* [#{sprintf('%.2x', idx)}] - #{master}"
|
731
|
+
end
|
732
|
+
end
|
733
|
+
|
734
|
+
# Dump absolute Form IDs
|
735
|
+
def dump_absolute_form_ids
|
736
|
+
@form_ids.sort.each do |form_id|
|
737
|
+
puts "* [#{form_id}] - #{absolute_form_id(form_id)}"
|
738
|
+
end
|
739
|
+
end
|
740
|
+
|
741
|
+
# Return the esp content as JSON
|
742
|
+
#
|
743
|
+
# Parameters::
|
744
|
+
# * *chunk* (Riffola::Chunk or nil): The node to be dumped, or nil for root [default = nil]
|
745
|
+
# Result::
|
746
|
+
# * Hash: JSON object
|
747
|
+
def to_json(chunk = nil)
|
748
|
+
esp_info = chunk.nil? ? { type: :root, description: 'root' } : chunk.instance_variable_get(:@esp_info)
|
749
|
+
json = {
|
750
|
+
name: chunk.nil? ? 'ROOT' : chunk.name
|
751
|
+
}
|
752
|
+
json[:type] = esp_info[:type] unless esp_info[:type].nil?
|
753
|
+
json[:description] = esp_info[:description] unless esp_info[:description].nil?
|
754
|
+
json[:decoded_data] = esp_info[:decoded_data] unless esp_info[:decoded_data].nil?
|
755
|
+
json[:decoded_header] = esp_info[:decoded_header] unless esp_info[:decoded_header].nil?
|
756
|
+
unless chunk.nil?
|
757
|
+
if esp_info[:decoded_header].nil?
|
758
|
+
header = chunk.header
|
759
|
+
json[:header] = (header.ascii_only? ? header : Base64.encode64(header)) unless header.empty?
|
760
|
+
end
|
761
|
+
if esp_info[:type] == :field && esp_info[:decoded_data].nil?
|
762
|
+
data = chunk.data
|
763
|
+
json[:data] = (data.ascii_only? ? data : Base64.encode64(data))
|
764
|
+
end
|
765
|
+
end
|
766
|
+
json[:sub_chunks] = @chunks_tree[chunk].
|
767
|
+
map { |sub_chunk| to_json(sub_chunk) }.
|
768
|
+
sort_by { |chunk_json| [chunk_json[:name], chunk_json[:description], chunk_json[:data]] } if @chunks_tree[chunk].size > 0
|
769
|
+
json
|
770
|
+
end
|
771
|
+
|
772
|
+
# Convert a Form ID into its absolute form.
|
773
|
+
# An absolute form ID is not dependent on the order of the masters and includes the master name.
|
774
|
+
#
|
775
|
+
# Parameters::
|
776
|
+
# * *form_id* (String): The original form ID
|
777
|
+
# Result::
|
778
|
+
# * String: The absolute Form ID
|
779
|
+
def absolute_form_id(form_id)
|
780
|
+
"#{@master_ids.key?(form_id[0..1]) ? @master_ids[form_id[0..1]] : "!!!#{form_id[0..1]}"}/#{form_id[2..7]}"
|
781
|
+
end
|
782
|
+
|
783
|
+
private
|
784
|
+
|
785
|
+
# Read a given chunk info
|
786
|
+
#
|
787
|
+
# Parameters::
|
788
|
+
# * *chunk* (Riffola::Chunk): Chunk to be read
|
789
|
+
def read_chunk(chunk)
|
790
|
+
puts "[ESP DEBUG] - Read chunk #{chunk.name}..." if @debug
|
791
|
+
description = nil
|
792
|
+
decoded_data = nil
|
793
|
+
subchunks = []
|
794
|
+
header = chunk.header
|
795
|
+
case chunk.name
|
796
|
+
when 'TES4'
|
797
|
+
# Always read fields of TES4 as they define the masters, which are needed for others
|
798
|
+
puts "[ESP DEBUG] - Read children chunks of #{chunk}" if @debug
|
799
|
+
subchunks = chunk.sub_chunks(sub_chunks_format: {
|
800
|
+
'*' => { header_size: 0, size_length: 2 },
|
801
|
+
'ONAM' => {
|
802
|
+
data_size_correction: proc do |file|
|
803
|
+
# Size of ONAM field is sometimes badly computed. Correct it.
|
804
|
+
file.seek(4, IO::SEEK_CUR)
|
805
|
+
stored_size = file.read(2).unpack('S').first
|
806
|
+
file.read(chunk.size).index('INTV') - stored_size
|
807
|
+
end
|
808
|
+
}
|
809
|
+
})
|
810
|
+
chunk_type = :record
|
811
|
+
when 'MAST'
|
812
|
+
description = chunk.data[0..-2].downcase
|
813
|
+
@masters << description
|
814
|
+
@master_ids[sprintf('%.2x', @master_ids.size)] = description
|
815
|
+
chunk_type = :field
|
816
|
+
when 'GRUP'
|
817
|
+
puts "[ESP DEBUG] - Read children chunks of #{chunk}" if @debug
|
818
|
+
subchunks = chunk.sub_chunks(sub_chunks_format: Hash[(['GRUP'] + KNOWN_GRUP_RECORDS_WITHOUT_FIELDS + KNOWN_GRUP_RECORDS_WITH_FIELDS).map do |known_sub_record_name|
|
819
|
+
[
|
820
|
+
known_sub_record_name,
|
821
|
+
{
|
822
|
+
header_size: 16,
|
823
|
+
data_size_correction: known_sub_record_name == 'GRUP' ? -24 : 0
|
824
|
+
}
|
825
|
+
]
|
826
|
+
end])
|
827
|
+
chunk_type = :group
|
828
|
+
when *KNOWN_GRUP_RECORDS_WITHOUT_FIELDS
|
829
|
+
# GRUP record having no fields
|
830
|
+
form_id_str = sprintf('%.8x', header[4..7].unpack('L').first)
|
831
|
+
@form_ids << form_id_str
|
832
|
+
description = "FormID: #{form_id_str}"
|
833
|
+
puts "[WARNING] - #{chunk} seems to have fields: #{chunk.data.inspect}" if @warnings && chunk.data[0..3] =~ /^\w{4}$/
|
834
|
+
chunk_type = :record
|
835
|
+
when *KNOWN_GRUP_RECORDS_WITH_FIELDS
|
836
|
+
# GRUP record having fields
|
837
|
+
form_id_str = sprintf('%.8x', header[4..7].unpack('L').first)
|
838
|
+
@form_ids << form_id_str
|
839
|
+
description = "FormID: #{form_id_str}"
|
840
|
+
if @decode_fields
|
841
|
+
puts "[ESP DEBUG] - Read children chunks of #{chunk}" if @debug
|
842
|
+
subchunks = chunk.sub_chunks(sub_chunks_format: { '*' => { header_size: 0, size_length: 2 } })
|
843
|
+
end
|
844
|
+
chunk_type = :record
|
845
|
+
when *KNOWN_FIELDS
|
846
|
+
# Field
|
847
|
+
record_module_name =
|
848
|
+
if Data.const_defined?(chunk.parent_chunk.name.to_sym)
|
849
|
+
chunk.parent_chunk.name.to_sym
|
850
|
+
elsif Data.const_defined?(:All)
|
851
|
+
:All
|
852
|
+
else
|
853
|
+
nil
|
854
|
+
end
|
855
|
+
unless record_module_name.nil?
|
856
|
+
record_module = Data.const_get(record_module_name)
|
857
|
+
data_class_name =
|
858
|
+
if record_module.const_defined?("#{record_module_name}_#{chunk.name}".to_sym)
|
859
|
+
"#{record_module_name}_#{chunk.name}".to_sym
|
860
|
+
elsif record_module.const_defined?("#{record_module_name}_All".to_sym)
|
861
|
+
"#{record_module_name}_All".to_sym
|
862
|
+
else
|
863
|
+
nil
|
864
|
+
end
|
865
|
+
unless data_class_name.nil?
|
866
|
+
data_info = record_module.const_get(data_class_name)
|
867
|
+
decoded_data = {}
|
868
|
+
data_info.read(chunk.data).each_pair do |property, value|
|
869
|
+
decoded_data[property] = value
|
870
|
+
end
|
871
|
+
end
|
872
|
+
end
|
873
|
+
chunk_type = :field
|
874
|
+
else
|
875
|
+
warning_desc = "Unknown chunk: #{chunk}. Data: #{chunk.data.inspect}"
|
876
|
+
if @ignore_unknown_chunks
|
877
|
+
puts "[WARNING] - #{warning_desc}" if @warnings
|
878
|
+
@unknown_chunks << chunk
|
879
|
+
chunk_type = :unknown
|
880
|
+
else
|
881
|
+
raise warning_desc
|
882
|
+
end
|
883
|
+
end
|
884
|
+
# Decorate the chunk with our info
|
885
|
+
esp_info = {
|
886
|
+
description: description,
|
887
|
+
type: chunk_type
|
888
|
+
}
|
889
|
+
esp_info[:decoded_data] = decoded_data unless decoded_data.nil?
|
890
|
+
unless header.empty?
|
891
|
+
header_class_name =
|
892
|
+
if Headers.const_defined?(chunk.name.to_sym)
|
893
|
+
chunk.name.to_sym
|
894
|
+
elsif Headers.const_defined?(:All)
|
895
|
+
:All
|
896
|
+
else
|
897
|
+
nil
|
898
|
+
end
|
899
|
+
unless header_class_name.nil?
|
900
|
+
header_info = Headers.const_get(header_class_name)
|
901
|
+
esp_info[:decoded_header] = {}
|
902
|
+
header_info.read(header).each_pair do |property, value|
|
903
|
+
esp_info[:decoded_header][property] = value
|
904
|
+
end
|
905
|
+
end
|
906
|
+
end
|
907
|
+
chunk.instance_variable_set(:@esp_info, esp_info)
|
908
|
+
@chunks_tree[chunk] = subchunks
|
909
|
+
subchunks.each.with_index do |subchunk, idx_subchunk|
|
910
|
+
read_chunk(subchunk)
|
911
|
+
end
|
912
|
+
end
|
913
|
+
|
914
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
4
|
+
# this file to always be loaded, without a need to explicitly require it in any
|
5
|
+
# files.
|
6
|
+
#
|
7
|
+
# Given that it is always loaded, you are encouraged to keep this file as
|
8
|
+
# light-weight as possible. Requiring heavyweight dependencies from this file
|
9
|
+
# will add to the boot time of your test suite on EVERY test run, even for an
|
10
|
+
# individual file that may not need all of that loaded. Instead, consider making
|
11
|
+
# a separate helper file that requires the additional dependencies and performs
|
12
|
+
# the additional setup, and require it from the spec files that actually need
|
13
|
+
# it.
|
14
|
+
#
|
15
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
16
|
+
RSpec.configure do |config|
|
17
|
+
# rspec-expectations config goes here. You can use an alternate
|
18
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
19
|
+
# assertions if you prefer.
|
20
|
+
config.expect_with :rspec do |expectations|
|
21
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
22
|
+
# and `failure_message` of custom matchers include text for helper methods
|
23
|
+
# defined using `chain`, e.g.:
|
24
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
25
|
+
# # => "be bigger than 2 and smaller than 4"
|
26
|
+
# ...rather than:
|
27
|
+
# # => "be bigger than 2"
|
28
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
29
|
+
end
|
30
|
+
|
31
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
32
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
33
|
+
config.mock_with :rspec do |mocks|
|
34
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
35
|
+
# a real object. This is generally recommended, and will default to
|
36
|
+
# `true` in RSpec 4.
|
37
|
+
mocks.verify_partial_doubles = true
|
38
|
+
end
|
39
|
+
|
40
|
+
# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
|
41
|
+
# have no way to turn it off -- the option exists only for backwards
|
42
|
+
# compatibility in RSpec 3). It causes shared context metadata to be
|
43
|
+
# inherited by the metadata hash of host groups and examples, rather than
|
44
|
+
# triggering implicit auto-inclusion in groups with matching metadata.
|
45
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
46
|
+
|
47
|
+
# The settings below are suggested to provide a good initial experience
|
48
|
+
# with RSpec, but feel free to customize to your heart's content.
|
49
|
+
=begin
|
50
|
+
# This allows you to limit a spec run to individual examples or groups
|
51
|
+
# you care about by tagging them with `:focus` metadata. When nothing
|
52
|
+
# is tagged with `:focus`, all examples get run. RSpec also provides
|
53
|
+
# aliases for `it`, `describe`, and `context` that include `:focus`
|
54
|
+
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
|
55
|
+
config.filter_run_when_matching :focus
|
56
|
+
|
57
|
+
# Allows RSpec to persist some state between runs in order to support
|
58
|
+
# the `--only-failures` and `--next-failure` CLI options. We recommend
|
59
|
+
# you configure your source control system to ignore this file.
|
60
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
61
|
+
|
62
|
+
# Limits the available syntax to the non-monkey patched syntax that is
|
63
|
+
# recommended. For more details, see:
|
64
|
+
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
65
|
+
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
66
|
+
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
67
|
+
config.disable_monkey_patching!
|
68
|
+
|
69
|
+
# This setting enables warnings. It's recommended, but in some cases may
|
70
|
+
# be too noisy due to issues in dependencies.
|
71
|
+
config.warnings = true
|
72
|
+
|
73
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
74
|
+
# file, and it's useful to allow more verbose output when running an
|
75
|
+
# individual spec file.
|
76
|
+
if config.files_to_run.one?
|
77
|
+
# Use the documentation formatter for detailed output,
|
78
|
+
# unless a formatter has already been configured
|
79
|
+
# (e.g. via a command-line flag).
|
80
|
+
config.default_formatter = "doc"
|
81
|
+
end
|
82
|
+
|
83
|
+
# Print the 10 slowest examples and example groups at the
|
84
|
+
# end of the spec run, to help surface which specs are running
|
85
|
+
# particularly slow.
|
86
|
+
config.profile_examples = 10
|
87
|
+
|
88
|
+
# Run specs in random order to surface order dependencies. If you find an
|
89
|
+
# order dependency and want to debug it, you can fix the order by providing
|
90
|
+
# the seed, which is printed after each run.
|
91
|
+
# --seed 1234
|
92
|
+
config.order = :random
|
93
|
+
|
94
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
95
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
96
|
+
# test failures related to randomization by passing the same `--seed` value
|
97
|
+
# as the one that triggered the failure.
|
98
|
+
Kernel.srand config.seed
|
99
|
+
=end
|
100
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: elder_scrolls_plugin
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Muriel Salvan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-11-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: riffola
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bindata
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.4'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.4'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: json-diff
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.4'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.4'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.10'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.10'
|
69
|
+
description: Library reading Bethesda's plugins files (.esp, .esm and .esl) files.
|
70
|
+
Provides a simple API to access plugins' data organized in chunks
|
71
|
+
email:
|
72
|
+
- muriel@x-aeon.com
|
73
|
+
executables:
|
74
|
+
- esp_dump
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files: []
|
77
|
+
files:
|
78
|
+
- bin/esp_dump
|
79
|
+
- lib/elder_scrolls_plugin.rb
|
80
|
+
- lib/elder_scrolls_plugin/version.rb
|
81
|
+
- spec/spec_helper.rb
|
82
|
+
homepage: https://github.com/Muriel-Salvan/elder_scrolls_plugin
|
83
|
+
licenses:
|
84
|
+
- BSD-4-Clause
|
85
|
+
metadata: {}
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
requirements: []
|
101
|
+
rubygems_version: 3.1.2
|
102
|
+
signing_key:
|
103
|
+
specification_version: 4
|
104
|
+
summary: Elder Scrolls Plugin - Reading Bethesda's esp, esm and esl files
|
105
|
+
test_files: []
|