diff --git a/pytai/kaitai/formats/amlogic_emmc_partitions.py b/pytai/kaitai/formats/amlogic_emmc_partitions.py new file mode 100644 index 0000000..1c310d5 --- /dev/null +++ b/pytai/kaitai/formats/amlogic_emmc_partitions.py @@ -0,0 +1,244 @@ +# Creative Commons Legal Code +# +# CC0 1.0 Universal +# +# CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE +# LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN +# ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS +# INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES +# REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS +# PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM +# THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED +# HEREUNDER. +# +# Statement of Purpose +# +# The laws of most jurisdictions throughout the world automatically confer +# exclusive Copyright and Related Rights (defined below) upon the creator +# and subsequent owner(s) (each and all, an "owner") of an original work of +# authorship and/or a database (each, a "Work"). +# +# Certain owners wish to permanently relinquish those rights to a Work for +# the purpose of contributing to a commons of creative, cultural and +# scientific works ("Commons") that the public can reliably and without fear +# of later claims of infringement build upon, modify, incorporate in other +# works, reuse and redistribute as freely as possible in any form whatsoever +# and for any purposes, including without limitation commercial purposes. +# These owners may contribute to the Commons to promote the ideal of a free +# culture and the further production of creative, cultural and scientific +# works, or to gain reputation or greater distribution for their Work in +# part through the use and efforts of others. +# +# For these and/or other purposes and motivations, and without any +# expectation of additional consideration or compensation, the person +# associating CC0 with a Work (the "Affirmer"), to the extent that he or she +# is an owner of Copyright and Related Rights in the Work, voluntarily +# elects to apply CC0 to the Work and publicly distribute the Work under its +# terms, with knowledge of his or her Copyright and Related Rights in the +# Work and the meaning and intended legal effect of CC0 on those rights. +# +# 1. Copyright and Related Rights. A Work made available under CC0 may be +# protected by copyright and related or neighboring rights ("Copyright and +# Related Rights"). Copyright and Related Rights include, but are not +# limited to, the following: +# +# i. the right to reproduce, adapt, distribute, perform, display, +# communicate, and translate a Work; +# ii. moral rights retained by the original author(s) and/or performer(s); +# iii. publicity and privacy rights pertaining to a person's image or +# likeness depicted in a Work; +# iv. rights protecting against unfair competition in regards to a Work, +# subject to the limitations in paragraph 4(a), below; +# v. rights protecting the extraction, dissemination, use and reuse of data +# in a Work; +# vi. database rights (such as those arising under Directive 96/9/EC of the +# European Parliament and of the Council of 11 March 1996 on the legal +# protection of databases, and under any national implementation +# thereof, including any amended or successor version of such +# directive); and +# vii. other similar, equivalent or corresponding rights throughout the +# world based on applicable law or treaty, and any national +# implementations thereof. +# +# 2. Waiver. To the greatest extent permitted by, but not in contravention +# of, applicable law, Affirmer hereby overtly, fully, permanently, +# irrevocably and unconditionally waives, abandons, and surrenders all of +# Affirmer's Copyright and Related Rights and associated claims and causes +# of action, whether now known or unknown (including existing as well as +# future claims and causes of action), in the Work (i) in all territories +# worldwide, (ii) for the maximum duration provided by applicable law or +# treaty (including future time extensions), (iii) in any current or future +# medium and for any number of copies, and (iv) for any purpose whatsoever, +# including without limitation commercial, advertising or promotional +# purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +# member of the public at large and to the detriment of Affirmer's heirs and +# successors, fully intending that such Waiver shall not be subject to +# revocation, rescission, cancellation, termination, or any other legal or +# equitable action to disrupt the quiet enjoyment of the Work by the public +# as contemplated by Affirmer's express Statement of Purpose. +# +# 3. Public License Fallback. Should any part of the Waiver for any reason +# be judged legally invalid or ineffective under applicable law, then the +# Waiver shall be preserved to the maximum extent permitted taking into +# account Affirmer's express Statement of Purpose. In addition, to the +# extent the Waiver is so judged Affirmer hereby grants to each affected +# person a royalty-free, non transferable, non sublicensable, non exclusive, +# irrevocable and unconditional license to exercise Affirmer's Copyright and +# Related Rights in the Work (i) in all territories worldwide, (ii) for the +# maximum duration provided by applicable law or treaty (including future +# time extensions), (iii) in any current or future medium and for any number +# of copies, and (iv) for any purpose whatsoever, including without +# limitation commercial, advertising or promotional purposes (the +# "License"). The License shall be deemed effective as of the date CC0 was +# applied by Affirmer to the Work. Should any part of the License for any +# reason be judged legally invalid or ineffective under applicable law, such +# partial invalidity or ineffectiveness shall not invalidate the remainder +# of the License, and in such case Affirmer hereby affirms that he or she +# will not (i) exercise any of his or her remaining Copyright and Related +# Rights in the Work or (ii) assert any associated claims and causes of +# action with respect to the Work, in either case contrary to Affirmer's +# express Statement of Purpose. +# +# 4. Limitations and Disclaimers. +# +# a. No trademark or patent rights held by Affirmer are waived, abandoned, +# surrendered, licensed or otherwise affected by this document. +# b. Affirmer offers the Work as-is and makes no representations or +# warranties of any kind concerning the Work, express, implied, +# statutory or otherwise, including without limitation warranties of +# title, merchantability, fitness for a particular purpose, non +# infringement, or the absence of latent or other defects, accuracy, or +# the present or absence of errors, whether or not discoverable, all to +# the greatest extent permissible under applicable law. +# c. Affirmer disclaims responsibility for clearing rights of other persons +# that may apply to the Work or any use thereof, including without +# limitation any person's Copyright and Related Rights in the Work. +# Further, Affirmer disclaims responsibility for obtaining any necessary +# consents, permissions or other rights required for any use of the +# Work. +# d. Affirmer understands and acknowledges that Creative Commons is not a +# party to this document and has no duty or obligation with respect to +# this CC0 or use of the Work. + +# This file was compiled from a KSY format file downloaded from: +# https://github.com/kaitai-io/kaitai_struct_formats + + +# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild + +import kaitaistruct +from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO +import collections + + +if getattr(kaitaistruct, 'API_VERSION', (0, 9)) < (0, 9): + raise Exception("Incompatible Kaitai Struct Python API: 0.9 or later is required, but you have %s" % (kaitaistruct.__version__)) + +class AmlogicEmmcPartitions(KaitaiStruct): + """This is an unnamed and undocumented partition table format implemented by + the bootloader and kernel that Amlogic provides for their Linux SoCs (Meson + series at least, and probably others). They appear to use this rather than GPT, + the industry standard, because their BootROM loads and executes the next stage + loader from offset 512 (0x200) on the eMMC, which is exactly where the GPT + header would have to start. So instead of changing their BootROM, Amlogic + devised this partition table, which lives at an offset of 36MiB (0x240_0000) + on the eMMC and so doesn't conflict. This parser expects as input just the + partition table from that offset. The maximum number of partitions in a table + is 32, which corresponds to a maximum table size of 1304 bytes (0x518). + + .. seealso:: + Source - http://aml-code.amlogic.com/kernel/common.git/tree/include/linux/mmc/emmc_partitions.h?id=18a4a87072ababf76ea08c8539e939b5b8a440ef + + + .. seealso:: + Source - http://aml-code.amlogic.com/kernel/common.git/tree/drivers/amlogic/mmc/emmc_partitions.c?id=18a4a87072ababf76ea08c8539e939b5b8a440ef + """ + SEQ_FIELDS = ["magic", "version", "num_partitions", "checksum", "partitions"] + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self._debug = collections.defaultdict(dict) + + def _read(self): + self._debug['magic']['start'] = self._io.pos() + self.magic = self._io.read_bytes(4) + self._debug['magic']['end'] = self._io.pos() + if not self.magic == b"\x4D\x50\x54\x00": + raise kaitaistruct.ValidationNotEqualError(b"\x4D\x50\x54\x00", self.magic, self._io, u"/seq/0") + self._debug['version']['start'] = self._io.pos() + self.version = (KaitaiStream.bytes_terminate(self._io.read_bytes(12), 0, False)).decode(u"UTF-8") + self._debug['version']['end'] = self._io.pos() + self._debug['num_partitions']['start'] = self._io.pos() + self.num_partitions = self._io.read_s4le() + self._debug['num_partitions']['end'] = self._io.pos() + if not self.num_partitions >= 1: + raise kaitaistruct.ValidationLessThanError(1, self.num_partitions, self._io, u"/seq/2") + if not self.num_partitions <= 32: + raise kaitaistruct.ValidationGreaterThanError(32, self.num_partitions, self._io, u"/seq/2") + self._debug['checksum']['start'] = self._io.pos() + self.checksum = self._io.read_u4le() + self._debug['checksum']['end'] = self._io.pos() + self._debug['partitions']['start'] = self._io.pos() + self.partitions = [] + for i in range(self.num_partitions): + if not 'arr' in self._debug['partitions']: + self._debug['partitions']['arr'] = [] + self._debug['partitions']['arr'].append({'start': self._io.pos()}) + _t_partitions = AmlogicEmmcPartitions.Partition(self._io, self, self._root) + _t_partitions._read() + self.partitions.append(_t_partitions) + self._debug['partitions']['arr'][i]['end'] = self._io.pos() + + self._debug['partitions']['end'] = self._io.pos() + + class Partition(KaitaiStruct): + SEQ_FIELDS = ["name", "size", "offset", "flags", "padding"] + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self._debug = collections.defaultdict(dict) + + def _read(self): + self._debug['name']['start'] = self._io.pos() + self.name = (KaitaiStream.bytes_terminate(self._io.read_bytes(16), 0, False)).decode(u"UTF-8") + self._debug['name']['end'] = self._io.pos() + self._debug['size']['start'] = self._io.pos() + self.size = self._io.read_u8le() + self._debug['size']['end'] = self._io.pos() + self._debug['offset']['start'] = self._io.pos() + self.offset = self._io.read_u8le() + self._debug['offset']['end'] = self._io.pos() + self._debug['flags']['start'] = self._io.pos() + self._raw_flags = self._io.read_bytes(4) + _io__raw_flags = KaitaiStream(BytesIO(self._raw_flags)) + self.flags = AmlogicEmmcPartitions.Partition.PartFlags(_io__raw_flags, self, self._root) + self.flags._read() + self._debug['flags']['end'] = self._io.pos() + self._debug['padding']['start'] = self._io.pos() + self.padding = self._io.read_bytes(4) + self._debug['padding']['end'] = self._io.pos() + + class PartFlags(KaitaiStruct): + SEQ_FIELDS = ["is_code", "is_cache", "is_data"] + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self._debug = collections.defaultdict(dict) + + def _read(self): + self._debug['is_code']['start'] = self._io.pos() + self.is_code = self._io.read_bits_int_le(1) != 0 + self._debug['is_code']['end'] = self._io.pos() + self._debug['is_cache']['start'] = self._io.pos() + self.is_cache = self._io.read_bits_int_le(1) != 0 + self._debug['is_cache']['end'] = self._io.pos() + self._debug['is_data']['start'] = self._io.pos() + self.is_data = self._io.read_bits_int_le(1) != 0 + self._debug['is_data']['end'] = self._io.pos() + + + + diff --git a/pytai/kaitai/formats/elf.py b/pytai/kaitai/formats/elf.py index 45750ad..0643326 100644 --- a/pytai/kaitai/formats/elf.py +++ b/pytai/kaitai/formats/elf.py @@ -669,7 +669,7 @@ def _read(self): self._debug['header']['end'] = self._io.pos() class EndianElf(KaitaiStruct): - SEQ_FIELDS = ["e_type", "machine", "e_version", "entry_point", "program_header_offset", "section_header_offset", "flags", "e_ehsize", "program_header_entry_size", "qty_program_header", "section_header_entry_size", "qty_section_header", "section_names_idx"] + SEQ_FIELDS = ["e_type", "machine", "e_version", "entry_point", "program_header_offset", "section_header_offset", "flags", "e_ehsize", "len_program_headers", "num_program_headers", "len_section_headers", "num_section_headers", "section_names_idx"] def __init__(self, _io, _parent=None, _root=None): self._io = _io self._parent = _parent @@ -726,18 +726,18 @@ def _read_le(self): self._debug['e_ehsize']['start'] = self._io.pos() self.e_ehsize = self._io.read_u2le() self._debug['e_ehsize']['end'] = self._io.pos() - self._debug['program_header_entry_size']['start'] = self._io.pos() - self.program_header_entry_size = self._io.read_u2le() - self._debug['program_header_entry_size']['end'] = self._io.pos() - self._debug['qty_program_header']['start'] = self._io.pos() - self.qty_program_header = self._io.read_u2le() - self._debug['qty_program_header']['end'] = self._io.pos() - self._debug['section_header_entry_size']['start'] = self._io.pos() - self.section_header_entry_size = self._io.read_u2le() - self._debug['section_header_entry_size']['end'] = self._io.pos() - self._debug['qty_section_header']['start'] = self._io.pos() - self.qty_section_header = self._io.read_u2le() - self._debug['qty_section_header']['end'] = self._io.pos() + self._debug['len_program_headers']['start'] = self._io.pos() + self.len_program_headers = self._io.read_u2le() + self._debug['len_program_headers']['end'] = self._io.pos() + self._debug['num_program_headers']['start'] = self._io.pos() + self.num_program_headers = self._io.read_u2le() + self._debug['num_program_headers']['end'] = self._io.pos() + self._debug['len_section_headers']['start'] = self._io.pos() + self.len_section_headers = self._io.read_u2le() + self._debug['len_section_headers']['end'] = self._io.pos() + self._debug['num_section_headers']['start'] = self._io.pos() + self.num_section_headers = self._io.read_u2le() + self._debug['num_section_headers']['end'] = self._io.pos() self._debug['section_names_idx']['start'] = self._io.pos() self.section_names_idx = self._io.read_u2le() self._debug['section_names_idx']['end'] = self._io.pos() @@ -779,18 +779,18 @@ def _read_be(self): self._debug['e_ehsize']['start'] = self._io.pos() self.e_ehsize = self._io.read_u2be() self._debug['e_ehsize']['end'] = self._io.pos() - self._debug['program_header_entry_size']['start'] = self._io.pos() - self.program_header_entry_size = self._io.read_u2be() - self._debug['program_header_entry_size']['end'] = self._io.pos() - self._debug['qty_program_header']['start'] = self._io.pos() - self.qty_program_header = self._io.read_u2be() - self._debug['qty_program_header']['end'] = self._io.pos() - self._debug['section_header_entry_size']['start'] = self._io.pos() - self.section_header_entry_size = self._io.read_u2be() - self._debug['section_header_entry_size']['end'] = self._io.pos() - self._debug['qty_section_header']['start'] = self._io.pos() - self.qty_section_header = self._io.read_u2be() - self._debug['qty_section_header']['end'] = self._io.pos() + self._debug['len_program_headers']['start'] = self._io.pos() + self.len_program_headers = self._io.read_u2be() + self._debug['len_program_headers']['end'] = self._io.pos() + self._debug['num_program_headers']['start'] = self._io.pos() + self.num_program_headers = self._io.read_u2be() + self._debug['num_program_headers']['end'] = self._io.pos() + self._debug['len_section_headers']['start'] = self._io.pos() + self.len_section_headers = self._io.read_u2be() + self._debug['len_section_headers']['end'] = self._io.pos() + self._debug['num_section_headers']['start'] = self._io.pos() + self.num_section_headers = self._io.read_u2be() + self._debug['num_section_headers']['end'] = self._io.pos() self._debug['section_names_idx']['start'] = self._io.pos() self.section_names_idx = self._io.read_u2be() self._debug['section_names_idx']['end'] = self._io.pos() @@ -1361,7 +1361,7 @@ def linked_section(self): if hasattr(self, '_m_linked_section'): return self._m_linked_section - if ((self.linked_section_idx != Elf.SectionHeaderIdxSpecial.undefined.value) and (self.linked_section_idx < self._root.header.qty_section_header)) : + if ((self.linked_section_idx != Elf.SectionHeaderIdxSpecial.undefined.value) and (self.linked_section_idx < self._root.header.num_section_headers)) : self._m_linked_section = self._root.header.section_headers[self.linked_section_idx] return getattr(self, '_m_linked_section', None) @@ -1948,11 +1948,11 @@ def program_headers(self): if self._is_le: self._raw__m_program_headers = [] self._m_program_headers = [] - for i in range(self.qty_program_header): + for i in range(self.num_program_headers): if not 'arr' in self._debug['_m_program_headers']: self._debug['_m_program_headers']['arr'] = [] self._debug['_m_program_headers']['arr'].append({'start': self._io.pos()}) - self._raw__m_program_headers.append(self._io.read_bytes(self.program_header_entry_size)) + self._raw__m_program_headers.append(self._io.read_bytes(self.len_program_headers)) _io__raw__m_program_headers = KaitaiStream(BytesIO(self._raw__m_program_headers[i])) _t__m_program_headers = Elf.EndianElf.ProgramHeader(_io__raw__m_program_headers, self, self._root, self._is_le) _t__m_program_headers._read() @@ -1962,11 +1962,11 @@ def program_headers(self): else: self._raw__m_program_headers = [] self._m_program_headers = [] - for i in range(self.qty_program_header): + for i in range(self.num_program_headers): if not 'arr' in self._debug['_m_program_headers']: self._debug['_m_program_headers']['arr'] = [] self._debug['_m_program_headers']['arr'].append({'start': self._io.pos()}) - self._raw__m_program_headers.append(self._io.read_bytes(self.program_header_entry_size)) + self._raw__m_program_headers.append(self._io.read_bytes(self.len_program_headers)) _io__raw__m_program_headers = KaitaiStream(BytesIO(self._raw__m_program_headers[i])) _t__m_program_headers = Elf.EndianElf.ProgramHeader(_io__raw__m_program_headers, self, self._root, self._is_le) _t__m_program_headers._read() @@ -1988,11 +1988,11 @@ def section_headers(self): if self._is_le: self._raw__m_section_headers = [] self._m_section_headers = [] - for i in range(self.qty_section_header): + for i in range(self.num_section_headers): if not 'arr' in self._debug['_m_section_headers']: self._debug['_m_section_headers']['arr'] = [] self._debug['_m_section_headers']['arr'].append({'start': self._io.pos()}) - self._raw__m_section_headers.append(self._io.read_bytes(self.section_header_entry_size)) + self._raw__m_section_headers.append(self._io.read_bytes(self.len_section_headers)) _io__raw__m_section_headers = KaitaiStream(BytesIO(self._raw__m_section_headers[i])) _t__m_section_headers = Elf.EndianElf.SectionHeader(_io__raw__m_section_headers, self, self._root, self._is_le) _t__m_section_headers._read() @@ -2002,11 +2002,11 @@ def section_headers(self): else: self._raw__m_section_headers = [] self._m_section_headers = [] - for i in range(self.qty_section_header): + for i in range(self.num_section_headers): if not 'arr' in self._debug['_m_section_headers']: self._debug['_m_section_headers']['arr'] = [] self._debug['_m_section_headers']['arr'].append({'start': self._io.pos()}) - self._raw__m_section_headers.append(self._io.read_bytes(self.section_header_entry_size)) + self._raw__m_section_headers.append(self._io.read_bytes(self.len_section_headers)) _io__raw__m_section_headers = KaitaiStream(BytesIO(self._raw__m_section_headers[i])) _t__m_section_headers = Elf.EndianElf.SectionHeader(_io__raw__m_section_headers, self, self._root, self._is_le) _t__m_section_headers._read() @@ -2022,7 +2022,7 @@ def section_names(self): if hasattr(self, '_m_section_names'): return self._m_section_names - if ((self.section_names_idx != Elf.SectionHeaderIdxSpecial.undefined.value) and (self.section_names_idx < self._root.header.qty_section_header)) : + if ((self.section_names_idx != Elf.SectionHeaderIdxSpecial.undefined.value) and (self.section_names_idx < self._root.header.num_section_headers)) : _pos = self._io.pos() self._io.seek(self.section_headers[self.section_names_idx].ofs_body) self._debug['_m_section_names']['start'] = self._io.pos() diff --git a/pytai/kaitai/formats/microsoft_pe.py b/pytai/kaitai/formats/microsoft_pe.py index a80960d..69e843f 100644 --- a/pytai/kaitai/formats/microsoft_pe.py +++ b/pytai/kaitai/formats/microsoft_pe.py @@ -689,22 +689,23 @@ class MachineType(Enum): unknown = 0 i386 = 332 r4000 = 358 - wcemipsv2 = 361 + wce_mips_v2 = 361 alpha = 388 sh3 = 418 - sh3dsp = 419 + sh3_dsp = 419 sh4 = 422 sh5 = 424 arm = 448 thumb = 450 - armnt = 452 + arm_nt = 452 am33 = 467 powerpc = 496 - powerpcfp = 497 + powerpc_fp = 497 ia64 = 512 mips16 = 614 - mipsfpu = 870 - mipsfpu16 = 1126 + alpha64_or_axp64 = 644 + mips_fpu = 870 + mips16_fpu = 1126 ebc = 3772 riscv32 = 20530 riscv64 = 20580 diff --git a/pytai/kaitai/formats/minecraft_nbt.py b/pytai/kaitai/formats/minecraft_nbt.py index 6e99c95..0738610 100644 --- a/pytai/kaitai/formats/minecraft_nbt.py +++ b/pytai/kaitai/formats/minecraft_nbt.py @@ -138,14 +138,14 @@ class MinecraftNbt(KaitaiStruct): """A structured binary format native to Minecraft for saving game data and transferring it over the network (in multiplayer), such as player data - ([`.dat`](https://minecraft.fandom.com/wiki/Player.dat_format); contains + ([`.dat`](https://minecraft.wiki/w/Player.dat_format); contains e.g. player's inventory and location), saved worlds ([`level.dat`]( - https://minecraft.fandom.com/wiki/Java_Edition_level_format#level.dat_format - ) and [Chunk format](https://minecraft.fandom.com/wiki/Chunk_format#NBT_structure)), + https://minecraft.wiki/w/Java_Edition_level_format#level.dat_format + ) and [Chunk format](https://minecraft.wiki/w/Chunk_format#NBT_structure)), list of saved multiplayer servers - ([`servers.dat`](https://minecraft.fandom.com/wiki/Servers.dat_format)) and so on - - see . + ([`servers.dat`](https://minecraft.wiki/w/Servers.dat_format)) and so on - + see . The entire file should be _gzip_-compressed (in accordance with the original specification [NBT.txt]( @@ -214,7 +214,7 @@ class MinecraftNbt(KaitaiStruct): .. seealso:: - Source - https://minecraft.fandom.com/wiki/NBT_format + Source - https://minecraft.wiki/w/NBT_format """ class Tag(Enum): diff --git a/pytai/kaitai/formats/pcap.py b/pytai/kaitai/formats/pcap.py index 77c6fbd..5b91520 100644 --- a/pytai/kaitai/formats/pcap.py +++ b/pytai/kaitai/formats/pcap.py @@ -452,17 +452,17 @@ def _read(self): self._debug['body']['start'] = self._io.pos() _on = self._root.hdr.network if _on == Pcap.Linktype.ppi: - self._raw_body = self._io.read_bytes(self.incl_len) + self._raw_body = self._io.read_bytes((self.incl_len if self.incl_len < self._root.hdr.snaplen else self._root.hdr.snaplen)) _io__raw_body = KaitaiStream(BytesIO(self._raw_body)) self.body = packet_ppi.PacketPpi(_io__raw_body) self.body._read() elif _on == Pcap.Linktype.ethernet: - self._raw_body = self._io.read_bytes(self.incl_len) + self._raw_body = self._io.read_bytes((self.incl_len if self.incl_len < self._root.hdr.snaplen else self._root.hdr.snaplen)) _io__raw_body = KaitaiStream(BytesIO(self._raw_body)) self.body = ethernet_frame.EthernetFrame(_io__raw_body) self.body._read() else: - self.body = self._io.read_bytes(self.incl_len) + self.body = self._io.read_bytes((self.incl_len if self.incl_len < self._root.hdr.snaplen else self._root.hdr.snaplen)) self._debug['body']['end'] = self._io.pos() diff --git a/pytai/kaitai/formats/protocol_body.py b/pytai/kaitai/formats/protocol_body.py index 20fb1a3..e7c1898 100644 --- a/pytai/kaitai/formats/protocol_body.py +++ b/pytai/kaitai/formats/protocol_body.py @@ -137,9 +137,9 @@ import ipv4_packet import icmp_packet +import tcp_segment import udp_datagram import ipv6_packet -import tcp_segment class ProtocolBody(KaitaiStruct): """Protocol body represents particular payload on transport level (OSI layer 4). @@ -366,7 +366,7 @@ def _read(self): self.hdr_ext_len = self._io.read_u1() self._debug['hdr_ext_len']['end'] = self._io.pos() self._debug['body']['start'] = self._io.pos() - self.body = self._io.read_bytes((self.hdr_ext_len - 1)) + self.body = self._io.read_bytes(((self.hdr_ext_len - 1) if self.hdr_ext_len > 0 else 1)) self._debug['body']['end'] = self._io.pos() self._debug['next_header']['start'] = self._io.pos() self.next_header = ProtocolBody(self.next_header_type, self._io) diff --git a/pytai/kaitai/formats/quake2_md2.py b/pytai/kaitai/formats/quake2_md2.py new file mode 100644 index 0000000..131f07d --- /dev/null +++ b/pytai/kaitai/formats/quake2_md2.py @@ -0,0 +1,710 @@ +# Creative Commons Legal Code +# +# CC0 1.0 Universal +# +# CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE +# LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN +# ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS +# INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES +# REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS +# PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM +# THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED +# HEREUNDER. +# +# Statement of Purpose +# +# The laws of most jurisdictions throughout the world automatically confer +# exclusive Copyright and Related Rights (defined below) upon the creator +# and subsequent owner(s) (each and all, an "owner") of an original work of +# authorship and/or a database (each, a "Work"). +# +# Certain owners wish to permanently relinquish those rights to a Work for +# the purpose of contributing to a commons of creative, cultural and +# scientific works ("Commons") that the public can reliably and without fear +# of later claims of infringement build upon, modify, incorporate in other +# works, reuse and redistribute as freely as possible in any form whatsoever +# and for any purposes, including without limitation commercial purposes. +# These owners may contribute to the Commons to promote the ideal of a free +# culture and the further production of creative, cultural and scientific +# works, or to gain reputation or greater distribution for their Work in +# part through the use and efforts of others. +# +# For these and/or other purposes and motivations, and without any +# expectation of additional consideration or compensation, the person +# associating CC0 with a Work (the "Affirmer"), to the extent that he or she +# is an owner of Copyright and Related Rights in the Work, voluntarily +# elects to apply CC0 to the Work and publicly distribute the Work under its +# terms, with knowledge of his or her Copyright and Related Rights in the +# Work and the meaning and intended legal effect of CC0 on those rights. +# +# 1. Copyright and Related Rights. A Work made available under CC0 may be +# protected by copyright and related or neighboring rights ("Copyright and +# Related Rights"). Copyright and Related Rights include, but are not +# limited to, the following: +# +# i. the right to reproduce, adapt, distribute, perform, display, +# communicate, and translate a Work; +# ii. moral rights retained by the original author(s) and/or performer(s); +# iii. publicity and privacy rights pertaining to a person's image or +# likeness depicted in a Work; +# iv. rights protecting against unfair competition in regards to a Work, +# subject to the limitations in paragraph 4(a), below; +# v. rights protecting the extraction, dissemination, use and reuse of data +# in a Work; +# vi. database rights (such as those arising under Directive 96/9/EC of the +# European Parliament and of the Council of 11 March 1996 on the legal +# protection of databases, and under any national implementation +# thereof, including any amended or successor version of such +# directive); and +# vii. other similar, equivalent or corresponding rights throughout the +# world based on applicable law or treaty, and any national +# implementations thereof. +# +# 2. Waiver. To the greatest extent permitted by, but not in contravention +# of, applicable law, Affirmer hereby overtly, fully, permanently, +# irrevocably and unconditionally waives, abandons, and surrenders all of +# Affirmer's Copyright and Related Rights and associated claims and causes +# of action, whether now known or unknown (including existing as well as +# future claims and causes of action), in the Work (i) in all territories +# worldwide, (ii) for the maximum duration provided by applicable law or +# treaty (including future time extensions), (iii) in any current or future +# medium and for any number of copies, and (iv) for any purpose whatsoever, +# including without limitation commercial, advertising or promotional +# purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +# member of the public at large and to the detriment of Affirmer's heirs and +# successors, fully intending that such Waiver shall not be subject to +# revocation, rescission, cancellation, termination, or any other legal or +# equitable action to disrupt the quiet enjoyment of the Work by the public +# as contemplated by Affirmer's express Statement of Purpose. +# +# 3. Public License Fallback. Should any part of the Waiver for any reason +# be judged legally invalid or ineffective under applicable law, then the +# Waiver shall be preserved to the maximum extent permitted taking into +# account Affirmer's express Statement of Purpose. In addition, to the +# extent the Waiver is so judged Affirmer hereby grants to each affected +# person a royalty-free, non transferable, non sublicensable, non exclusive, +# irrevocable and unconditional license to exercise Affirmer's Copyright and +# Related Rights in the Work (i) in all territories worldwide, (ii) for the +# maximum duration provided by applicable law or treaty (including future +# time extensions), (iii) in any current or future medium and for any number +# of copies, and (iv) for any purpose whatsoever, including without +# limitation commercial, advertising or promotional purposes (the +# "License"). The License shall be deemed effective as of the date CC0 was +# applied by Affirmer to the Work. Should any part of the License for any +# reason be judged legally invalid or ineffective under applicable law, such +# partial invalidity or ineffectiveness shall not invalidate the remainder +# of the License, and in such case Affirmer hereby affirms that he or she +# will not (i) exercise any of his or her remaining Copyright and Related +# Rights in the Work or (ii) assert any associated claims and causes of +# action with respect to the Work, in either case contrary to Affirmer's +# express Statement of Purpose. +# +# 4. Limitations and Disclaimers. +# +# a. No trademark or patent rights held by Affirmer are waived, abandoned, +# surrendered, licensed or otherwise affected by this document. +# b. Affirmer offers the Work as-is and makes no representations or +# warranties of any kind concerning the Work, express, implied, +# statutory or otherwise, including without limitation warranties of +# title, merchantability, fitness for a particular purpose, non +# infringement, or the absence of latent or other defects, accuracy, or +# the present or absence of errors, whether or not discoverable, all to +# the greatest extent permissible under applicable law. +# c. Affirmer disclaims responsibility for clearing rights of other persons +# that may apply to the Work or any use thereof, including without +# limitation any person's Copyright and Related Rights in the Work. +# Further, Affirmer disclaims responsibility for obtaining any necessary +# consents, permissions or other rights required for any use of the +# Work. +# d. Affirmer understands and acknowledges that Creative Commons is not a +# party to this document and has no duty or obligation with respect to +# this CC0 or use of the Work. + +# This file was compiled from a KSY format file downloaded from: +# https://github.com/kaitai-io/kaitai_struct_formats + + +# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild + +import kaitaistruct +from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO +from enum import Enum +import collections + + +if getattr(kaitaistruct, 'API_VERSION', (0, 9)) < (0, 9): + raise Exception("Incompatible Kaitai Struct Python API: 0.9 or later is required, but you have %s" % (kaitaistruct.__version__)) + +class Quake2Md2(KaitaiStruct): + """The MD2 format is used for 3D animated models in id Sofware's Quake II. + + A model consists of named `frames`, each with the same number of `vertices` + (`vertices_per_frame`). Each such vertex has a `position` and `normal` in + model space. Each vertex has the same topological "meaning" across frames, in + terms of triangle and texture info; it just varies in position and normal for + animation purposes. + + How the vertices form triangles is defined via disjoint `triangles` or via + `gl_cmds` (which allows strip and fan topology). Each triangle contains three + `vertex_indices` into frame vertices, and three `tex_point_indices` into + global `tex_coords`. Each texture point has pixel coords `s_px` and `t_px` + ranging from 0 to `skin_{width,height}_px` respectively, along with + `{s,t}_normalized` ranging from 0 to 1 for your convenience. + + A GL command has a `primitive` type (`TRIANGLE_FAN` or `TRIANGLE_STRIP`) along + with some `vertices`. Each GL vertex contains `tex_coords_normalized` from 0 + to 1, and a `vertex_index` into frame vertices. + + A model may also contain `skins`, which are just file paths to PCX images. + However, this is empty for many models, in which case it is up to the client + (e.g. Q2PRO) to offer skins some other way (e.g. by similar filename in the + current directory). + + There are 198 `frames` in total, partitioned into a fixed set of ranges used + for different animations. Each frame has a standard `name` for humans, but the + client just uses their index and the name can be arbitrary. The name, start + frame index and frame count of each animation can be looked up in the arrays + `anim_names`, `anim_start_indices`, and `anim_num_frames` respectively. This + information is summarized in the following table: + + ``` + | INDEX | NAME | SUFFIX | NOTES | + |:--------:|--------:|:-------|:-------------------------------------------------------| + | 0-39 | stand | 01-40 | Idle animation | + | 40-45 | run | 1-6 | Full run cycle | + | 46-53 | attack | 1-8 | Shoot, reload; some weapons just repeat 1st few frames | + | 54-57 | pain1 | 01-04 | Q2Pro also uses this for switching weapons | + | 58-61 | pain2 | 01-04 | | + | 62-65 | pain3 | 01-04 | | + | 66-71 | jump | 1-6 | Starts at height and lands on feet | + | 72-83 | flip | 01-12 | Flipoff, i.e. middle finger | + | 84-94 | salute | 01-11 | | + | 95-111 | taunt | 01-17 | | + | 112-122 | wave | 01-11 | Q2Pro plays this backwards for a handgrenade toss | + | 123-134 | point | 01-12 | | + | 135-153 | crstnd | 01-19 | Idle while crouching | + | 154-159 | crwalk | 1-6 | | + | 160-168 | crattak | 1-9 | | + | 169-172 | crpain | 1-4 | | + | 173-177 | crdeath | 1-5 | | + | 178-183 | death1 | 01-06 | | + | 184-189 | death2 | 01-06 | | + | 190-197 | death3 | 01-08 | | + ``` + + The above are filled in for player models; for the separate weapon models, + the final frame is 173 "g_view" (unknown purpose) since weapons aren't shown + during death animations. `a_grenades.md2`, the handgrenade weapon model, is + the same except that the `wave` frames are blank (according to the default + female model files). This is likely due to its dual use as a grenade throw + animation where this model must leave the player's model. + + .. seealso:: + Source - https://icculus.org/~phaethon/q3a/formats/md2-schoenblum.html + + + .. seealso:: + Source - http://tfc.duke.free.fr/coding/md2-specs-en.html + + + .. seealso:: + Source - http://tastyspleen.net/~panjoo/downloads/quake2_model_frames.html + + + .. seealso:: + Source - http://wiki.polycount.com/wiki/OldSiteResourcesQuake2FramesList + """ + + class GlPrimitive(Enum): + triangle_strip = 0 + triangle_fan = 1 + SEQ_FIELDS = ["magic", "version", "skin_width_px", "skin_height_px", "bytes_per_frame", "num_skins", "vertices_per_frame", "num_tex_coords", "num_triangles", "num_gl_cmds", "num_frames", "ofs_skins", "ofs_tex_coords", "ofs_triangles", "ofs_frames", "ofs_gl_cmds", "ofs_eof"] + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self._debug = collections.defaultdict(dict) + + def _read(self): + self._debug['magic']['start'] = self._io.pos() + self.magic = self._io.read_bytes(4) + self._debug['magic']['end'] = self._io.pos() + if not self.magic == b"\x49\x44\x50\x32": + raise kaitaistruct.ValidationNotEqualError(b"\x49\x44\x50\x32", self.magic, self._io, u"/seq/0") + self._debug['version']['start'] = self._io.pos() + self.version = self._io.read_u4le() + self._debug['version']['end'] = self._io.pos() + if not self.version == 8: + raise kaitaistruct.ValidationNotEqualError(8, self.version, self._io, u"/seq/1") + self._debug['skin_width_px']['start'] = self._io.pos() + self.skin_width_px = self._io.read_u4le() + self._debug['skin_width_px']['end'] = self._io.pos() + self._debug['skin_height_px']['start'] = self._io.pos() + self.skin_height_px = self._io.read_u4le() + self._debug['skin_height_px']['end'] = self._io.pos() + self._debug['bytes_per_frame']['start'] = self._io.pos() + self.bytes_per_frame = self._io.read_u4le() + self._debug['bytes_per_frame']['end'] = self._io.pos() + self._debug['num_skins']['start'] = self._io.pos() + self.num_skins = self._io.read_u4le() + self._debug['num_skins']['end'] = self._io.pos() + self._debug['vertices_per_frame']['start'] = self._io.pos() + self.vertices_per_frame = self._io.read_u4le() + self._debug['vertices_per_frame']['end'] = self._io.pos() + self._debug['num_tex_coords']['start'] = self._io.pos() + self.num_tex_coords = self._io.read_u4le() + self._debug['num_tex_coords']['end'] = self._io.pos() + self._debug['num_triangles']['start'] = self._io.pos() + self.num_triangles = self._io.read_u4le() + self._debug['num_triangles']['end'] = self._io.pos() + self._debug['num_gl_cmds']['start'] = self._io.pos() + self.num_gl_cmds = self._io.read_u4le() + self._debug['num_gl_cmds']['end'] = self._io.pos() + self._debug['num_frames']['start'] = self._io.pos() + self.num_frames = self._io.read_u4le() + self._debug['num_frames']['end'] = self._io.pos() + self._debug['ofs_skins']['start'] = self._io.pos() + self.ofs_skins = self._io.read_u4le() + self._debug['ofs_skins']['end'] = self._io.pos() + self._debug['ofs_tex_coords']['start'] = self._io.pos() + self.ofs_tex_coords = self._io.read_u4le() + self._debug['ofs_tex_coords']['end'] = self._io.pos() + self._debug['ofs_triangles']['start'] = self._io.pos() + self.ofs_triangles = self._io.read_u4le() + self._debug['ofs_triangles']['end'] = self._io.pos() + self._debug['ofs_frames']['start'] = self._io.pos() + self.ofs_frames = self._io.read_u4le() + self._debug['ofs_frames']['end'] = self._io.pos() + self._debug['ofs_gl_cmds']['start'] = self._io.pos() + self.ofs_gl_cmds = self._io.read_u4le() + self._debug['ofs_gl_cmds']['end'] = self._io.pos() + self._debug['ofs_eof']['start'] = self._io.pos() + self.ofs_eof = self._io.read_u4le() + self._debug['ofs_eof']['end'] = self._io.pos() + + class Vertex(KaitaiStruct): + SEQ_FIELDS = ["position", "normal_index"] + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self._debug = collections.defaultdict(dict) + + def _read(self): + self._debug['position']['start'] = self._io.pos() + self.position = Quake2Md2.CompressedVec(self._io, self, self._root) + self.position._read() + self._debug['position']['end'] = self._io.pos() + self._debug['normal_index']['start'] = self._io.pos() + self.normal_index = self._io.read_u1() + self._debug['normal_index']['end'] = self._io.pos() + + @property + def normal(self): + if hasattr(self, '_m_normal'): + return self._m_normal + + self._m_normal = self._root.anorms_table[self.normal_index] + return getattr(self, '_m_normal', None) + + + class CompressedVec(KaitaiStruct): + SEQ_FIELDS = ["x_compressed", "y_compressed", "z_compressed"] + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self._debug = collections.defaultdict(dict) + + def _read(self): + self._debug['x_compressed']['start'] = self._io.pos() + self.x_compressed = self._io.read_u1() + self._debug['x_compressed']['end'] = self._io.pos() + self._debug['y_compressed']['start'] = self._io.pos() + self.y_compressed = self._io.read_u1() + self._debug['y_compressed']['end'] = self._io.pos() + self._debug['z_compressed']['start'] = self._io.pos() + self.z_compressed = self._io.read_u1() + self._debug['z_compressed']['end'] = self._io.pos() + + @property + def x(self): + if hasattr(self, '_m_x'): + return self._m_x + + self._m_x = ((self.x_compressed * self._parent._parent.scale.x) + self._parent._parent.translate.x) + return getattr(self, '_m_x', None) + + @property + def y(self): + if hasattr(self, '_m_y'): + return self._m_y + + self._m_y = ((self.y_compressed * self._parent._parent.scale.y) + self._parent._parent.translate.y) + return getattr(self, '_m_y', None) + + @property + def z(self): + if hasattr(self, '_m_z'): + return self._m_z + + self._m_z = ((self.z_compressed * self._parent._parent.scale.z) + self._parent._parent.translate.z) + return getattr(self, '_m_z', None) + + + class Triangle(KaitaiStruct): + SEQ_FIELDS = ["vertex_indices", "tex_point_indices"] + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self._debug = collections.defaultdict(dict) + + def _read(self): + self._debug['vertex_indices']['start'] = self._io.pos() + self.vertex_indices = [] + for i in range(3): + if not 'arr' in self._debug['vertex_indices']: + self._debug['vertex_indices']['arr'] = [] + self._debug['vertex_indices']['arr'].append({'start': self._io.pos()}) + self.vertex_indices.append(self._io.read_u2le()) + self._debug['vertex_indices']['arr'][i]['end'] = self._io.pos() + + self._debug['vertex_indices']['end'] = self._io.pos() + self._debug['tex_point_indices']['start'] = self._io.pos() + self.tex_point_indices = [] + for i in range(3): + if not 'arr' in self._debug['tex_point_indices']: + self._debug['tex_point_indices']['arr'] = [] + self._debug['tex_point_indices']['arr'].append({'start': self._io.pos()}) + self.tex_point_indices.append(self._io.read_u2le()) + self._debug['tex_point_indices']['arr'][i]['end'] = self._io.pos() + + self._debug['tex_point_indices']['end'] = self._io.pos() + + + class Frame(KaitaiStruct): + SEQ_FIELDS = ["scale", "translate", "name", "vertices"] + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self._debug = collections.defaultdict(dict) + + def _read(self): + self._debug['scale']['start'] = self._io.pos() + self.scale = Quake2Md2.Vec3f(self._io, self, self._root) + self.scale._read() + self._debug['scale']['end'] = self._io.pos() + self._debug['translate']['start'] = self._io.pos() + self.translate = Quake2Md2.Vec3f(self._io, self, self._root) + self.translate._read() + self._debug['translate']['end'] = self._io.pos() + self._debug['name']['start'] = self._io.pos() + self.name = (KaitaiStream.bytes_terminate(self._io.read_bytes(16), 0, False)).decode(u"ascii") + self._debug['name']['end'] = self._io.pos() + self._debug['vertices']['start'] = self._io.pos() + self.vertices = [] + for i in range(self._root.vertices_per_frame): + if not 'arr' in self._debug['vertices']: + self._debug['vertices']['arr'] = [] + self._debug['vertices']['arr'].append({'start': self._io.pos()}) + _t_vertices = Quake2Md2.Vertex(self._io, self, self._root) + _t_vertices._read() + self.vertices.append(_t_vertices) + self._debug['vertices']['arr'][i]['end'] = self._io.pos() + + self._debug['vertices']['end'] = self._io.pos() + + + class GlCmdsList(KaitaiStruct): + SEQ_FIELDS = ["items"] + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self._debug = collections.defaultdict(dict) + + def _read(self): + if not (self._io.is_eof()): + self._debug['items']['start'] = self._io.pos() + self.items = [] + i = 0 + while True: + if not 'arr' in self._debug['items']: + self._debug['items']['arr'] = [] + self._debug['items']['arr'].append({'start': self._io.pos()}) + _t_items = Quake2Md2.GlCmd(self._io, self, self._root) + _t_items._read() + _ = _t_items + self.items.append(_) + self._debug['items']['arr'][len(self.items) - 1]['end'] = self._io.pos() + if _.cmd_num_vertices == 0: + break + i += 1 + self._debug['items']['end'] = self._io.pos() + + + + class TexPoint(KaitaiStruct): + SEQ_FIELDS = ["s_px", "t_px"] + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self._debug = collections.defaultdict(dict) + + def _read(self): + self._debug['s_px']['start'] = self._io.pos() + self.s_px = self._io.read_u2le() + self._debug['s_px']['end'] = self._io.pos() + self._debug['t_px']['start'] = self._io.pos() + self.t_px = self._io.read_u2le() + self._debug['t_px']['end'] = self._io.pos() + + @property + def s_normalized(self): + if hasattr(self, '_m_s_normalized'): + return self._m_s_normalized + + self._m_s_normalized = ((self.s_px + 0.0) / self._root.skin_width_px) + return getattr(self, '_m_s_normalized', None) + + @property + def t_normalized(self): + if hasattr(self, '_m_t_normalized'): + return self._m_t_normalized + + self._m_t_normalized = ((self.t_px + 0.0) / self._root.skin_height_px) + return getattr(self, '_m_t_normalized', None) + + + class Vec3f(KaitaiStruct): + SEQ_FIELDS = ["x", "y", "z"] + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self._debug = collections.defaultdict(dict) + + def _read(self): + self._debug['x']['start'] = self._io.pos() + self.x = self._io.read_f4le() + self._debug['x']['end'] = self._io.pos() + self._debug['y']['start'] = self._io.pos() + self.y = self._io.read_f4le() + self._debug['y']['end'] = self._io.pos() + self._debug['z']['start'] = self._io.pos() + self.z = self._io.read_f4le() + self._debug['z']['end'] = self._io.pos() + + + class GlVertex(KaitaiStruct): + SEQ_FIELDS = ["tex_coords_normalized", "vertex_index"] + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self._debug = collections.defaultdict(dict) + + def _read(self): + self._debug['tex_coords_normalized']['start'] = self._io.pos() + self.tex_coords_normalized = [] + for i in range(2): + if not 'arr' in self._debug['tex_coords_normalized']: + self._debug['tex_coords_normalized']['arr'] = [] + self._debug['tex_coords_normalized']['arr'].append({'start': self._io.pos()}) + self.tex_coords_normalized.append(self._io.read_f4le()) + self._debug['tex_coords_normalized']['arr'][i]['end'] = self._io.pos() + + self._debug['tex_coords_normalized']['end'] = self._io.pos() + self._debug['vertex_index']['start'] = self._io.pos() + self.vertex_index = self._io.read_u4le() + self._debug['vertex_index']['end'] = self._io.pos() + + + class GlCmd(KaitaiStruct): + SEQ_FIELDS = ["cmd_num_vertices", "vertices"] + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self._debug = collections.defaultdict(dict) + + def _read(self): + self._debug['cmd_num_vertices']['start'] = self._io.pos() + self.cmd_num_vertices = self._io.read_s4le() + self._debug['cmd_num_vertices']['end'] = self._io.pos() + self._debug['vertices']['start'] = self._io.pos() + self.vertices = [] + for i in range(self.num_vertices): + if not 'arr' in self._debug['vertices']: + self._debug['vertices']['arr'] = [] + self._debug['vertices']['arr'].append({'start': self._io.pos()}) + _t_vertices = Quake2Md2.GlVertex(self._io, self, self._root) + _t_vertices._read() + self.vertices.append(_t_vertices) + self._debug['vertices']['arr'][i]['end'] = self._io.pos() + + self._debug['vertices']['end'] = self._io.pos() + + @property + def num_vertices(self): + if hasattr(self, '_m_num_vertices'): + return self._m_num_vertices + + self._m_num_vertices = (-(self.cmd_num_vertices) if self.cmd_num_vertices < 0 else self.cmd_num_vertices) + return getattr(self, '_m_num_vertices', None) + + @property + def primitive(self): + if hasattr(self, '_m_primitive'): + return self._m_primitive + + self._m_primitive = (Quake2Md2.GlPrimitive.triangle_fan if self.cmd_num_vertices < 0 else Quake2Md2.GlPrimitive.triangle_strip) + return getattr(self, '_m_primitive', None) + + + @property + def anim_num_frames(self): + if hasattr(self, '_m_anim_num_frames'): + return self._m_anim_num_frames + + self._m_anim_num_frames = b"\x28\x06\x08\x04\x04\x04\x06\x0C\x0B\x11\x0B\x0C\x13\x06\x09\x04\x05\x06\x06\x08" + return getattr(self, '_m_anim_num_frames', None) + + @property + def anorms_table(self): + """ + .. seealso:: + Quake anorms.h - https://github.com/skullernet/q2pro/blob/f4faabd/src/common/math.c#L80 + from + """ + if hasattr(self, '_m_anorms_table'): + return self._m_anorms_table + + self._m_anorms_table = [[-0.525731, 0.000000, 0.850651], [-0.442863, 0.238856, 0.864188], [-0.295242, 0.000000, 0.955423], [-0.309017, 0.500000, 0.809017], [-0.162460, 0.262866, 0.951056], [0.000000, 0.000000, 1.000000], [0.000000, 0.850651, 0.525731], [-0.147621, 0.716567, 0.681718], [0.147621, 0.716567, 0.681718], [0.000000, 0.525731, 0.850651], [0.309017, 0.500000, 0.809017], [0.525731, 0.000000, 0.850651], [0.295242, 0.000000, 0.955423], [0.442863, 0.238856, 0.864188], [0.162460, 0.262866, 0.951056], [-0.681718, 0.147621, 0.716567], [-0.809017, 0.309017, 0.500000], [-0.587785, 0.425325, 0.688191], [-0.850651, 0.525731, 0.000000], [-0.864188, 0.442863, 0.238856], [-0.716567, 0.681718, 0.147621], [-0.688191, 0.587785, 0.425325], [-0.500000, 0.809017, 0.309017], [-0.238856, 0.864188, 0.442863], [-0.425325, 0.688191, 0.587785], [-0.716567, 0.681718, -0.147621], [-0.500000, 0.809017, -0.309017], [-0.525731, 0.850651, 0.000000], [0.000000, 0.850651, -0.525731], [-0.238856, 0.864188, -0.442863], [0.000000, 0.955423, -0.295242], [-0.262866, 0.951056, -0.162460], [0.000000, 1.000000, 0.000000], [0.000000, 0.955423, 0.295242], [-0.262866, 0.951056, 0.162460], [0.238856, 0.864188, 0.442863], [0.262866, 0.951056, 0.162460], [0.500000, 0.809017, 0.309017], [0.238856, 0.864188, -0.442863], [0.262866, 0.951056, -0.162460], [0.500000, 0.809017, -0.309017], [0.850651, 0.525731, 0.000000], [0.716567, 0.681718, 0.147621], [0.716567, 0.681718, -0.147621], [0.525731, 0.850651, 0.000000], [0.425325, 0.688191, 0.587785], [0.864188, 0.442863, 0.238856], [0.688191, 0.587785, 0.425325], [0.809017, 0.309017, 0.500000], [0.681718, 0.147621, 0.716567], [0.587785, 0.425325, 0.688191], [0.955423, 0.295242, 0.000000], [1.000000, 0.000000, 0.000000], [0.951056, 0.162460, 0.262866], [0.850651, -0.525731, 0.000000], [0.955423, -0.295242, 0.000000], [0.864188, -0.442863, 0.238856], [0.951056, -0.162460, 0.262866], [0.809017, -0.309017, 0.500000], [0.681718, -0.147621, 0.716567], [0.850651, 0.000000, 0.525731], [0.864188, 0.442863, -0.238856], [0.809017, 0.309017, -0.500000], [0.951056, 0.162460, -0.262866], [0.525731, 0.000000, -0.850651], [0.681718, 0.147621, -0.716567], [0.681718, -0.147621, -0.716567], [0.850651, 0.000000, -0.525731], [0.809017, -0.309017, -0.500000], [0.864188, -0.442863, -0.238856], [0.951056, -0.162460, -0.262866], [0.147621, 0.716567, -0.681718], [0.309017, 0.500000, -0.809017], [0.425325, 0.688191, -0.587785], [0.442863, 0.238856, -0.864188], [0.587785, 0.425325, -0.688191], [0.688191, 0.587785, -0.425325], [-0.147621, 0.716567, -0.681718], [-0.309017, 0.500000, -0.809017], [0.000000, 0.525731, -0.850651], [-0.525731, 0.000000, -0.850651], [-0.442863, 0.238856, -0.864188], [-0.295242, 0.000000, -0.955423], [-0.162460, 0.262866, -0.951056], [0.000000, 0.000000, -1.000000], [0.295242, 0.000000, -0.955423], [0.162460, 0.262866, -0.951056], [-0.442863, -0.238856, -0.864188], [-0.309017, -0.500000, -0.809017], [-0.162460, -0.262866, -0.951056], [0.000000, -0.850651, -0.525731], [-0.147621, -0.716567, -0.681718], [0.147621, -0.716567, -0.681718], [0.000000, -0.525731, -0.850651], [0.309017, -0.500000, -0.809017], [0.442863, -0.238856, -0.864188], [0.162460, -0.262866, -0.951056], [0.238856, -0.864188, -0.442863], [0.500000, -0.809017, -0.309017], [0.425325, -0.688191, -0.587785], [0.716567, -0.681718, -0.147621], [0.688191, -0.587785, -0.425325], [0.587785, -0.425325, -0.688191], [0.000000, -0.955423, -0.295242], [0.000000, -1.000000, 0.000000], [0.262866, -0.951056, -0.162460], [0.000000, -0.850651, 0.525731], [0.000000, -0.955423, 0.295242], [0.238856, -0.864188, 0.442863], [0.262866, -0.951056, 0.162460], [0.500000, -0.809017, 0.309017], [0.716567, -0.681718, 0.147621], [0.525731, -0.850651, 0.000000], [-0.238856, -0.864188, -0.442863], [-0.500000, -0.809017, -0.309017], [-0.262866, -0.951056, -0.162460], [-0.850651, -0.525731, 0.000000], [-0.716567, -0.681718, -0.147621], [-0.716567, -0.681718, 0.147621], [-0.525731, -0.850651, 0.000000], [-0.500000, -0.809017, 0.309017], [-0.238856, -0.864188, 0.442863], [-0.262866, -0.951056, 0.162460], [-0.864188, -0.442863, 0.238856], [-0.809017, -0.309017, 0.500000], [-0.688191, -0.587785, 0.425325], [-0.681718, -0.147621, 0.716567], [-0.442863, -0.238856, 0.864188], [-0.587785, -0.425325, 0.688191], [-0.309017, -0.500000, 0.809017], [-0.147621, -0.716567, 0.681718], [-0.425325, -0.688191, 0.587785], [-0.162460, -0.262866, 0.951056], [0.442863, -0.238856, 0.864188], [0.162460, -0.262866, 0.951056], [0.309017, -0.500000, 0.809017], [0.147621, -0.716567, 0.681718], [0.000000, -0.525731, 0.850651], [0.425325, -0.688191, 0.587785], [0.587785, -0.425325, 0.688191], [0.688191, -0.587785, 0.425325], [-0.955423, 0.295242, 0.000000], [-0.951056, 0.162460, 0.262866], [-1.000000, 0.000000, 0.000000], [-0.850651, 0.000000, 0.525731], [-0.955423, -0.295242, 0.000000], [-0.951056, -0.162460, 0.262866], [-0.864188, 0.442863, -0.238856], [-0.951056, 0.162460, -0.262866], [-0.809017, 0.309017, -0.500000], [-0.864188, -0.442863, -0.238856], [-0.951056, -0.162460, -0.262866], [-0.809017, -0.309017, -0.500000], [-0.681718, 0.147621, -0.716567], [-0.681718, -0.147621, -0.716567], [-0.850651, 0.000000, -0.525731], [-0.688191, 0.587785, -0.425325], [-0.587785, 0.425325, -0.688191], [-0.425325, 0.688191, -0.587785], [-0.425325, -0.688191, -0.587785], [-0.587785, -0.425325, -0.688191], [-0.688191, -0.587785, -0.425325]] + return getattr(self, '_m_anorms_table', None) + + @property + def tex_coords(self): + if hasattr(self, '_m_tex_coords'): + return self._m_tex_coords + + _pos = self._io.pos() + self._io.seek(self.ofs_tex_coords) + self._debug['_m_tex_coords']['start'] = self._io.pos() + self._m_tex_coords = [] + for i in range(self.num_tex_coords): + if not 'arr' in self._debug['_m_tex_coords']: + self._debug['_m_tex_coords']['arr'] = [] + self._debug['_m_tex_coords']['arr'].append({'start': self._io.pos()}) + _t__m_tex_coords = Quake2Md2.TexPoint(self._io, self, self._root) + _t__m_tex_coords._read() + self._m_tex_coords.append(_t__m_tex_coords) + self._debug['_m_tex_coords']['arr'][i]['end'] = self._io.pos() + + self._debug['_m_tex_coords']['end'] = self._io.pos() + self._io.seek(_pos) + return getattr(self, '_m_tex_coords', None) + + @property + def triangles(self): + if hasattr(self, '_m_triangles'): + return self._m_triangles + + _pos = self._io.pos() + self._io.seek(self.ofs_triangles) + self._debug['_m_triangles']['start'] = self._io.pos() + self._m_triangles = [] + for i in range(self.num_triangles): + if not 'arr' in self._debug['_m_triangles']: + self._debug['_m_triangles']['arr'] = [] + self._debug['_m_triangles']['arr'].append({'start': self._io.pos()}) + _t__m_triangles = Quake2Md2.Triangle(self._io, self, self._root) + _t__m_triangles._read() + self._m_triangles.append(_t__m_triangles) + self._debug['_m_triangles']['arr'][i]['end'] = self._io.pos() + + self._debug['_m_triangles']['end'] = self._io.pos() + self._io.seek(_pos) + return getattr(self, '_m_triangles', None) + + @property + def frames(self): + if hasattr(self, '_m_frames'): + return self._m_frames + + _pos = self._io.pos() + self._io.seek(self.ofs_frames) + self._debug['_m_frames']['start'] = self._io.pos() + self._raw__m_frames = [] + self._m_frames = [] + for i in range(self.num_frames): + if not 'arr' in self._debug['_m_frames']: + self._debug['_m_frames']['arr'] = [] + self._debug['_m_frames']['arr'].append({'start': self._io.pos()}) + self._raw__m_frames.append(self._io.read_bytes(self.bytes_per_frame)) + _io__raw__m_frames = KaitaiStream(BytesIO(self._raw__m_frames[i])) + _t__m_frames = Quake2Md2.Frame(_io__raw__m_frames, self, self._root) + _t__m_frames._read() + self._m_frames.append(_t__m_frames) + self._debug['_m_frames']['arr'][i]['end'] = self._io.pos() + + self._debug['_m_frames']['end'] = self._io.pos() + self._io.seek(_pos) + return getattr(self, '_m_frames', None) + + @property + def anim_names(self): + if hasattr(self, '_m_anim_names'): + return self._m_anim_names + + self._m_anim_names = [u"stand", u"run", u"attack", u"pain1", u"pain2", u"pain3", u"jump", u"flip", u"salute", u"taunt", u"wave", u"point", u"crstnd", u"crwalk", u"crattak", u"crpain", u"crdeath", u"death1", u"death2", u"death3"] + return getattr(self, '_m_anim_names', None) + + @property + def gl_cmds(self): + if hasattr(self, '_m_gl_cmds'): + return self._m_gl_cmds + + _pos = self._io.pos() + self._io.seek(self.ofs_gl_cmds) + self._debug['_m_gl_cmds']['start'] = self._io.pos() + self._raw__m_gl_cmds = self._io.read_bytes((4 * self.num_gl_cmds)) + _io__raw__m_gl_cmds = KaitaiStream(BytesIO(self._raw__m_gl_cmds)) + self._m_gl_cmds = Quake2Md2.GlCmdsList(_io__raw__m_gl_cmds, self, self._root) + self._m_gl_cmds._read() + self._debug['_m_gl_cmds']['end'] = self._io.pos() + self._io.seek(_pos) + return getattr(self, '_m_gl_cmds', None) + + @property + def skins(self): + if hasattr(self, '_m_skins'): + return self._m_skins + + _pos = self._io.pos() + self._io.seek(self.ofs_skins) + self._debug['_m_skins']['start'] = self._io.pos() + self._m_skins = [] + for i in range(self.num_skins): + if not 'arr' in self._debug['_m_skins']: + self._debug['_m_skins']['arr'] = [] + self._debug['_m_skins']['arr'].append({'start': self._io.pos()}) + self._m_skins.append((KaitaiStream.bytes_terminate(self._io.read_bytes(64), 0, False)).decode(u"ascii")) + self._debug['_m_skins']['arr'][i]['end'] = self._io.pos() + + self._debug['_m_skins']['end'] = self._io.pos() + self._io.seek(_pos) + return getattr(self, '_m_skins', None) + + @property + def anim_start_indices(self): + if hasattr(self, '_m_anim_start_indices'): + return self._m_anim_start_indices + + self._m_anim_start_indices = b"\x00\x28\x2E\x36\x3A\x3E\x42\x48\x54\x5F\x70\x7B\x87\x9A\xA0\xA9\xAD\xB2\xB8\xBE" + return getattr(self, '_m_anim_start_indices', None) + + diff --git a/pytai/kaitai/formats/tcp_segment.py b/pytai/kaitai/formats/tcp_segment.py index c3238c8..2ade512 100644 --- a/pytai/kaitai/formats/tcp_segment.py +++ b/pytai/kaitai/formats/tcp_segment.py @@ -140,7 +140,7 @@ class TcpSegment(KaitaiStruct): guarantees of delivery, order of segments and avoidance of duplicate delivery. """ - SEQ_FIELDS = ["src_port", "dst_port", "seq_num", "ack_num", "b12", "b13", "window_size", "checksum", "urgent_pointer", "body"] + SEQ_FIELDS = ["src_port", "dst_port", "seq_num", "ack_num", "data_offset", "reserved", "flags", "window_size", "checksum", "urgent_pointer", "options", "body"] def __init__(self, _io, _parent=None, _root=None): self._io = _io self._parent = _parent @@ -160,12 +160,17 @@ def _read(self): self._debug['ack_num']['start'] = self._io.pos() self.ack_num = self._io.read_u4be() self._debug['ack_num']['end'] = self._io.pos() - self._debug['b12']['start'] = self._io.pos() - self.b12 = self._io.read_u1() - self._debug['b12']['end'] = self._io.pos() - self._debug['b13']['start'] = self._io.pos() - self.b13 = self._io.read_u1() - self._debug['b13']['end'] = self._io.pos() + self._debug['data_offset']['start'] = self._io.pos() + self.data_offset = self._io.read_bits_int_be(4) + self._debug['data_offset']['end'] = self._io.pos() + self._debug['reserved']['start'] = self._io.pos() + self.reserved = self._io.read_bits_int_be(4) + self._debug['reserved']['end'] = self._io.pos() + self._io.align_to_byte() + self._debug['flags']['start'] = self._io.pos() + self.flags = TcpSegment.Flags(self._io, self, self._root) + self.flags._read() + self._debug['flags']['end'] = self._io.pos() self._debug['window_size']['start'] = self._io.pos() self.window_size = self._io.read_u2be() self._debug['window_size']['end'] = self._io.pos() @@ -175,8 +180,53 @@ def _read(self): self._debug['urgent_pointer']['start'] = self._io.pos() self.urgent_pointer = self._io.read_u2be() self._debug['urgent_pointer']['end'] = self._io.pos() + if ((self.data_offset * 4) - 20) != 0: + self._debug['options']['start'] = self._io.pos() + self.options = self._io.read_bytes(((self.data_offset * 4) - 20)) + self._debug['options']['end'] = self._io.pos() + self._debug['body']['start'] = self._io.pos() self.body = self._io.read_bytes_full() self._debug['body']['end'] = self._io.pos() + class Flags(KaitaiStruct): + """TCP header flags as defined "TCP Header Flags" registry. + """ + SEQ_FIELDS = ["cwr", "ece", "urg", "ack", "psh", "rst", "syn", "fin"] + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self._debug = collections.defaultdict(dict) + + def _read(self): + self._debug['cwr']['start'] = self._io.pos() + self.cwr = self._io.read_bits_int_be(1) != 0 + self._debug['cwr']['end'] = self._io.pos() + self._debug['ece']['start'] = self._io.pos() + self.ece = self._io.read_bits_int_be(1) != 0 + self._debug['ece']['end'] = self._io.pos() + self._debug['urg']['start'] = self._io.pos() + self.urg = self._io.read_bits_int_be(1) != 0 + self._debug['urg']['end'] = self._io.pos() + self._debug['ack']['start'] = self._io.pos() + self.ack = self._io.read_bits_int_be(1) != 0 + self._debug['ack']['end'] = self._io.pos() + self._debug['psh']['start'] = self._io.pos() + self.psh = self._io.read_bits_int_be(1) != 0 + self._debug['psh']['end'] = self._io.pos() + self._debug['rst']['start'] = self._io.pos() + self.rst = self._io.read_bits_int_be(1) != 0 + self._debug['rst']['end'] = self._io.pos() + self._debug['syn']['start'] = self._io.pos() + self.syn = self._io.read_bits_int_be(1) != 0 + self._debug['syn']['end'] = self._io.pos() + self._debug['fin']['start'] = self._io.pos() + self.fin = self._io.read_bits_int_be(1) != 0 + self._debug['fin']['end'] = self._io.pos() + + + def __repr__(self): + return (u"|CWR" if self.cwr else u"") + (u"|ECE" if self.ece else u"") + (u"|URG" if self.urg else u"") + (u"|ACK" if self.ack else u"") + (u"|PSH" if self.psh else u"") + (u"|RST" if self.rst else u"") + (u"|SYN" if self.syn else u"") + (u"|FIN" if self.fin else u"") + diff --git a/pytai/kaitai/formats/uefi_te.py b/pytai/kaitai/formats/uefi_te.py index e618c93..d896db9 100644 --- a/pytai/kaitai/formats/uefi_te.py +++ b/pytai/kaitai/formats/uefi_te.py @@ -191,26 +191,29 @@ class MachineType(Enum): unknown = 0 i386 = 332 r4000 = 358 - wcemipsv2 = 361 + wce_mips_v2 = 361 alpha = 388 sh3 = 418 - sh3dsp = 419 + sh3_dsp = 419 sh4 = 422 sh5 = 424 arm = 448 thumb = 450 - armnt = 452 + arm_nt = 452 am33 = 467 powerpc = 496 - powerpcfp = 497 + powerpc_fp = 497 ia64 = 512 mips16 = 614 - mipsfpu = 870 - mipsfpu16 = 1126 + alpha64_or_axp64 = 644 + mips_fpu = 870 + mips16_fpu = 1126 ebc = 3772 riscv32 = 20530 riscv64 = 20580 riscv128 = 20776 + loongarch32 = 25138 + loongarch64 = 25188 amd64 = 34404 m32r = 36929 arm64 = 43620 diff --git a/pytai/kaitai/formats/vlq_base128_be.py b/pytai/kaitai/formats/vlq_base128_be.py index 4d8718b..2a02a11 100644 --- a/pytai/kaitai/formats/vlq_base128_be.py +++ b/pytai/kaitai/formats/vlq_base128_be.py @@ -178,7 +178,7 @@ def _read(self): class Group(KaitaiStruct): """One byte group, clearly divided into 7-bit "value" chunk and 1-bit "continuation" flag. """ - SEQ_FIELDS = ["b"] + SEQ_FIELDS = ["has_next", "value"] def __init__(self, _io, _parent=None, _root=None): self._io = _io self._parent = _parent @@ -186,27 +186,12 @@ def __init__(self, _io, _parent=None, _root=None): self._debug = collections.defaultdict(dict) def _read(self): - self._debug['b']['start'] = self._io.pos() - self.b = self._io.read_u1() - self._debug['b']['end'] = self._io.pos() - - @property - def has_next(self): - """If true, then we have more bytes to read.""" - if hasattr(self, '_m_has_next'): - return self._m_has_next - - self._m_has_next = (self.b & 128) != 0 - return getattr(self, '_m_has_next', None) - - @property - def value(self): - """The 7-bit (base128) numeric value chunk of this group.""" - if hasattr(self, '_m_value'): - return self._m_value - - self._m_value = (self.b & 127) - return getattr(self, '_m_value', None) + self._debug['has_next']['start'] = self._io.pos() + self.has_next = self._io.read_bits_int_be(1) != 0 + self._debug['has_next']['end'] = self._io.pos() + self._debug['value']['start'] = self._io.pos() + self.value = self._io.read_bits_int_be(7) + self._debug['value']['end'] = self._io.pos() @property diff --git a/pytai/kaitai/formats/vlq_base128_le.py b/pytai/kaitai/formats/vlq_base128_le.py index c22f151..7bc2b27 100644 --- a/pytai/kaitai/formats/vlq_base128_le.py +++ b/pytai/kaitai/formats/vlq_base128_le.py @@ -183,7 +183,7 @@ def _read(self): class Group(KaitaiStruct): """One byte group, clearly divided into 7-bit "value" chunk and 1-bit "continuation" flag. """ - SEQ_FIELDS = ["b"] + SEQ_FIELDS = ["has_next", "value"] def __init__(self, _io, _parent=None, _root=None): self._io = _io self._parent = _parent @@ -191,27 +191,12 @@ def __init__(self, _io, _parent=None, _root=None): self._debug = collections.defaultdict(dict) def _read(self): - self._debug['b']['start'] = self._io.pos() - self.b = self._io.read_u1() - self._debug['b']['end'] = self._io.pos() - - @property - def has_next(self): - """If true, then we have more bytes to read.""" - if hasattr(self, '_m_has_next'): - return self._m_has_next - - self._m_has_next = (self.b & 128) != 0 - return getattr(self, '_m_has_next', None) - - @property - def value(self): - """The 7-bit (base128) numeric value chunk of this group.""" - if hasattr(self, '_m_value'): - return self._m_value - - self._m_value = (self.b & 127) - return getattr(self, '_m_value', None) + self._debug['has_next']['start'] = self._io.pos() + self.has_next = self._io.read_bits_int_be(1) != 0 + self._debug['has_next']['end'] = self._io.pos() + self._debug['value']['start'] = self._io.pos() + self.value = self._io.read_bits_int_be(7) + self._debug['value']['end'] = self._io.pos() @property diff --git a/tests/resources/elf.xml b/tests/resources/elf.xml index 2cdbabb..b9be95f 100644 --- a/tests/resources/elf.xml +++ b/tests/resources/elf.xml @@ -16,10 +16,10 @@ - - - - + + + + diff --git a/utils/compile_kaitai_formats.py b/utils/compile_kaitai_formats.py index 264fed7..2094283 100644 --- a/utils/compile_kaitai_formats.py +++ b/utils/compile_kaitai_formats.py @@ -87,7 +87,7 @@ def compile_kaitai_formats() -> None: print(f"\nThe following unknown licenses were encountered:\n{unknown_licenses}") def main(): - parser = argparse.ArgumentParser(description='Cloand and compile Kaitai formats') + parser = argparse.ArgumentParser(description='Clone and compile Kaitai formats') parser.add_argument("-s", "--skip-clone", action = "store_true", default = False, help = "Skip cloning the Kaitai struct repo") args = parser.parse_args()