Hi everyone,
I’m currently diving into .skeleton files and trying to understand their binary structure to write a Python script that can convert them into XML. This has become necessary because the OgreXMLConverter tool is unable to handle some of the .skeleton files I’m working with, leaving me stuck.
Here’s what I’ve figured out so far about the structure of these .skeleton files:
The file starts with a header, followed by the bytes 00 00 00.
Then comes the name of a bone (UTF-8 encoded).
After that is the data for the bone with ID 0, followed by another 00 00 00.
This pattern repeats for the next bone (with ID 1), and so on.
There are too many bones in the skeleton files, and each bone seems to have a meaningful name.
While I’ve pieced together some information, I lack a comprehensive understanding of the exact binary structure and how to accurately parse the data.
My Goals:
Fully understand the binary format of .skeleton files.
Write a Python script to convert .skeleton files into XML.
I would greatly appreciate any help the community can provide, whether it’s documentation, sample code, or advice based on your experience. Additionally, if anyone has encountered issues with OgreXMLConverter and found workarounds, I’d love to hear about them.
This project is very important to me, and I believe the collective knowledge of this community can help make it a success. Because I want to import skeleton into Unity but the community does not have a tool to convert to a format that Unity supports, I can only import into blender and export to FBX or GLSL to import into Unity, so I have to find a way to extract skeleton to Xml to be able to import into blender. Thank you in advance for any assistance or insights you can share!
Below is the python code I wrote to convert, but it still cannot read the structure of skeleton file.
Code: Select all
import struct
import xml.etree.ElementTree as ET
# Chunk ID constants
SKELETON_HEADER = 0x1000
SKELETON_BONE = 0x2000
SKELETON_BONE_PARENT = 0x3000
SKELETON_ANIMATION = 0x4000
SKELETON_ANIMATION_TRACK = 0x4100
SKELETON_ANIMATION_TRACK_KEYFRAME = 0x4110
def read_string(data, offset):
end = data.find(b'\x00', offset)
if end == -1:
raise ValueError("Null-terminated string not found.")
string = data[offset:end].decode('utf-8')
return string, end + 1
def parse_skeleton(data):
offset = 0
skeleton = ET.Element('Skeleton')
while offset < len(data):
if len(data) - offset < 6:
break
chunk_id, length = struct.unpack_from('<HI', data, offset)
offset += 6 # Move past chunk ID and length
if chunk_id == SKELETON_HEADER:
version, offset = read_string(data, offset)
header = ET.SubElement(skeleton, 'Header')
header.set('Version', version)
elif chunk_id == SKELETON_BONE:
name, offset = read_string(data, offset)
handle, = struct.unpack_from('<H', data, offset)
offset += 2
position = struct.unpack_from('<fff', data, offset)
offset += 12
orientation = struct.unpack_from('<ffff', data, offset)
offset += 16
scale = struct.unpack_from('<fff', data, offset)
offset += 12
bone = ET.SubElement(skeleton, 'Bone')
bone.set('Name', name)
bone.set('Handle', str(handle))
bone.set('Position', f'{position[0]} {position[1]} {position[2]}')
bone.set('Orientation', f'{orientation[0]} {orientation[1]} {orientation[2]} {orientation[3]}')
bone.set('Scale', f'{scale[0]} {scale[1]} {scale[2]}')
elif chunk_id == SKELETON_BONE_PARENT:
child_handle, parent_handle = struct.unpack_from('<HH', data, offset)
offset += 4
parent = ET.SubElement(skeleton, 'BoneParent')
parent.set('ChildHandle', str(child_handle))
parent.set('ParentHandle', str(parent_handle))
elif chunk_id == SKELETON_ANIMATION:
name, offset = read_string(data, offset)
length, = struct.unpack_from('<f', data, offset)
offset += 4
animation = ET.SubElement(skeleton, 'Animation')
animation.set('Name', name)
animation.set('Length', str(length))
elif chunk_id == SKELETON_ANIMATION_TRACK:
bone_index, = struct.unpack_from('<H', data, offset)
offset += 2
track = ET.SubElement(animation, 'Track')
track.set('BoneIndex', str(bone_index))
elif chunk_id == SKELETON_ANIMATION_TRACK_KEYFRAME:
time, = struct.unpack_from('<f', data, offset)
offset += 4
rotation = struct.unpack_from('<ffff', data, offset)
offset += 16
translation = struct.unpack_from('<fff', data, offset)
offset += 12
scale = struct.unpack_from('<fff', data, offset)
offset += 12
keyframe = ET.SubElement(track, 'KeyFrame')
keyframe.set('Time', str(time))
keyframe.set('Rotation', f'{rotation[0]} {rotation[1]} {rotation[2]} {rotation[3]}')
keyframe.set('Translation', f'{translation[0]} {translation[1]} {translation[2]}')
keyframe.set('Scale', f'{scale[0]} {scale[1]} {scale[2]}')
else:
offset += length - 6 # Skip unknown chunks
tree = ET.ElementTree(skeleton)
return tree
def convert_to_xml(input_file, output_file):
with open(input_file, 'rb') as f:
data = f.read()
tree = parse_skeleton(data)
tree.write(output_file, encoding='utf-8', xml_declaration=True)
# Example usage
input_file = "ninja.skeleton"
output_file = "ninja.skeleton.xml"
convert_to_xml(input_file, output_file)