elder_scrolls_plugin 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|