from lib.fileutils import * from dataclasses import dataclass, field from typing import List, Union import math import prettyprinter from prettyprinter import pprint import sys prettyprinter.install_extras(exclude=["ipython", "django", "ipython_repr_pretty", "attrs"]) def with_offset(func): def wrapped_do_offset(*args, **kwargs): offset = kwargs.pop("offset", None) b = args[1] old = b.tell() if offset: b.seek(offset) r = func(*args, **kwargs) if offset: b.seek(old) return r return wrapped_do_offset NAM_FlagHasHandles = (1 << 0) NAM_FlagCaseInsensitive = (1 << 1) ANM_NODE_FLAG_POS_ANIMATED = (1 << 0) ANM_NODE_FLAG_ROT_ANIMATED = (1 << 1) ANM_NODE_FLAG_ROL_ANIMATED = (1 << 2) ANM_NODE_FLAG_SCL_ANIMATED = (1 << 3) ANM_NODE_FLAG_AFF_ANIMATED = (1 << 4) ANM_NODE_FLAG_USR_ANIMATED = (1 << 5) ANM_NODE_FLAG_ANIMATED = ((1 << 6) - 1) NAM_STRING_KEY = 0 NAM_HANDLE_KEY = 1 class ANM_KeyType: @classmethod def get_name(cls, i): for d in cls.__dict__: if cls.__dict__[d] == i: return d return None ANM_KEYS_TYPE_NULL = 0 ANM_KEYS_TYPE_REAL_TCB = 1 ANM_KEYS_TYPE_REAL_BEZ = 2 ANM_KEYS_TYPE_REAL_LIN = 3 ANM_KEYS_TYPE_VEC3_TCB = 4 ANM_KEYS_TYPE_VEC3_BEZ = 5 ANM_KEYS_TYPE_VEC3_LIN = 6 ANM_KEYS_TYPE_QUAT_TCB = 7 ANM_KEYS_TYPE_QUAT_BEZ = 8 ANM_KEYS_TYPE_QUAT_LIN = 9 ANM_KEYS_TYPE_AFFINE_SAM = 10 @dataclass class Vec3: x: float y: float z: float def distance(self, other: 'Vec3') -> float: return math.sqrt(math.pow(self.x - other.x, 2) + math.pow(self.y - other.y, 2) + math.pow(self.z - other.z, 2)) @classmethod @with_offset def create(cls, b): x = read_float_and_print("Vec3->x", b) y = read_float_and_print("Vec3->y", b) z = read_float_and_print("Vec3->z", b) return cls(x=x, y=y, z=z) @dataclass class Vec3Indexed(Vec3): index: int @classmethod @with_offset def create(cls, b): x = read_float_and_print("Vec3Indexed->x", b) y = read_float_and_print("Vec3Indexed->y", b) z = read_float_and_print("Vec3Indexed->z", b) index = read_int_and_print("Vec3Indexed->index", b) return cls(x=x, y=y, z=z, index=index) @dataclass class Vec3IndexedWeighted(Vec3Indexed): weight: float @classmethod @with_offset def create(cls, b): x = read_float_and_print("Vec3IndexedWeighted->x", b) y = read_float_and_print("Vec3IndexedWeighted->y", b) z = read_float_and_print("Vec3IndexedWeighted->z", b) index = read_int_and_print("Vec3IndexedWeighted->index", b) weight = read_float_and_print("Vec3IndexedWeighted->weight", b) return cls(x=x, y=y, z=z, index=index, weight=weight) @dataclass class Vec4(Vec3): w: float @classmethod @with_offset def create(cls, b): x = read_float_and_print("Vec4->x", b) y = read_float_and_print("Vec4->y", b) z = read_float_and_print("Vec4->z", b) w = read_float_and_print("Vec4->w", b) return cls(x=x, y=y, z=z, w=w) @dataclass class AnmKey: time: int @classmethod @with_offset def create(cls, b): time = read_byte_and_print("ANM_Key->Time", b) return cls(time=time) @dataclass class AnmKeys: keys_type: int ort_before: int ort_after: int flags: int p_interpolate: int num_keys: int keys: list @classmethod @with_offset def create(cls, b): keys_type = read_byte_and_print("ANM_Keys->Type", b) debug_print("ANM_KeyType: %s" % ANM_KeyType.get_name(keys_type)) ort_before = read_byte_and_print("ANM_Keys->ORTBefore", b) ort_after = read_byte_and_print("ANM_Keys->ORTAfter", b) flags = read_byte_and_print("ANM_Keys->Flags", b) p_interpolate = read_int_and_print("ANM_Keys->Interpolate", b) num_keys = read_int_and_print("ANM_Keys->NumKeys", b) p_keys = read_int_and_print("ANM_Keys->Keys", b) keys = [] _old = b.tell() b.seek(p_keys) for _ in range(num_keys): keys.append(AnmKey.create(b)) b.seek(_old) return cls(keys_type=keys_type, ort_before=ort_before, ort_after=ort_after, flags=flags, p_interpolate=p_interpolate, num_keys=num_keys, keys=keys) @dataclass class AnmObj: custom: int = field(repr=False) skin: int = field(repr=False) zero_cluster: int total_number_vertices: int num_clusters: int clusters: List['AnmCluster'] @classmethod @with_offset def create(cls, b): custom = read_int_and_print("ANM_Obj->Custom", b) skin = read_int_and_print("ANM_Obj->Skin", b) zero = read_int_and_print("ANM_Obj->ZeroCluster", b) total_vertices = read_int_and_print("ANM_Obj->TotalNumberOfVertices", b) num_clusters = read_int_and_print("ANM_Obj->NumberOfClusters", b) clusters_offset = read_int_and_print("ANM_Obj->Clusters", b) clusters = [] _old1 = b.tell() b.seek(clusters_offset) for _ in range(num_clusters): cluster = AnmCluster.create(b) clusters.append(cluster) b.seek(_old1) r = cls(custom=custom, skin=skin, zero_cluster=zero, total_number_vertices=total_vertices, num_clusters=num_clusters, clusters=clusters) for cluster in clusters: cluster.obj = r return r @dataclass class AnmCluster: custom: int = field(repr=False) obj: Union[AnmObj, None] = field(repr=False) handle: int bounding_box: List[Vec3] = field(repr=False) # size 2 num_vertices: int vertices: List[Vec3Indexed] = field(repr=False) bone_name: str = "" @classmethod @with_offset def create(cls, b): custom = read_int_and_print("ANM_Cluster->Custom", b) obj_offset = read_int_and_print("ANM_Cluster->Obj", b) if obj_offset == 0: # ugly hack, dunno why we need that custom = read_int_and_print("ANM_Cluster->Custom", b) obj_offset = read_int_and_print("ANM_Cluster->Obj", b) obj = None # is set by parent Obj handle = read_int_and_print("ANM_Cluster->Handle", b) bbox1 = Vec3.create(b) bbox2 = Vec3.create(b) bounding_box = [bbox1, bbox2] num_vertices = read_int_and_print("ANM_Cluster->num_vertices", b) vertices = [] for _ in range(num_vertices): v = Vec3Indexed.create(b) vertices.append(v) return cls(custom=custom, obj=obj, handle=handle, bounding_box=bounding_box, num_vertices=num_vertices, vertices=vertices) class NamDictionnary: @staticmethod @with_offset def create(cls, b, depth=1): r = {} read_int_and_print("NAM_Dictionnary->MemGroup", b) # MAP_MemGroup* namemapper = read_int_and_print("NAM_Dictionnary->pNameMapper", b) # MAP_Mapper* flags = read_int_and_print("NAM_Dictionnary->Flags", b) # X_BOOL debug_print("Flags: NAM_FlagHasHandles: %s, NAM_FlagCaseInsensitive: %s" % ( flags & NAM_FlagHasHandles, flags & NAM_FlagCaseInsensitive)) read_short_and_print("NAM_Dictionnary->NameHandle", b) # NAM_NameHandle -> X_UINT16 b.seek(namemapper) read_int_and_print("MemGroup*", b) # MAP_MemGroup* read_int_and_print("MemBaseSize", b) # X_UINT read_int_and_print("MemBlocks", b) # X_UINT read_int_and_print("MemSize", b) # X_UINT read_int_and_print("MemFree", b) # X_UINT read_int_and_print("Mem*", b) # MAP_Mapping* read_int_and_print("MaxMappings", b) # X_UINT totalmappings = read_int_and_print("TotalMappings", b) # X_UINT total_keys = read_int_and_print("TotalKeys", b) # X_UINT p_key_contexts = read_int_and_print("KeyContexts*", b) # MAP_KeyContext* for key in range(total_keys): key_context_size = [ 4, # X_UINT Size 4, # MAP_BinSearchCallback* Callback 4, # X_UINT Index 4, # X_INT Test 4, # MAP_Mapping **LUT 4, # X_UINT Reserve 4, # X_VOID *Data ] key_context_size = sum(key_context_size) key_context_begin = p_key_contexts + key * key_context_size debug_print("getting keys for type %s" % key) b.seek(key_context_begin) # NAM_Dictionnary.KeyContexts[KEY] # we are now in the KeyContext for type "key" debug_print("Current keycontext: %s" % b.tell()) keytype_size = read_int_and_print("\tSize", b) # MAP_BinSearchContext -> X_UINT read_int_and_print("\t\tpCallback", b) # MAP_BinSearchContext -> MAP_BinSearchCallback* read_int_and_print("\t\tIndex", b) # MAP_BinSearchContext -> X_UINT read_int_and_print("\t\tTest", b) # MAP_BinSearchContext -> X_INT p_lut = read_int_and_print("\tMAP_Mapping** LUT", b) # MAP_Mapping** read_int_and_print("\tReserve", b) # X_UINT read_int_and_print("\tpData", b) # X_VOID* # i=0; tant que l'addresse que renvoie MAP_MappingGet (Dictionary->NameMapper, i, NAM_STRING_KEY) != 0; i++ # mappingget: return Mapper->KeyContexts[Key].LUT[Index]; """ """ key_index = 0 while True: if key_index >= totalmappings: break b.seek(p_lut + key_index * 4) # NAM_Dictionnary.KeyContexts[KEY].LUT[INDEX] -> LA je suis au MAP_Mapping* == NAM_Name* debug_print("MAP_Mapping*") addr_begin_mapping = read_int_and_print("MAP_Mapping*", b) # MAP_MapIndex* -> X_UINT16* if addr_begin_mapping == 0: break b.seek(addr_begin_mapping) # we are now in MAP_Mapping == NAM_Name header """ struct _MAP_Mapping { MAP_MapIndex TotalSize; }; typedef X_UINT16 MAP_MapIndex; """ debug_print("MAP_Mapping") read_short_and_print("TotalSize", b) # MAP_MapIndex* -> X_UINT16* after_mapping = b.tell() b.seek(after_mapping + key * 2) debug_print("MAP_MapIndex at %s" % b.tell()) index = read_short_and_print("MAP_MapIndex value", b) b.seek(addr_begin_mapping+index) debug_print("STRINGDATA* OU HANDLEDATA* at %s" % b.tell()) # pdata = read_int_and_print("Data*", b) # b.seek(pdata) if key == NAM_STRING_KEY: val = read_int_and_print("ValueOrCustom", b) # X_INT or X_VOID* name = read_string_until_none_and_print("name", b) if flags & NAM_FlagHasHandles: r[key_index] = {"Name": name} elif depth > 1: item = NamDictionnary.create(cls, b, depth - 1, offset=val) r[name] = item else: item = cls.create(b, offset=val) r[name] = item if key == NAM_HANDLE_KEY: read_short_and_print("RefCnt", b) # NAM_NameRefCnt == X_UINT16 handle = read_short_and_print("Handle", b) # NAM_NameHandle == X_UINT16 r[key_index]["Handle"] = handle key_index += 1 debug_print("Key_index=%s" % key_index) return r @dataclass class GeoMat4x4: m: List[float] @classmethod @with_offset def create(cls, b): m = [] for _ in range(4): for _j in range(4): m.append(read_float(b)) return cls(m=m) @dataclass class GeoMat: mat_class: int s: GeoMat4x4 @classmethod @with_offset def create(cls, b): mat_class = read_int(b) s = GeoMat4x4.create(b) return cls(mat_class=mat_class, s=s) @dataclass class GeoAffine: transform: Vec3 quat: Vec4 scale: Vec3 affine_class: int @classmethod @with_offset def create(cls, b): trans = Vec3.create(b) quat = Vec4.create(b) scale = Vec3.create(b) affine_class = read_int(b) return cls(transform=trans, quat=quat, scale=scale, affine_class=affine_class) @dataclass class AnmTransform: time: int affine: GeoAffine @classmethod @with_offset def create(cls, b): time = read_int(b) affine = GeoAffine.create(b) return cls(time=time, affine=affine) @dataclass class AnmNode: custom: int node_type: int x_form_type: int flags: int p_anim: int p_parent: int p_target: int p_usercall: int p_usercontext: int stamp: int geo_mat: GeoMat transform: AnmTransform pos_keys: Union[AnmKeys, int] rot_keys: Union[AnmKeys, int] scl_keys: Union[AnmKeys, int] @classmethod @with_offset def create(cls, b): custom = read_int_and_print("ANM_Node->Custom", b) node_type = read_short_and_print("ANM_Node->Type", b) x_form_type = read_short_and_print("ANM_Node->XformType", b) flags = read_int_and_print("ANM_Node->Flags", b) p_anim = read_int_and_print("ANM_Node->pAnim", b) p_parent = read_int_and_print("ANM_Node->pParent", b) p_target = read_int_and_print("ANM_Node->pTarget", b) p_usercall = read_int_and_print("ANM_Node->pUserCall", b) p_usercontext = read_int_and_print("ANM_Node->pUserContext", b) stamp = read_int_and_print("ANM_Node->Stamp", b) geo_mat = GeoMat.create(b) transform = AnmTransform.create(b) pos_keys = read_int_and_print("ANM_Node->pPosKeys", b) rot_keys = read_int_and_print("ANM_Node->pRotKeys", b) scl_keys = read_int_and_print("ANM_Node->pSclKeys", b) if pos_keys > 0: pos_keys = AnmKeys.create(b, offset=pos_keys) if rot_keys > 0: rot_keys = AnmKeys.create(b, offset=rot_keys) if scl_keys > 0: scl_keys = AnmKeys.create(b, offset=scl_keys) return cls(custom=custom, node_type=node_type, x_form_type=x_form_type, flags=flags, p_anim=p_anim, p_parent=p_parent, p_target=p_target, p_usercall=p_usercall, p_usercontext=p_usercontext, stamp=stamp, geo_mat=geo_mat, transform=transform, pos_keys=pos_keys, rot_keys=rot_keys, scl_keys=scl_keys) @dataclass class AnmAnim: custom: int fileversion: int image: int memgroup: int start_time: int end_time: int ticks_per_frame: int framerate: int tree_dictionnary: dict default_obj_dictionnary: dict num_nodes: int extra_stamp: int extra_flags: int extra_time1: int extra_stamp1: int extra_time2: int extra_stamp2: int extra_factor: float extra_other_anim: int name: str @classmethod @with_offset def create(cls, b): custom = read_int_and_print("ANM_Anim->Custom", b) version = read_int_and_print("ANM_Anim->FileVersion", b) image = read_int_and_print("ANM_Anim->pImage", b) memgroup = read_int_and_print("ANM_Anim->pMemGroup", b) start_time = read_int_and_print("ANM_Anim->StartTime", b) end_time = read_int_and_print("ANM_Anim->EndTime", b) ticks_per_frame = read_int_and_print("TicksPerFrame", b) framerate = read_int_and_print("FrameRate", b) treedict = read_int_and_print("pTreeDictionary", b) defaultobjdict = read_int_and_print("pDefaultObjDictionary", b) num_nodes = read_int_and_print("NumberOfNodes", b) stamp = read_int_and_print("ANM_AnimExtra->Stamp", b) flags = read_int_and_print("ANM_AnimExtra->Flags", b) time1 = read_int_and_print("ANM_AnimExtra->Time1", b) stamp1 = read_int_and_print("ANM_AnimExtra->Stamp1", b) time2 = read_int_and_print("ANM_AnimExtra->Time2", b) stamp2 = read_int_and_print("ANM_AnimExtra->Stamp2", b) factor = read_float_and_print("ANM_AnimExtra->Factor", b) otheranim = read_int_and_print("ANM_AnimExtra->OtherAnim", b) name = read_string_until_none(b) treedict = NamDictionnary.create(AnmNode, b, depth=2, offset=treedict) defaultobjdict = NamDictionnary.create(AnmNode, b, offset=defaultobjdict) return cls(custom=custom, fileversion=version, image=image, memgroup=memgroup, start_time=start_time, end_time=end_time, ticks_per_frame=ticks_per_frame, framerate=framerate, tree_dictionnary=treedict, default_obj_dictionnary=defaultobjdict, num_nodes=num_nodes, extra_stamp=stamp, extra_flags=flags, extra_time1=time1, extra_stamp1=stamp1, extra_time2=time2, extra_stamp2=stamp2, extra_factor=factor, extra_other_anim=otheranim, name=name) @dataclass class AnmSkin: custom: int file_version: int image: int memgroup: int node_dictionnary: dict tree_dictionnary: dict default_obj_dictionnary: dict total_number_trees: int total_number_objs: int total_number_clusters: int name: str @staticmethod def link(nodedict, defaultobjtree): m = {} for node_index in nodedict: node = nodedict[node_index] m[node["Handle"]] = node["Name"] for obj_name, obj in defaultobjtree.items(): for cluster in obj.clusters: cluster.bone_name = m[cluster.handle] @classmethod @with_offset def create(cls, b): custom = read_int_and_print("pCustom", b) version = read_int_and_print("FileVersion", b) image = read_int_and_print("pImage", b) memgroup = read_int_and_print("pMemGroup", b) node = read_int_and_print("pNodeDictionary", b) tree = read_int_and_print("pTreeDictionary", b) defaultobj = read_int_and_print("pDefaultObjDictionary", b) num_trees = read_int_and_print("TotalNumberOfTrees", b) num_objs = read_int_and_print("TotalNumberOfObjs", b) num_clusters = read_int_and_print("TotalNumberOfClusters", b) name = read_string_until_none_and_print("Name", b) nodedict = NamDictionnary.create(AnmObj, b, offset=node) treedict = NamDictionnary.create(AnmObj, b, depth=2, offset=tree) defaultobjdict = NamDictionnary.create(AnmObj, b, offset=defaultobj) AnmSkin.link(nodedict, defaultobjdict) return cls(custom=custom, file_version=version, image=image, memgroup=memgroup, node_dictionnary=nodedict, tree_dictionnary=treedict, default_obj_dictionnary=defaultobjdict, total_number_trees=num_trees, total_number_objs=num_objs, total_number_clusters=num_clusters, name=name) @dataclass class AnmFile: @staticmethod def parse(b, cls): _ = read_int_and_print("size", b) memgroupoffset = read_int_and_print("memgroupoffset", b) b.seek(memgroupoffset) read_int_and_print("\tNode", b) read_int_and_print("\tSize", b) read_int_and_print("\tFlags", b) read_int_and_print("pMemGroup", b) read_int_and_print("MemList", b) read_int_and_print("TotalSize", b) read_int_and_print("TotalBlocks", b) base = read_int_and_print("pBase", b) return cls.create(b, offset=base)