diff --git a/UnityPy/math/Color.py b/UnityPy/math/Color.py index fced5e1c..9390aff8 100644 --- a/UnityPy/math/Color.py +++ b/UnityPy/math/Color.py @@ -1,6 +1,8 @@ +from dataclasses import dataclass from .Vector4 import Vector4 +@dataclass class Color: R: float G: float @@ -8,17 +10,13 @@ class Color: A: float def __init__(self, r: float = 0.0, g: float = 0.0, b: float = 0.0, a: float = 0.0): + if not all(isinstance(v, (int, float)) for v in (r, g, b, a)): + raise TypeError("All components must be numeric.") self.R = r self.G = g self.B = b self.A = a - def __eq__(self, other): - if isinstance(other, Color): - return self.__dict__ == other.__dict__ - else: - return False - def __add__(self, other): return Color( self.R + other.R, self.G + other.G, self.B + other.B, self.A + other.A @@ -37,7 +35,7 @@ def __mul__(self, other): else: return Color(self.R * other, self.G * other, self.B * other, self.A * other) - def __div__(self, other): + def __truediv__(self, other): if isinstance(other, Color): return Color( self.R / other.R, self.G / other.G, self.B / other.B, self.A / other.A @@ -46,10 +44,13 @@ def __div__(self, other): return Color(self.R / other, self.G / other, self.B / other, self.A / other) def __eq__(self, other): - return self.__dict__ == other.__dict__ + if isinstance(other, Color): + return self.__dict__ == other.__dict__ + else: + return False def __ne__(self, other): - return self.__dict__ != other.__dict__ + return not (self == other) def Vector4(self): return Vector4(self.R, self.G, self.B, self.A) diff --git a/UnityPy/math/Half.py b/UnityPy/math/Half.py deleted file mode 100644 index 6d56e1b9..00000000 --- a/UnityPy/math/Half.py +++ /dev/null @@ -1,102 +0,0 @@ -import struct -import math - -MaxValue = 65504.0 -MinValue = -65504.0 - - -def ToHalf(*args) -> float: - """ - Converts the input into a half-float. - Inputs: - unsigned integer - or - buffer (bytes, buffer) - offset - """ - # int input -> pack as UInt16 - if len(args) == 1: - data = struct.pack("H", args[0]) - val = struct.unpack("e", data)[0] - # buffer input - elif len(args) == 2: - val = struct.unpack_from("e", args[0], args[1])[0] - else: - raise ValueError("Invalid amount of arguments") - - if math.isnan(val): - # print('Nan') - return 0 - elif math.isinf(val): - return MaxValue - - return val - - -# #CONSTANTS -# Epsilon = ToHalf(0x0001) -# MaxValue = ToHalf(0x7bff) -# MinValue = ToHalf(0xfbff) -# NaN = ToHalf(0xfe00) -# NegativeInfinity = ToHalf(0xfc00) -# PositiveInfinity = ToHalf(0x7c00) - -# class Float16Compressor: -# def __init__(self): -# self.temp = 0 - -# def compress(self, float32): -# F16_EXPONENT_BITS = 0x1F -# F16_EXPONENT_SHIFT = 10 -# F16_EXPONENT_BIAS = 15 -# F16_MANTISSA_BITS = 0x3ff -# F16_MANTISSA_SHIFT = (23 - F16_EXPONENT_SHIFT) -# F16_MAX_EXPONENT = (F16_EXPONENT_BITS << F16_EXPONENT_SHIFT) - -# a = struct.pack('>f', float32) -# b = binascii.hexlify(a) - -# f32 = int(b, 16) -# f16 = 0 -# sign = (f32 >> 16) & 0x8000 -# exponent = ((f32 >> 23) & 0xff) - 127 -# mantissa = f32 & 0x007fffff - -# if exponent == 128: -# f16 = sign | F16_MAX_EXPONENT -# if mantissa: -# f16 |= (mantissa & F16_MANTISSA_BITS) -# elif exponent > 15: -# f16 = sign | F16_MAX_EXPONENT -# elif exponent > -15: -# exponent += F16_EXPONENT_BIAS -# mantissa >>= F16_MANTISSA_SHIFT -# f16 = sign | exponent << F16_EXPONENT_SHIFT | mantissa -# else: -# f16 = sign -# return f16 - -# def decompress(self, float16): -# s = int((float16 >> 15) & 0x00000001) # sign -# e = int((float16 >> 10) & 0x0000001f) # exponent -# f = int(float16 & 0x000003ff) # fraction - -# if e == 0: -# if f == 0: -# return int(s << 31) -# else: -# while not (f & 0x00000400): -# f = f << 1 -# e -= 1 -# e += 1 -# f &= ~0x00000400 -# # print(s,e,f) -# elif e == 31: -# if f == 0: -# return int((s << 31) | 0x7f800000) -# else: -# return int((s << 31) | 0x7f800000 | (f << 13)) - -# e = e + (127 - 15) -# f = f << 13 -# return int((s << 31) | (e << 23) | f) diff --git a/UnityPy/math/Matrix4x4.py b/UnityPy/math/Matrix4x4.py index a38c5967..e09f2ac0 100644 --- a/UnityPy/math/Matrix4x4.py +++ b/UnityPy/math/Matrix4x4.py @@ -1,31 +1,43 @@ +from dataclasses import dataclass +from typing import MutableSequence, Sequence, Union from .Vector3 import Vector3 +@dataclass class Matrix4x4: - M: list + M: MutableSequence[float] - def __init__(self, values): - if len(values) != 16: - raise ValueError( - "There must be sixteen and only sixteen input values for Matrix." - ) - self.M = values + def __init__(self, values: Sequence[Union[int, float]]): + if not isinstance(values, Sequence) or len(values) != 16: + raise ValueError("Values must be a sequence with 16 elements.") + if not all(isinstance(v, (int, float)) for v in values): + raise TypeError("All values must be numeric.") + self.M = [float(v) for v in values] def __getitem__(self, index): if isinstance(index, tuple): - index = index[0] + index[1] * 4 + row, col = index + if not (0 <= row < 4 and 0 <= col < 4): + raise IndexError("Row and column indices must in range [0, 3].") + index = row + col * 4 + if not (0 <= index < 16): + raise IndexError("Index out of range for Matrix4x4.") return self.M[index] def __setitem__(self, index, value): if isinstance(index, tuple): - # row, column - index = index[0] + index[1] * 4 + row, col = index + if not (0 <= row < 4 and 0 <= col < 4): + raise IndexError("Row and column indices must in range [0, 3].") + index = row + col * 4 + if not (0 <= index < 16): + raise IndexError("Index out of range for Matrix4x4.") self.M[index] = value def __eq__(self, other): if not isinstance(other, Matrix4x4): return False - print() + return all(abs(a - b) < 1e-6 for a, b in zip(self.M, other.M)) def __mul__(lhs, rhs): res = Matrix4x4([0] * 16) @@ -134,7 +146,10 @@ def __mul__(lhs, rhs): @staticmethod def Scale(vector: Vector3): return Matrix4x4( - [vector.X, 0, 0, 0, 0, vector.Y, 0, 0, 0, 0, vector.Z, 0, 0, 0, 0, 1] + [vector.X, 0, 0, 0, + 0, vector.Y, 0, 0, + 0, 0, vector.Z, 0, + 0, 0, 0, 1] ) @property diff --git a/UnityPy/math/Quaternion.py b/UnityPy/math/Quaternion.py index e315785f..6c27afec 100644 --- a/UnityPy/math/Quaternion.py +++ b/UnityPy/math/Quaternion.py @@ -1,50 +1,32 @@ +from dataclasses import dataclass + + +@dataclass class Quaternion: X: float Y: float Z: float W: float - - def __init__(self, x: float = 0.0, y: float = 0.0, z: float = 0.0, w: float = 0.0): - self._data = [0.0] * 4 - self.X = x - self.Y = y - self.Z = z - self.W = w - - @property - def X(self) -> float: - return self._data[0] - - @X.setter - def X(self, value: float): - self._data[0] = value - - @property - def Y(self) -> float: - return self._data[1] - - @Y.setter - def Y(self, value: float): - self._data[1] = value - - @property - def Z(self) -> float: - return self._data[2] - - @Z.setter - def Z(self, value: float): - self._data[2] = value - - @property - def W(self) -> float: - return self._data[3] - - @W.setter - def W(self, value: float): - self._data[3] = value - - def __getitem__(self, value): - return self._data[value] - + + def __init__(self, x: float = 0.0, y: float = 0.0, z: float = 0.0, w: float = 1.0): + if not all(isinstance(v, (int, float)) for v in (x, y, z, w)): + raise TypeError("All components must be numeric.") + self.X = float(x) + self.Y = float(y) + self.Z = float(z) + self.W = float(w) + + def __getitem__(self, index): + return (self.X, self.Y, self.Z, self.W)[index] + def __setitem__(self, index, value): - self._data[index] = value + if index == 0: + self.X = value + elif index == 1: + self.Y = value + elif index == 2: + self.Z = value + elif index == 3: + self.W = value + else: + raise IndexError("Index out of range") diff --git a/UnityPy/math/Rectangle.py b/UnityPy/math/Rectangle.py index d32ca639..a198ec43 100644 --- a/UnityPy/math/Rectangle.py +++ b/UnityPy/math/Rectangle.py @@ -1,3 +1,7 @@ +from dataclasses import dataclass + + +@dataclass class Rectangle: height: int width: int diff --git a/UnityPy/math/Vector2.py b/UnityPy/math/Vector2.py index e20601a6..c876bca3 100644 --- a/UnityPy/math/Vector2.py +++ b/UnityPy/math/Vector2.py @@ -1,160 +1,83 @@ +from dataclasses import dataclass +from math import sqrt + + +kEpsilon = 0.00001 + + +@dataclass class Vector2: - X: float - Y: float - - def __init__(self, x: float, y: float): - self.X = x - self.Y = y - - -""" -using System; -using System.Runtime.InteropServices; - -namespace AssetStudio -{ - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct Vector2 : IEquatable - { - public float X; - public float Y; - - public Vector2(float x, float y) - { - X = x; - Y = y; - } - - public float this[int index] - { - get - { - switch (index) - { - case 0: return X; - case 1: return Y; - default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Vector2 index!"); - } - } - - set - { - switch (index) - { - case 0: X = value; break; - case 1: Y = value; break; - default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Vector2 index!"); - } - } - } - - public override int GetHashCode() - { - return X.GetHashCode() ^ (Y.GetHashCode() << 2); - } - - public override bool Equals(object other) - { - if (!(other is Vector2)) - return false; - return Equals((Vector2)other); - } - - public bool Equals(Vector2 other) - { - return X.Equals(other.X) && Y.Equals(other.Y); - } - - public void Normalize() - { - var length = Length(); - if (length > kEpsilon) - { - var invNorm = 1.0f / length; - X *= invNorm; - Y *= invNorm; - } - else - { - X = 0; - Y = 0; - } - } - - public float Length() - { - return (float)Math.Sqrt(LengthSquared()); - } - - public float LengthSquared() - { - return X * X + Y * Y; - } - - public static Vector2 Zero => new Vector2(); - - public static Vector2 operator +(Vector2 a, Vector2 b) - { - return new Vector2(a.X + b.X, a.Y + b.Y); - } - - public static Vector2 operator -(Vector2 a, Vector2 b) - { - return new Vector2(a.X - b.X, a.Y - b.Y); - } - - public static Vector2 operator *(Vector2 a, Vector2 b) - { - return new Vector2(a.X * b.X, a.Y * b.Y); - } - - public static Vector2 operator /(Vector2 a, Vector2 b) - { - return new Vector2(a.X / b.X, a.Y / b.Y); - } - - public static Vector2 operator -(Vector2 a) - { - return new Vector2(-a.X, -a.Y); - } - - public static Vector2 operator *(Vector2 a, float d) - { - return new Vector2(a.X * d, a.Y * d); - } - - public static Vector2 operator *(float d, Vector2 a) - { - return new Vector2(a.X * d, a.Y * d); - } - - public static Vector2 operator /(Vector2 a, float d) - { - return new Vector2(a.X / d, a.Y / d); - } - - public static bool operator ==(Vector2 lhs, Vector2 rhs) - { - return (lhs - rhs).LengthSquared() < kEpsilon * kEpsilon; - } - - public static bool operator !=(Vector2 lhs, Vector2 rhs) - { - return !(lhs == rhs); - } - - public static implicit operator Vector3(Vector2 v) - { - return new Vector3(v.X, v.Y, 0); - } - - public static implicit operator Vector4(Vector2 v) - { - return new Vector4(v.X, v.Y, 0.0F, 0.0F); - } - - private const float kEpsilon = 0.00001F; - } -} - -""" + '''https://github.com/Unity-Technologies/UnityCsReference/blob/master/Runtime/Export/Math/Vector2.cs''' + + X: float = 0.0 + Y: float = 0.0 + + def __init__(self, x: float = 0.0, y: float = 0.0): + if not all(isinstance(v, (int, float)) for v in (x, y)): + raise TypeError("All components must be numeric.") + self.X = float(x) + self.Y = float(y) + + def __getitem__(self, index): + return (self.X, self.Y)[index] + + def __setitem__(self, index, value): + if index == 0: + self.X = value + elif index == 1: + self.Y = value + else: + raise IndexError("Index out of range") + + def __hash__(self): + return self.X.__hash__() ^ (self.Y.__hash__() << 2) + + def normalize(self): + length = self.length() + if length > kEpsilon: + invNorm = 1.0 / length + self.X *= invNorm + self.Y *= invNorm + else: + self.X = self.Y = 0.0 + + Normalize = normalize + + def length(self): + return sqrt(self.lengthSquared()) + + Length = length + + def lengthSquared(self): + return self.X ** 2 + self.Y ** 2 + + LengthSquared = lengthSquared + + @staticmethod + def Zero(): + return Vector2(0, 0) + + @staticmethod + def One(): + return Vector2(1, 1) + + def __add__(a, b): + return Vector2(a.X + b.X, a.Y + b.Y) + + def __sub__(a, b): + return Vector2(a.X - b.X, a.Y - b.Y) + + def __mul__(a, d): + return Vector2(a.X * d, a.Y * d) + + def __truediv__(a, d): + return Vector2(a.X / d, a.Y / d) + + def __eq__(lhs, rhs): + if isinstance(rhs, Vector2): + diff = lhs - rhs + return diff.lengthSquared() < kEpsilon * kEpsilon + return False + + def __ne__(lhs, rhs): + return not (lhs == rhs) diff --git a/UnityPy/math/Vector3.py b/UnityPy/math/Vector3.py index 5e19f994..a821ec1d 100644 --- a/UnityPy/math/Vector3.py +++ b/UnityPy/math/Vector3.py @@ -1,5 +1,6 @@ from dataclasses import dataclass from math import sqrt +from typing import Sequence kEpsilon = 0.00001 @@ -7,16 +8,29 @@ @dataclass class Vector3: + '''https://github.com/Unity-Technologies/UnityCsReference/blob/master/Runtime/Export/Math/Vector3.cs''' + X: float = 0.0 Y: float = 0.0 Z: float = 0.0 def __init__(self, *args): - if len(args) == 3 or len(args) == 1 and isinstance(args[0], (tuple, list)): - self.X, self.Y, self.Z = args - elif len(args) == 1: + from .Vector4 import Vector4 + if len(args) == 1: + args = args[0] + + if isinstance(args, Sequence): + if len(args) == 3: # args=(x, y, z) + self.X, self.Y, self.Z = args + elif len(args) == 0: # args=() + self.X = self.Y = self.Z = 0.0 + else: + raise TypeError("Invalid argument length for Vector3") + elif isinstance(args, Vector4): # dirty patch for Vector4 - self.__dict__ = args[0].__dict__ + self.X, self.Y, self.Z = args.X, args.Y, args.Z + else: + raise TypeError("If only 1 argument passed, it must be a sequence or Vector4") def __getitem__(self, index): return (self.X, self.Y, self.Z)[index] @@ -32,13 +46,11 @@ def __setitem__(self, index, value): raise IndexError("Index out of range") def __hash__(self): - return self.X.__hash__() ^ (self.Y.__hash__() << 2) ^ (self.Z.__hash__() >> 2) - - def __eq__(self, other): - if isinstance(other, Vector3): - return self.X == other.X and self.Y == other.Y and self.Z == other.Z - else: - return False + return ( + self.X.__hash__() ^ + (self.Y.__hash__() << 2) ^ + (self.Z.__hash__() >> 2) + ) def normalize(self): length = self.length() @@ -48,21 +60,19 @@ def normalize(self): self.Y *= invNorm self.Z *= invNorm else: - X = 0 - Y = 0 - Z = 0 + self.X = self.Y = self.Z = 0.0 - def Normalize(self): - self.normalize() + Normalize = normalize def length(self): - return sqrt(self.LengthSquared()) + return sqrt(self.lengthSquared()) + + Length = length - def Length(self): - return self.length() + def lengthSquared(self): + return self.X ** 2 + self.Y ** 2 + self.Z ** 2 - def LengthSquared(self): - return self.X ** 2 + self.Y ** 2 + self.Y ** 2 + LengthSquared = lengthSquared @staticmethod def Zero(): @@ -81,11 +91,14 @@ def __sub__(a, b): def __mul__(a, d): return Vector3(a.X * d, a.Y * d, a.Z * d) - def __div__(a, d): + def __truediv__(a, d): return Vector3(a.X / d, a.Y / d, a.Z / d) def __eq__(lhs, rhs): - return (lhs - rhs).LengthSquared() < kEpsilon + if isinstance(rhs, Vector3): + diff = lhs - rhs + return diff.lengthSquared() < kEpsilon * kEpsilon + return False def __ne__(lhs, rhs): return not (lhs == rhs) diff --git a/UnityPy/math/Vector4.py b/UnityPy/math/Vector4.py index 5b90096e..d6e87c8b 100644 --- a/UnityPy/math/Vector4.py +++ b/UnityPy/math/Vector4.py @@ -1,155 +1,112 @@ +from dataclasses import dataclass +from math import sqrt +from typing import Sequence + + +kEpsilon = 0.00001 + + +@dataclass class Vector4: - X: float - Y: float - Z: float - W: float + '''https://github.com/Unity-Technologies/UnityCsReference/blob/master/Runtime/Export/Math/Vector4.cs''' + + X: float = 0.0 + Y: float = 0.0 + Z: float = 0.0 + W: float = 0.0 def __init__(self, *args): - if len(args) == 4: # float x, float y, float z, float w - self.X = args[0] - self.Y = args[1] - self.Z = args[2] - self.W = args[3] - elif len(args) == 2: # Vector3 value, float w - self.X = args[0].X - self.Y = args[0].Y - self.Z = args[0].Z - self.W = args[1] - - """ - public float this[int index] - { - get - { - switch (index) - { - case 0: return X; - case 1: return Y; - case 2: return Z; - case 3: return W; - default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Vector4 index!"); - } - } - - set - { - switch (index) - { - case 0: X = value; break; - case 1: Y = value; break; - case 2: Z = value; break; - case 3: W = value; break; - default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Vector4 index!"); - } - } - } - - - public override int GetHashCode() - { - return X.GetHashCode() ^ (Y.GetHashCode() << 2) ^ (Z.GetHashCode() >> 2) ^ (W.GetHashCode() >> 1); - } - - public override bool Equals(object other) - { - if (!(other is Vector4)) - return false; - return Equals((Vector4)other); - } - - public bool Equals(Vector4 other) - { - return X.Equals(other.X) && Y.Equals(other.Y) && Z.Equals(other.Z) && W.Equals(other.W); - } - - public void Normalize() - { - var length = Length(); - if (length > kEpsilon) - { - var invNorm = 1.0f / length; - X *= invNorm; - Y *= invNorm; - Z *= invNorm; - W *= invNorm; - } - else - { - X = 0; - Y = 0; - Z = 0; - W = 0; - } - } - - public float Length() - { - return (float)Math.Sqrt(LengthSquared()); - } - - public float LengthSquared() - { - return X * X + Y * Y + Z * Z + W * W; - } - - public static Vector4 Zero => new Vector4(); - - public static Vector4 operator +(Vector4 a, Vector4 b) - { - return new Vector4(a.X + b.X, a.Y + b.Y, a.Z + b.Z, a.W + b.W); - } - - public static Vector4 operator -(Vector4 a, Vector4 b) - { - return new Vector4(a.X - b.X, a.Y - b.Y, a.Z - b.Z, a.W - b.W); - } - - public static Vector4 operator -(Vector4 a) - { - return new Vector4(-a.X, -a.Y, -a.Z, -a.W); - } - - public static Vector4 operator *(Vector4 a, float d) - { - return new Vector4(a.X * d, a.Y * d, a.Z * d, a.W * d); - } - - public static Vector4 operator *(float d, Vector4 a) - { - return new Vector4(a.X * d, a.Y * d, a.Z * d, a.W * d); - } - - public static Vector4 operator /(Vector4 a, float d) - { - return new Vector4(a.X / d, a.Y / d, a.Z / d, a.W / d); - } - - public static bool operator ==(Vector4 lhs, Vector4 rhs) - { - return (lhs - rhs).LengthSquared() < kEpsilon * kEpsilon; - } - - public static bool operator !=(Vector4 lhs, Vector4 rhs) - { - return !(lhs == rhs); - } - - public static implicit operator Vector2(Vector4 v) - { - return new Vector2(v.X, v.Y); - } - - public static implicit operator Vector3(Vector4 v) - { - return new Vector3(v.X, v.Y, v.Z); - } - - public static implicit operator Color(Vector4 v) - { - return new Color(v.X, v.Y, v.Z, v.W); - } - - private const float kEpsilon = 0.00001F; - } -} -""" + if len(args) == 1: + args = args[0] + + if isinstance(args, Sequence): + if len(args) == 4: # args=(x, y, z, w) + self.X, self.Y, self.Z, self.W = args + elif len(args) == 2: # args=(Vector3, w) + self.X, self.Y, self.Z = args[0] + self.W = args[1] + elif len(args) == 0: # args=() + self.X = self.Y = self.Z = self.W = 0.0 + else: + raise TypeError("Invalid argument length for Vector4") + else: + raise TypeError("If only 1 argument passed, it must be a sequence") + + def __getitem__(self, index): + return (self.X, self.Y, self.Z, self.W)[index] + + def __setitem__(self, index, value): + if index == 0: + self.X = value + elif index == 1: + self.Y = value + elif index == 2: + self.Z = value + elif index == 3: + self.W = value + else: + raise IndexError("Index out of range") + + def __hash__(self): + return ( + self.X.__hash__() ^ + (self.Y.__hash__() << 2) ^ + (self.Z.__hash__() >> 2) ^ + (self.W.__hash__() >> 1) + ) + + def normalize(self): + length = self.length() + if length > kEpsilon: + invNorm = 1.0 / length + self.X *= invNorm + self.Y *= invNorm + self.Z *= invNorm + self.W *= invNorm + else: + self.X = self.Y = self.Z = self.W = 0.0 + + Normalize = normalize + + def length(self): + return sqrt(self.lengthSquared()) + + Length = length + + def lengthSquared(self): + return self.X ** 2 + self.Y ** 2 + self.Z ** 2 + self.W ** 2 + + LengthSquared = lengthSquared + + @staticmethod + def Zero(): + return Vector4(0, 0, 0, 0) + + @staticmethod + def One(): + return Vector4(1, 1, 1, 1) + + def __add__(a, b): + return Vector4(a.X + b.X, a.Y + b.Y, a.Z + b.Z, a.W + b.W) + + def __sub__(a, b): + return Vector4(a.X - b.X, a.Y - b.Y, a.Z - b.Z, a.W - b.W) + + def __mul__(a, d): + return Vector4(a.X * d, a.Y * d, a.Z * d, a.W * d) + + def __truediv__(a, d): + return Vector4(a.X / d, a.Y / d, a.Z / d, a.W / d) + + def __eq__(lhs, rhs): + if isinstance(rhs, Vector4): + diff = lhs - rhs + return diff.lengthSquared() < kEpsilon * kEpsilon + return False + + def __ne__(lhs, rhs): + return not (lhs == rhs) + + def Vector3(self): + from .Vector3 import Vector3 + return Vector3(self.X, self.Y, self.Z)