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