import os, struct, math from . import smg_common # python file to read the important information out of a BCK file # will try its best to decode the information either on big/little endian # https://humming-owl.neocities.org/smg-stuff/pages/tutorials/bck # what this file will do is the following: # pre_read_bck_file() takes the first look into the BCK and it checks if a BCK is correct. # On the way it assigns all the variables to bck_raw_info (smg_bck_raw struct). # If the file is correct, then read_bck_file() will assign the actually useful variables # to a smg_bck_anim structure and return that structure # # in case of any error pre_read_bck_file() returns a string that can # be read by a human to identify what it is wrong with the BCK file # if all is good it will return exactly that (as a string) # all the raw variables on a BCK file class smg_bck_raw: def __init__(self): self.endian = None self.header = self.header() self.ank1 = self.ank1() def __str__(self): rtn = "### SMG_BCK_RAW - START\n" rtn += "Endian: %s\n" % (self.endian) rtn += self.header.__str__() rtn += self.ank1.__str__() rtn += "### SMG_BCK_RAW - END\n" return rtn # header class header: def __init__(self): self.magic = None self.ftype = None self.file_size = None self.section_count = None self.unknown1 = None def __str__(self): rtn = " ### HEADER START\n" rtn += " Magic: %s\n" % (self.magic.__str__()) rtn += " File type: %s\n" % (self.ftype.__str__()) rtn += " File size: %s\n" % (self.file_size.__str__()) rtn += " Section count: %s\n" % (self.section_count.__str__()) rtn += " Unknown 1: %s\n" % (self.unknown1.__str__()) rtn += " ### HEADER END\n" return rtn # ank1 class ank1: def __init__(self): self.magic = None self.size = None self.loop_mode = None self.rot_lshift = None self.anim_length = None self.bone_count = None self.scale_arr_length = None self.rot_arr_length = None self.transl_arr_length = None self.anim_data_offset = None self.scale_arr_offset = None self.rot_arr_offset = None self.transl_arr_offset = None self.anim_data = [] # list of length bone_count self.scale_arr = [] # list of length scale_arr_length self.rot_arr = [] # list of length rot_arr_length self.transl_arr = [] # list of length transl_arr_length def __str__(self): rtn = " ### ANK1 - START\n" rtn += " Magic: %s\n" % (self.magic.__str__()) rtn += " Section size: %s\n" % (self.size.__str__()) rtn += " Loop mode: %s\n" % (self.loop_mode.__str__()) rtn += " Rotation left shift: %s\n" % (self.rot_lshift.__str__()) rtn += " Anim length: %s\n" % (self.anim_length.__str__()) rtn += " Bone count: %s\n" % (self.bone_count.__str__()) rtn += " Scale array length: %s\n" % (self.scale_arr_length.__str__()) rtn += " Rotation array length: %s\n" % (self.rot_arr_length.__str__()) rtn += " Translation array length: %s\n" % (self.transl_arr_length.__str__()) rtn += " Anim data offset: %s\n" % (self.anim_data_offset.__str__()) rtn += " Scale array offset: %s\n" % (self.scale_arr_offset.__str__()) rtn += " Rotation array offset: %s\n" % (self.rot_arr_offset.__str__()) rtn += " Translation array offset: %s\n" % (self.transl_arr_offset.__str__()) rtn += " Animation data:\n" for i in range(len(self.anim_data)): rtn += " Bone %d\n" % (i) rtn += "%s" % (self.anim_data[i].__str__()) rtn += " Scale array: %s\n" % (self.scale_arr.__str__()) rtn += " Rotation array: %s\n" % (self.rot_arr.__str__()) rtn += " Translation array: %s\n" % (self.transl_arr.__str__()) rtn += " ### ANK1 - END\n" return rtn # bone animation data table class anim_data: def __init__(self): self.comp = [self.comp(), # scale x self.comp(), # rot x self.comp(), # transl x self.comp(), # scale y self.comp(), # rot y self.comp(), # transl y self.comp(), # scale z self.comp(), # rot z self.comp()] # transl z def __str__(self): rtn = " Scale X: %s" % (self.comp[0].__str__()) rtn += " Rot X: %s" % (self.comp[1].__str__()) rtn += " Transl X: %s" % (self.comp[2].__str__()) rtn += " Scale Y: %s" % (self.comp[3].__str__()) rtn += " Rot Y: %s" % (self.comp[4].__str__()) rtn += " Transl Y: %s" % (self.comp[5].__str__()) rtn += " Scale Z: %s" % (self.comp[6].__str__()) rtn += " Rot Z: %s" % (self.comp[7].__str__()) rtn += " Transl Z: %s" % (self.comp[8].__str__()) return rtn # the animation data for each of the animation components class comp: def __init__(self): self.keyframe_count = None self.anim_data_index = None self.interpolation_mode = None def __str__(self): return "%s %s %s\n" % (self.keyframe_count.__str__(), self.anim_data_index.__str__(), self.interpolation_mode.__str__()) # the actually useful information encoded in the BCK file class smg_bck_anim: def __init__(self): self.loop_mode = None self.anim_length = None self.bone_count = None self.anim_data = [] # list of length bone_count def __str__(self): rtn = "### SMG_BCK_ANIM START\n" rtn += " Loop mode: %s\n" % (self.loop_mode.__str__()) rtn += " Animation length: %s\n" % (self.anim_length.__str__()) rtn += " Bone count: %s\n" % (self.bone_count.__str__()) rtn += " Animation data:\n" for i in range(len(self.anim_data)): rtn += " Bone %d\n" % (i) rtn += "%s" % (self.anim_data[i].__str__()) rtn += "### SMG_BCK_ANIM END" return rtn # anim_data class anim_data: def __init__(self): self.comp = [self.comp(), # scale x self.comp(), # rot x self.comp(), # transl x self.comp(), # scale y self.comp(), # rot y self.comp(), # transl y self.comp(), # scale z self.comp(), # rot z self.comp()] # transl z def __str__(self): rtn = " Scale X: %s" % (self.comp[0].__str__()) rtn += " Rot X: %s" % (self.comp[1].__str__()) rtn += " Transl X: %s" % (self.comp[2].__str__()) rtn += " Scale Y: %s" % (self.comp[3].__str__()) rtn += " Rot Y: %s" % (self.comp[4].__str__()) rtn += " Transl Y: %s" % (self.comp[5].__str__()) rtn += " Scale Z: %s" % (self.comp[6].__str__()) rtn += " Rot Z: %s" % (self.comp[7].__str__()) rtn += " Transl Z: %s" % (self.comp[8].__str__()) return rtn # anim_data class comp: def __init__(self): self.kf_count = None self.interp_mode = None self.time = [] # list of length kf_count (if kf_count > 1) self.value = [] # list of length kf_count (the only list available to read if kf_count == 1) self.in_slope = [] # list of length kf_count (if kf_count > 1) self.out_slope = [] # list of length kf_count (if kf_count > 1) def __str__(self): rtn = "%s %s\n" % (self.kf_count, self.interp_mode) rtn += " time: %s\n" % (self.time.__str__()) rtn += " value: %s\n" % (self.value.__str__()) rtn += " in slope: %s\n" % (self.in_slope.__str__()) rtn += " out slope: %s\n" % (self.out_slope.__str__()) return rtn # create a global variable to hold temporal information bck_raw_info = None bck_raw_error_str = "bck-raw-error: " bck_anim_error_str = "bck-anim-error: " pad_str = "hoot" f = None # main function # will read and will check while reading def read_bck_file(filepath): # make global variables editable global f global bck_raw_info # "pre read" the file result_str = pre_read_bck_file(filepath) print(result_str) # all good bck_anim_info = None if (result_str == bck_raw_error_str + "all good"): # construct the data structure that is easier to deal with print(bck_raw_info) bck_anim_info = smg_bck_anim() # assign the easy variables bck_anim_info.loop_mode = bck_raw_info.ank1.loop_mode bck_anim_info.anim_length = bck_raw_info.ank1.anim_length bck_anim_info.bone_count = bck_raw_info.ank1.bone_count # construct the animation tracks # iterate over each bone for i in range(bck_anim_info.bone_count): bck_anim_info.anim_data.append(smg_bck_anim.anim_data()) # iterate over each animation component for j in range(9): bck_anim_info.anim_data[i].comp[j].kf_count = bck_raw_info.ank1.anim_data[i].comp[j].keyframe_count bck_anim_info.anim_data[i].comp[j].interp_mode = bck_raw_info.ank1.anim_data[i].comp[j].interpolation_mode data_index = bck_raw_info.ank1.anim_data[i].comp[j].anim_data_index arr = None # select the array and the items per read rot_mult = 1 # rotation consideration (convert to radians) if (j == 0 or j == 3 or j == 6): # scale arr = bck_raw_info.ank1.scale_arr elif (j == 1 or j == 4 or j == 7): # rotation arr = bck_raw_info.ank1.rot_arr rot_mult = (math.pi * math.pow(2, bck_raw_info.ank1.rot_lshift)) / 0x7FFF elif (j == 2 or j == 5 or j == 8): # translation arr = bck_raw_info.ank1.transl_arr # assign the data # 1 keyframe if (bck_anim_info.anim_data[i].comp[j].kf_count == 1): bck_anim_info.anim_data[i].comp[j].time.append(None) bck_anim_info.anim_data[i].comp[j].value.append(arr[data_index] * rot_mult) bck_anim_info.anim_data[i].comp[j].in_slope.append(None) bck_anim_info.anim_data[i].comp[j].out_slope.append(None) # more than 1 elif (bck_anim_info.anim_data[i].comp[j].kf_count > 1): for k in range(bck_anim_info.anim_data[i].comp[j].kf_count): # time bck_anim_info.anim_data[i].comp[j].time.append(arr[data_index]) data_index += 1 # value bck_anim_info.anim_data[i].comp[j].value.append(arr[data_index] * rot_mult) data_index += 1 # in slope bck_anim_info.anim_data[i].comp[j].in_slope.append(arr[data_index] * rot_mult) data_index += 1 # out slope if (bck_anim_info.anim_data[i].comp[j].interp_mode == 1): bck_anim_info.anim_data[i].comp[j].out_slope.append(arr[data_index] * rot_mult) data_index += 1 else: bck_anim_info.anim_data[i].comp[j].out_slope.append(bck_anim_info.anim_data[i].comp[j].in_slope[-1]) # done! f.close() f = None bck_raw_info = None return bck_anim_info # function to check a BCK file before getting its full information out def pre_read_bck_file(filepath): # check its size first if (os.path.getsize(filepath) <= 32): return bck_raw_error_str + "file size" # make global variables editable global f global bck_raw_info # open the file f = open(filepath, "rb") # holder for variables bck_raw_info = smg_bck_raw(); # header # magic bck_raw_info.header.magic = f.read(4).decode("ascii") if (bck_raw_info.header.magic == "J3D1"): bck_raw_info.endian = "BIG" elif (bck_raw_info.header.magic == "1D3J"): bck_raw_info.endian = "LITTLE" else: return bck_raw_error_str + "magic" bck_raw_info.header.magic = "J3D1" # variable to set for struct.unpack byte order reading endian_ch = ">" # big endian if (bck_raw_info.endian == "LITTLE"): endian_ch = "<" # ftype bck_raw_info.header.ftype = f.read(4).decode("ascii") if ((bck_raw_info.header.ftype == "bck1" and bck_raw_info.endian != "BIG") and (bck_raw_info.header.ftype == "1kcb" and bck_raw_info.endian != "LITTLE")): return bck_raw_error_str + "ftype" bck_raw_info.header.ftype = "bck1" # file size bck_raw_info.header.file_size = struct.unpack(endian_ch + "I", f.read(4))[0] if (bck_raw_info.header.file_size != os.path.getsize(filepath)): return bck_raw_error_str + "file size" # section count bck_raw_info.header.section_count = struct.unpack(endian_ch + "I", f.read(4))[0] if (bck_raw_info.header.section_count != 1): return bck_raw_error_str + "section count" # unknown 1 bck_raw_info.header.unknown1 = list(f.read(16)) for i in range(16): if (bck_raw_info.header.unknown1[i] != 0xFF): return bck_raw_error_str + "unknown 1" # ank1 section # magic bck_raw_info.ank1.magic = f.read(4).decode("ascii") if ((bck_raw_info.ank1.magic == "ANK1" and bck_raw_info.endian != "BIG") and (bck_raw_info.ank1.magic == "1KNA" and bck_raw_info.endian != "LITTLE")): return bck_raw_error_str + "ank1 magic" bck_raw_info.ank1.magic = "ANK1" # size bck_raw_info.ank1.size = struct.unpack(endian_ch + "I", f.read(4))[0] if (bck_raw_info.ank1.size != bck_raw_info.header.file_size - 32): return bck_raw_error_str + "ank1 size" # loop mode bck_raw_info.ank1.loop_mode = struct.unpack(endian_ch + "B", f.read(1))[0] if (bck_raw_info.ank1.loop_mode > 0x04): return bck_raw_error_str + "ank1 loop mode" # rotation left shift bck_raw_info.ank1.rot_lshift = struct.unpack(endian_ch + "B", f.read(1))[0] # animation length bck_raw_info.ank1.anim_length = struct.unpack(endian_ch + "H", f.read(2))[0] # bone count bck_raw_info.ank1.bone_count = struct.unpack(endian_ch + "H", f.read(2))[0] # scale array length bck_raw_info.ank1.scale_arr_length = struct.unpack(endian_ch + "H", f.read(2))[0] # rotation array length bck_raw_info.ank1.rot_arr_length = struct.unpack(endian_ch + "H", f.read(2))[0] # translation array length bck_raw_info.ank1.transl_arr_length = struct.unpack(endian_ch + "H", f.read(2))[0] # offsets # bone animation data offset bck_raw_info.ank1.anim_data_offset = struct.unpack(endian_ch + "I", f.read(4))[0] if (bck_raw_info.ank1.anim_data_offset + (bck_raw_info.ank1.bone_count * 9 * 6) > bck_raw_info.ank1.size): return bck_raw_error_str + "ank1 bone animation data offset" # scale array offset bck_raw_info.ank1.scale_arr_offset = struct.unpack(endian_ch + "I", f.read(4))[0] if (bck_raw_info.ank1.scale_arr_offset + (bck_raw_info.ank1.scale_arr_length * 4) > bck_raw_info.ank1.size): return bck_raw_error_str + "ank1 scale array offset" # rotation array offset bck_raw_info.ank1.rot_arr_offset = struct.unpack(endian_ch + "I", f.read(4))[0] if (bck_raw_info.ank1.rot_arr_offset + (bck_raw_info.ank1.rot_arr_length * 2) > bck_raw_info.ank1.size): return bck_raw_error_str + "ank1 rotation array offset" # translation array offset bck_raw_info.ank1.transl_arr_offset = struct.unpack(endian_ch + "I", f.read(4))[0] if (bck_raw_info.ank1.transl_arr_offset + (bck_raw_info.ank1.transl_arr_length * 4) > bck_raw_info.ank1.size): return bck_raw_error_str + "ank1 translation array offset" # refer to the offsets to read the animation data always (SMG does this) # bone animation data f.seek(32 + bck_raw_info.ank1.anim_data_offset) # iterate over the bones for i in range(bck_raw_info.ank1.bone_count): # append a new item in the empty list bck_raw_info.ank1.anim_data.append(smg_bck_raw.ank1.anim_data()) # iterate over the animation components for j in range(9): bck_raw_info.ank1.anim_data[i].comp[j].keyframe_count = struct.unpack(endian_ch + "H", f.read(2))[0] bck_raw_info.ank1.anim_data[i].comp[j].anim_data_index = struct.unpack(endian_ch + "H", f.read(2))[0] bck_raw_info.ank1.anim_data[i].comp[j].interpolation_mode = struct.unpack(endian_ch + "H", f.read(2))[0] # temporal shortcuts for the variables above kf_count = bck_raw_info.ank1.anim_data[i].comp[j].keyframe_count data_index = bck_raw_info.ank1.anim_data[i].comp[j].anim_data_index interp_mode = bck_raw_info.ank1.anim_data[i].comp[j].interpolation_mode # store this file position old_file_pos = f.tell() # check the interpolation mode and if nothing overflows if (interp_mode > 1): return bck_raw_error_str + "ank1 interpolation mode" # variables to be used later item_read_size = None arr_offset = None item_read_type = None # scale or translation if (j == 0 or j == 3 or j == 6): item_read_size = 4 arr_offset = bck_raw_info.ank1.scale_arr_offset item_read_type = "f" # rotation elif (j == 1 or j == 4 or j == 7): item_read_size = 2 arr_offset = bck_raw_info.ank1.rot_arr_offset item_read_type = "h" # translation elif (j == 2 or j == 5 or j == 8): item_read_size = 4 arr_offset = bck_raw_info.ank1.transl_arr_offset item_read_type = "f" # how many items are read from the component arrays # (depends on the interpolation mode and on the keyframe count) number_of_items_per_kf = 1 if (kf_count > 1 and interp_mode == 0): # soft interpolation number_of_items_per_kf = 3 elif (kf_count > 1 and interp_mode == 1): # custom interpolation number_of_items_per_kf = 4 # check overflow if (arr_offset + (item_read_size * data_index) + (item_read_size * kf_count * number_of_items_per_kf) > bck_raw_info.ank1.size): return bck_raw_error_str + "ank1 anim data overflow" # read the respective arrays to check time consistency old_time = None cur_time = None value = None slope = None # go to that part of the file and read the respective animation data f.seek(32 + arr_offset + (item_read_size * data_index)) # read the animation data if (number_of_items_per_kf == 1): # single value read # value value = struct.unpack(endian_ch + item_read_type, f.read(item_read_size))[0] else: # (3 or 4) * kf_count value reads # iterate over the number of keyframes for k in range(kf_count): # time cur_time = struct.unpack(endian_ch + item_read_type, f.read(item_read_size))[0] if (old_time != None): if (old_time >= cur_time): return bck_raw_error_str + "ank1 keyframe time" old_time = cur_time # value value = struct.unpack(endian_ch + item_read_type, f.read(item_read_size))[0] # slope slope = struct.unpack(endian_ch + item_read_type, f.read(item_read_size))[0] if (number_of_items_per_kf == 4): slope = struct.unpack(endian_ch + item_read_type, f.read(item_read_size))[0] # return to the bone animation data table f.seek(old_file_pos) # scale, rotation and translation arrays data f.seek(32 + bck_raw_info.ank1.scale_arr_offset) for i in range(bck_raw_info.ank1.scale_arr_length): bck_raw_info.ank1.scale_arr.append(struct.unpack(endian_ch + "f", f.read(4))[0]) f.seek(32 + bck_raw_info.ank1.rot_arr_offset) for i in range(bck_raw_info.ank1.rot_arr_length): bck_raw_info.ank1.rot_arr.append(struct.unpack(endian_ch + "h", f.read(2))[0]) f.seek(32 + bck_raw_info.ank1.transl_arr_offset) for i in range(bck_raw_info.ank1.transl_arr_length): bck_raw_info.ank1.transl_arr.append(struct.unpack(endian_ch + "f", f.read(4))[0]) # finally done bruh return bck_raw_error_str + "all good" # check if a smg_bck_anim structure is good def check_smg_bck_anim(anim): # check if the information in the smg_bck_anim struct is valid # the only stuff I can check is time consistency (frame -3 goes before frame 2) # and that the lengths of the arrays are all good (also the variable types) if (type(anim) != smg_bck_anim): return bck_anim_error_str + "smg_bck_anim struct" if(type(anim.anim_data) != list or len(anim.anim_data) != anim.bone_count): return bck_anim_error_str + "number of bones or anim_data list" # iterate over the bones for bone in anim.anim_data: if (type(bone.comp) != list or len(bone.comp) != 9): return bck_anim_error_str + "number of components or comp list" # iterate over the anim components for comp in bone.comp: # check object types and integer data if (type(comp) != smg_bck_anim.anim_data.comp): return bck_anim_error_str + "anim_data struct" if (type(comp.kf_count) != int or comp.kf_count <= 0): print(comp.kf_count) return bck_anim_error_str + "keyframe count" if (type(comp.interp_mode) != int or (comp.interp_mode != 0 and comp.interp_mode != 1)): return bck_anim_error_str + "interpolation mode" if (type(comp.time) != list or len(comp.time) != comp.kf_count): return bck_anim_error_str + "time list" if (type(comp.value) != list or len(comp.value) != comp.kf_count): return bck_anim_error_str + "value list" if (type(comp.in_slope) != list or len(comp.in_slope) != comp.kf_count): return bck_anim_error_str + "in_slope list" if (type(comp.out_slope) != list or len(comp.out_slope) != comp.kf_count): return bck_anim_error_str + "out_slope list" # check time consistency for i in range(comp.kf_count): if (i == 0): continue if (comp.time[i - 1] >= comp.time[i]): return bck_anim_error_str + "time inconsistency" # all is good return bck_anim_error_str + "all good" # create smg_bck_raw from smg_bck_anim # assumes angles are in radians # and that the timings between the keyframes are t0 = 0 and tf = 1 # (cubic hermite spline) def create_smg_bck_raw(anim): # calls check_smg_bck_anim() result = check_smg_bck_anim(anim) print(result) if (result != bck_anim_error_str + "all good"): return None # build a new raw structure and return it raw = smg_bck_raw() raw.endian = "BIG" # header raw.header.magic = "J3D1" raw.header.ftype = "bck1" raw.header.file_size = 0 # update later raw.header.section_count = 1 raw.header.unknown1 = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] # find the file size (bytes) # find if there can be merged tracks (same value animation tracks) # find out the rot_lshift number # ank1 section raw.ank1.magic = "ANK1" raw.ank1.size = 0 # update at the end raw.ank1.loop_mode = anim.loop_mode raw.ank1.rot_lshift = 0 # update now raw.ank1.anim_length = anim.anim_length raw.ank1.bone_count = anim.bone_count # check all the rotation lists and get the largest value/slope max_angle_value_slope_mag = 0 for i in range(anim.bone_count): for j in range(3): for k in range(anim.anim_data[i].comp[(j * 3) + 1].kf_count): if (abs(anim.anim_data[i].comp[(j * 3) + 1].value[k]) > max_angle_value_slope_mag): max_angle_value_slope_mag = abs(anim.anim_data[i].comp[(j * 3) + 1].value[k]) # ~ if (k > 0): # ~ if (abs(anim.anim_data[i].comp[(j * 3) + 1].in_slope[k]) > max_angle_value_slope_mag): # ~ max_angle_value_slope_mag = abs(anim.anim_data[i].comp[(j * 3) + 1].in_slope[k]) # ~ if (k < anim.anim_data[i].comp[(j * 3) + 1].kf_count - 1): # ~ if (abs(anim.anim_data[i].comp[(j * 3) + 1].out_slope[k]) > max_angle_value_slope_mag): # ~ max_angle_value_slope_mag = abs(anim.anim_data[i].comp[(j * 3) + 1].out_slope[k]) # calculate rot_lshift so that the largest value can be represented if (max_angle_value_slope_mag > math.pi): raw.ank1.rot_lshift = int(math.ceil(math.log2(max_angle_value_slope_mag / math.pi))) # ^ ceil of it because I am forcing the average to be represented # as 0x7FFF which cannot be done as rot_lshift needs to be an integer else: # angles can be represented between -180 and 180 raw.ank1.rot_lshift = 0 # start writing the animation data # iterate over the bones for i in range(anim.bone_count): # add a section for a bone raw.ank1.anim_data.append(smg_bck_raw.ank1.anim_data()) # iterate over the animation components for j in range(9): # get the animation component array arr = None # scale if (j == 0 or j == 3 or j == 6): arr = raw.ank1.scale_arr # rot elif (j == 1 or j == 4 or j == 7): arr = raw.ank1.rot_arr # translate elif (j == 2 or j == 5 or j == 8): arr = raw.ank1.transl_arr # get the last index to write in the array cur_index = len(arr) # keyframe_count, interpolation_mode raw.ank1.anim_data[i].comp[j].keyframe_count = anim.anim_data[i].comp[j].kf_count raw.ank1.anim_data[i].comp[j].interpolation_mode = anim.anim_data[i].comp[j].interp_mode # number of items to write on each animation array number_of_items = 1 if (anim.anim_data[i].comp[j].kf_count > 1): if (raw.ank1.anim_data[i].comp[j].interpolation_mode == 0): number_of_items = 3 elif (raw.ank1.anim_data[i].comp[j].interpolation_mode == 1): number_of_items = 4 # variable to check if the animation track data # can be found already in the component array match_found = False # rotation consideration rot_mult = 1 if (j == 1 or j == 4 or j == 7): rot_mult = (0x7FFF / (math.pi * math.pow(2, raw.ank1.rot_lshift))) # check if it is a single keyframe value if (number_of_items == 1): # check if there is an equivalent value around the already written data if (anim.anim_data[i].comp[j].value[0] in arr): match_found = True for k in range(len(arr)): if (arr[k] == anim.anim_data[i].comp[j].value[0] * rot_mult): raw.ank1.anim_data[i].comp[j].anim_data_index = k break # something was found, index was already assigned if (match_found == True): continue # otherwise, update the array and assign the new index raw.ank1.anim_data[i].comp[j].anim_data_index = cur_index arr.append(anim.anim_data[i].comp[j].value[0] * rot_mult) # or if it is more else: # iterate over all the other values in the scale array to see if a match is found for k in range(len(arr)): # check if there is enough space left in the array if (k + (anim.anim_data[i].comp[j].kf_count * number_of_items) > len(arr)): break # check if the section from the array is the same as the data to match tmp = arr[k : k + (anim.anim_data[i].comp[j].kf_count * number_of_items)] match_found = True for l in range((anim.anim_data[i].comp[j].kf_count)): # 3 items to check if ((tmp[int((l * number_of_items) + 0)] != anim.anim_data[i].comp[j].time[l]) or (tmp[int((l * number_of_items) + 1)] != anim.anim_data[i].comp[j].value[l] * rot_mult) or (tmp[int((l * number_of_items) + 2)] != anim.anim_data[i].comp[j].in_slope[l] * rot_mult)): match_found = False break # 4 items to check if ((anim.anim_data[i].comp[j].interp_mode == 1) and (tmp[int((l * number_of_items) + 3)] != anim.anim_data[i].comp[j].out_slope[l] * rot_mult)): match_found = False break # something was found, assign the index and break out of the loop if (match_found == True): raw.ank1.anim_data[i].comp[j].anim_data_index = k break # something was found, index was already assigned if (match_found == True): continue # otherwise, update the array and assign the new index raw.ank1.anim_data[i].comp[j].anim_data_index = cur_index # iterate over the frames and assign the values for k in range(anim.anim_data[i].comp[j].kf_count): arr.append(anim.anim_data[i].comp[j].time[k]) arr.append(anim.anim_data[i].comp[j].value[k] * rot_mult) arr.append(anim.anim_data[i].comp[j].in_slope[k] * rot_mult) if (anim.anim_data[i].comp[j].interp_mode == 1): arr.append(anim.anim_data[i].comp[j].out_slope[k] * rot_mult) # update the animation arrays # scale if (j == 0 or j == 3 or j == 6): raw.ank1.scale_arr = arr # rot elif (j == 1 or j == 4 or j == 7): raw.ank1.rot_arr = arr # translate elif (j == 2 or j == 5 or j == 8): raw.ank1.transl_arr = arr # assign these variables now # dont be crazy with it an assign the data tables to the "standard offsets" raw.ank1.scale_arr_length = len(raw.ank1.scale_arr) raw.ank1.rot_arr_length = len(raw.ank1.rot_arr) raw.ank1.transl_arr_length = len(raw.ank1.transl_arr) pad_str = smg_common.padding() raw.ank1.anim_data_offset = 0x40 # yes raw.ank1.scale_arr_offset = raw.ank1.anim_data_offset + (raw.ank1.bone_count * 9 * 6) raw.ank1.scale_arr_offset += len(pad_str.string_fill(32, raw.ank1.scale_arr_offset)) raw.ank1.rot_arr_offset = raw.ank1.scale_arr_offset + (raw.ank1.scale_arr_length * 4) raw.ank1.rot_arr_offset += len(pad_str.string_fill(32, raw.ank1.rot_arr_offset)) raw.ank1.transl_arr_offset = raw.ank1.rot_arr_offset + (raw.ank1.rot_arr_length * 2) raw.ank1.transl_arr_offset += len(pad_str.string_fill(32, raw.ank1.transl_arr_offset)) # section size and file size raw.ank1.size = raw.ank1.transl_arr_offset + (raw.ank1.transl_arr_length * 4) raw.ank1.size += len(pad_str.string_fill(32, raw.ank1.size)) raw.header.file_size = 32 + raw.ank1.size # done! return raw # write smg_bck_raw def write_smg_bck_raw(raw, filepath, endian_ch): # assumes smg_bck_raw struct is correct so don't even attempt in making one yourself # use create_smg_bck_raw() to get a raw struct from an anim struct # use struct.pack() to write in different endian orders f = open(filepath, "wb") # header f.write(struct.pack(endian_ch + "I", struct.unpack(">I", raw.header.magic.encode("ascii"))[0])) f.write(struct.pack(endian_ch + "I", struct.unpack(">I", raw.header.ftype.encode("ascii"))[0])) f.write(struct.pack(endian_ch + "I", raw.header.file_size)) f.write(struct.pack(endian_ch + "I", raw.header.section_count)) f.write(bytes(raw.header.unknown1)) # ank1 f.write(struct.pack(endian_ch + "I", struct.unpack(">I", raw.ank1.magic.encode("ascii"))[0])) f.write(struct.pack(endian_ch + "I", raw.ank1.size)) f.write(struct.pack(endian_ch + "B", raw.ank1.loop_mode)) f.write(struct.pack(endian_ch + "B", raw.ank1.rot_lshift)) f.write(struct.pack(endian_ch + "H", raw.ank1.anim_length)) f.write(struct.pack(endian_ch + "H", raw.ank1.bone_count)) f.write(struct.pack(endian_ch + "H", raw.ank1.scale_arr_length)) f.write(struct.pack(endian_ch + "H", raw.ank1.rot_arr_length)) f.write(struct.pack(endian_ch + "H", raw.ank1.transl_arr_length)) f.write(struct.pack(endian_ch + "I", raw.ank1.anim_data_offset)) f.write(struct.pack(endian_ch + "I", raw.ank1.scale_arr_offset)) f.write(struct.pack(endian_ch + "I", raw.ank1.rot_arr_offset)) f.write(struct.pack(endian_ch + "I", raw.ank1.transl_arr_offset)) pad = smg_common.padding() f.write(pad.string_fill(32, 0x24)) # anim data for i in range(raw.ank1.bone_count): for j in range(9): f.write(struct.pack(endian_ch + "H", raw.ank1.anim_data[i].comp[j].keyframe_count)) f.write(struct.pack(endian_ch + "H", raw.ank1.anim_data[i].comp[j].anim_data_index)) f.write(struct.pack(endian_ch + "H", raw.ank1.anim_data[i].comp[j].interpolation_mode)) f.write(pad.string_fill(32, raw.ank1.anim_data_offset + (raw.ank1.bone_count * 9 * 6))) # scale array for i in range(raw.ank1.scale_arr_length): f.write(struct.pack(endian_ch + "f", raw.ank1.scale_arr[i])) f.write(pad.string_fill(32, raw.ank1.scale_arr_offset + (raw.ank1.scale_arr_length * 4))) # rotation array for i in range(raw.ank1.rot_arr_length): # check if the slope is larger in magnitude than the max representation container # (truncate the value to the max representation) value = int(raw.ank1.rot_arr[i]) if (value > 0x7FFF): value = 0x7FFF elif (value < -0x7FFF): value = -0x7FFF f.write(struct.pack(endian_ch + "h", value)) f.write(pad.string_fill(32, raw.ank1.rot_arr_offset + (raw.ank1.rot_arr_length * 2))) # translation array for i in range(raw.ank1.transl_arr_length): f.write(struct.pack(endian_ch + "f", raw.ank1.transl_arr[i])) f.write(pad.string_fill(32, raw.ank1.transl_arr_offset + (raw.ank1.transl_arr_length * 4))) # done! f.close()