Class: ElderScrollsPlugin

Inherits:
Object
  • Object
show all
Defined in:
lib/elder_scrolls_plugin.rb,
lib/elder_scrolls_plugin/version.rb

Defined Under Namespace

Modules: Data, Headers Classes: Door, FormId, IslandNavMesh, Label, RoundedFloat, Triangle, Vertex

Constant Summary collapse

KNOWN_GRUP_RECORDS_WITHOUT_FIELDS =
%w(
  NAVM
  CELL
  LAND
  NPC_
)
KNOWN_GRUP_RECORDS_WITH_FIELDS =
%w(
  AACT
  ACHR
  ACTI
  ADDN
  ALCH
  AMMO
  ANIO
  APPA
  ARMA
  ARMO
  ARTO
  ASPC
  ASTP
  AVIF
  BOOK
  BPTD
  CAMS
  CLAS
  CLDC
  CLFM
  CLMT
  COBJ
  COLL
  CONT
  CPTH
  CSTY
  DEBR
  DIAL
  DLBR
  DLVW
  DOBJ
  DOOR
  DUAL
  ECZN
  EFSH
  ENCH
  EQUP
  EXPL
  EYES
  FACT
  FLOR
  FLST
  FSTP
  FSTS
  FURN
  GLOB
  GMST
  GRAS
  HAIR
  HAZD
  HDPT
  IDLE
  IDLM
  IMAD
  IMGS
  INFO
  INGR
  IPCT
  IPDS
  KEYM
  KYWD
  LCRT
  LCTN
  LGTM
  LIGH
  LSCR
  LTEX
  LVLI
  LVLN
  LVSP
  MATO
  MATT
  MESG
  MGEF
  MISC
  MOVT
  MSTT
  MUSC
  MUST
  NAVI
  OTFT
  PACK
  PERK
  PROJ
  PWAT
  QUST
  RACE
  REFR
  REGN
  RELA
  REVB
  RFCT
  RGDL
  SCEN
  SCOL
  SCPT
  SCRL
  SHOU
  SLGM
  SMBN
  SMEN
  SMQN
  SNCT
  SNDR
  SOPM
  SOUN
  SPEL
  SPGD
  STAT
  TACT
  TREE
  TXST
  VTYP
  WATR
  WEAP
  WOOP
  WRLD
  WTHR
)
KNOWN_FIELDS =
%w(
  00TX
  10TX
  20TX
  30TX
  40TX
  50TX
  60TX
  70TX
  80TX
  90TX
  :0TX
  ;0TX
  <0TX
  =0TX
  >0TX
  ?0TX
  @0TX
  A0TX
  ACEC
  ACEP
  ACID
  ACPR
  ACSR
  ACUN
  AHCF
  AHCM
  ALCA
  ALCL
  ALCO
  ALDN
  ALEA
  ALED
  ALEQ
  ALFA
  ALFC
  ALFD
  ALFE
  ALFI
  ALFL
  ALFR
  ALID
  ALLS
  ALNA
  ALNT
  ALPC
  ALRT
  ALSP
  ALST
  ALUA
  ANAM
  ATKD
  ATKE
  AVSK
  B0TX
  BAMT
  BIDS
  BNAM
  BOD2
  BPND
  BPNI
  BPNN
  BPNT
  BPTN
  C0TX
  CIS1
  CIS2
  CITC
  CNAM
  CNTO
  COCT
  COED
  CRDT
  CRVA
  CTDA
  D0TX
  DALC
  DATA
  DEMO
  DESC
  DEST
  DEVA
  DFTF
  DFTM
  DMAX
  DMDL
  DMDS
  DMDT
  DMIN
  DNAM
  DODT
  DSTD
  DSTF
  E0TX
  EAMT
  ECOR
  EDID
  EFID
  EFIT
  EITM
  ENAM
  ENIT
  EPF2
  EPF3
  EPFD
  EPFT
  ETYP
  F0TX
  FLMV
  FLTR
  FLTV
  FNAM
  FNPR
  FTSF
  FTSM
  FULL
  G0TX
  GNAM
  H0TX
  HCLF
  HEAD
  HEDR
  HNAM
  HTID
  ICO2
  ICON
  IDLA
  IDLC
  IDLF
  IDLT
  IMSP
  INAM
  INCC
  INDX
  INTV
  ITXT
  JNAM
  K0TX
  KNAM
  KSIZ
  KWDA
  L0TX
  LLCT
  LNAM
  LTMP
  LVLD
  LVLF
  LVLG
  LVLI
  LVLO
  MDOB
  MHDT
  MNAM
  MO2S
  MO2T
  MO3S
  MO3T
  MO4S
  MO4T
  MO5T
  MOD2
  MOD3
  MOD4
  MOD5
  MODL
  MODS
  MODT
  MPAI
  MPAV
  MTNM
  NAM0
  NAM1
  NAM2
  NAM3
  NAM4
  NAM5
  NAM7
  NAM8
  NAM9
  LCEC
  LCEP
  LCID
  LCPR
  LCSR
  LCUN
  NAMA
  NAME
  NEXT
  NNAM
  NVER
  NVMI
  NVPP
  OBND
  OCOR
  ONAM
  PDTO
  PFIG
  PFPC
  PHTN
  PHWT
  PKC2
  PKCU
  PKDT
  PLDT
  PLVD
  PNAM
  POBA
  POCA
  POEA
  PRCB
  PRKC
  PRKE
  PRKF
  PSDT
  PTDA
  QNAM
  QOBJ
  QSDT
  QSTA
  QTGL
  RCEC
  RCLR
  RCPR
  RCSR
  RCUN
  RDAT
  RDMO
  RDSA
  RDWT
  RNAM
  RNMV
  RPLD
  RPLI
  RPRF
  RPRM
  SDSC
  SLCP
  SNAM
  SNDD
  SNMV
  SOUL
  SPCT
  SPIT
  SPLO
  SPMV
  SWMV
  TCLT
  TIFC
  TINC
  TIND
  TINI
  TINL
  TINP
  TINT
  TINV
  TIRS
  TNAM
  TPIC
  TRDT
  TWAT
  TX00
  TX01
  TX02
  TX03
  TX04
  TX05
  TX07
  UNAM
  UNES
  VENC
  VEND
  VENV
  VMAD
  VNAM
  VTCK
  WBDT
  WCTR
  WKMV
  WNAM
  XACT
  XALP
  XAPD
  XAPR
  XCNT
  XEMI
  XESP
  XEZN
  XIS2
  XLCM
  XLCN
  XLIB
  XLIG
  XLKR
  XLOC
  XLRL
  XLRM
  XLRT
  XLTW
  XMBO
  XMBR
  XMRK
  XNAM
  XNDP
  XOCP
  XOWN
  XPOD
  XPPA
  XPRD
  XPRM
  XRDS
  XRGB
  XRGD
  XRMR
  XSCL
  XTEL
  XTNM
  XTRI
  XWCN
  XWCU
  XWEM
  XXXX
  YNAM
  ZNAM
)
VERSION =
'0.0.2'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file_name, decode_only_tes4: false, ignore_unknown_chunks: false, decode_fields: true, warnings: true, debug: false) ⇒ ElderScrollsPlugin

Constructor

Parameters
  • file_name (String): ESP file name

  • decode_only_tes4 (Boolean): Do we decode only the TES4 header? [default: false]

  • ignore_unknown_chunks (Boolean): Do we ignore unknown chunks? [default: false]

  • decode_fields (Boolean): Do we decode fields content? [default: true]

  • warnings (Boolean): Do we activate warnings? [default: true]

  • debug (Boolean): Do we activate debugging logs? [default: false]



672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
# File 'lib/elder_scrolls_plugin.rb', line 672

def initialize(file_name, decode_only_tes4: false, ignore_unknown_chunks: false, decode_fields: true, warnings: true, debug: false)
  @file_name = file_name
  @decode_only_tes4 = decode_only_tes4
  @ignore_unknown_chunks = ignore_unknown_chunks
  @decode_fields = decode_fields
  @warnings = warnings
  @debug = debug
  # Get the list of masters
  @masters = []
  # Internal mapping of first 2 digits of a FormID to the corresponding master name
  @master_ids = {}
  # List of form ids being defined
  @form_ids = []
  # Unknown chunks encountered during decoding
  @unknown_chunks = []
  # Configure the current parser
  ElderScrollsPlugin.current_esp = self
  # Tree of chunks (nil for root)
  # Hash< Chunk or nil, Array<Chunk> >
  @chunks_tree = {}
  chunks = Riffola.read(@file_name, chunks_format: {
    '*' => { header_size: 8 },
    'TES4' => { header_size: 16 },
    'GRUP' => { data_size_correction: -24, header_size: 16 }
  }, debug: @debug, warnings: @warnings) do |chunk|
    # Decode the TES4 to get the masters
    read_chunk(chunk) if chunk.name == 'TES4'
    !decode_only_tes4 || chunk.name != 'TES4'
  end
  # We just finished parsing TES4, update the masters index
  @master_ids.merge!(sprintf('%.2x', @master_ids.size) => File.basename(@file_name))
  @chunks_tree[nil] = chunks
  unless decode_only_tes4
    chunks.each do |chunk|
      # Don't read TES4 twice, especially because we already have our master IDs parsed
      read_chunk(chunk) unless chunk.name == 'TES4'
    end
  end
end

Instance Attribute Details

#chunks_treeObject (readonly)

Hash< Chunk or nil, Array<Chunk> >: The chunks tree, with nil being the root node



24
25
26
# File 'lib/elder_scrolls_plugin.rb', line 24

def chunks_tree
  @chunks_tree
end

#mastersObject (readonly)

Array<String>: Ordered list of masters



27
28
29
# File 'lib/elder_scrolls_plugin.rb', line 27

def masters
  @masters
end

#unknown_chunksObject (readonly)

Array<Riffola::Chunk>: Unknown chunks encountered



30
31
32
# File 'lib/elder_scrolls_plugin.rb', line 30

def unknown_chunks
  @unknown_chunks
end

Class Method Details

.current_espObject

Get the current esp being read (useful for BinData decoding types that depend on the esp)

Result
  • ElderScrollsPlugin: The current esp



19
20
21
# File 'lib/elder_scrolls_plugin.rb', line 19

def self.current_esp
  @esp
end

.current_esp=(esp) ⇒ Object

Set the current esp being read (useful for BinData decoding types that depend on the esp)

Parameters
  • esp (ElderScrollsPlugin): The current esp



11
12
13
# File 'lib/elder_scrolls_plugin.rb', line 11

def self.current_esp=(esp)
  @esp = esp
end

Instance Method Details

#absolute_form_id(form_id) ⇒ Object

Convert a Form ID into its absolute form. An absolute form ID is not dependent on the order of the masters and includes the master name.

Parameters
  • form_id (String): The original form ID

Result
  • String: The absolute Form ID



778
779
780
# File 'lib/elder_scrolls_plugin.rb', line 778

def absolute_form_id(form_id)
  "#{@master_ids.key?(form_id[0..1]) ? @master_ids[form_id[0..1]] : "!!!#{form_id[0..1]}"}/#{form_id[2..7]}"
end

#dump(chunk = nil, output_prefix = '') ⇒ Object

Output a node of the chunks tree

Parameters
  • chunk (Riffola::Chunk or nil): The node to be dumped, or nil for root [default = nil]

  • output_prefix (String): Output prefix [default = ”]



717
718
719
720
721
722
723
724
# File 'lib/elder_scrolls_plugin.rb', line 717

def dump(chunk = nil, output_prefix = '')
  esp_info = chunk.nil? ? nil : chunk.instance_variable_get(:@esp_info)
  sub_chunks = @chunks_tree[chunk]
  puts "#{output_prefix}+- #{chunk.nil? ? 'ROOT' : "#{chunk.name}#{esp_info[:description].nil? ? '' : " - #{esp_info[:description]}"}"}#{sub_chunks.empty? ? '' : " (#{sub_chunks.size} sub-chunks)"}"
  sub_chunks.each.with_index do |sub_chunk, idx_sub_chunk|
    dump(sub_chunk, "#{output_prefix}#{idx_sub_chunk == sub_chunks.size - 1 ? ' ' : '|'}  ")
  end
end

#dump_absolute_form_idsObject

Dump absolute Form IDs



734
735
736
737
738
# File 'lib/elder_scrolls_plugin.rb', line 734

def dump_absolute_form_ids
  @form_ids.sort.each do |form_id|
    puts "* [#{form_id}] - #{absolute_form_id(form_id)}"
  end
end

#dump_mastersObject

Dump masters



727
728
729
730
731
# File 'lib/elder_scrolls_plugin.rb', line 727

def dump_masters
  @masters.each.with_index do |master, idx|
    puts "* [#{sprintf('%.2x', idx)}] - #{master}"
  end
end

#to_json(chunk = nil) ⇒ Object

Return the esp content as JSON

Parameters
  • chunk (Riffola::Chunk or nil): The node to be dumped, or nil for root [default = nil]

Result
  • Hash: JSON object



746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
# File 'lib/elder_scrolls_plugin.rb', line 746

def to_json(chunk = nil)
  esp_info = chunk.nil? ? { type: :root, description: 'root' } : chunk.instance_variable_get(:@esp_info)
  json = {
    name: chunk.nil? ? 'ROOT' : chunk.name
  }
  json[:type] = esp_info[:type] unless esp_info[:type].nil?
  json[:description] = esp_info[:description] unless esp_info[:description].nil?
  json[:decoded_data] = esp_info[:decoded_data] unless esp_info[:decoded_data].nil?
  json[:decoded_header] = esp_info[:decoded_header] unless esp_info[:decoded_header].nil?
  unless chunk.nil?
    if esp_info[:decoded_header].nil?
      header = chunk.header
      json[:header] = (header.ascii_only? ? header : Base64.encode64(header)) unless header.empty?
    end
    if esp_info[:type] == :field && esp_info[:decoded_data].nil?
      data = chunk.data
      json[:data] = (data.ascii_only? ? data : Base64.encode64(data))
    end
  end
  json[:sub_chunks] = @chunks_tree[chunk].
    map { |sub_chunk| to_json(sub_chunk) }.
    sort_by { |chunk_json| [chunk_json[:name], chunk_json[:description], chunk_json[:data]] } if @chunks_tree[chunk].size > 0
  json
end