# Copyright (C) MatrixEditor 2023-2024## This program is free software: you can redistribute it and/or modify# it under the terms of the GNU General Public License as published by# the Free Software Foundation, either version 3 of the License, or# (at your option) any later version.## This program is distributed in the hope that it will be useful,# but WITHOUT ANY WARRANTY; without even the implied warranty of# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the# GNU General Public License for more details.## You should have received a copy of the GNU General Public License# along with this program. If not, see <https://www.gnu.org/licenses/>.# NOTE: If you decide to use the annotation feature, you have to manually# apply the S_EVAL_ANNOTATIONS option to all structs# from __future__ import annotationsimportenumimportsysfromcaterpillar.fieldsimport*fromcaterpillar.shortcutsimport(struct,this,unpack_file,opt,LittleEndian,ctx,pack_file,)try:# colorized + structured outputfromrichimportprintexceptImportError:pass# These flags will be applied to all structs and fieldsopt.set_struct_flags(opt.S_REPLACE_TYPES,opt.S_SLOTS)opt.set_field_flags(VARINT_LSB)try:fromnumpyimportarrayopt.O_ARRAY_FACTORY.value=arrayexceptImportError:pass
[docs]@struct(order=LittleEndian)classNIBHeader:"""Example class doc comment"""# Here we define a constant value, which will raise an exception# upon a different parsed value.magic:b"NIBArchive""""example field doc comment"""# Primitive types can be used just like thisunknown_1:int32"""second field doc comment"""unknown_2:int32object_count:int32offset_objects:int32key_count:int32offset_keys:int32value_count:int32offset_values:int32class_name_count:int32offset_class_names:int32
@struct(order=LittleEndian)classNIBClassName:# NOTE: the custom class 'vint' is marked as a singleton class. Therefore,# we can use it directly. Otherwise, we have to create an instance first,# before we can use the struct class.length:vintextras_count:vint# This struct will remove all extra null-bytes paddingname:CString(this.length)# Arrays can be created just like this:extras:int32[this.extras_count]# Note that the returned string instance here may contain extra null-bytes# at the end.NIBKey=Prefixed(vint,encoding="utf-8")classValueType(enum.Enum):UNKNOWN=-1INT8=0INT16=1INT32=2INT64=3BOOL_TRUE=4BOOL_FALSE=5FLOAT=6DOUBLE=7DATA=8NIL=9OBJECT_REF=10# The raw data is just copied from the stream. If we don't specify an# encoding, the raw bytes or copied.NIBData=Prefixed(vint)@struct(order=LittleEndian)classNIBValue:key:vint# NOTE the use of a default value; otherwise None would be set.type:Enum(ValueType,uint8,ValueType.UNKNOWN)# The field below describes a simple switch-case structure.value:Field(this.type)>>{ValueType.INT8:int8,ValueType.INT16:int16,ValueType.INT32:int32,ValueType.INT64:int64,ValueType.BOOL_TRUE:Computed(True),ValueType.BOOL_FALSE:Computed(False),ValueType.FLOAT:float32,ValueType.DOUBLE:double,ValueType.DATA:NIBData,ValueType.NIL:Computed(None),ValueType.OBJECT_REF:int32,# The following line shows how to manually return the parsed value (in# this case it would be the result of this.type). NOTE that the value# is only stored temporarily in the current context (not this-context).## If this option is not specified and none of the above matched the input,# an exception will be thrown.DEFAULT_OPTION:Computed(ctx._value),}@struct(order=LittleEndian)classNIBObject:# same usage as abovename:vintvalues:vintvalue_count:vint@struct(order=LittleEndian)classNIBArchive:# Nested structs can be defined as simle as this:header:NIBHeader# All following fields are marked with '@': The parser will jump temporarily# to the position specified after the operator. Use | F_KEEP_POSITION to# continue parsing at the resulting positionobjects:NIBObject[this.header.object_count]@this.header.offset_objectskeys:NIBKey[this.header.key_count]@this.header.offset_keysvalues:NIBValue[this.header.value_count]@this.header.offset_values# NOTE: we can reference parsed values using 'this.foo' but methods or properties# can't be used.class_names:NIBClassName[this.header.class_name_count]@this.header.offset_class_names# print(NIBArchive.__struct__.fields)if__name__=="__main__":obj=unpack_file(NIBArchive,sys.argv[1])print(obj)pack_file(obj,sys.argv[2],use_tempfile=True)