stephen-bunn / bethesda-structs

A wrapper for Bethesda's popular plugin/archive file formats

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Bethesda Structs Logo

PyPi Status Supported Versions License Build Status Documentation Status Say Thanks!
Waffle

Made with: Python

This project is archived as I can no longer maintain it!
Sorry for any inconvenience <3.

About

Modding Bethesda's games can be a fine-art.
This package intends to provide clean and accessible methods for parsing and understanding Bethesda's filetypes.

For example:

There are so many "unarchiver" tools for Bethesda's archives (.bsa and .ba2), but no good programmatic way to read these filetypes. Using this package, understanding every little detail about an archive is simple and straight-forward (see BSA Usage and BA2 Usage).

All contributions welcome!
For more advanced usage and information, check out the documentation.
The supported filetypes are parsers not writers.
We do not currently support the writing of archives or plugins.

Installation

Because this is glorious Python, installing bethesda-structs should be super-duper simple.

Using PyPi

The fastest and quickest way to install this packages is by simply using pipenv (or if you're oldschool pip).

$ pipenv install bethesda-structs

Using Git

You can install this package using Git by simply cloning the repo and building the package yourself!

$ git clone https://github.com/stephen-bunn/bethesda-structs.git
$ pipenv install --dev
$ pipenv run python setup.py install

Usage

Using bethesda-structs is designed to be straight-forward and intuitive.
Below are some short examples of parsing various filetypes.

ESP

The ability to parse plugin files is super helpful for understanding the additions and changes that are made to the game.
Currently the only other real tool that can expose this information to you is TESEdit and its sibling applications.

This package aims to provide simple, programmatic access to the in-depth details of a plugin!

Because of how long it takes to build complete subrecord parers for a given plugin version, the only currently supported plugins are:

  • FNVPlugin - Fallout: New Vegas (partial)
  • F03Plugin - Fallout 3 (partial and experimental)

>>> from bethesda_structs.plugin.fnv import FNVPlugin >>> plugin = FNVPlugin.parse_file('/media/sf_VMShared/esp/fnv/NVWillow.esp') >>> print(plugin) FNVPlugin(filepath='/media/sf_VMShared/esp/fnv/NVWillow.esp') >>> >>> # print plugin header (is a record) ... >>> print(plugin.container.header) Container: type = u'TES4' (total 4) data_size = 163 flags = Container: master = True id = 0 revision = 0 version = 15 data = b'HEDRx0cx00x1fx85xab?x97x12x00x00#xad'... (truncated, total 163) subrecords = ListContainer: Container: type = u'HEDR' (total 4) data_size = 12 data = b'x1fx85xab?x97x12x00x00#xadrx00' (total 12) parsed = Container: value = Container: version = 1.340000033378601 num_records = 4759 next_object_id = 896291 description = u'Header' (total 6) Container: type = u'CNAM' (total 4) data_size = 9 data = b'llamaRCAx00' (total 9) parsed = Container: value = u'llamaRCA' (total 8) description = u'Author' (total 6) Container: type = u'SNAM' (total 4) data_size = 16 data = b'NVWillow v.1.10x00' (total 16) parsed = Container: value = u'NVWillow v.1.10' (total 15) description = u'Description' (total 11) Container: type = u'MAST' (total 4) data_size = 14 data = b'FalloutNV.esmx00' (total 14) parsed = Container: value = u'FalloutNV.esm' (total 13) description = u'Master Plugin' (total 13) Container: type = u'DATA' (total 4) data_size = 8 data = b'x00x00x00x00x00x00x00x00' (total 8) parsed = Container: value = 0 description = u'File Size' (total 9) Container: type = u'ONAM' (total 4) data_size = 68 data = b'Vxe3x0cx00xc3xe3x0cx00xc4xe3x0cx00xc5xe3x0cx00'... (truncated, total 68) parsed = Container: value = ListContainer: 844630 844739 844740 844741 1372461 1372463 1383111 1385321 1387301 1387302 1387303 1387304 1387906 1457771 1479505 1520201 1544392 description = u'Overridden Records' (total 18) >>> >>> # iterate over KEYM records (only 1 in this plugin) ... >>> for record in plugin.iter_records('KEYM'): ... print(record) ... Container: type = u'KEYM' (total 4) data_size = 279 flags = Container: id = 17415634 revision = 0 version = 15 data = b'EDIDx17x00WillowNova'... (truncated, total 279) subrecords = ListContainer: Container: type = u'EDID' (total 4) data_size = 23 data = b'WillowNovacBunga'... (truncated, total 23) parsed = Container: value = u'WillowNovacBungalowKey' (total 22) description = u'Editor ID' (total 9) Container: type = u'OBND' (total 4) data_size = 12 data = b'xffxffxfcxffx00x00x01x00x04x00x00x00' (total 12) parsed = Container: value = Container: X1 = -1 Y1 = -4 Z1 = 0 X2 = 1 Y2 = 4 Z2 = 0 description = u'Object Bounds' (total 13) Container: type = u'FULL' (total 4) data_size = 27 data = b'Dino Dee-lite Bu'... (truncated, total 27) parsed = Container: value = u'Dino Dee-lite Bungalow Key' (total 26) description = u'Name' (total 4) Container: type = u'MODL' (total 4) data_size = 23 data = b'Clutter\Key01Dir'... (truncated, total 23) parsed = Container: value = u'Clutter\Key01Dirty.NIF' (total 22) description = u'Model Filename' (total 14) Container: type = u'ICON' (total 4) data_size = 48 data = b'Interface\Icons\'... (truncated, total 48) parsed = Container: value = u'Interface\Icons\PipboyImages\Ite'... (truncated, total 47) description = u'Large Icon Filename' (total 19) Container: type = u'MICO' (total 4) data_size = 66 data = b'Interface\Icons\'... (truncated, total 66) parsed = Container: value = u'Interface\Icons\PipboyImages_sma'... (truncated, total 65) description = u'Small Icon Filename' (total 19) Container: type = u'SCRI' (total 4) data_size = 4 data = b'T.nx01' (total 4) parsed = Container: value = FormID(form_id=17444436, forms=['SCPT']) description = u'Script' (total 6) Container: type = u'YNAM' (total 4) data_size = 4 data = b'xbbx10x07x00' (total 4) parsed = Container: value = FormID(form_id=463035, forms=['SOUN']) description = u'Sound - Pick Up' (total 15) Container: type = u'ZNAM' (total 4) data_size = 4 data = b'xbcx10x07x00' (total 4) parsed = Container: value = FormID(form_id=463036, forms=['SOUN']) description = u'Sound - Drop' (total 12) Container: type = u'DATA' (total 4) data_size = 8 data = b'x00x00x00x00x00x00x00x00' (total 8) parsed = Container: value = Container: value = 0 weight = 0.0 description = u'Data' (total 4)

BSA

Bethesda's default archive structure.

>>> from bethesda_structs.archive.bsa import BSAArchive >>> archive = BSAArchive.parse_file('/media/sf_VMShared/bsa/Campfire.bsa') >>> print(archive) BSAArchive(filepath=PosixPath('/media/sf_VMShared/bsa/Campfire.bsa')) >>> >>> # print archive header ... >>> print(archive.container.header) Container: magic = b'BSAx00' (total 4) version = 105 directory_offset = 36 archive_flags = Container: directories_named = True files_named = True directory_count = 4 file_count = 493 directory_names_length = 50 file_names_length = 14839 file_flags = Container: >>> >>> # print last directory block, containing 1 file record ... >>> print(archive.container.directory_blocks[-1]) Container: name = u'meshes\mpsx00' (total 11) file_records = ListContainer: Container: hash = 16183754957220078963 size = 2384 offset = 25094933 >>> >>> # print archived filenames (only first 5, 488 more) ... >>> print(archive.container.file_names) ListContainer: _camp_objectplacementindicatorthread01.psc _camp_objectplacementindicatorthread02.psc _camp_objectplacementindicatorthread03.psc _camp_tentsitlayscript.psc campcampfire.psc ... >>> >>> # extract archive to directory ... >>> archive.extract('/home/USER/Downloads')

BA2

Bethesda's second archive structure (used in Fallout 4).
BTDX archives (BA2) are harder to extract than their previous version BA2.

The two available archive subtypes are both supported.

General (GNRL)

Used to store generic files in a compressed/bundled file.

>>> from bethesda_structs.archive.btdx import BTDXArchive >>> archive = BTDXArchive.parse_file('/media/sf_VMShared/ba2/CheatTerminal - Main.ba2') >>> print(archive) BTDXArchive(filepath=PosixPath('/media/sf_VMShared/ba2/CheatTerminal - Main.ba2')) >>> >>> # print archive header ... >>> print(archive.container.header) Container: magic = b'BTDX' (total 4) version = 1 type = u'GNRL' (total 4) file_count = 982 names_offset = 3600179 >>> >>> # print first archive file entry ... >>> print(archive.container.files[0]) Container: hash = 153050373 ext = u'pex' (total 3) directory_hash = 1081231424 offset = 35376 packed_size = 0 unpacked_size = 887 >>> >>> # extract archive to directory ... >>> archive.extract('/home/USER/Downloads')

Direct Draw (DX10)

Used to store (specifically) Microsoft Direct Draw textures.

>>> from bethesda_structs.archive.btdx import BTDXArchive >>> archive = BTDXArchive.parse_file('/media/sf_VMShared/ba2/AK74m - Textures.ba2') >>> print(archive) BTDXArchive(filepath=PosixPath('/media/sf_VMShared/ba2/AK74m - Textures.ba2')) >>> >>> # print archive header ... >>> print(archive.container.header) Container: magic = b'BTDX' (total 4) version = 1 type = u'DX10' (total 4) file_count = 116 names_offset = 329069673 >>> >>> # print first archive file entry ... >>> print(archive.container.files[0]) Container: header = Container: hash = 362144756 ext = u'dds' (total 3) directory_hash = 1416395408 chunks_count = 4 chunk_header_size = 24 height = 2048 width = 2048 mips_count = 12 format = 99 chunks = ListContainer: Container: offset = 11136 packed_size = 2714729 unpacked_size = 4194304 start_mip = 0 end_mip = 0 Container: offset = 2725865 packed_size = 840614 unpacked_size = 1048576 start_mip = 1 end_mip = 1 Container: offset = 3566479 packed_size = 217598 unpacked_size = 262144 start_mip = 2 end_mip = 2 Container: offset = 3784077 packed_size = 71579 unpacked_size = 87408 start_mip = 3 end_mip = 11 >>> >>> # extract archive to directory ... >>> archive.extract('/home/USER/Downloads')

About

A wrapper for Bethesda's popular plugin/archive file formats

License:MIT License


Languages

Language:Python 99.9%Language:Shell 0.1%