Last active
November 22, 2023 16:03
-
-
Save mrwonko/3083023 to your computer and use it in GitHub Desktop.
Jedi Knight 2/3 .gla animation subrange extruder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/python | |
GLA_IDENT = b"2LGA" | |
GLA_VERSION = 6 | |
import struct | |
class MdxaHeader: | |
def __init__( self ): | |
self.name = "" | |
self.scale = 1 # does not seem to be used by Jedi Academy anyway - or is it? I need it in import! | |
self.numFrames = -1 | |
self.ofsFrames = -1 | |
self.numBones = -1 | |
self.ofsCompBonePool = -1 | |
self.ofsSkel = -1 # this is also MdxaSkelOffsets.baseOffset + MdxaSkelOffsets.boneOffsets[0] - probably a historic leftover | |
self.ofsEnd = -1 | |
def loadFromFile( self, file ): | |
# check ident | |
ident, = struct.unpack( "4s", file.read( 4 ) ) | |
if ident != GLA_IDENT: | |
print( "File does not start with ", GLA_IDENT, " but ", ident, " - no GLA!" ) | |
return False | |
version, = struct.unpack( "i", file.read( 4 ) ) | |
if version != GLA_VERSION: | |
print( "Wrong gla file version! (" + str( version ) + " should be " + str( mrw_g2_constants.GLA_VERSION) + ")" ) | |
return False | |
self.name = file.read( 64 ) | |
self.scale, self.numFrames, self.ofsFrames, self.numBones, self.ofsCompBonePool, self.ofsSkel, self.ofsEnd = struct.unpack( "f6i", file.read( 7*4 ) ) | |
return True | |
def saveToFile( self, file ): | |
file.write( struct.pack( "4si", GLA_IDENT, GLA_VERSION ) ) | |
file.write( self.name ) | |
file.write( struct.pack( "f6i", self.scale, self.numFrames, self.ofsFrames, self.numBones, self.ofsCompBonePool, self.ofsSkel, self.ofsEnd ) ) | |
return True | |
def getSize( self ): | |
return 2*4 + 64*1 + 7*4 | |
class MdxaBone: | |
def __init__( self ): | |
self.data = None | |
def loadFromFile( self, file ): | |
front = file.read( 64 + 4 + 4 + 12*4 + 12*4 ) | |
rawNumChildren = file.read( 4 ) | |
numChildren, = struct.unpack( "I", rawNumChildren ) | |
self.data = front + rawNumChildren + file.read( 4 * numChildren ) | |
return True | |
def saveToFile( self, file ): | |
file.write( self.data ) | |
return True | |
def getSize( self ): | |
return len( self.data ) | |
def ExtractSubRanges(filenameIn, filenameOut, frameRanges ): | |
# open file | |
file = open( filenameIn, "rb" ) | |
# load header | |
header = MdxaHeader() | |
if not header.loadFromFile( file ): | |
return False | |
# load bone offsets (just after header) | |
boneOffsets = list(struct.unpack( "{}I".format( header.numBones ), file.read( 4 * header.numBones ) )) | |
# load bones | |
def loadBone( offset ): | |
file.seek( offset ) | |
bone = MdxaBone() | |
assert( bone.loadFromFile( file ) ) | |
return bone | |
bones = [ loadBone( header.getSize() + offset ) for offset in boneOffsets ] | |
# load requested frames, remembering the indices used | |
framesIn = [] | |
numFramesOut = 0 | |
for frameRange in frameRanges: | |
file.seek( header.ofsFrames + 3 * header.numBones * frameRange[0] ) | |
framesIn.extend([ [ struct.unpack("I", file.read( 3 ) + b"\0" )[0] for _ in range( header.numBones ) ] for _ in range( frameRange[1] ) ]) | |
numFramesOut += frameRange[1] | |
# load the requested indices from the compBonePool, building a bone index old-new lookup table | |
framesOut = [] | |
indexLookup = {} | |
compBonePoolOut = [] | |
for frame in framesIn: | |
frameOut = [] | |
for index in frame: | |
indexOut = None | |
if index in indexLookup: | |
indexOut = indexLookup[index] | |
else: | |
indexOut = len(compBonePoolOut) | |
indexLookup[index] = indexOut | |
file.seek( header.ofsCompBonePool + 14 * index ) | |
compBonePoolOut.append( file.read( 14 ) ) | |
frameOut.append( indexOut ) | |
framesOut.append( frameOut ) | |
# change header | |
header.numFrames = numFramesOut | |
# calculate new offsets | |
header.ofsSkel = header.getSize() + header.numBones * 4 | |
offset = header.numBones * 4 | |
boneOffsets = [] | |
for bone in bones: | |
boneOffsets.append( offset ) | |
offset += bone.getSize() | |
offset += header.getSize() | |
header.ofsFrames = offset | |
offset += numFramesOut * header.numBones * 3 | |
if offset % 4 != 0: | |
offset += 4 - (offset % 4) | |
header.ofsCompBonePool = offset | |
header.ofsEnd = offset + len( compBonePoolOut ) * 14 | |
# open file | |
file = open(filenameOut, "wb") | |
# save new header | |
if not header.saveToFile( file ): | |
return False | |
# save bone offsets | |
for ofs in boneOffsets: | |
file.write( struct.pack( "I", ofs ) ) | |
if file.tell() != header.getSize() + header.numBones * 4: | |
print( "Bug in script: bone offsets not written properly!" ) | |
return False | |
# save bones | |
file.seek(header.ofsSkel) | |
for bone, offset in zip( bones, boneOffsets ): | |
if file.tell() != header.getSize() + offset: | |
print( "Bug in script: bone offset not correct!", file.tell(), header.getSize(), offset ) | |
return False | |
if not bone.saveToFile( file ): | |
return False | |
# save frames | |
file.seek(header.ofsFrames) | |
for frame in framesOut: | |
for index in frame: | |
file.write( struct.pack( "I", index )[0:3] ) # only write first 3 bytes of each index | |
# save compBonePool | |
file.seek(header.ofsCompBonePool) | |
for compBone in compBonePoolOut: | |
file.write( compBone ) | |
return True | |
import sys # contains sys.argv list | |
usage = "Usage:\n\ | |
gla_range.py source.gla destination.gla startFrame frameCount\n\ | |
gla_range.py source.gla destination.gla animation.cfg animationName [animationName, ...]" | |
if len( sys.argv ) < 5: | |
print( usage ) | |
exit() | |
def isInt(var): | |
try: | |
int(var) | |
return True | |
except ValueError: | |
return False; | |
try: | |
# extract given range | |
ExtractSubRanges( sys.argv[1], sys.argv[2], [(int(sys.argv[3]), int( sys.argv[4] ))] ) | |
except ValueError: | |
# extract given animation | |
# parse given animation.cfg | |
file = open( sys.argv[3] ) | |
availableAnimations = {} | |
for line in file: | |
parts = line.split() | |
if len(parts) > 0 and parts[0][0:2] != "//": | |
try: | |
availableAnimations[parts[0].lower()] = ( int(parts[1]), int(parts[2]) ) | |
except: | |
print( "Malformed line \"" + line + "\" in " + sys.argv[3] ) | |
animations = [] | |
for animName in sys.argv[4:]: | |
if animName.lower() not in availableAnimations: | |
print( "No \"" + animName + "\" animaton found in " + sys.argv[3] ) | |
else: | |
animations.append( availableAnimations[animName.lower()] ) | |
ExtractSubRanges( sys.argv[1], sys.argv[2], animations ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This script can extract individual frames from a Jedi Academy .gla animation file. The result can then be combined with other animations using the glamerge tool.
Run using Python.