Skip to content

Commit

Permalink
Fix: some type hints (tools/extractor, legacy patch stubs) (#285)
Browse files Browse the repository at this point in the history
* fix tools.extractor type hints

* classes.generated - add legacy patch type hints

* Creating type stubs for legacy patches

This is better than putting all patched classes into __init__.pyi, and may be worth it given the backwards-compatible immutability of the legacy patch.

* better legacy patch stub style

* Remove meaningless stub definitions for legacy patches.
  • Loading branch information
Nattsu39 authored Nov 18, 2024
1 parent 8fa1464 commit d2c6697
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 17 deletions.
31 changes: 31 additions & 0 deletions UnityPy/classes/legacy_patch/AudioClip.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from typing import Dict, List, Optional

from UnityPy.classes.generated import SampleClip, StreamedResource

class AudioClip(SampleClip):
m_Name: str
m_3D: Optional[bool] = None
m_Ambisonic: Optional[bool] = None
m_AudioData: Optional[List[int]] = None
m_BitsPerSample: Optional[int] = None
m_Channels: Optional[int] = None
m_CompressionFormat: Optional[int] = None
m_Format: Optional[int] = None
m_Frequency: Optional[int] = None
m_IsTrackerFormat: Optional[bool] = None
m_Legacy3D: Optional[bool] = None
m_Length: Optional[float] = None
m_LoadInBackground: Optional[bool] = None
m_LoadType: Optional[int] = None
m_PreloadAudioData: Optional[bool] = None
m_Resource: Optional[StreamedResource] = None
m_Stream: Optional[int] = None
m_SubsoundIndex: Optional[int] = None
m_Type: Optional[int] = None
m_UseHardware: Optional[bool] = None

@property
def extension(self) -> str: ...

@property
def samples(self) -> Dict[str, bytes]: ...
26 changes: 26 additions & 0 deletions UnityPy/classes/legacy_patch/GameObject.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from typing import List, Tuple, Union

from UnityPy.classes import Component, PPtr
from UnityPy.classes.generated import ComponentPair, EditorExtension

class GameObject(EditorExtension):
m_Component: Union[List[ComponentPair], List[Tuple[int, PPtr[Component]]]]
m_IsActive: Union[bool, int]
m_Layer: int
m_Name: str
m_Tag: int

@property
def m_Components(self) -> List[PPtr[Component]]: ...
@property
def m_Animator(self) -> Union[PPtr[Component], None]: ...
@property
def m_Animation(self) -> Union[PPtr[Component], None]: ...
@property
def m_Transform(self) -> Union[PPtr[Component], None]: ...
@property
def m_SkinnedMeshRenderer(self) -> Union[PPtr[Component], None]: ...
@property
def m_MeshRenderer(self) -> Union[PPtr[Component], None]: ...
@property
def m_MeshFilter(self) -> Union[PPtr[Component], None]: ...
50 changes: 50 additions & 0 deletions UnityPy/classes/legacy_patch/Mesh.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from typing import List, Optional, Union

from UnityPy.classes.generated import (AABB, BlendShapeData, BoneInfluence,
BoneWeights4, CompressedMesh,
MeshBlendShape, MeshBlendShapeVertex,
MinMaxAABB, NamedObject, StreamingInfo,
SubMesh, VariableBoneCountWeights,
VertexData)
from UnityPy.classes.math import (ColorRGBA, Matrix4x4f, Vector2f, Vector3f,
Vector4f)

class Mesh(NamedObject):
m_BindPose: List[Matrix4x4f]
m_CompressedMesh: CompressedMesh
m_IndexBuffer: List[int]
m_LocalAABB: AABB
m_MeshCompression: int
m_MeshUsageFlags: int
m_Name: str
m_SubMeshes: List[SubMesh]
m_BakedConvexCollisionMesh: Optional[List[int]] = None
m_BakedTriangleCollisionMesh: Optional[List[int]] = None
m_BoneNameHashes: Optional[List[int]] = None
m_BonesAABB: Optional[List[MinMaxAABB]] = None
m_CollisionTriangles: Optional[List[int]] = None
m_CollisionVertexCount: Optional[int] = None
m_Colors: Optional[List[ColorRGBA]] = None
m_CookingOptions: Optional[int] = None
m_IndexFormat: Optional[int] = None
m_IsReadable: Optional[bool] = None
m_KeepIndices: Optional[bool] = None
m_KeepVertices: Optional[bool] = None
m_MeshMetrics_0_: Optional[float] = None
m_MeshMetrics_1_: Optional[float] = None
m_Normals: Optional[List[Vector3f]] = None
m_RootBoneNameHash: Optional[int] = None
m_ShapeVertices: Optional[List[MeshBlendShapeVertex]] = None
m_Shapes: Optional[Union[BlendShapeData, List[MeshBlendShape]]] = None
m_Skin: Optional[Union[List[BoneInfluence], List[BoneWeights4]]] = None
m_StreamCompression: Optional[int] = None
m_StreamData: Optional[StreamingInfo] = None
m_Tangents: Optional[List[Vector4f]] = None
m_UV: Optional[List[Vector2f]] = None
m_UV1: Optional[List[Vector2f]] = None
m_Use16BitIndices: Optional[int] = None
m_VariableBoneCountWeights: Optional[VariableBoneCountWeights] = None
m_VertexData: Optional[VertexData] = None
m_Vertices: Optional[List[Vector3f]] = None

def export(self, format: str = "obj") -> str: ...
8 changes: 8 additions & 0 deletions UnityPy/classes/legacy_patch/Renderer.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from UnityPy.classes import PPtr
from UnityPy.classes.generated import Component
from UnityPy.classes.legacy_patch import GameObject

class Renderer(Component):
m_GameObject: PPtr[GameObject]

def export(self, export_dir: str) -> None: ...
25 changes: 25 additions & 0 deletions UnityPy/classes/legacy_patch/Shader.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from typing import List, Optional, Tuple, Union

from UnityPy.classes import PPtr
from UnityPy.classes.generated import (GUID, NamedObject, SerializedShader,
Texture)

class Shader(NamedObject):
m_Name: str
compressedBlob: Optional[List[int]] = None
compressedLengths: Optional[Union[List[int], List[List[int]]]] = None
decompressedLengths: Optional[Union[List[int], List[List[int]]]] = None
decompressedSize: Optional[int] = None
m_AssetGUID: Optional[GUID] = None
m_Dependencies: Optional[List[PPtr[Shader]]] = None
m_NonModifiableTextures: Optional[List[Tuple[str, PPtr[Texture]]]] = None
m_ParsedForm: Optional[SerializedShader] = None
m_PathName: Optional[str] = None
m_Script: Optional[str] = None
m_ShaderIsBaked: Optional[bool] = None
m_SubProgramBlob: Optional[List[int]] = None
offsets: Optional[Union[List[int], List[List[int]]]] = None
platforms: Optional[List[int]] = None
stageCounts: Optional[List[int]] = None

def export(self) -> str: ...
29 changes: 29 additions & 0 deletions UnityPy/classes/legacy_patch/Sprite.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from typing import List, Optional, Tuple

from PIL.Image import Image

from UnityPy.classes import PPtr
from UnityPy.classes.generated import (GUID, MonoBehaviour, NamedObject, Rectf,
SpriteAtlas, SpriteBone,
SpriteRenderData)
from UnityPy.classes.math import Vector2f, Vector4f

class Sprite(NamedObject):
m_Extrude: int
m_Name: str
m_Offset: Vector2f
m_PixelsToUnits: float
m_RD: SpriteRenderData
m_Rect: Rectf
m_AtlasTags: Optional[List[str]] = None
m_Bones: Optional[List[SpriteBone]] = None
m_Border: Optional[Vector4f] = None
m_IsPolygon: Optional[bool] = None
m_PhysicsShape: Optional[List[List[Vector2f]]] = None
m_Pivot: Optional[Vector2f] = None
m_RenderDataKey: Optional[Tuple[GUID, int]] = None
m_ScriptableObjects: Optional[List[PPtr[MonoBehaviour]]] = None
m_SpriteAtlas: Optional[PPtr[SpriteAtlas]] = None

@property
def image(self) -> Image: ...
45 changes: 45 additions & 0 deletions UnityPy/classes/legacy_patch/Texture2D.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from typing import BinaryIO, List, Optional, Union

from PIL.Image import Image

from UnityPy.classes.generated import GLTextureSettings, StreamingInfo, Texture

class Texture2D(Texture):
image_data: bytes
m_CompleteImageSize: int
m_Height: int
m_ImageCount: int
m_IsReadable: bool
m_LightmapFormat: int
m_Name: str
m_TextureDimension: int
m_TextureFormat: int
m_TextureSettings: GLTextureSettings
m_Width: int
m_ColorSpace: Optional[int] = None
m_DownscaleFallback: Optional[bool] = None
m_ForcedFallbackFormat: Optional[int] = None
m_IgnoreMasterTextureLimit: Optional[bool] = None
m_IgnoreMipmapLimit: Optional[bool] = None
m_IsAlphaChannelOptional: Optional[bool] = None
m_IsPreProcessed: Optional[bool] = None
m_MipCount: Optional[int] = None
m_MipMap: Optional[bool] = None
m_MipmapLimitGroupName: Optional[str] = None
m_MipsStripped: Optional[int] = None
m_PlatformBlob: Optional[List[int]] = None
m_ReadAllowed: Optional[bool] = None
m_StreamData: Optional[StreamingInfo] = None
m_StreamingMipmaps: Optional[bool] = None
m_StreamingMipmapsPriority: Optional[int] = None

@property
def image(self) -> Image: ...
def set_image(
self,
img: Union[Image, str, BinaryIO],
target_format: Optional[int] = None,
mipmap_count: int = 1,
) -> None: ...
def get_image_data(self) -> bytes: ...

29 changes: 29 additions & 0 deletions UnityPy/classes/legacy_patch/Texture2DArray.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from typing import List, Optional

from PIL.Image import Image

from UnityPy.classes.generated import GLTextureSettings, StreamingInfo, Texture

class Texture2DArray(Texture):
image_data: bytes
m_ColorSpace: int
m_DataSize: int
m_Depth: int
m_Format: int
m_Height: int
m_IsReadable: bool
m_MipCount: int
m_Name: str
m_TextureSettings: GLTextureSettings
m_Width: int
m_DownscaleFallback: Optional[bool] = None
m_ForcedFallbackFormat: Optional[int] = None
m_IgnoreMipmapLimit: Optional[bool] = None
m_IsAlphaChannelOptional: Optional[bool] = None
m_MipmapLimitGroupName: Optional[str] = None
m_MipsStripped: Optional[int] = None
m_StreamData: Optional[StreamingInfo] = None
m_UsageMode: Optional[int] = None

@property
def images(self) -> List[Image]: ...
34 changes: 17 additions & 17 deletions UnityPy/tools/extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
from io import BytesIO
from pathlib import Path
from typing import Callable, Dict, List, Union
from typing import Callable, Dict, List, Optional, Tuple, Union

import UnityPy
from UnityPy.classes import (
Expand All @@ -19,6 +19,7 @@
Texture2D,
)
from UnityPy.enums.ClassIDType import ClassIDType
from UnityPy.files import SerializedFile


def export_obj(
Expand All @@ -27,8 +28,8 @@ def export_obj(
append_name: bool = False,
append_path_id: bool = False,
export_unknown_as_typetree: bool = False,
asset_filter: Callable[[Object], bool] = None,
) -> List[int]:
asset_filter: Optional[Callable[[Object], bool]] = None,
) -> List[Tuple[SerializedFile, int]]:
"""Exports the given object to the given filepath.
Args:
Expand Down Expand Up @@ -76,8 +77,8 @@ def extract_assets(
ignore_first_container_dirs: int = 0,
append_path_id: bool = False,
export_unknown_as_typetree: bool = False,
asset_filter: Callable[[Object], bool] = None,
) -> List[int]:
asset_filter: Optional[Callable[[Object], bool]] = None,
) -> List[Tuple[SerializedFile, int]]:
"""Extracts some or all assets from the given source.
Args:
Expand All @@ -90,7 +91,7 @@ def extract_assets(
asset_filter (func(object)->bool, optional): Determines whether to export an object. Defaults to all objects.
Returns:
List[int]: [description]
List[Tuple[SerializedFile, int]]: [description]
"""
# load source
env = UnityPy.load(src)
Expand Down Expand Up @@ -153,15 +154,15 @@ def defaulted_export_index(type: ClassIDType):
###############################################################################


def exportTextAsset(obj: TextAsset, fp: str, extension: str = ".txt") -> List[int]:
def exportTextAsset(obj: TextAsset, fp: str, extension: str = ".txt") -> List[Tuple[SerializedFile, int]]:
if not extension:
extension = ".txt"
with open(f"{fp}{extension}", "wb") as f:
f.write(obj.m_Script.encode("utf-8", "surrogateescape"))
return [(obj.assets_file, obj.object_reader.path_id)]


def exportFont(obj: Font, fp: str, extension: str = "") -> List[int]:
def exportFont(obj: Font, fp: str, extension: str = "") -> List[Tuple[SerializedFile, int]]:
# TODO - export glyphs
if obj.m_FontData:
extension = ".ttf"
Expand All @@ -172,15 +173,15 @@ def exportFont(obj: Font, fp: str, extension: str = "") -> List[int]:
return [(obj.assets_file, obj.object_reader.path_id)]


def exportMesh(obj: Mesh, fp: str, extension=".obj") -> List[int]:
def exportMesh(obj: Mesh, fp: str, extension=".obj") -> List[Tuple[SerializedFile, int]]:
if not extension:
extension = ".obj"
with open(f"{fp}{extension}", "wt", encoding="utf8", newline="") as f:
f.write(obj.export())
return [(obj.assets_file, obj.object_reader.path_id)]


def exportShader(obj: Shader, fp: str, extension=".txt") -> List[int]:
def exportShader(obj: Shader, fp: str, extension=".txt") -> List[Tuple[SerializedFile, int]]:
if not extension:
extension = ".txt"
with open(f"{fp}{extension}", "wt", encoding="utf8", newline="") as f:
Expand All @@ -190,7 +191,7 @@ def exportShader(obj: Shader, fp: str, extension=".txt") -> List[int]:

def exportMonoBehaviour(
obj: Union[MonoBehaviour, Object], fp: str, extension: str = ""
) -> List[int]:
) -> List[Tuple[SerializedFile, int]]:
export = None

if obj.object_reader.serialized_type.node:
Expand Down Expand Up @@ -224,7 +225,7 @@ def exportMonoBehaviour(
return [(obj.assets_file, obj.object_reader.path_id)]


def exportAudioClip(obj: AudioClip, fp: str, extension: str = "") -> List[int]:
def exportAudioClip(obj: AudioClip, fp: str, extension: str = "") -> List[Tuple[SerializedFile, int]]:
samples = obj.samples
if len(samples) == 0:
pass
Expand All @@ -239,7 +240,7 @@ def exportAudioClip(obj: AudioClip, fp: str, extension: str = "") -> List[int]:
return [(obj.assets_file, obj.object_reader.path_id)]


def exportSprite(obj: Sprite, fp: str, extension: str = ".png") -> List[int]:
def exportSprite(obj: Sprite, fp: str, extension: str = ".png") -> List[Tuple[SerializedFile, int]]:
if not extension:
extension = ".png"
obj.image.save(f"{fp}{extension}")
Expand All @@ -254,16 +255,15 @@ def exportSprite(obj: Sprite, fp: str, extension: str = ".png") -> List[int]:
return exported


def exportTexture2D(obj: Texture2D, fp: str, extension: str = ".png") -> List[int]:
def exportTexture2D(obj: Texture2D, fp: str, extension: str = ".png") -> List[Tuple[SerializedFile, int]]:
if not extension:
extension = ".png"
if obj.m_Width:
# textures can be empty
obj.image.save(f"{fp}{extension}")
return [(obj.assets_file, obj.path_id)]


def exportGameObject(obj: GameObject, fp: str, extension: str = "") -> List[int]:
def exportGameObject(obj: GameObject, fp: str, extension: str = "") -> List[Tuple[SerializedFile, int]]:
exported = [(obj.assets_file, obj.path_id)]
refs = crawl_obj(obj)
if refs:
Expand Down Expand Up @@ -299,7 +299,7 @@ def exportGameObject(obj: GameObject, fp: str, extension: str = "") -> List[int]
MONOBEHAVIOUR_TYPETREES: Dict["Assembly-Name.dll", Dict["Class-Name", List[Dict]]] = {}


def crawl_obj(obj: Object, ret: dict = None) -> Dict[int, Union[Object, PPtr]]:
def crawl_obj(obj: Object, ret: Optional[dict] = None) -> Dict[int, Union[Object, PPtr]]:
"""Crawls through the data struture of the object and returns a list of all the components."""
if not ret:
ret = {}
Expand Down

0 comments on commit d2c6697

Please sign in to comment.