From a5bdfdab2ec68ebfe99535a54b3b0cd8b95a87d6 Mon Sep 17 00:00:00 2001 From: Owl Date: Sat, 23 Aug 2025 00:29:45 -0400 Subject: bck exporter seems to be working --- bck_funcs.py | 227 ++++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 146 insertions(+), 81 deletions(-) (limited to 'bck_funcs.py') diff --git a/bck_funcs.py b/bck_funcs.py index d8c1162..8e2d6bb 100644 --- a/bck_funcs.py +++ b/bck_funcs.py @@ -199,7 +199,7 @@ class smg_bck_anim: # create a global variable to hold temporal information bck_raw_info = None -bck_error_str = "bck-error: " +bck_raw_error_str = "bck-raw-error: " bck_anim_error_str = "bck-anim-error: " pad_str = "hoot" f = None @@ -217,7 +217,7 @@ def read_bck_file(filepath): # all good bck_anim_info = None - if (result_str == bck_error_str + "all good"): + 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() @@ -287,7 +287,7 @@ def pre_read_bck_file(filepath): # check its size first if (os.path.getsize(filepath) <= 32): - return bck_error_str + "file size" + return bck_raw_error_str + "file size" # make global variables editable global f @@ -309,7 +309,7 @@ def pre_read_bck_file(filepath): elif (bck_raw_info.header.magic == "1D3J"): bck_raw_info.endian = "LITTLE" else: - return bck_error_str + "magic" + return bck_raw_error_str + "magic" bck_raw_info.header.magic = "J3D1" # variable to set for struct.unpack byte order reading @@ -321,24 +321,24 @@ def pre_read_bck_file(filepath): 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" + 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_error_str + "file size" + 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_error_str + "section count" + 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_error_str + "unknown 1" + return bck_raw_error_str + "unknown 1" ############## # ank1 section @@ -347,18 +347,18 @@ def pre_read_bck_file(filepath): 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" + 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_error_str + "ank1 size" + 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_error_str + "ank1 loop mode" + 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] @@ -379,22 +379,22 @@ def pre_read_bck_file(filepath): 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" + 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_error_str + "ank1 scale array offset" + 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_error_str + "ank1 rotation array offset" + 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_error_str + "ank1 translation array offset" + return bck_raw_error_str + "ank1 translation array offset" ######################################################################## # refer to the offsets to read the animation data always (SMG does this) @@ -421,7 +421,7 @@ def pre_read_bck_file(filepath): # check the interpolation mode and if nothing overflows if (interp_mode > 1): - return bck_error_str + "ank1 interpolation mode" + return bck_raw_error_str + "ank1 interpolation mode" # variables to be used later item_read_size = None @@ -454,7 +454,7 @@ def pre_read_bck_file(filepath): # 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" + return bck_raw_error_str + "ank1 anim data overflow" # read the respective arrays to check time consistency old_time = None @@ -476,7 +476,7 @@ def pre_read_bck_file(filepath): 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" + 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] @@ -500,7 +500,7 @@ def pre_read_bck_file(filepath): 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" + return bck_raw_error_str + "all good" # check if a smg_bck_anim structure is good def check_smg_bck_anim(anim): @@ -523,16 +523,17 @@ def check_smg_bck_anim(anim): for comp in bone.comp: # check object types and integer data - if (type(comp) != smg_bck_anim.anim_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.kf_count != 0 and comp.kf_count != 1)): + 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.values) != list or len(comp.values) != comp.kf_count): - return bck_anim_error_str + "values 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): @@ -549,6 +550,9 @@ def check_smg_bck_anim(anim): 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() @@ -563,7 +567,7 @@ def create_smg_bck_raw(anim): # header raw.header.magic = "J3D1" - raw.header.ftype = "btp1" + raw.header.ftype = "bck1" raw.header.file_size = 0 # update later raw.header.section_count = 1 raw.header.unknown1 = [0xFF, 0xFF, 0xFF, 0xFF, @@ -583,33 +587,38 @@ def create_smg_bck_raw(anim): 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 + # 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(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 + 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.anim_data()) + raw.ank1.anim_data.append(smg_bck_raw.ank1.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 + + # get the animation component array arr = None # scale if (j == 0 or j == 3 or j == 6): @@ -620,6 +629,7 @@ def create_smg_bck_raw(anim): # 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 @@ -633,64 +643,69 @@ def create_smg_bck_raw(anim): elif (raw.ank1.anim_data[i].comp[j].interpolation_mode == 1): number_of_items = 4 - # check if it is a single keyframe value + # 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 - 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 + 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 - arr.append(anim.anim_data[i].comp[j].values[0]) + # 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 - k = 0 - while ((k + (anim.anim_data[i].comp[j].kf_count * number_of_items)) < len(arr)): - # check coincidence - l = 0 + 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 - 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])): + 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 - # interpolation mode == 1 + # 4 items to check if ((anim.anim_data[i].comp[j].interp_mode == 1) - and (anim.anim_data[i].comp[j].out_slope[l] != arr[k + l + 3])): + and (tmp[int((l * number_of_items) + 3)] != anim.anim_data[i].comp[j].out_slope[l] * rot_mult)): match_found = False break - l += number_of_items - # something was found + # 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 - # continue to next loop - k += 1 # something was found, index was already assigned if (match_found == True): continue - # else append the new data + # 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 scale values - k = 0 - while (k < anim.anim_data[i].comp[j].kf_count): + # 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]) - arr.append(anim.anim_data[i].comp[j].in_slope[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]) - k += 1 + arr.append(anim.anim_data[i].comp[j].out_slope[k] * rot_mult) # update the animation arrays # scale @@ -703,7 +718,8 @@ def create_smg_bck_raw(anim): elif (j == 2 or j == 5 or j == 8): raw.ank1.transl_arr = arr - # assign these variables now + # 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) @@ -717,7 +733,7 @@ def create_smg_bck_raw(anim): 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)) + 1 + raw.ank1.size += len(pad_str.string_fill(32, raw.ank1.size)) raw.header.file_size = 32 + raw.ank1.size # done! @@ -726,11 +742,60 @@ def create_smg_bck_raw(anim): # 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() + # 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 - # dont be crazy with it an assign the data tables to the "standard offsets" - - global f 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 surpaces 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() -- cgit v1.2.3-70-g09d2