bethesda文件格式的解析器
bethesda-structs的Python项目详细描述
关于
modding bethesda的游戏可以是一门艺术。
这个包打算提供clean和accessible方法来解析和理解bethesda的文件类型。
例如:
Bethesda的归档文件有很多“非归档”工具(.bsa和.ba2),但是没有读取这些文件类型的好的编程方法。 使用这个包,理解关于存档的每一个小细节都是简单而直接的(请参见BSA Usage和BA2 Usage)。
有关更高级的用法和信息,check out the documentation。
The supported filetypes are parsers not writers.We do not currently support the writing of archives or plugins.
安装
因为这是光荣的python,所以安装bethesda-structs应该非常简单。
使用git
只需克隆repo并自己构建这个包,就可以使用git安装这个包!
$ git clone https://github.com/stephen-bunn/bethesda-structs.git $ pipenv install --dev $ pipenv run python setup.py install
用法
使用bethesda-structs是为了直接直观。
下面是一些分析各种文件类型的简短示例。
esp
分析插件文件的能力对于理解游戏中的添加和更改非常有帮助。
目前only可以向您公开此信息的其他真正工具是TESEdit及其同级应用程序。
此软件包旨在提供对插件的深入细节的简单编程访问!
Because of how long it takes to build complete subrecord parers for a given plugin version, the only currently supported plugins are:
- ^{tt5}$ - Fallout: New Vegas (partial)
- ^{tt6}$ - 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'HEDR\x0c\x00\x1f\x85\xab?\x97\x12\x00\x00#\xad'... (truncated, total 163) subrecords = ListContainer: Container: type = u'HEDR' (total 4) data_size = 12 data = b'\x1f\x85\xab?\x97\x12\x00\x00#\xad\r\x00' (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'llamaRCA\x00' (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.10\x00' (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.esm\x00' (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'\x00\x00\x00\x00\x00\x00\x00\x00' (total 8) parsed = Container: value = 0 description = u'File Size' (total 9) Container: type = u'ONAM' (total 4) data_size = 68 data = b'V\xe3\x0c\x00\xc3\xe3\x0c\x00\xc4\xe3\x0c\x00\xc5\xe3\x0c\x00'... (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'EDID\x17\x00WillowNova'... (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'\xff\xff\xfc\xff\x00\x00\x01\x00\x04\x00\x00\x00' (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.\n\x01' (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'\xbb\x10\x07\x00' (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'\xbc\x10\x07\x00' (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'\x00\x00\x00\x00\x00\x00\x00\x00' (total 8) parsed = Container: value = Container: value = 0 weight = 0.0 description = u'Data' (total 4)
牛血清白蛋白
贝塞斯达的默认存档结构。
>>> 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'BSA\x00' (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\\mps\x00' (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
贝塞斯达的第二个档案结构(用于放射性尘埃4)。
BTDX存档(BA2)比以前的版本BA2更难提取。
两个可用的存档子类型都受支持。
一般(GNRL)
用于将通用文件存储在压缩/捆绑文件中。
>>> 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')
直接绘制(DX10)
用于存储(特别是)Microsoft Direct Draw纹理。
>>> 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')