See More

import math from enum import IntEnum from .NamedObject import NamedObject from .PPtr import PPtr from ..enums import ClassIDType from ..math import Quaternion, Vector3 from ..streams import EndianBinaryReader try: from UnityPy import UnityPyBoost except ImportError: UnityPyBoost = None def uint(num): if num < 0 or num > 4294967295: return num % 4294967296 return num class Keyframe: def __init__(self, reader, readerFunc): self.time = reader.read_float() self.value = readerFunc() self.inSlope = readerFunc() self.outSlope = readerFunc() if reader.version >= (2018,): # 2018 and up self.weightedMode = reader.read_int() self.inWeight = readerFunc() self.outWeight = readerFunc() class AnimationCurve: def __init__(self, reader, readerFunc): version = reader.version numCurves = reader.read_int() self.m_Curve = [Keyframe(reader, readerFunc) for _ in range(numCurves)] self.m_PreInfinity = reader.read_int() self.m_PostInfinity = reader.read_int() if version >= (5, 3): # 5.3 and up self.m_RotationOrder = reader.read_int() class QuaternionCurve: def __init__(self, reader): self.curve = AnimationCurve(reader, reader.read_quaternion) # self.path = reader.read_aligned_string() class PackedFloatVector: def __init__(self, reader): self.m_NumItems = reader.read_u_int() self.m_Range = reader.read_float() self.m_Start = reader.read_float() numData = reader.read_int() self.m_Data = reader.read_bytes(numData) reader.align_stream() self.m_BitSize = reader.read_byte() reader.align_stream() def save(self, writer): writer.write_u_int(self.m_NumItems) writer.write_float(self.m_Range) writer.write_float(self.m_Start) writer.write_int(len(self.m_Data)) writer.write_bytes(self.m_Data) writer.align_stream() writer.write_byte(self.m_BitSize) writer.align_stream() def UnpackFloats( self, itemCountInChunk: int, chunkStride: int, start: int = 0, numChunks: int = -1, ): if UnityPyBoost: return UnityPyBoost.unpack_floats( self.m_NumItems, self.m_Range, self.m_Start, bytes(self.m_Data), self.m_BitSize, itemCountInChunk, chunkStride, start, numChunks, ) bitPos: int = self.m_BitSize * start indexPos: int = bitPos // 8 bitPos %= 8 scale: float = (1.0 / self.m_Range) if self.m_Range else float("inf") if numChunks == -1: numChunks = self.m_NumItems // itemCountInChunk end = int(chunkStride * numChunks / 4) data = [] for index in range(0, end, chunkStride // 4): for i in range(itemCountInChunk): x = 0 # uint bits = 0 while bits < self.m_BitSize: x |= uint( (self.m_Data[indexPos] >> bitPos) << bits ) # (uint)((m_Data[indexPos] >> bitPos) << bits) num = min(self.m_BitSize - bits, 8 - bitPos) bitPos += num bits += num if bitPos == 8: # indexPos += 1 bitPos = 0 x &= uint((1 << self.m_BitSize) - 1) # (uint)(1 << m_BitSize) - 1u denomi = scale * ((1 << self.m_BitSize) - 1) data.append((x / denomi if denomi else float("inf")) + self.m_Start) return data class PackedIntVector: def __init__(self, reader): self.m_NumItems = reader.read_u_int() numData = reader.read_int() self.m_Data = reader.read_bytes(numData) reader.align_stream() self.m_BitSize = reader.read_byte() reader.align_stream() def save(self, writer): writer.write_u_int(self.m_NumItems) writer.write_int(len(self.m_Data)) writer.write_bytes(self.m_Data) writer.align_stream() writer.write_byte(self.m_BitSize) writer.align_stream() def UnpackInts(self): if UnityPyBoost: return UnityPyBoost.unpack_ints( self.m_NumItems, bytes(self.m_Data), self.m_BitSize ) data = [0] * self.m_NumItems indexPos = 0 bitPos = 0 for i in range(self.m_NumItems): bits = 0 data[i] = 0 while bits < self.m_BitSize: data[i] |= (self.m_Data[indexPos] >> bitPos) << bits num = min(self.m_BitSize - bits, 8 - bitPos) bitPos += num bits += num if bitPos == 8: indexPos += 1 bitPos = 0 data[i] &= (1 << self.m_BitSize) - 1 return data class PackedQuatVector: def __init__(self, reader): self.m_NumItems = reader.read_u_int() numData = reader.read_int() self.m_Data = reader.read_bytes(numData) reader.align_stream() def UnpackQuats(self): m_Data = self.m_Data data = [None] * self.m_NumItems indexPos = 0 bitPos = 0 for i in range(self.m_NumItems): flags = 0 bits = 0 while bits < 3: flags |= (m_Data[indexPos] >> bitPos) << bits # unit num = min(3 - bits, 8 - bitPos) bitPos += num bits += num if bitPos == 8: # indexPos += 1 bitPos = 0 flags &= 7 q = Quaternion() sum = 0 for j in range(4): if (flags & 3) != j: # bitSize = 9 if ((flags & 3) + 1) % 4 == j else 10 x = 0 bits = 0 while bits < bitSize: x |= (m_Data[indexPos] >> bitPos) << bits # uint num = min(bitSize - bits, 8 - bitPos) bitPos += num bits += num if bitPos == 8: # indexPos += 1 bitPos = 0 x &= (1 << bitSize) - 1 # unit q[j] = x / (0.5 * ((1 << bitSize) - 1)) - 1 sum += q[j] * q[j] lastComponent = flags & 3 # int q[lastComponent] = math.sqrt(1 - sum) # float if (flags & 4) != 0: # 0u q[lastComponent] = -q[lastComponent] data.append(q) return data class CompressedAnimationCurve: def __init__(self, reader): self.m_Path = reader.read_aligned_string() self.m_Times = PackedIntVector(reader) self.m_Values = PackedQuatVector(reader) self.m_Slopes = PackedFloatVector(reader) self.m_PreInfinity = reader.read_int() self.m_PostInfinity = reader.read_int() class Vector3Curve: def __init__(self, reader): self.curve = AnimationCurve(reader, reader.read_vector3) # Vector3 self.path = reader.read_aligned_string() class FloatCurve: def __init__(self, reader): self.curve = AnimationCurve(reader, reader.read_float) # Float self.attribute = reader.read_aligned_string() self.path = reader.read_aligned_string() self.classID = ClassIDType(reader.read_int()) self.script = PPtr(reader) # MonoScript class PPtrKeyframe: def __init__(self, reader): self.time = reader.read_float() self.value = PPtr(reader) # Object class PPtrCurve: def __init__(self, reader): numCurves = reader.read_int() self.curve = [PPtrKeyframe(reader) for _ in range(numCurves)] self.attribute = reader.read_aligned_string() self.path = reader.read_aligned_string() self.classID = reader.read_int() self.script = PPtr(reader) # MonoScript class AABB: def __init__(self, reader): self.m_Center = reader.read_vector3() self.m_Extent = reader.read_vector3() def save(self, writer): writer.write_vector3(self.m_Center) writer.write_vector3(self.m_Extent) class xform: def __init__(self, reader): version = reader.version self.t = ( reader.read_vector3() if version >= (5, 4) else Vector3(reader.read_vector4()) ) # 5.4 and up self.q = reader.read_quaternion() self.s = ( reader.read_vector3() if version >= (5, 4) else Vector3(reader.read_vector4()) ) # 5.4 and up class HandPose: def __init__(self, reader): self.m_GrabX = xform(reader) self.m_DoFArray = reader.read_float_array() self.m_Override = reader.read_float() self.m_CloseOpen = reader.read_float() self.m_InOut = reader.read_float() self.m_Grab = reader.read_float() class HumanGoal: def __init__(self, reader): version = reader.version self.m_X = xform(reader) self.m_WeightT = reader.read_float() self.m_WeightR = reader.read_float() if version >= (5,): # 5.0 and up self.m_HintT = ( reader.read_vector3() if version >= (5, 4) else Vector3(reader.read_vector4()) ) # 5.4 and up self.m_HintWeightT = reader.read_float() class HumanPose: def __init__(self, reader): version = reader.version self.m_RootX = xform(reader) self.m_LookAtPosition = ( reader.read_vector3() if version >= (5, 4) else Vector3(reader.read_vector4()) ) # 5.4 and up self.m_LookAtWeight = reader.read_vector4() numGoals = reader.read_int() self.m_GoalArray = [HumanGoal(reader) for _ in range(numGoals)] self.m_LeftHandPose = HandPose(reader) self.m_RightHandPose = HandPose(reader) self.m_DoFArray = reader.read_float_array() if version >= (5, 2): # 5.2 and up numTDof = reader.read_int() self.m_TDoFArray = [ reader.read_vector3() if version >= (5, 4) else Vector3(reader.read_vector4()) # 5.4 and up for _ in range(numTDof) ] class StreamedCurveKey: def __init__(self, reader): self.index = reader.read_int() self.coeff = reader.read_float_array(4) self.outSlope = self.coeff[2] self.value = self.coeff[3] def CalculateNextInSlope(self, dx: float, rhs): """ :param dx: float :param rhs: StreamedCurvedKey :return: """ # Stepped if self.coeff[0] == 0 and self.coeff[1] == 0 and self.coeff[2] == 0: return float("inf") dx = max(dx, 0.0001) dy = rhs.value - self.value length = 1.0 / (dx * dx) d1 = self.outSlope * dx d2 = dy + dy + dy - d1 - d1 - self.coeff[1] / length return d2 / dx class StreamedFrame: def __init__(self, reader): self.time = reader.read_float() numKeys = reader.read_int() self.keyList = [StreamedCurveKey(reader) for _ in range(numKeys)] class StreamedClip: def __init__(self, reader): self.data = reader.read_u_int_array() self.curveCount = reader.read_u_int() def ReadData(self): frameList = [] buffer = b"".join(val.to_bytes(4, "big") for val in self.data) reader = EndianBinaryReader(buffer) while reader.Position < reader.Length: frameList.append(StreamedFrame(reader)) for frameIndex in range(2, len(frameList) - 1): frame = frameList[frameIndex] for curveKey in frame.keyList: i = frameIndex - 1 while i >= 0: preFrame = frameList[i] try: preCurveKey = [ x for x in preFrame.keyList if x.index == curveKey.index ][0] curveKey.inSlope = preCurveKey.CalculateNextInSlope( frame.time - preFrame.time, curveKey ) break except IndexError: pass i -= 1 return frameList class DenseClip: def __init__(self, reader): self.m_FrameCount = reader.read_int() self.m_CurveCount = reader.read_u_int() self.m_SampleRate = reader.read_float() self.m_BeginTime = reader.read_float() self.m_SampleArray = reader.read_float_array() class ConstantClip: def __init__(self, reader): self.data = reader.read_float_array() class ValueConstant: def __init__(self, reader): version = reader.version self.m_ID = reader.read_u_int() if version < (5, 5): # 5.5 down self.m_TypeID = reader.read_u_int() self.m_Type = reader.read_u_int() self.m_Index = reader.read_u_int() class ValueArrayConstant: def __init__(self, reader): numVals = reader.read_int() self.m_ValueArray = [ValueConstant(reader) for _ in range(numVals)] class Clip: def __init__(self, reader): version = reader.version self.m_StreamedClip = StreamedClip(reader) self.m_DenseClip = DenseClip(reader) if version >= (4, 3): # 4.3 and up self.m_ConstantClip = ConstantClip(reader) if version < (2018, 3): # 2018.3 down self.m_Binding = ValueArrayConstant(reader) class ValueDelta: def __init__(self, reader): self.m_Start = reader.read_float() self.m_Stop = reader.read_float() class ClipMuscleConstant: def __init__(self, reader): version = reader.version self.m_DeltaPose = HumanPose(reader) self.m_StartX = xform(reader) if version >= (5, 5): # 5.5 and up self.m_StopX = xform(reader) self.m_LeftFootStartX = xform(reader) self.m_RightFootStartX = xform(reader) if version < (5,): # 5.0 down self.m_MotionStartX = xform(reader) self.m_MotionStopX = xform(reader) self.m_AverageSpeed = ( reader.read_vector3() if version >= (5, 4) else Vector3(reader.read_vector4()) ) # 5.4 and up self.m_Clip = Clip(reader) self.m_StartTime = reader.read_float() self.m_StopTime = reader.read_float() self.m_OrientationOffsetY = reader.read_float() self.m_Level = reader.read_float() self.m_CycleOffset = reader.read_float() self.m_AverageAngularSpeed = reader.read_float() self.m_IndexArray = reader.read_int_array() if version < (4, 3): # 4.3 down self.m_AdditionalCurveIndexArray = reader.read_int_array() numDeltas = reader.read_int() self.m_ValueArrayDelta = [ValueDelta(reader) for _ in range(numDeltas)] if version >= (5, 3): # 5.3 and up self.m_ValueArrayReferencePose = reader.read_float_array() self.m_Mirror = reader.read_boolean() if version >= (4, 3): # 4.3 and up self.m_LoopTime = reader.read_boolean() self.m_LoopBlend = reader.read_boolean() self.m_LoopBlendOrientation = reader.read_boolean() self.m_LoopBlendPositionY = reader.read_boolean() self.m_LoopBlendPositionXZ = reader.read_boolean() if version >= (5, 5): # 5.5 and up self.m_StartAtOrigin = reader.read_boolean() self.m_KeepOriginalOrientation = reader.read_boolean() self.m_KeepOriginalPositionY = reader.read_boolean() self.m_KeepOriginalPositionXZ = reader.read_boolean() self.m_HeightFromFeet = reader.read_boolean() reader.align_stream() class GenericBinding: def __init__(self, reader): version = reader.version self.path = reader.read_u_int() self.attribute = reader.read_u_int() self.script = PPtr(reader) # Object if version >= (5, 6): # 5.6 and up self.typeID = ClassIDType(reader.read_int()) else: self.typeID = ClassIDType(reader.read_u_short()) self.customType = reader.read_byte() self.isPPtrCurve = reader.read_byte() reader.align_stream() class AnimationClipBindingConstant: def __init__(self, reader): numBindings = reader.read_int() self.genericBindings = [GenericBinding(reader) for _ in range(numBindings)] numMappings = reader.read_int() self.pptrCurveMapping = [PPtr(reader) for _ in range(numMappings)] # Object def FindBinding(self, index): curves = 0 for b in self.genericBindings: if b.typeID == ClassIDType.Transform: # switch = b.attribute if switch in [1, 3, 4]: # case 1: #kBindTransformPosition # case 3: #kBindTransformScale # case 4: #kBindTransformEuler curves += 3 elif switch == 2: # kBindTransformRotation curves += 4 else: curves += 1 else: curves += 1 if curves > index: return b return None class AnimationType(IntEnum): kLegacy = (1,) kGeneric = (2,) kHumanoid = 3 class AnimationClip(NamedObject): def __init__(self, reader): super().__init__(reader=reader) version = reader.version if version >= (5,): # 5.0 and up self.m_Legacy = reader.read_boolean() elif version >= (4,): # 4.0 and up self.m_AnimationType = AnimationType(reader.read_int()) if self.m_AnimationType == AnimationType.kLegacy: # self.m_Legacy = True else: self.m_Legacy = True self.m_Compressed = reader.read_boolean() if version >= (4, 3): # 4.3 and up self.m_UseHighQualityCurve = reader.read_boolean() reader.align_stream() numRCurves = reader.read_int() self.m_RotationCurves = [QuaternionCurve(reader) for _ in range(numRCurves)] numCRCurves = reader.read_int() self.m_CompressedRotationCurves = [ CompressedAnimationCurve(reader) for _ in range(numCRCurves) ] if version >= (5, 3): # 5.3 and up numEulerCurves = reader.read_int() self.m_EulerCurves = [Vector3Curve(reader) for _ in range(numEulerCurves)] numPCurves = reader.read_int() self.m_PositionCurves = [Vector3Curve(reader) for _ in range(numPCurves)] numSCurves = reader.read_int() self.m_ScaleCurves = [Vector3Curve(reader) for _ in range(numSCurves)] numFCurves = reader.read_int() self.m_FloatCurves = [FloatCurve(reader) for _ in range(numFCurves)] if version >= (4, 3): # 4.3 and up numPtrCurves = reader.read_int() self.m_PPtrCurves = [PPtrCurve(reader) for _ in range(numPtrCurves)] self.m_SampleRate = reader.read_float() self.m_WrapMode = reader.read_int() if version >= (3, 4): # 3.4 and up self.m_Bounds = AABB(reader) if version >= (4,): # 4.0 and up self.m_MuscleClipSize = reader.read_u_int() self.m_MuscleClip = ClipMuscleConstant(reader) if version >= (4, 3): # 4.3 and up self.m_ClipBindingConstant = AnimationClipBindingConstant(reader) # m_HasGenericRootTransform 2018.3 # m_HasMotionFloatCurves 2018.3 # numEvents = reader.read_int() # self.m_Events = [ # AnimationEvent(reader) # for _ in range(numEvents) # ]