#! /bin/python3
import struct
import zlib
import sys
import traceback
import math
from bones import *
#Ver 0 .geo pre-header:
#Offset Size Description
#0 4 'ziplen' ? Compressed header data size + 4
#4 4 Header size (Must be non-zero for old formats.)
#8 variable zlib Compressed header data
#variable 4 Dummy data?
#Version 2+ .geo pre-header:
#Offset Size Description
#0 4 'ziplen' ? Compressed header data size + 12
#4 4 Legacy header size (Must be 0 for new file formats.)
#8 4 Version number 2 to 5, and 7 to 8
#12 4 Header size
#16 variable zlib Compressed header data
#.geo header
#Offset Size Description
#0 4 i32: gld->datasize
#4 4 i32: texname_blocksize
#8 4 i32: objname_blocksize
#12 4 i32: texidx_blocksize
#16 4 i32: lodinfo_blocksize (only present in version 2 to 6)
#? texname_blocksize PackNames block of texture names
#? objname_blocksize objnames
#? texidx_blocksize texidx
#? lodinfo_blocksize lodinfo (only present in version 2 to 6)
#? ModelHeader ???
#PackNames block:
#Size Description
#4 name_count
#4*name_count offset of the name, relative to the start of name_block
#variable name_block (ASCIIZ?)
#ModelHeader:
#Size Description
#124 ASCIIZ name.
#4 Model pointer (0 in file?)
#4 Float track length (technically a fallback if not specified elsewhere?)
#4 Pointer to an array of Model pointers (-1 in file?)
#*note: pointers aren't really needed.
#Model:
#version 0 to 2: (struct ModelFormatOnDisk_v2: "Common/seq/anim.c" )
#Offset Size Description
#0 4 u32: Flags
#4 4 f32: radius
#8 4 ptr: VBO?
#12 4 i32: tex_count
#16 2 i16: id ("I am this bone" ?)
#18 1 u8: blend_mode
#19 1 u8: loadstate
#20 4 ptr(BoneInfo)
#24 4 ptr(TrickNode)
#28 4 i32: vert_count
#32 4 i32: tri_count
#36 4 ptr: TexID
#40 32 PolyGrid: polygrid
#72 4 ptr(CTri): ctris ?
#76 4 ptr(i32): tags
#80 4 ptr(char): name
#84 4 ptr(AltPivotInfo): api
#88 4 ptr(ModelExtra): extra
#92 12 Vec3: scale
#104 12 Vec3: min
#116 12 Vec3: max
#120 4 ptr(GeoLoadData): gld
#124 84 PackBlockOnDisk
#208
#version 3+: "Common/seq/anim.c"
# Offset Size Description
# 0 4 i32: size
# 4 4 f32: radius
# 8 4 i32: tex_count
# 12 4 ptr(BoneInfo): boneinfo ?
# 16 4 i32: vert_count
# 20 4 i32: tri_count
#ver 8+ 24 4 i32: reflection_quad_count
# +4 4 i32: tex_idx
# +4 32 PolyGrid: grid
# +32 4 ptr(char): name
# +4 4 ptr(AltPivotInfo):
# +4 12 Vec3: scale
# +12 12 Vec3: min
# +12 12 Vec3: max
# +12 12 PackData: pack.tris
# +12 12 PackData: pack.verts
# +12 12 PackData: pack.norms
# +12 12 PackData: pack.sts
# +12 12 PackData: pack.sts3
# +12 12 PackData: pack.weights
# +12 12 PackData: pack.matidxs
# +12 12 PackData: pack.grid
#Ver 4 +12 12 PackData: pack.lmap_utransforms
#Ver 4 +12 12 PackData: pack.lmap_vtransforms
#Ver 7+ +12 12 PackData: pack.reductions
#Ver 8+ +12 12 PackData: pack.reflection_quads
#Ver 7+ +12 12 f32[3]: autolod_dists
# +12 2 i16: id
#PackBlockOnDisk: "Common/seq/anim.c"
#Offset Size Description
#0 12 PackData: tris
#12 12 PackData: verts
#24 12 PackData: norms
#36 12 PackData: sts
#48 12 PackData: weights
#60 12 PackData: matidxs
#72 12 PackData: grid
#84
#PackData:
#Offset Size Description
#0 4 i32: packsize, The compressed size of this data block. 0 if this is uncompressed.
#4 4 u32: unpacksize, The size of this data block when uncompressed.
#8 4 ptr(u8): data, The offset of this block of data inside the .geo's main data block.
#12
#struct PolyGrid: "libs/UtilitiesLib/components/gridpoly.h"
#Offset Size Description
#0 4 ptr(PolyCell): cell
#4 12 Vec3: pos
#16 4 f32: size
#20 4 f32: inv_size
#24 4 i32: tag
#28 4 i32: num_bits
#32
#PolyCell:
#Offset Size Description
#0 4 ptr(ptr(PolyCell)): children
#4 4 ptr(u16): tri_idxs, Triangle indexes.
#8 4 i32: tri_count
#BoneInfo
#Offset Size Description
#0 4 i32: numbones
#4 4*15 i32[15]: bone_ID, Bones used by this geometry.
#64
#Reductions
#Size Description
#4 int: num_reductions
#num_reductions*4 int[]:num_tris_left
#num_reductions*4 int[]:error_values
#num_reductions*4 int[]:remap_counts
#num_reductions*4 int[]:changes_counts
#4 int: total_remaps
#total_remaps*4 int[]:remaps
#4 int: total_remap_tris
#total_remap_tris*4 int[]:remap_tris
#4 int: total_changes
#total_changes*4 int[]:changes
#4 int: positions_delta_length
#positions_delta_length byte[]: compressDelta(positions, 3, total_changes, "f", 0x8000)
#4 int: total_changes_delta_length
#total_changes_delta_length byte[]: compressDelta(changes, 2, total_changes, "f", 0x1000)
ZERO_BYTE = struct . pack ( " B " , 0 )
#def unbyte(v):
# return struct.unpack("B", v)[0]
if sys . version_info [ 0 ] < 3 :
byte = chr
unbyte = ord
else :
def byte ( v ) :
#return struct.pack("B", v)
return bytes ( ( v , ) )
def unbyte ( v ) :
return v
def unpackNames ( data ) :
""" Extract strings from data, as layed out as a PackNames structure. """
( length , ) = struct . unpack ( " <i " , data [ 0 : 4 ] )
strings = [ ]
data_strings = data [ length * 4 + 4 : ]
for i in range ( length ) :
#todo: scan string for zero
( offset , ) = struct . unpack ( " <i " , data [ i * 4 + 4 : i * 4 + 4 + 4 ] )
for j in range ( offset , len ( data_strings ) ) :
if unbyte ( data_strings [ j ] ) == 0 :
strings . append ( data_strings [ offset : j ] )
break
pass
pass
return strings
def packNames ( strings ) :
""" Convert a sequence of strings into a PackNames structure. """
data_strings = b " "
data = struct . pack ( " <i " , len ( strings ) )
for s in strings :
data + = struct . pack ( " <i " , len ( data_strings ) )
data_strings + = s + ZERO_BYTE
return data + data_strings
def unpackStrings ( data ) :
""" Convert a block of ASCIIZ strings into list of strings. """
strings = [ ]
start = 0
for i in range ( len ( data ) ) :
if unbyte ( data [ i ] ) == 0 :
strings . append ( data [ start : i ] )
start = i + 1
pass
pass
return strings
def packStrings ( strings ) :
""" Convert a list of strings into a block of ASCIIZ strings """
data = b " "
for s in strings :
data + = s + ZERO_BYTE
return data
def extractString ( data , offset = 0 ) :
""" Extract an ASCIIZ string from a fixed length buffer. """
for i in range ( offset , len ( data ) ) :
if unbyte ( data [ i ] ) == 0 :
return data [ offset : i ]
pass
return data
def storeString ( string , length ) :
""" Store an ASCIIZ string into a fixed length buffer. If the string is longer than the buffer the output will be truncated, with \\ x00 character guarenteed at the end. """
if len ( string ) > = length :
return string [ 0 : length - 1 ] + ZERO_BYTE
else :
return string + ZERO_BYTE * ( length - len ( string ) )
def uncompressDeltas ( src , stride , count , pack_type ) :
""" Expand a list a delta compressed with deltas
src : Source data containg 3 parts :
- delta codes : Contain ' count ' * ' stride ' 2 bit size code fields . The end of the block is padded to the byte boundary .
- float scale : A byte with the exponent for scaling floating point deltas . This part is present with integer types , but unused .
- byte data : This is read and processed according to the codes specified in the delta codes block .
stride : the number of channels in the data ( 1 to 3 )
count : the number off elements in the data
pack_type : Is a character indicating the type of packing . Valid values are " f " ( float32 ) , " H " ( unsigned16 ) , " I " ( unsigned32 ) ( these match the struct modules types ) .
The returned data is an array of arrays , with the inner most arrays being ' stride ' long , and the outer array being ' count ' in length .
"""
if len ( src ) < = 0 :
return None
#Compute offset to the byte data after all the bit fields.
byte_offset = int ( ( 2 * count * stride + 7 ) / 8 )
#print("stride: %d count: %d pack_type: %s" % (stride, count, pack_type))
#print("src:%s" % ([src],))
float_scale = float ( 1 << unbyte ( src [ byte_offset ] ) )
float_scale_inv = 1.0 / float_scale
byte_offset + = 1
current_bit = 0
if pack_type in " f " :
fLast = [ 0.0 ] * stride
else :
iLast = [ 0 ] * stride
out_data = [ ]
for i in range ( count ) :
row_data = [ ]
for j in range ( stride ) :
code = ( unbyte ( src [ current_bit >> 3 ] ) >> ( current_bit & 0x7 ) ) & 0x3
current_bit + = 2
if code == 0 :
iDelta = 0
elif code == 1 :
iDelta = unbyte ( src [ byte_offset ] ) - 0x7f
byte_offset + = 1
elif code == 2 :
iDelta = (
unbyte ( src [ byte_offset ] ) |
( unbyte ( src [ byte_offset + 1 ] ) << 8 )
) - 0x7fff
byte_offset + = 2
elif code == 3 :
( iDelta , ) = struct . unpack ( " <i " , src [ byte_offset : byte_offset + 4 ] )
byte_offset + = 4
if pack_type == " f " :
if code == 3 :
( fDelta , ) = struct . unpack ( " <f " , src [ byte_offset - 4 : byte_offset ] )
else :
fDelta = iDelta * float_scale_inv
fLast [ j ] + = fDelta
row_data . append ( fLast [ j ] )
elif pack_type == " I " :
iLast [ j ] + = iDelta + 1
iLast [ j ] & = 0xffffffff
row_data . append ( iLast [ j ] )
elif pack_type == " H " :
iLast [ j ] + = iDelta + 1
iLast [ j ] & = 0xffff
row_data . append ( iLast [ j ] )
out_data . append ( row_data )
return out_data
def quantF32 ( v , float_scale , float_scale_inv ) :
i = int ( v * float_scale )
i & = ~ 1
return i * float_scale_inv
def compressDeltas ( src , stride , count , pack_type , float_scale ) :
if float_scale != 0 :
float_scale_inv = 1.0 / float_scale
else :
float_scale_inv = 1.0
codes = [ 0 ] * ( stride * count + 3 ) #over fill it by 3 for easier conversion into byte data
float_scale_byte = byte ( int ( math . log ( float_scale , 2 ) ) )
bytes_data = b " "
if pack_type in " f " :
fLast = [ 0.0 ] * stride
else :
iLast = [ 0 ] * stride
if pack_type == " I " :
iMask = 0xffffffff
else :
iMask = 0xffff
k = 0
for i in range ( count ) :
for j in range ( stride ) :
if pack_type == " f " :
fDelta = quantF32 ( src [ i ] [ j ] , float_scale , float_scale_inv ) - fLast [ j ]
val8 = int ( fDelta * float_scale + 0x7f )
val16 = int ( fDelta * float_scale + 0x7fff )
else :
t = src [ i ] [ j ]
iDelta = t - iLast [ j ] - 1
iLast [ j ] = t
val8 = iDelta + 0x7f
val16 = iDelta + 0x7fff
val32 = iDelta ;
if val8 == 0x7f :
codes [ k ] = 0
elif val8 & ~ 0xff == 0 :
codes [ k ] = 1
bytes_data + = byte ( val8 )
if pack_type == " f " :
fLast [ j ] = ( val8 - 0x7f ) * float_scale_inv + fLast [ j ] ;
elif pack_type == " H " or val16 & ~ 0xffff == 0 :
codes [ k ] = 2
bytes_data + = struct . pack ( " <H " , val16 )
if pack_type == " f " :
fLast [ j ] = ( val16 - 0x7fff ) * float_scale_inv + fLast [ j ] ;
elif pack_type == " I " :
codes [ k ] = 3
bytes_data + = struct . pack ( " <I " , val32 & iMask )
else :
codes [ k ] = 3
bytes_data + = struct . pack ( " <f " , fDelta )
fLast [ j ] = fDelta + fLast [ j ]
k + = 1
code_data = b " "
for i in range ( 0 , stride * count , 4 ) :
v = ( ( codes [ i + 0 ] << 0 ) |
( codes [ i + 1 ] << 2 ) |
( codes [ i + 2 ] << 4 ) |
( codes [ i + 3 ] << 6 ) )
code_data + = byte ( v )
return code_data + float_scale_byte + bytes_data
def inferSizes ( size , pack_list , other_list ) :
""" Infer the size of the blocks in ' other_list ' as infered by the total size and items in ' pack_list ' . """
starts = [ ]
for p in pack_list :
s = p [ 2 ]
if s in starts :
continue
starts . append ( s )
for o in other_list :
if o in starts :
continue
starts . append ( o )
starts . sort ( )
output = [ ]
for o in other_list :
if o == 0 :
output . append ( ( 0 , 0 , 0 ) )
continue
i = starts . index ( o ) + 1
if i < 0 or i > = len ( starts ) :
output . append ( ( 0 , 0 , o ) )
continue
output . append ( ( 0 , starts [ i ] - o , o ) )
return tuple ( output )
class PolyCell :
def __init__ ( self , model ) :
self . model = model
self . children = None
self . tri_idxs = [ ]
self . tri_count = 0
def decode ( self , data , offset ) :
( children_offset , tri_idxs_offset , self . tri_count ) = struct . unpack ( " <iii " , data [ offset : offset + 12 ] )
if children_offset == 0 :
self . children = None
else :
self . children = [ None ] * 8
children_offsets = struct . unpack ( " <iiiiiiii " , data [ children_offset : children_offset + 4 * 8 ] )
for i in range ( 8 ) :
if children_offsets [ i ] == 0 :
continue
self . children [ i ] = PolyCell ( self . model )
self . children [ i ] . decode ( data , children_offsets [ i ] )
if tri_idxs_offset == 0 :
self . tri_idxs = [ ]
else :
self . tri_idxs = struct . unpack ( " < " + " H " * self . tri_count , data [ tri_idxs_offset : tri_idxs_offset + self . tri_count * 2 ] )
def dump ( self , indent ) :
print ( indent + " tri_idxs: %s " % ( [ self . tri_idxs ] , ) )
print ( indent + " children? %s " % ( self . children is not None , ) )
if self . children is not None :
for c in self . children :
if c is not None :
c . dump ( indent + " " )
class PolyGrid :
def __init__ ( self , model ) :
self . model = model
self . cell = None
pass
def parsePolyGridData ( self , data ) :
self . cell = PolyCell ( self . model )
self . cell . decode ( data , 0 )
pass
def dump ( self ) :
if self . cell is not None :
self . cell . dump ( " " )
class Data :
def __init__ ( self , rawdata = b " " , off = 0 ) :
self . data = rawdata
self . offset = off
def setData ( self , rawdata , off = 0 ) :
self . data = rawdata
self . offset = off
def seek ( self , off ) :
self . offset = off
def seekEnd ( self , off = 0 ) :
self . offset = len ( self . data ) + off
def seekRel ( self , off = 0 ) :
self . offset + = off
def decode ( self , fmt ) :
size = struct . calcsize ( fmt )
val = struct . unpack ( fmt , self . data [ self . offset : self . offset + size ] )
self . offset + = size
return val
def encode ( self , fmt , * args ) :
data = struct . pack ( fmt , * args )
self . write ( data )
def read ( self , length ) :
val = self . data [ self . offset : self . offset + length ]
self . offset + = length
return val
def write ( self , data ) :
#todo: handle
if self . offset == len ( self . data ) :
self . data + = data
self . offset = len ( self . data )
elif self . offset > len ( self . data ) :
self . data + = ZERO_BYTE * ( self . offset - len ( self . data ) )
self . data + = data
self . offset = len ( self . data )
elif self . offset + len ( data ) > = len ( self . data ) :
self . data = self . data [ 0 : self . offset ] + data
else :
self . data = self . data [ 0 : self . offset ] + data + self . data [ self . offset + len ( data ) : ]
def truncate ( self , offset = None ) :
if offset is None :
offset = self . offset
self . data [ 0 : offset ]
class Reductions :
def __init__ ( self , model ) :
self . model = model
def decode ( self , data ) :
( self . num_reductions , ) = data . decode ( " <i " )
self . num_tris_left = data . decode ( " < " + " i " * self . num_reductions )
self . error_values = data . decode ( " < " + " f " * self . num_reductions )
self . remap_counts = data . decode ( " < " + " i " * self . num_reductions )
self . changes_counts = data . decode ( " < " + " i " * self . num_reductions )
( self . total_remaps , ) = data . decode ( " <i " )
self . remaps = data . decode ( " < " + " i " * ( self . total_remaps * 3 ) )
( self . total_remap_tris , ) = data . decode ( " <i " )
self . remap_tris = data . decode ( " < " + " i " * self . total_remap_tris )
( self . total_changes , ) = data . decode ( " <i " )
self . changes = data . decode ( " < " + " i " * self . total_changes )
( positions_delta_length , ) = data . decode ( " <i " )
#print("positions_delta_length: %s" % (positions_delta_length, ))
#print("data remaining: %s" %([data.data[data.offset : ]], ))
#self.dump()
self . positions = uncompressDeltas ( data . read ( positions_delta_length ) , 3 , self . total_changes , " f " )
( tex1s_delta_length , ) = data . decode ( " <i " )
self . tex1s = uncompressDeltas ( data . read ( tex1s_delta_length ) , 2 , self . total_changes , " f " )
#print("data remaining: %s" %([data.data[data.offset : ]], ))
def encode ( self ) :
data = b " "
data + = struct . pack ( " <i " , self . num_reductions )
data + = struct . pack ( " < " + " i " * self . num_reductions , * self . num_tris_left )
data + = struct . pack ( " < " + " f " * self . num_reductions , * self . error_values )
data + = struct . pack ( " < " + " i " * self . num_reductions , * self . remap_counts )
data + = struct . pack ( " < " + " i " * self . num_reductions , * self . changes_counts )
data + = struct . pack ( " <i " , self . total_remaps )
data + = struct . pack ( " < " + " i " * ( self . total_remaps * 3 ) , * self . remaps )
data + = struct . pack ( " <i " , self . total_remap_tris )
data + = struct . pack ( " < " + " i " * self . total_remap_tris , * self . remap_tris )
data + = struct . pack ( " <i " , self . total_changes )
data + = struct . pack ( " < " + " i " * self . total_changes , * self . changes )
d = compressDeltas ( self . positions , 3 , self . total_changes , " f " , 0x8000 )
data + = struct . pack ( " <i " , len ( d ) )
data + = d
d = compressDeltas ( self . tex1s , 2 , self . total_changes , " f " , 0x1000 )
data + = struct . pack ( " <i " , len ( d ) )
data + = d
return data
def dump ( self ) :
print ( " reductions: " )
print ( " num_reductions: %s " % ( self . num_reductions , ) )
print ( " num_tris_left: %s " % ( self . num_tris_left , ) )
print ( " error_values: %s " % ( self . error_values , ) )
print ( " remap_counts: %s " % ( self . remap_counts , ) )
print ( " changes_counts: %s " % ( self . changes_counts , ) )
print ( " total_remaps: %s " % ( self . total_remaps , ) )
print ( " remaps: %s " % ( self . remaps , ) )
print ( " total_remap_tris: %s " % ( self . total_remap_tris , ) )
print ( " remap_tris: %s " % ( self . remap_tris , ) )
print ( " total_changes: %s " % ( self . total_changes , ) )
print ( " changes: %s " % ( self . changes , ) )
print ( " positions: %s " % ( self . positions , ) )
print ( " tex1s: %s " % ( self . tex1s , ) )
class Model :
def __init__ ( self , geo ) :
self . geo = geo
self . radius = None
self . tex_count = None
self . vert_count = None
self . tri_count = None
self . reflection_quad_count = None
self . tex_idx = None
pass
def parseHeaderDataV2 ( self ) :
( self . flags , ) = self . geo . getHeaderElement ( " <I " )
( self . radius , ) = self . geo . getHeaderElement ( " <f " )
self . geo . header_offset + = 4 #ptr(VBO)
#print("VBO: %s" % (self.geo.getHeaderElement("<i"), ))
( self . tex_count , ) = self . geo . getHeaderElement ( " <i " )
( self . id , blend_mode , loadstate ) = self . geo . getHeaderElement ( " <hBB " )
#print("blend_mode, loadstate: %d, %d" % (blend_mode, loadstate))
( self . boneinfo_ptr , ) = self . geo . getHeaderElement ( " <i " )
self . geo . header_offset + = 4 #ptr(TrickNode)
#print("TrickNode: %s" % (self.geo.getHeaderElement("<i"), ))
( self . vert_count , self . tri_count ) = self . geo . getHeaderElement ( " <ii " )
( self . tex_idx_ptr , ) = self . geo . getHeaderElement ( " <i " )
self . grid_header = self . geo . getHeaderElement ( " <ifffffii " )
self . geo . header_offset + = 4 #ptr(CTri)
self . geo . header_offset + = 4 #ptr(tags)
#print("CTri: %s" % (self.geo.getHeaderElement("<i"), ))
#print("Tags: %s" % (self.geo.getHeaderElement("<i"), ))
( self . name_ptr , self . api_ptr ) = self . geo . getHeaderElement ( " <ii " )
#print("???: %s" % (self.geo.getHeaderElement("<f"), ))
self . geo . header_offset + = 4 #ptr(ModelExtra)
self . scale = list ( self . geo . getHeaderElement ( " <fff " ) )
self . min = list ( self . geo . getHeaderElement ( " <fff " ) )
self . max = list ( self . geo . getHeaderElement ( " <fff " ) )
self . geo . header_offset + = 4 #ptr(GeoLoadData)
#print("GeoLoadData: %s" % (self.geo.getHeaderElement("<i"), ))
self . pack_tris = self . geo . getHeaderElement ( " <iii " )
self . pack_verts = self . geo . getHeaderElement ( " <iii " )
self . pack_norms = self . geo . getHeaderElement ( " <iii " )
self . pack_sts = self . geo . getHeaderElement ( " <iii " )
self . pack_sts3 = ( 0 , 0 , 0 )
self . pack_weights = self . geo . getHeaderElement ( " <iii " )
self . pack_matidxs = self . geo . getHeaderElement ( " <iii " )
self . pack_grid = self . geo . getHeaderElement ( " <iii " )
self . pack_reductions = ( 0 , 0 , 0 )
self . pack_reflection_quads = ( 0 , 0 , 0 )
self . name = extractString ( self . geo . header_objname_data , self . name_ptr )
self . autolod_dists = [ - 1 , - 1 , - 1 ]
self . skipped_data = None
#self.dump()
def parseHeaderData ( self ) :
if self . geo . version < = 2 :
self . parseHeaderDataV2 ( )
return
( size , ) = struct . unpack ( " <i " , self . geo . header_data [ self . geo . header_offset : self . geo . header_offset + 4 ] )
final_offset = self . geo . header_offset + size
self . geo . header_offset + = 4
( self . radius , ) = self . geo . getHeaderElement ( " <f " )
( self . tex_count , self . boneinfo_ptr ) = self . geo . getHeaderElement ( " <ii " )
( self . vert_count , self . tri_count ) = self . geo . getHeaderElement ( " <ii " )
if self . geo . version > = 8 :
( self . reflection_quad_count , ) = self . geo . getHeaderElement ( " <i " )
( self . tex_idx_ptr , ) = self . geo . getHeaderElement ( " <i " )
self . grid_header = self . geo . getHeaderElement ( " <ifffffii " )
( self . name_ptr , self . api_ptr ) = self . geo . getHeaderElement ( " <ii " )
self . scale = list ( self . geo . getHeaderElement ( " <fff " ) )
self . min = list ( self . geo . getHeaderElement ( " <fff " ) )
self . max = list ( self . geo . getHeaderElement ( " <fff " ) )
self . pack_tris = self . geo . getHeaderElement ( " <iii " )
self . pack_verts = self . geo . getHeaderElement ( " <iii " )
self . pack_norms = self . geo . getHeaderElement ( " <iii " )
self . pack_sts = self . geo . getHeaderElement ( " <iii " )
self . pack_sts3 = self . geo . getHeaderElement ( " <iii " )
self . pack_weights = self . geo . getHeaderElement ( " <iii " )
self . pack_matidxs = self . geo . getHeaderElement ( " <iii " )
self . pack_grid = self . geo . getHeaderElement ( " <iii " )
if self . geo . version == 4 :
#pack.lmap_utransforms, pack.lmap_vtransforms
self . geo . header_offset + = 12 * 2
if self . geo . version > = 7 :
self . pack_reductions = self . geo . getHeaderElement ( " <iii " )
else :
self . pack_reductions = ( 0 , 0 , 0 )
if self . geo . version > = 8 :
self . pack_reflection_quads = self . geo . getHeaderElement ( " <iii " )
else :
self . pack_reflection_quads = ( 0 , 0 , 0 )
pack_list = [
self . pack_tris ,
self . pack_verts ,
self . pack_norms ,
self . pack_sts ,
self . pack_sts3 ,
self . pack_weights ,
self . pack_matidxs ,
self . pack_grid ,
#pack.lmap_utransforms, pack.lmap_vtransforms
self . pack_reductions ,
self . pack_reflection_quads ,
]
other_list = [
self . boneinfo_ptr ,
self . name_ptr ,
self . api_ptr ,
]
( self . pack_boneinfo , self . pack_name , self . pack_api ) = inferSizes ( self . geo . main_data_size , pack_list , other_list )
self . name = extractString ( self . geo . header_objname_data , self . name_ptr )
#self.geo.header_offset = final_offset - 12 - 2
if self . geo . version < = 7 :
#lightmap_size
self . geo . header_offset + = 4
if self . geo . version > = 7 :
self . autolod_dists = list ( self . geo . getHeaderElement ( " <fff " ) )
else :
self . autolod_dists = [ - 1 , - 1 , - 1 ]
#(self.mystery, ) = self.geo.getHeaderElement("<f")
( self . id , ) = self . geo . getHeaderElement ( " <h " )
#self.mystery = self.geo.getHeaderElement("<fh")
#print("automatic offset: %d final offset: %d" % (self.geo.header_offset, final_offset))
self . skipped_data = self . geo . header_data [ self . geo . header_offset : final_offset ]
self . geo . header_offset = final_offset
def parseData ( self ) :
self . tris_data = self . geo . getDataBlock ( self . pack_tris )
self . verts_data = self . geo . getDataBlock ( self . pack_verts )
self . norms_data = self . geo . getDataBlock ( self . pack_norms )
self . sts_data = self . geo . getDataBlock ( self . pack_sts )
self . sts3_data = self . geo . getDataBlock ( self . pack_sts3 )
self . weights_data = self . geo . getDataBlock ( self . pack_weights )
self . matidxs_data = self . geo . getDataBlock ( self . pack_matidxs )
self . grid_data = self . geo . getDataBlock ( self . pack_grid )
self . reductions_data = self . geo . getDataBlock ( self . pack_reductions )
self . reflection_quads_data = self . geo . getDataBlock ( self . pack_reflection_quads )
self . tris = uncompressDeltas ( self . tris_data , 3 , self . tri_count , " I " )
self . verts = uncompressDeltas ( self . verts_data , 3 , self . vert_count , " f " )
self . norms = uncompressDeltas ( self . norms_data , 3 , self . vert_count , " f " )
self . sts = uncompressDeltas ( self . sts_data , 2 , self . vert_count , " f " )
self . sts3 = uncompressDeltas ( self . sts3_data , 2 , self . vert_count , " f " )
if self . boneinfo_ptr != 0 :
self . geo . seekMainData ( self . boneinfo_ptr )
( self . bone_count , ) = self . geo . getMainElement ( " <i " )
self . bone_ids = self . geo . getMainElement ( " < " + ( " i " * 15 ) )
else :
self . bone_count = 0
self . bone_ids = [ 0 ] * 15
if len ( self . weights_data ) == 0 :
self . weights = None
self . weight_bones = None
else :
self . weights = [ ]
self . weight_bones = [ ]
for i in range ( self . vert_count ) :
w = unbyte ( self . weights_data [ i ] ) / 255.0
b = self . matidxs_data [ i * 2 : i * 2 + 2 ]
b1 = self . bone_ids [ unbyte ( b [ 0 ] ) / / 3 ]
b2 = self . bone_ids [ unbyte ( b [ 1 ] ) / / 3 ]
self . weights . append ( [ w , 1 - w ] )
self . weight_bones . append ( [ b1 , b2 ] )
if len ( self . grid_data ) == 0 :
self . polygrid = None
else :
self . polygrid = PolyGrid ( self )
self . polygrid . parsePolyGridData ( self . grid_data )
if len ( self . reductions_data ) == 0 :
self . reductions = None
else :
#self.reductions = None
#self.dump()
self . reductions = Reductions ( self )
self . reductions . decode ( Data ( self . reductions_data ) )
if len ( self . reflection_quads_data ) == 0 :
self . reflection_quads = None
else :
#todo:
self . reflection_quads = None
#if self.geo.version <= 2:
# self.dump()
def packDeltas ( self , data , stride , count , pack_type ) :
deltas = compressDeltas ( data , stride , count , pack_type )
pack = self . geo . encodeMainDataPacked ( deltas )
def rebuildWeightsAndBones ( self ) :
self . weights_data = b " "
self . matidxs_data = b " "
if self . weights is None or len ( self . weights ) == 0 :
self . bone_count = 0
self . bone_ids = [ 0 ] * 15
return
self . bone_ids = [ ]
bone_lookup = { }
for wb in self . weight_bones :
for wbx in wb :
if wbx not in bone_lookup :
bone_lookup [ wbx ] = len ( self . bone_ids )
self . bone_ids . append ( wbx )
#print(self.weights)
for i in range ( len ( self . weights ) ) :
#Copy values to avoid corruption.
w = self . weights [ i ] [ : ]
wb = self . weight_bones [ i ] [ : ]
if len ( w ) > 2 :
#.geos only support 2 weights per vertex.
#todo: get only largest weights?
w = w [ 0 : 2 ]
wb = wb [ 0 : 2 ]
if len ( w ) == 1 or wb [ 0 ] == wb [ 1 ] :
self . weights_data + = byte ( 255 )
self . matidxs_data + = byte ( bone_lookup [ wb [ 0 ] ] * 3 ) + ZERO_BYTE
continue
if w [ 0 ] + w [ 1 ] == 0 :
w [ 0 ] = 0.5
w [ 1 ] = 0.5
w [ 0 ] = w [ 0 ] / float ( w [ 0 ] + w [ 1 ] )
if w [ 0 ] < 0 :
w [ 0 ] = 0.0
elif w [ 0 ] > 1 :
w [ 0 ] = 1.0
#print("weights: %s -> %s" % (self.weights[i], w))
self . weights_data + = byte ( int ( math . floor ( 255 * w [ 0 ] + 0.5 ) ) )
self . matidxs_data + = byte ( bone_lookup [ wb [ 0 ] ] * 3 ) + byte ( bone_lookup [ wb [ 1 ] ] * 3 )
self . bone_count = len ( self . bone_ids )
self . bone_ids + = [ 0 ] * ( 15 - self . bone_count )
def encode ( self ) :
#Regenerate dynamic data
#todo: build PolyGrid
if self . geo . version > = 7 :
#todo: build reductions
if self . reductions is not None :
self . reductions_data = self . reductions . encode ( )
else :
self . reductions_data = b " "
pass
self . rebuildWeightsAndBones ( )
#Encode data into the main block.
#note: PackData should go first, otherwise other ptr types might point to the start of the data block, which would result in a 0 point, which is treated as not present.
self . pack_tris = self . geo . encodeMainDataPackedDeltas ( self . tris , 3 , len ( self . tris ) , " I " , 1 )
self . pack_verts = self . geo . encodeMainDataPackedDeltas ( self . verts , 3 , len ( self . verts ) , " f " , 0x8000 )
self . pack_norms = self . geo . encodeMainDataPackedDeltas ( self . norms , 3 , len ( self . norms ) , " f " , 0x100 )
self . pack_sts = self . geo . encodeMainDataPackedDeltas ( self . sts , 2 , len ( self . verts ) , " f " , 0x1000 )
self . pack_sts3 = self . geo . encodeMainDataPackedDeltas ( self . sts3 , 2 , len ( self . verts ) , " f " , 0x8000 )
self . pack_weights = self . geo . encodeMainDataPacked ( self . weights_data )
self . pack_matidxs = self . geo . encodeMainDataPacked ( self . matidxs_data )
self . pack_grid = self . geo . encodeMainDataPacked ( self . grid_data )
self . pack_reductions = self . geo . encodeMainDataPacked ( self . reductions_data )
self . pack_reflection_quads = self . geo . encodeMainDataPacked ( self . reflection_quads_data )
bone_data = struct . pack ( " < " + " i " * ( 1 + 15 ) , self . bone_count , * self . bone_ids )
self . boneinfo_ptr = self . geo . encodeMainData ( bone_data )
#Encode shared header data
print ( " unhandled: model.texidx_ptr ! " )
self . texidx_ptr = 0
self . name_ptr = len ( self . geo . header_objname_data )
self . geo . header_objname_data + = self . name + ZERO_BYTE
self . geo . header_objnames . append ( self . name )
print ( " unhandled: model.api_ptr ! " )
self . api_ptr = 0
#Encode the header
self . header_data = b " "
self . header_data + = struct . pack ( " <f " , self . radius )
self . header_data + = struct . pack ( " <i " , self . tex_count )
self . header_data + = struct . pack ( " <i " , self . boneinfo_ptr )
self . header_data + = struct . pack ( " <i " , self . vert_count )
self . header_data + = struct . pack ( " <i " , self . tri_count )
if self . geo . version > = 8 :
if self . reflection_quads is None or len ( self . reflection_quads ) :
self . reflection_quads_count = 0
else :
self . reflection_quads_count = len ( self . reflection_quads )
self . header_data + = struct . pack ( " <i " , self . reflection_quads_count )
pass
self . header_data + = struct . pack ( " <i " , self . texidx_ptr )
self . header_data + = struct . pack ( " <ifffffii " , * self . grid_header )
self . header_data + = struct . pack ( " <i " , self . name_ptr )
self . header_data + = struct . pack ( " <i " , self . api_ptr )
self . header_data + = struct . pack ( " <fff " , * self . scale )
self . header_data + = struct . pack ( " <fff " , * self . min )
self . header_data + = struct . pack ( " <fff " , * self . max )
self . header_data + = struct . pack ( " <iii " , * self . pack_tris )
self . header_data + = struct . pack ( " <iii " , * self . pack_verts )
self . header_data + = struct . pack ( " <iii " , * self . pack_norms )
self . header_data + = struct . pack ( " <iii " , * self . pack_sts )
self . header_data + = struct . pack ( " <iii " , * self . pack_sts3 )
self . header_data + = struct . pack ( " <iii " , * self . pack_weights )
self . header_data + = struct . pack ( " <iii " , * self . pack_matidxs )
self . header_data + = struct . pack ( " <iii " , * self . pack_grid )
if self . geo . version == 4 :
#pack.lmap_utransforms, pack.lmap_vtransforms
self . header_data + = struct . pack ( " <iiiiii " , 0 , 0 , 0 , 0 , 0 , 0 )
if self . geo . version > = 7 :
self . header_data + = struct . pack ( " <iii " , * self . pack_reductions )
pass
if self . geo . version > = 8 :
self . header_data + = struct . pack ( " <iii " , * self . pack_reflection_quads )
pass
self . header_data + = struct . pack ( " <fff " , * self . autolod_dists )
self . header_data + = struct . pack ( " <h " , self . id )
self . header_data = struct . pack ( " <i " , len ( self . header_data ) + 4 ) + self . header_data
pass
def encodeHeader ( self ) :
self . geo . header_data + = self . header_data
def dump ( self ) :
print ( " radius: %s " % self . radius )
print ( " tex_count: %s " % self . tex_count )
print ( " boneinfo_ptr: %s " % self . boneinfo_ptr )
print ( " vert_count: %s " % self . vert_count )
print ( " tri_count: %s " % self . tri_count )
print ( " name_ptr: %s " % self . name_ptr )
print ( " api_ptr: %s " % self . api_ptr )
print ( " grid: %s " % ( self . grid_header , ) )
print ( " scale: %s " % self . scale )
print ( " min: %s " % self . min )
print ( " max: %s " % self . max )
print ( " pack_tris: %s " % ( self . pack_tris , ) )
print ( " pack_verts: %s " % ( self . pack_verts , ) )
print ( " pack_norms: %s " % ( self . pack_norms , ) )
print ( " pack_sts: %s " % ( self . pack_sts , ) )
print ( " pack_sts3: %s " % ( self . pack_sts3 , ) )
print ( " pack_weights: %s " % ( self . pack_weights , ) )
print ( " pack_matidxs: %s " % ( self . pack_matidxs , ) )
print ( " pack_grid: %s " % ( self . pack_grid , ) )
#print(" pack_boneinfo: %s" % (self.pack_boneinfo, ))
#print(" pack_name: %s" % (self.pack_name, ))
#print(" pack_api: %s" % (self.pack_api, ))
print ( " autolod_dists: %s " % ( self . autolod_dists , ) )
print ( " id: %s " % ( self . id , ) )
#print(" mystery: %s" % (self.mystery, ))
print ( " skipped_data: %s " % ( [ self . skipped_data ] , ) )
print ( " name: %s " % self . name )
#print(" tris_data: %s" % ([self.tris_data], ))
#print(" verts_data: %s" % ([self.verts_data], ))
#print(" sts_data: %s" % ([self.sts_data], ))
#print(" sts3_data: %s" % ([self.sts3_data], ))
#print(" weights_data: %s" % ([self.weights_data], ))
#print(" matidxs_data: %s" % ([self.matidxs_data], ))
#print(" grid_data: %s" % ([self.grid_data], ))
#print(" reductions_data: %s" % ([self.reductions_data], ))
print ( " reflection_quads_data: %s " % ( [ self . reflection_quads_data ] , ) )
print ( " tris: %s " % ( [ self . tris ] , ) )
print ( " verts: %s " % ( [ self . verts ] , ) )
print ( " norms: %s " % ( [ self . norms ] , ) )
print ( " weights: %s " % ( [ self . weights ] , ) )
print ( " weight_bones (matidxs): %s " % ( [ self . weight_bones ] , ) )
if self . polygrid == None :
print ( " grid: None " )
else :
print ( " grid: " )
self . polygrid . dump ( )
if self . reductions == None :
print ( " reductions: None " )
else :
self . reductions . dump ( )
print ( " sts: %s " % ( [ self . sts ] , ) )
print ( " sts3: %s " % ( [ self . sts3 ] , ) )
print ( " bone_count: %d " % self . bone_count )
print ( " bone_ids: %s " % ( self . bone_ids , ) )
for i in range ( self . bone_count ) :
print ( " : %s " % ( BONES_LIST [ self . bone_ids [ i ] ] , ) )
class Geo :
def __init__ ( self ) :
self . data = b " "
self . offset = 0
self . version = - 1
pass
def loadFromData ( self , data ) :
self . data = data
self . offset = 0
self . parseData ( )
pass
def loadFromFile ( self , filein ) :
self . data = filein . read ( )
self . offset = 0
self . parseData ( )
pass
def saveToData ( self ) :
self . encode ( )
return self . data
def saveToFile ( self , fileout ) :
self . encode ( )
fileout . write ( self . data )
def parseData ( self ) :
( ziplen , self . header_size ) = struct . unpack ( " <ii " , self . data [ self . offset : self . offset + 8 ] )
if self . header_size != 0 :
self . version = 0
self . offset + = 8
ziplen - = 4
else :
( self . version ,
self . header_size ) = struct . unpack ( " <ii " , self . data [ self . offset + 8 : self . offset + 8 + 8 ] )
ziplen - = 12
self . offset + = 16
#print("%s" % [self.data[self.offset : self.offset + ziplen]])
self . header_data = zlib . decompress ( self . data [ self . offset : self . offset + ziplen ] ) #, bufsize = self.header_size)
self . offset + = ziplen
if self . version == 0 :
self . offset + = 4
self . header_offset = 0
( self . main_data_size ,
self . texname_blocksize ,
self . objname_blocksize ,
self . texidx_blocksize ) = struct . unpack ( " <iiii " , self . header_data [ self . header_offset : self . header_offset + 16 ] )
self . header_offset + = 16
if self . version > = 2 and self . version < = 6 :
( self . lodinfo_blocksize , ) = struct . unpack ( " <i " , self . header_data [ self . header_offset : self . header_offset + 4 ] )
self . header_offset + = 4
else :
self . lodinfo_blocksize = 0
self . header_texname_data = self . header_data [ self . header_offset : self . header_offset + self . texname_blocksize ]
self . header_offset + = self . texname_blocksize
self . header_objname_data = self . header_data [ self . header_offset : self . header_offset + self . objname_blocksize ]
self . header_offset + = self . objname_blocksize
self . header_texidx_data = self . header_data [ self . header_offset : self . header_offset + self . texidx_blocksize ]
self . header_offset + = self . texidx_blocksize
self . header_lodinfo_data = self . header_data [ self . header_offset : self . header_offset + self . lodinfo_blocksize ]
self . header_offset + = self . lodinfo_blocksize
self . header_modelheader_data = self . header_data [ self . header_offset : self . header_offset + 124 + 4 + 4 + 4 + 4 ]
self . header_offset + = 124 + 4 + 4 + 4 + 4
self . header_texnames = unpackNames ( self . header_texname_data )
self . header_objnames = unpackStrings ( self . header_objname_data )
self . header_modelheader_name = extractString ( self . header_modelheader_data [ 0 : 124 ] )
( self . header_modelheader_tracklength , ) = struct . unpack ( " <f " , self . header_modelheader_data [ 124 + 4 : 124 + 4 + 4 ] )
( self . header_modelheader_modelcount , ) = struct . unpack ( " <i " , self . header_modelheader_data [ 124 + 4 + 4 + 4 : 124 + 4 + 4 + 4 + 4 ] )
#print("main offset: %d 0x%x" % (self.offset, self.offset))
self . main_data = self . data [ self . offset : self . offset + self . main_data_size ]
#print(" %s" % [self.main_data])
self . models = [ ]
#self.dump()
for i in range ( self . header_modelheader_modelcount ) :
self . models . append ( Model ( self ) )
self . models [ - 1 ] . parseHeaderData ( )
self . models [ - 1 ] . parseData ( )
#if i < len(self.header_objnames):
# self.models[-1].name = self.header_objnames[i]
def getHeaderElement ( self , fmt ) :
size = struct . calcsize ( fmt )
data = struct . unpack ( fmt , self . header_data [ self . header_offset : self . header_offset + size ] )
self . header_offset + = size
return data
def getDataBlock ( self , tup ) :
#print("%s" % (tup, ))
( packsize , unpacksize , offset ) = tup
if packsize == 0 :
return self . main_data [ offset : offset + unpacksize ]
rawdata = self . main_data [ offset : offset + packsize ]
#print("%s" % [rawdata])
try :
data = zlib . decompress ( rawdata )
except Exception as e :
print ( " %s " % ( tup , ) )
print ( " %s " % [ rawdata ] )
traceback . print_exc ( )
#data = rawdata
raise e
#todo: sanity check size?
return data
def encode ( self ) :
self . data = None
self . version = 8
self . main_data = b " "
self . header_data = b " "
self . header_objnames = [ ]
self . header_objname_data = b " "
self . lodinfo_data = b " "
#Encode models into main data
for m in self . models :
m . encode ( )
#todo: convert data
print ( " texname unhandled! " )
print ( " texidx unhandled! " )
#Encode information into header data.
self . header_data + = struct . pack ( " <iiii " , len ( self . main_data ) , len ( self . header_texname_data ) , len ( self . header_objname_data ) , len ( self . header_texidx_data ) )
if self . version > = 2 and self . version < = 6 :
self . header_data + = struct . pack ( " <i " , len ( self . lodinfo_data ) )
self . header_data + = self . header_texname_data + self . header_objname_data + self . header_texidx_data
if self . version > = 2 and self . version < = 6 :
self . header_data + = self . lodinfo_data
#Encode the main model header.
self . header_data + = storeString ( self . header_modelheader_name , 124 )
self . header_data + = struct . pack ( " <ifii " , 0 , self . header_modelheader_tracklength , 0 , len ( self . models ) )
#Encode model headers into header data.
for m in self . models :
m . encodeHeader ( )
zheader_data = zlib . compress ( self . header_data )
if self . version == 0 :
preheader = struct . pack ( " <ii " , len ( zheader_data ) + 4 , len ( self . header_data ) )
else :
preheader = struct . pack ( " <iiii " , len ( zheader_data ) + 12 , 0 , self . version , len ( self . header_data ) )
if self . version == 0 :
zheader + = struct . pack ( " <i " , 0 )
self . data = preheader + zheader_data + self . main_data
def encodeObjName ( self , name ) :
o = len ( self . header_objname_data )
self . header_objname_data + = name + ZERO_BYTE
self . header_objnames . append ( name )
return o
def encodeMainData ( self , data ) :
o = len ( self . main_data )
self . main_data + = data
return o
def encodeMainDataPacked ( self , data ) :
#todo: minimum compression?
o = len ( self . main_data )
d = zlib . compress ( data )
if len ( d ) > = len ( data ) :
self . main_data + = data
return ( 0 , len ( data ) , o )
else :
self . main_data + = d
return ( len ( d ) , len ( data ) , o )
def encodeMainDataPackedDeltas ( self , data , stride , count , pack_type , float_scale ) :
if data is None :
return ( 0 , 0 , 0 )
deltas = compressDeltas ( data , stride , count , pack_type , float_scale )
pack = self . encodeMainDataPacked ( deltas )
return pack
def dump ( self ) :
print ( " version: %d " % self . version )
print ( " header_size: expected: %d actual: %d " % ( self . header_size , len ( self . header_data ) ) )
#print("header_data: %s" % [self.header_data])
print ( " main_data_size: %d " % self . main_data_size )
print ( " header_data sizes: texname: %d objname: %d texidx: %d lodinfo: %d " % ( self . texname_blocksize , self . objname_blocksize , self . texidx_blocksize , self . lodinfo_blocksize ) )
print ( " header_texname_data: %s " % [ self . header_texname_data ] )
print ( " header_texnames: %s " % [ self . header_texnames ] )
print ( " header_objname_data: %s " % [ self . header_objname_data ] )
print ( " header_objnames: %s " % [ self . header_objnames ] )
print ( " header_texidx_data: %s " % [ self . header_texidx_data ] )
print ( " header_lodinfo_data: %s " % [ self . header_lodinfo_data ] )
print ( " header_modelheader_name: %s " % [ self . header_modelheader_name ] )
print ( " header_modelheader_tracklength: %s " % self . header_modelheader_tracklength )
print ( " header_modelheader_modelcount: %s " % self . header_modelheader_modelcount )
print ( " header_modelheader_data: %s " % [ self . header_modelheader_data [ 124 : ] ] )
print ( " header remaining: %d " % ( ( len ( self . header_data ) - self . header_offset ) , ) )
print ( " header remaining: %s " % ( [ self . header_data [ self . header_offset : ] ] , ) )
#%d objname: %d texidx: %d lodinfo: %d" % (self.texname_blocksize, self.objname_blocksize, self.texidx_blocksize, self.lodinfo_blocksize)
for i in range ( len ( self . models ) ) :
print ( " Model %d : " % ( i , ) )
self . models [ i ] . dump ( )
def seekMainData ( self , offset ) :
self . main_data_offset = offset
def skipMainData ( self , skip ) :
if type ( skip ) is int :
self . main_data_offset + = skip
else :
self . main_data_offset + = struct . calcsize ( skip )
def getMainElement ( self , fmt ) :
size = struct . calcsize ( fmt )
data = struct . unpack ( fmt , self . main_data [ self . main_data_offset : self . main_data_offset + size ] )
self . main_data_offset + = size
return data
def getElement ( self , offset , fmt ) :
size = struct . calcsize ( fmt )
data = struct . unpack ( fmt , self . main_data [ offset : offset + size ] )
return data
if __name__ == " __main__ " :
if len ( sys . argv ) < = 1 :
print ( " Usage: " )
print ( " %s <file_in> [<file_out>] " )
print ( " Test loads a .geo file, dumps its content, and optionally writes its content out. " )
exit ( 0 )
fh = open ( sys . argv [ 1 ] , " rb " )
print ( sys . argv )
if len ( sys . argv ) < = 2 :
fho = None
else :
fho = open ( sys . argv [ 2 ] , " wb " )
geo = Geo ( )
geo . loadFromFile ( fh )
if fho is not None :
#geo.dump()
geo . saveToFile ( fho )
else :
geo . dump ( )
#print("%s" % [geo.header_data])