#! /usr/bin/python3 import sys try: from .bones import * from .util import * from .geomesh import GeoMesh, GeoFace, GeoVertex from .compression_anim import * except: from bones import * from util import * from geomesh import GeoMesh, GeoFace, GeoVertex from compression_anim import * #.anim header #Offset Size Description #0 4 (int32) headerSize Size of this header plus BoneAnimTrack data and skeleton hierarchy data. #4 256 (asciiz) name #260 256 (asciiz) baseAnimName #516 4 (float32) max_hip_displacement #520 4 (float32) length #524 4 (ptr u32) bone_tracks Offset to array of BoneAnimTrack structures. #528 4 (int32) bone_track_count #532 4 (int32) rotation_compression_type ?? #536 4 (int32) position_compression_type ?? #540 4 (ptr u32) skeletonHeirarchy ?? SkeletonHeirarchy #Below are reserved for use by the game. #544 4 (ptr u32) backupAnimTrack *SkeletonAnimTrack #548 4 (int32) loadstate #552 4 (float32) lasttimeused #556 4 (int32) fileAge #560 4*9 (int32[9]) spare_room #596 - #BoneAnimTrack #0 4 (ptr u32) rot_idx ?? Offset to the rotation data. Format depends on compression method. #4 4 (ptr u32) pos_idx ?? Offset to the position data. Format depends on compression method. #8 2 (u16) rot_fullkeycount ?? #10 2 (u16) pos_fullkeycount ?? #12 2 (u16) rot_count ?? #14 2 (u16) pos_count ?? #16 1 (char) id Bone ID. #17 1 (char) flags ?? #18 2 (u16) pack_pad Padding to push the structure to 4 byte alignment. #20 - #SkeletonHeirarchy #0 4 (int) heirarchy_root ?? First bone in the hierarchy. #4 12*100 (BoneLink[BONES_ON_DISK]) #1204 - #BONES_ON_DISK = 100 #BoneLink #0 4 (int) child ?? First child bone? #4 4 (int) next ?? The next sibling bone? #8 4 (int/BoneId) id The ID of this bone. #12 - ROTATION_UNCOMPRESSED = 1 << 0 ROTATION_COMPRESSED_TO_5_BYTES = 1 << 1 ROTATION_COMPRESSED_TO_8_BYTES = 1 << 2 POSITION_UNCOMPRESS = 1 << 3 POSITION_COMPRESSED_TO_6_BYTES = 1 << 4 ROTATION_DELTACODED = 1 << 5 POSITION_DELTACODED = 1 << 6 ROTATION_COMPRESSED_NONLINEAR = 1 << 7 ROTATION_MASK = ROTATION_UNCOMPRESSED | ROTATION_COMPRESSED_TO_5_BYTES | ROTATION_COMPRESSED_TO_8_BYTES | ROTATION_DELTACODED | ROTATION_COMPRESSED_NONLINEAR POSITION_MASK = POSITION_UNCOMPRESS | POSITION_COMPRESSED_TO_6_BYTES | POSITION_DELTACODED class BoneLink: def __init__(self, data_or_child = None, next = None, boneid = None): self.child = -1 self.next = -1 self.boneid = -1 if data_or_child is None: return if next is None: (self.child, self.next, self.boneid) = data_or_child.decode(" 1.0: require_full = True self.flags = self.flags & ~POSITION_MASK #Set flags and endode data self.position_data = b"" if require_full: self.flags = self.flags | POSITION_UNCOMPRESS for pos in self.positions: self.position_data += struct.pack(" 1.0: # require_full = True self.flags = self.flags & ~ROTATION_MASK #Set flags and endode data self.rotation_data = b"" if require_full: self.flags = self.flags | ROTATION_UNCOMPRESS for rot in self.rotattions: self.rotation_data += struct.pack(" len(BONES_LIST): print("bone_id: %s (?)" % (self.bone_id, )) else: print("bone_id: %s %s" % (self.bone_id, BONES_LIST[self.bone_id])) print("flags: 0x%2.2x" % (self.flags, )) print("padding: %s" % (self.padding, )) print("rotations: %s" % (self.rotations, )) print("positions: %s" % (self.positions, )) class Anim: def __init__(self): self.data = b"" self.offset = 0 self.version = -1 self.header_size = -1 self.header_name = "" self.header_base_anim_name = "" self.header_max_hip_displacement = 0 self.header_length = 0 self.header_bone_tracks_offset = 0 self.header_bone_track_count = 0 self.header_rotation_compression_type = 0 self.header_position_compression_type = 0 self.header_skeleton_hierarchy_offset = 0 self.header_backup_anim_track = 0 self.header_load_state = 0 self.header_last_time_used = 0 self.header_file_age = 0 self.header_spare_room = b"" self.bone_tracks = [] self.skeleton_hierarchy = None pass def loadFromData(self, data): self.data = Data(data) self.offset = 0 self.parseData() pass def loadFromFile(self, filein): self.data = Data(filein.read()) self.offset = 0 self.parseData() pass def saveToData(self): self.encode() return self.data.data def saveToFile(self, fileout): self.encode() fileout.write(self.data.data) def parseData(self): self.data.seek(0) self.header_size = self.data.decode("i")[0] self.header_name = extractString(self.data.read(256)) self.header_base_anim_name = extractString(self.data.read(256)) self.header_max_hip_displacement = self.data.decode("f")[0] self.header_length = self.data.decode("f")[0] self.header_bone_tracks_offset = self.data.decode("I")[0] self.header_bone_track_count = self.data.decode("i")[0] self.header_rotation_compression_type = self.data.decode("i")[0] self.header_position_compression_type = self.data.decode("i")[0] self.header_skeleton_hierarchy_offset = self.data.decode("I")[0] self.header_backup_anim_track = self.data.decode("I")[0] self.header_load_state = self.data.decode("i")[0] self.header_last_time_used = self.data.decode("f")[0] self.header_file_age = self.data.decode("f")[0] self.header_spare_room = self.data.read(4 * 9) self.bone_tracks = [] if self.header_bone_tracks_offset > 0: self.data.seek(self.header_bone_tracks_offset) for i in range(self.header_bone_track_count): self.bone_tracks.append(BoneAnimTrack(self.data.read(20), self.data)) self.skeleton_hierarchy = None if self.header_skeleton_hierarchy_offset > 0: self.data.seek(self.header_skeleton_hierarchy_offset) self.skeleton_hierarchy = SkeletonHierarchy() if self.header_skeleton_hierarchy_offset > self.header_bone_tracks_offset: skel_size = self.header_size - self.header_skeleton_hierarchy_offset else: skel_size = self.header_bone_tracks_offset - self.header_skeleton_hierarchy_offset self.skeleton_hierarchy.parseData(self.data, skel_size) pass def encode(self): if isinstance(self.header_name, str): self.header_name = bytes(self.header_name, "utf-8") if isinstance(self.header_base_anim_name, str): self.header_base_anim_name = bytes(self.header_base_anim_name, "utf-8") header_base_size = 596 header_bone_tracks_size = 20 * len(self.bone_tracks) if self.skeleton_hierarchy is None: skel_data = b"" header_skeleton_hierarhy_size = 0 else: skel_data = self.skeleton_hierarchy.encode() header_skeleton_hierarhy_size = len(skel_data) #todo: verify all defined bones are present in the track data header_total_size = header_base_size + header_bone_tracks_size + header_skeleton_hierarhy_size header_bone_track_data = b"" bone_track_data = Data() for bt in self.bone_tracks: header_bone_track_data += bt.encode(header_total_size, bone_track_data) if len(header_bone_track_data) != header_bone_tracks_size: #Verify the header is the right length. raise Exception("Internal error: Header bone tracks size mismatch! Got: %s Expected: %s" % (len(header_bone_tracks), header_bone_tracks_size)) self.header_size = header_total_size #self.header_max_hip_displacement = 0 self.header_length = 0 for bt in self.bone_tracks: self.header_length = max(self.header_length, len(bt.positions), len(bt.rotations)) self.header_length = max(0, self.header_length - 1) self.header_bone_tracks_offset = header_base_size + header_skeleton_hierarhy_size self.header_bone_track_count = len(self.bone_tracks) self.header_rotation_compression_type = 0 self.header_position_compression_type = 0 if self.skeleton_hierarchy is None: self.header_skeleton_hierarchy_offset = 0 else: self.header_skeleton_hierarchy_offset = header_base_size self header_data = ( struct.pack("= len(bones): return False if bones[bone_id].boneid != bone_id: return False if not self.checkSkeletonBonesBody(bones[bone_id].next, seen): return False if not self.checkSkeletonBonesBody(bones[bone_id].child, seen): return False return True if __name__ == "__main__": if len(sys.argv) <= 1 or len(sys.argv) > 3: print("Usage:") print(" %s []" % (sys.argv[0], )) print("Test loads a .anim file, dumps its content, and optionally writes its content out.") exit(0) fh = open(sys.argv[1], "rb") anim = Anim() anim.loadFromFile(fh) fh.close() #print(sys.argv) if len(sys.argv) <= 2: fho = None else: fho = open(sys.argv[2], "wb") if fho is not None: data = anim.saveToData() #anim.dump() fho.write(data) else: anim.dump() #print("%s" % [anim.header_data])