summaryrefslogtreecommitdiff
path: root/bck_funcs.py
diff options
context:
space:
mode:
Diffstat (limited to 'bck_funcs.py')
-rw-r--r--bck_funcs.py730
1 files changed, 730 insertions, 0 deletions
diff --git a/bck_funcs.py b/bck_funcs.py
new file mode 100644
index 0000000..8ea2c72
--- /dev/null
+++ b/bck_funcs.py
@@ -0,0 +1,730 @@
+import os, struct, math
+
+# 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 important variables to the final bck_raw_info (smg_bck class).
+# If the file is correct, then read_bck_file() will assign the remaining variables, if any
+# and return the respective structure (a copy of it, not the reference).
+#
+# 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_error_str = "bck-error: "
+bck_anim_error_str = "bck-anim-error: "
+pad_str = "This is padding data to alignme"
+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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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):
+ return bck_anim_error_str + "anim_data struct"
+ if (type(comp.kf_count) != int or comp.kf_count <= 0):
+ return bck_anim_error_str + "keyframe count"
+ if (type(comp.interp_mode) != int or (comp.kf_count != 0 and comp.kf_count != 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.values) != list or len(comp.values) != comp.kf_count):
+ return bck_anim_error_str + "values 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
+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 = "btp1"
+ raw.header.file_size = 32 # 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 = 36 # 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 average value
+ avg_angle_mag = 0
+ angle_count = 0
+ for i in range(anim.bone_count):
+ for j in range(3):
+ for k in range(len(anim.anim_data[i].comp[(j * 3) + 1].kf_count)):
+ max_angle_mag += abs(anim.anim_data[i].comp[(j * 3) + 1].values[k])
+ angle_count += 1
+ # calculate rot_lshift so that this value can be represented
+ # (shit can go crazy if this value is very large)
+ avg_angle_mag = avg_angle_mag / angle_count
+ raw.ank1.rot_lshift = int(math.ceil(math.log2(avg_angle_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
+
+ # 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.anim_data())
+
+ # iterate over the animation components
+ for j in range(9):
+ # in case the same dataset can be found already
+ # written in one of the animation data arrays
+ match_found = True
+ 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
+ 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
+
+ # 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
+ match_found = False
+ k = 0
+ while (k < len(arr)):
+ if (anim.anim_data[i].comp[j].values[0] == arr[k]):
+ raw.ank1.anim_data[i].comp[j].anim_data_index = k
+ match_found = True
+ break
+ k += 1
+ # something was found, index was already assigned
+ if (match_found == True):
+ continue
+ # otherwise, update the array
+ arr.append(anim.anim_data[i].comp[j].values[0])
+
+ # or if it is more
+ else:
+ # iterate over all the other values in the scale array to see if a match is found
+ k = 0
+ while ((k + (anim.anim_data[i].comp[j].kf_count * number_of_items)) < len(arr)):
+ # check coincidence
+ l = 0
+ match_found = True
+ while (l < anim.anim_data[i].comp[j].kf_count):
+ # check value equality
+ if ((anim.anim_data[i].comp[j].time[l] != arr[k + l + 0])
+ or (anim.anim_data[i].comp[j].value[l] != arr[k + l + 1])
+ or (anim.anim_data[i].comp[j].in_slope[l] != arr[k + l + 2])):
+ match_found = False
+ break
+ # interpolation mode == 1
+ if ((anim.anim_data[i].comp[j].interp_mode == 1)
+ and (anim.anim_data[i].comp[j].out_slope[l] != arr[k + l + 3])):
+ match_found = False
+ break
+ l += number_of_items
+ # something was found
+ if (match_found == True):
+ raw.ank1.anim_data[i].comp[j].anim_data_index = k
+ break
+ # continue to next loop
+ k += 1
+ # something was found, index was already assigned
+ if (match_found == True):
+ continue
+ # else append the new data
+ raw.ank1.anim_data[i].comp[j].anim_data_index = cur_index
+ # iterate over the frames and assign the scale values
+ k = 0
+ while (k < 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])
+ arr.append(anim.anim_data[i].comp[j].in_slope[k])
+ if (anim.anim_data[i].comp[j].interp_mode == 1):
+ arr.append(anim.anim_data[i].comp[j].out_slope[k])
+ k += 1
+
+ # 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
+ 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)
+ raw.ank1.anim_data_offset = 0x40
+ raw.ank1.scale_arr_offset = None
+ raw.ank1.rot_arr_offset = None
+ raw.ank1.transl_arr_offset = None
+ raw.ank1.anim_data = [] # list of length bone_count
+ raw.ank1.scale_arr = [] # list of length scale_arr_length
+ raw.ank1.rot_arr = [] # list of length rot_arr_length
+ raw.ank1.transl_arr = [] # list of length transl_arr_length
+
+ 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()
+ # use struct.pack() to write in different endian orders
+ # dont be crazy with it an assign the data tables to the "standard offsets"
+
+ global f
+ f = open(filepath, "wb")
+ f.close()