Disney Infinity Scene Format
Posted: Mon Mar 17, 2014 1:11 am
Someone already figured out that *.vbuf and *.ibuf are just plain vertex/index data. viewtopic.php?f=16&t=11187
There is absolutely no meta information inside vbuf ibuf, actual model info are stored in *.bent and *.oct file.
Based on my research *.bent, *.oct, *.banm, *.mer *.tup(from cars2) are in exactly same binary format.
so I'll Just call that a scene format
here is the format:
TL DR: here is the script, python 3.2.
this script prints whole reversed document to stdout
you can redirect output to a file.
I reversed most formats (like 95+%), there is still some unknown format.
OK End of Scene Format
------------------------------------------------------------------
Let's talk about model and animation now.
I'll just use fro_elsa as example (elsa from frozen)
link to files here (I didn't include the whole folder, there are like 1000 animations lol)
http://www.mediafire.com/download/y0c5s ... o_elsa.zip
*.txt is reversed files using my script
fro_elsa.bent file includes animation mapping [anim_name]->[file_name]
fro_elsa.oct file includes texture,index,vertex and maybe ???bones???
fro_elsa.mtb is material bundle, format unknown
texture/*.tbody are the textures in DDS format, their name should be some sort of hash value.
characters/*/*.[oct/banm] are animation files. they are all in Scene Format.
===Index/Vertex===
each index/vertex pair are called Idata/Vdata in fro_elsa.oct file
example:
I have no idea what are these number represents
===Bones===
I am not sure how bones stored.
there are some "Influences" in fro_elsa.oct
and also some weird subnetwork and datanode stuff....
===Animations===
I believe animation data is stored in
*.oct->SubNetworkPool->SubNetwork->DataNodePool->DataNode->ClipDataBlock
but the clip data block is just a array of binary data.
I have no idea how that works lol xD
Just take a look at reversed .txt file
If you find out something new pls post here.
Feel free to modify my script
There is absolutely no meta information inside vbuf ibuf, actual model info are stored in *.bent and *.oct file.
Based on my research *.bent, *.oct, *.banm, *.mer *.tup(from cars2) are in exactly same binary format.
so I'll Just call that a scene format
here is the format:
Code: Select all
header section: int8[61]
detail:
magic = int8[12] '\x29\x76\x01\x45\xcd\xcc\x8c\x3f\x00\x00\x00\x00'
something = int8[10] some flags maybe ?
padding = int8[39]
strings section: c-style 0 ending strings
detail:
"string_1",0,"string_2",0...,"string_n",0,0x01,0x00,0x00,0x00
notices:
strings array should starts with "", string_1 actually means index at 1.
data section:
data structure are indentation based. each data has its indentation,type and name.
So, there is NO character for begin/end, structure looks like this
SceneNodes =
Node "1" =
name = "hello"
type = "node"
Textures =
Texture "1" =
name = "xxx"
blahblah
read until end:
flag = int16
nameindex = int16
name = strings[nameindex]
indentation = flag // 0x400
format = flag % 0x400
*format is really complex, you can just read the python script i provided.
Code: Select all
# Disney Infinity .oct .bent .banm .mer reader
# Author: zzh8829
# Email: zzh8829#gmail.com
import os
import io
import pprint
import sys
import struct
types = {
'int8_t': 'b',
'uint8_t': 'B',
'int16_t': 'h',
'uint16_t': 'H',
'int32_t': 'i',
'uint32_t': 'I',
'int64_t': 'q',
'uint64_t': 'Q',
'float': 'f',
'double': 'd',
'char': 'c',
'bool': '?',
'pad': 'x',
'void*': 'P',
}
class BStream:
def __init__(self, **kwargs):
if "file" in kwargs:
self.stream = open(kwargs["file"], "rb")
elif "stream" in kwargs:
self.stream = kwargs["stream"]
elif "bytes" in kwargs:
self.stream = io.BytesIO(kwargs["bytes"])
else:
raise Exception("UnityStream arguments error")
def read(self, type_name='char'):
if isinstance(type_name,int):
return self.unpack('%ds'%type_name)[0]
fmt = types[type_name.lower()]
return self.unpack(fmt)[0]
def unpack(self, fmt):
return struct.unpack(fmt, self.stream.read(struct.calcsize(fmt)))
def read_cstring(self):
string = ""
while True:
char = self.read('char')
if ord(char) == 0:
break
string += char.decode("utf-8")
return string
def read_string(self):
return self.unpack('%ds'%self.read('uint32_t'))[0].decode('utf-8')
def read_all(self):
return self.read(self.size() - self.get_position())
def read_int12(self):
return int.from_bytes(self.read(3),byteorder="little")
def get_position(self):
return self.stream.tell()
def set_position(self, pos, whence=0):
self.stream.seek(pos, whence)
def size(self):
pos = self.get_position()
self.set_position(0,2)
end = self.get_position()
self.set_position(pos,0)
return end
def align(self, alignment=4):
self.set_position((self.get_position() + alignment - 1) // alignment * alignment)
def read_oct(stream):
file_size = stream.size()
magic = stream.read(12)
header = stream.read(10)
padding = stream.read(39)
strings = [""]
s = ""
while s!="\x01":
s = stream.read_cstring()
strings.append(s)
padding = stream.read(2)
while stream.get_position() != file_size:
flag = stream.read("uint16_t")
name = strings[stream.read("uint16_t")]
indent,format = divmod(flag,0x400)
print("\t"*(indent-1) + name + "[%04x]"%format, end=" = ")
# unknown sign all treat as unsigned !!!
if format == 0x01:
print()
elif format == 0x05:
data = strings[stream.read("uint16_t")]
print("'%s'"%data)
elif format == 0x0A:
count = stream.read("uint8_t")
data = []
for i in range(count):
data.append(strings[stream.read("uint16_t")])
print(data)
elif format == 0x0B:
data = strings[stream.read("uint16_t")]
print("'%s'"%data)
elif format == 0x12:
count = stream.read("uint8_t")
data = []
for i in range(count):
data.append(stream.read("float"))
print(data)
elif format == 0x13:
data = stream.read("float")
print(data)
elif format == 0x1A:
count = stream.read("uint8_t")
data = []
for i in range(count):
data.append(stream.read("int8_t"))
print(data)
elif format == 0x1B:
data = stream.read("int8_t")
print(data)
elif format == 0x23:
count = stream.read("uint8_t")
data = []
for i in range(count):
data.append(stream.read("uint8_t"))
print(data)
elif format == 0x4A:
count = stream.read("uint16_t")
data = []
for i in range(count):
data.append(strings[stream.read("uint16_t")])
print(data)
elif format == 0x5A:
count = stream.read("uint16_t")
data = []
for i in range(count):
data.append(stream.read("uint8_t"))
print(data)
elif format == 0x63: # binary data
count = stream.read("uint16_t")
data = []
for i in range(count):
data.append(stream.read("uint8_t"))
print(data)
elif format == 0x11A:
count = stream.read("uint8_t")
data = []
for i in range(count):
data.append(stream.read("uint16_t"))
print(data)
elif format == 0x11B:
data = stream.read("uint16_t")
print(data)
elif format == 0x15A:
count = stream.read("uint16_t")
data = []
for i in range(count):
data.append(stream.read("uint16_t"))
print(data)
elif format == 0x21A:
count = stream.read("uint8_t")
data = []
for i in range(count):
data.append(stream.read_int12())
print(data)
elif format == 0x21B:
data = stream.read_int12()
print(data)
elif format == 0x31B:
data = stream.read("uint32_t")
print(data)
else:
print("unknown format: %x offset: %x"%(flag,stream.get_position()))
sys.stderr.write("unknown format: %x offset: %x\n"%(flag,stream.get_position()))
print(stream.read_all()[:100])
break
if __name__ == '__main__':
read_oct(BStream(file="fro_elsa.oct"))
you can redirect output to a file.
I reversed most formats (like 95+%), there is still some unknown format.
OK End of Scene Format
------------------------------------------------------------------
Let's talk about model and animation now.
I'll just use fro_elsa as example (elsa from frozen)
link to files here (I didn't include the whole folder, there are like 1000 animations lol)
http://www.mediafire.com/download/y0c5s ... o_elsa.zip
*.txt is reversed files using my script
fro_elsa.bent file includes animation mapping [anim_name]->[file_name]
fro_elsa.oct file includes texture,index,vertex and maybe ???bones???
fro_elsa.mtb is material bundle, format unknown
texture/*.tbody are the textures in DDS format, their name should be some sort of hash value.
characters/*/*.[oct/banm] are animation files. they are all in Scene Format.
===Index/Vertex===
each index/vertex pair are called Idata/Vdata in fro_elsa.oct file
example:
Code: Select all
VertexBufferPool[0001] =
VertexBuffer[0005] = '0'
Name[000b] = 'Static'
Flags[001b] = 73
Size[021b] = 216760
HeapLoc[001b] = 0
FileName[000b] = 'fro_elsa_0.vbuf'
IndexBufferPool[0001] =
IndexBuffer[0005] = '0'
Width[001b] = 2
Name[000b] = 'Static'
Flags[001b] = 91
Size[021b] = 53610
FileName[000b] = 'fro_elsa_0.ibuf'
SceneTreeNodePool
...
Node
...
Primitives[0001] =
Primitive[0005] = '0'
MaterialName[000b] = 'characters__fro_elsa__materials__fro_elsa_pupil__skinning_isEnabled_true'
MaterialReference[001b] = 0
vformatCRC[031b] = 1630786171
RenderCaps[011b] = 4096
BillboardType[001b] = -1
OcclusionType[001b] = 0
OcclusionCheckRadius[0013] = 0.25
OcclusionFadeKp[0013] = 0.10000000149011612
Idata[011a] = [0, 0, 0, 1728]
Vdata[021a] = [2, 5419, 0, 0, 24, 0, 130056, 16]
Primitive[0005] = '1'
MaterialName[000b] = 'characters__fro_elsa__materials__fro_elsa_head__skinning_isEnabled_true'
MaterialReference[001b] = 1
vformatCRC[031b] = 1630786171
RenderCaps[011b] = 4096
BillboardType[001b] = -1
OcclusionType[001b] = 0
OcclusionCheckRadius[0013] = 0.25
OcclusionFadeKp[0013] = 0.10000000149011612
Idata[011a] = [0, 1728, 0, 14097]
Vdata[021a] = [2, 5419, 0, 0, 24, 0, 130056, 16]
===Bones===
I am not sure how bones stored.
there are some "Influences" in fro_elsa.oct
and also some weird subnetwork and datanode stuff....
===Animations===
I believe animation data is stored in
*.oct->SubNetworkPool->SubNetwork->DataNodePool->DataNode->ClipDataBlock
but the clip data block is just a array of binary data.
I have no idea how that works lol xD
Just take a look at reversed .txt file
If you find out something new pls post here.
Feel free to modify my script