diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..ef40a1e2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +tests/samples/* filter=lfs diff=lfs merge=lfs -text +UnityPy/resources/* filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c06c9cc8..700306b5 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -58,6 +58,9 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + submodules: recursive + lfs: true # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 88ae37c6..cd917a79 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,6 +11,7 @@ jobs: - uses: actions/checkout@v4 with: submodules: recursive + lfs: true - name: Set up Python uses: actions/setup-python@v5 @@ -19,7 +20,7 @@ jobs: - name: Build sdist run: pipx run build --sdist - + - name: Install sdist run: pip install dist/*.tar.gz @@ -27,7 +28,7 @@ jobs: with: name: "sdist" path: dist/*.tar.gz - + build_wheels: name: Build wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} @@ -41,19 +42,20 @@ jobs: - uses: actions/checkout@v4 with: submodules: recursive - + lfs: true + - name: Build wheels - uses: joerick/cibuildwheel@v2.21.2 + uses: joerick/cibuildwheel@v3.3 env: CIBW_TEST_SKIP: "*" CIBW_SKIP: "pp*" - + - uses: actions/upload-artifact@v4 with: name: "${{ matrix.os }}" path: ./wheelhouse/*.whl retention-days: 1 - + build_manylinux_wheels_ubuntu: name: Build manylinux wheels on ubuntu-latest runs-on: ubuntu-latest @@ -63,6 +65,7 @@ jobs: - uses: actions/checkout@v4 with: submodules: recursive + lfs: true - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -70,7 +73,7 @@ jobs: platforms: all - name: Build wheels - uses: joerick/cibuildwheel@v2.21.2 + uses: joerick/cibuildwheel@v3.3 env: CIBW_TEST_SKIP: "*" CIBW_SKIP: "pp* *-musllinux*" @@ -90,6 +93,7 @@ jobs: - uses: actions/checkout@v4 with: submodules: recursive + lfs: true - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -97,11 +101,11 @@ jobs: platforms: all - name: Build wheels - uses: joerick/cibuildwheel@v2.21.2 + uses: joerick/cibuildwheel@v3.3 env: CIBW_TEST_SKIP: "*" CIBW_SKIP: "pp* *-manylinux*" - # fmod requires: + # fmod requires: # default via musl: -exclude flag # libdl.so.2 => /lib/ld-musl-x86_64.so.1 (0x7faeb127d000) # librt.so.1 => /lib/ld-musl-x86_64.so.1 (0x7faeb127d000) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f347d2dc..d322f25d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,11 +12,14 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-13] + os: [ubuntu-latest, windows-latest, macos-latest] steps: - uses: actions/checkout@v4 - + with: + submodules: recursive + lfs: true + - name: Set up Python uses: actions/setup-python@v5 with: @@ -29,7 +32,7 @@ jobs: - name: Check code (ruff check) run: ruff check - + - name: Check code style (ruff format) run: ruff format --check diff --git a/README.md b/README.md index 4686c947..baf92789 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,13 @@ Via the typetree structure all object types can be edited in their native forms. ```python # modification via dict: - raw_dict = obj.read_typetree() + raw_dict = obj.parse_as_dict() # modify raw dict - obj.save_typetree(raw_dict) + obj.patch(raw_dict) # modification via parsed class - instance = obj.read() + instance = obj.parse_as_object() # modify instance - obj.save(instance) + obj.patch(instance) ``` If you need advice or if you want to talk about (game) data-mining, @@ -100,10 +100,10 @@ def unpack_all_assets(source_folder: str, destination_folder: str): # process specific object types if obj.type.name in ["Texture2D", "Sprite"]: # parse the object data - data = obj.read() + data = obj.parse_as_object() # create destination path - dest = os.path.join(destination_folder, data.name) + dest = os.path.join(destination_folder, data.m_Name) # make sure that the extension is correct # you probably only want to do so with images/textures @@ -116,7 +116,7 @@ def unpack_all_assets(source_folder: str, destination_folder: str): # alternative way which keeps the original path for path,obj in env.container.items(): if obj.type.name in ["Texture2D", "Sprite"]: - data = obj.read() + data = obj.parse_as_object() # create dest based on original path dest = os.path.join(destination_folder, *path.split("/")) # make sure that the dir of that path exists @@ -187,7 +187,51 @@ The objects with a file path can be found in the `.container` dict - `{path : ob Objects \([ObjectReader class](UnityPy/files/ObjectReader.py)\) contain the _actual_ files, e.g., textures, text files, meshes, settings, ... -To acquire the actual data of an object it has to be read first. This happens via the `.read()` function. This isn't done automatically to save time because only a small part of the objects are of interest. Serialized objects can be set with raw data using `.set_raw_data(data)` or modified with `.save()` function, if supported. +To acquire the actual data of an object it has to be parsed first. +This happens via the parse functions mentioned below. +This isn't done automatically to save time as only a small part of the objects are usually of interest. +Serialized objects can be set with raw data using `.set_raw_data(data)` or modified with `.save()` function, if supported. + +For object types with ``m_Name`` you can use ``.peek_name()`` to only read the name of the parsed object without parsing it completely, which is way faster. + +There are two general parsing functions, ``.parse_as_object()`` and ``.parse_as_dict()``. +``parse_as_dict`` parses the object data into a dict. +``parse_as_object`` parses the object data into a class. If the class is a Unity class, it's stub class from ``UnityPy.classes(.generated)`` will be used, if it's an unknown one, then it will be parsed into an ``UnknownObject``, which simply acts as interface for the otherwise parsed dict. +Some special classes, namely those below, have additional handlers added to their class for easier interaction with them. + +The ``.patch(item)`` function can be used on all object (readers) to replace their data with the changed item, which has to be either a dict or of the class the object represents. + +#### Example + +```py +for obj in env.objects: + if obj.type.name == "the type you want": + if obj.peek_name() != "the specific object you want": + continue + # parsing + instance = obj.parse_as_object() + dic = obj.parse_as_dict() + + # modifying + instance.m_Name = "new name" + dic["m_Name"] = "new name" + + # saving + obj.patch(instance) + obj.patch(dic) +``` + +#### Legacy + +Following functions are legacy functions that will be removed in the future when major version 2 hits. +The modern versions are equivalent to them and have a more correct type hints. + +| Legacy | Modern | +|---------------|-----------------| +| read | parse_as_object | +| read_typetree | parse_as_dict | +| save_typetree | patch | + ## Important Object Types @@ -207,14 +251,14 @@ from PIL import Image for obj in env.objects: if obj.type.name == "Texture2D": # export texture - data = obj.read() - path = os.path.join(export_dir, f"{data.m_Name}.png") - data.image.save(path) + tex = obj.parse_as_object() + path = os.path.join(export_dir, f"{tex.m_Name}.png") + tex.image.save(path) # edit texture - fp = os.path.join(replace_dir, f"{data.m_Name}.png") + fp = os.path.join(replace_dir, f"{tex.m_Name}.png") pil_img = Image.open(fp) - data.image = pil_img - data.save() + tex.image = pil_img + tex.save() ``` ### Sprite @@ -232,9 +276,9 @@ Unlike most other extractors (including AssetStudio), UnityPy merges those two i ```python for obj in env.objects: if obj.type.name == "Sprite": - data = obj.read() - path = os.path.join(export_dir, f"{data.m_Name}.png") - data.image.save(path) + sprite = obj.parse_as_object() + path = os.path.join(export_dir, f"{sprite.m_Name}.png") + sprite.image.save(path) ``` ### TextAsset @@ -244,7 +288,8 @@ TextAssets are usually normal text files. - `.m_Name` - `.m_Script` - str -Some games save binary data as TextAssets. As ``m_Script`` gets handled as str by default, +Some games save binary data as TextAssets. +As ``m_Script`` gets handled as str by default, use ``m_Script.encode("utf-8", "surrogateescape")`` to retrieve the original binary data. **Export** @@ -253,15 +298,15 @@ use ``m_Script.encode("utf-8", "surrogateescape")`` to retrieve the original bin for obj in env.objects: if obj.type.name == "TextAsset": # export asset - data = obj.read() - path = os.path.join(export_dir, f"{data.m_Name}.txt") + txt = obj.parse_as_object() + path = os.path.join(export_dir, f"{txt.m_Name}.txt") with open(path, "wb") as f: - f.write(data.m_Script.encode("utf-8", "surrogateescape")) + f.write(txt.m_Script.encode("utf-8", "surrogateescape")) # edit asset - fp = os.path.join(replace_dir, f"{data.m_Name}.txt") + fp = os.path.join(replace_dir, f"{txt.m_Name}.txt") with open(fp, "rb") as f: - data.m_Script = f.read().decode("utf-8", "surrogateescape")) - data.save() + txt.m_Script = f.read().decode("utf-8", "surrogateescape") + txt.save() ``` ### MonoBehaviour @@ -272,6 +317,7 @@ In such cases see the 2nd example (TypeTreeGenerator) below. - `.m_Name` - `.m_Script` +- custom data **Export** @@ -281,22 +327,16 @@ import json for obj in env.objects: if obj.type.name == "MonoBehaviour": # export - if obj.serialized_type.node: - # save decoded data - tree = obj.read_typetree() - fp = os.path.join(extract_dir, f"{tree['m_Name']}.json") - with open(fp, "wt", encoding = "utf8") as f: - json.dump(tree, f, ensure_ascii = False, indent = 4) + # save decoded data + tree = obj.parse_as_dict() + fp = os.path.join(extract_dir, f"{tree['m_Name']}.json") + with open(fp, "wt", encoding = "utf8") as f: + json.dump(tree, f, ensure_ascii = False, indent = 4) # edit - if obj.serialized_type.node: - tree = obj.read_typetree() - # apply modifications to the data within the tree - obj.save_typetree(tree) - else: - data = obj.read(check_read=False) - with open(os.path.join(replace_dir, data.m_Name)) as f: - data.save(raw_data = f.read()) + tree = obj.parse_as_dict() + # apply modifications to the data within the tree + obj.patch(tree) ``` **TypeTreeGenerator** @@ -328,7 +368,7 @@ env.typetree_generator = generator for obj in objects: if obj.type.name == "MonoBehaviour": # automatically tries to use the generator in the background if necessary - x = obj.read() + x = obj.parse_as_object() ``` @@ -352,7 +392,7 @@ for name, data in clip.samples.items(): ```python if obj.type.name == "Font": - font: Font = obj.read() + font: Font = obj.parse_as_object() if font.m_FontData: extension = ".ttf" if font.m_FontData[0:4] == b"OTTO": @@ -389,8 +429,9 @@ export_dir: str if mesh_renderer.m_GameObject: # get the name of the model - game_object = mesh_renderer.m_GameObject.read() - export_dir = os.path.join(export_dir, game_object.m_Name) + game_obj_reader = mesh_renderer.m_GameObject.deref() + game_obj_name = game_obj_reader.peek_name() + export_dir = os.path.join(export_dir, game_obj_name) mesh_renderer.export(export_dir) ``` @@ -411,9 +452,9 @@ from PIL import Image for obj in env.objects: if obj.type.name == "Texture2DArray": # export texture - data = obj.read() - for i, image in enumerate(data.images): - image.save(os.path.join(path, f"{data.m_Name}_{i}.png")) + tex_arr = obj.parse_as_object() + for i, image in enumerate(tex_arr.images): + image.save(os.path.join(path, f"{tex_arr.m_Name}_{i}.png")) # editing isn't supported yet! ``` diff --git a/UnityPy/__init__.py b/UnityPy/__init__.py index d578d59f..96f2b0ea 100644 --- a/UnityPy/__init__.py +++ b/UnityPy/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.23.0" +__version__ = "1.25.1" from .environment import Environment as Environment from .helpers.ArchiveStorageManager import ( diff --git a/UnityPy/classes/Object.py b/UnityPy/classes/Object.py index 2438de07..c6ea2373 100644 --- a/UnityPy/classes/Object.py +++ b/UnityPy/classes/Object.py @@ -32,6 +32,27 @@ def save(self) -> None: self.object_reader.save_typetree(self) + def __copy__(self) -> Object: + clz = self.__class__ + data = { + key: value + for key, value in self.__dict__.items() + if isinstance(key, str) and (not key.startswith("__") or key == "__node__") and not callable(value) + } + try: + # covers UnknownObject + return clz(**data) + except TypeError: + from ..helpers.TypeTreeHelper import get_annotation_keys + + keys = set(self.__dict__.keys()) + annotation_keys = get_annotation_keys(clz) + extra_keys = keys - annotation_keys + instance = clz(**{key: data[key] for key in annotation_keys}) + for key in extra_keys: + setattr(instance, key, data[key]) + return instance + __all__ = [ "Object", diff --git a/UnityPy/classes/generated.py b/UnityPy/classes/generated.py index 721b1846..70a336bb 100644 --- a/UnityPy/classes/generated.py +++ b/UnityPy/classes/generated.py @@ -52,6 +52,7 @@ class AnnotationManager(Object): m_FadeGizmos: Optional[bool] = None m_IconSize: Optional[float] = None m_ShowGrid: Optional[bool] = None + m_ShowLODLabels: Optional[bool] = None m_ShowSelectionOutline: Optional[bool] = None m_ShowSelectionWire: Optional[bool] = None m_Use3dGizmos: Optional[bool] = None @@ -102,11 +103,32 @@ class AudioBuildInfo(Object): m_IsAudioDisabled: bool +@unitypy_define +class BlockShaderSourceArtifact(Object): + shaderName: str + shaderSource: str + + @unitypy_define class BuiltAssetBundleInfoSet(Object): bundleInfos: List[BuiltAssetBundleInfo] +@unitypy_define +class ContentSummary(Object): + m_assetStatsList: List[AssetStats] + m_generatedFileCount: int + m_generatedFileSize: int + m_headerSize: int + m_objectCount: int + m_resourceDataSize: int + m_resourceFileCount: int + m_serializedFileCount: int + m_serializedFileSize: int + m_sizeReusedContentInOutputDirectory: int + m_typeStatsList: List[TypeStats] + + @unitypy_define class Derived(Object): pass @@ -251,7 +273,7 @@ class AudioSource(AudioBehaviour): Spatialize: Optional[bool] = None SpatializePostEffects: Optional[bool] = None m_ExtensionPropertyValues: Optional[List[ExtensionPropertyValue]] = None - m_Resource: Optional[PPtr[AudioResource]] = None + m_Resource: Optional[Union[PPtr[AudioResource], PPtr[Object]]] = None reverbZoneMixCustomCurve: Optional[AnimationCurve] = None @@ -284,7 +306,7 @@ class AudioDistortionFilter(AudioFilter): @unitypy_define class AudioEchoFilter(AudioFilter): m_DecayRatio: float - m_Delay: Union[int, float] + m_Delay: Union[float, int] m_DryMix: float m_Enabled: int m_GameObject: PPtr[GameObject] @@ -447,6 +469,7 @@ class Canvas(Behaviour): m_SortingOrder: Optional[int] = None m_TargetDisplay: Optional[int] = None m_UpdateRectTransformForStandalone: Optional[int] = None + m_UseReflectionProbes: Optional[bool] = None m_VertexColorAlwaysGammaSpace: Optional[bool] = None @@ -476,13 +499,8 @@ class Cloth(Behaviour): m_SelfCollisionDistance: Optional[float] = None m_SelfCollisionStiffness: Optional[float] = None m_SleepThreshold: Optional[float] = None - m_SolverFrequency: Optional[Union[int, float]] = None - m_SphereColliders: Optional[ - Union[ - List[ClothSphereColliderPair], - List[Tuple[PPtr[SphereCollider], PPtr[SphereCollider]]], - ] - ] = None + m_SolverFrequency: Optional[Union[float, int]] = None + m_SphereColliders: Optional[Union[List[ClothSphereColliderPair], List[Tuple[PPtr[SphereCollider], PPtr[SphereCollider]]]]] = None m_StretchingStiffness: Optional[float] = None m_UseContinuousCollision: Optional[bool] = None m_UseGravity: Optional[bool] = None @@ -768,23 +786,24 @@ class Effector2D(Behaviour): @unitypy_define class AreaEffector2D(Effector2D): - m_AngularDrag: float m_ColliderMask: BitField - m_Drag: float m_Enabled: int m_ForceMagnitude: float m_ForceTarget: int m_ForceVariation: float m_GameObject: PPtr[GameObject] + m_AngularDamping: Optional[float] = None + m_AngularDrag: Optional[float] = None + m_Drag: Optional[float] = None m_ForceAngle: Optional[float] = None m_ForceDirection: Optional[float] = None + m_LinearDamping: Optional[float] = None m_UseColliderMask: Optional[bool] = None m_UseGlobalAngle: Optional[bool] = None @unitypy_define class BuoyancyEffector2D(Effector2D): - m_AngularDrag: float m_ColliderMask: BitField m_Density: float m_Enabled: int @@ -792,9 +811,12 @@ class BuoyancyEffector2D(Effector2D): m_FlowMagnitude: float m_FlowVariation: float m_GameObject: PPtr[GameObject] - m_LinearDrag: float m_SurfaceLevel: float m_UseColliderMask: bool + m_AngularDamping: Optional[float] = None + m_AngularDrag: Optional[float] = None + m_LinearDamping: Optional[float] = None + m_LinearDrag: Optional[float] = None @unitypy_define @@ -818,10 +840,8 @@ class PlatformEffector2D(Effector2D): @unitypy_define class PointEffector2D(Effector2D): - m_AngularDrag: float m_ColliderMask: BitField m_DistanceScale: float - m_Drag: float m_Enabled: int m_ForceMagnitude: float m_ForceMode: int @@ -829,6 +849,10 @@ class PointEffector2D(Effector2D): m_ForceTarget: int m_ForceVariation: float m_GameObject: PPtr[GameObject] + m_AngularDamping: Optional[float] = None + m_AngularDrag: Optional[float] = None + m_Drag: Optional[float] = None + m_LinearDamping: Optional[float] = None m_UseColliderMask: Optional[bool] = None @@ -1237,7 +1261,6 @@ class LensFlare(Behaviour): class Light(Behaviour): m_Color: ColorRGBA m_Cookie: PPtr[Texture] - m_CookieSize: float m_CullingMask: BitField m_DrawHalo: bool m_Enabled: int @@ -1258,6 +1281,8 @@ class Light(Behaviour): m_BoundingSphereOverride: Optional[Vector4f] = None m_CCT: Optional[float] = None m_ColorTemperature: Optional[float] = None + m_CookieSize: Optional[float] = None + m_CookieSize2D: Optional[Vector2f] = None m_EnableSpotReflector: Optional[bool] = None m_FalloffTable: Optional[FalloffTable] = None m_ForceVisible: Optional[bool] = None @@ -1267,6 +1292,7 @@ class Light(Behaviour): m_LuxAtDistance: Optional[float] = None m_RenderingLayerMask: Optional[int] = None m_Shape: Optional[int] = None + m_ShapeRadius: Optional[float] = None m_UseBoundingSphereOverride: Optional[bool] = None m_UseColorTemperature: Optional[bool] = None m_UseViewFrustumForShadowCasterCull: Optional[bool] = None @@ -1454,6 +1480,7 @@ class SortingGroup(Behaviour): m_GameObject: PPtr[GameObject] m_SortingLayer: int m_SortingOrder: int + m_Sort3DAs2D: Optional[bool] = None m_SortAtRoot: Optional[bool] = None m_SortingLayerID: Optional[int] = None @@ -1555,6 +1582,7 @@ class VisualEffect(Behaviour): m_AllowInstancing: Optional[int] = None m_InitialEventName: Optional[str] = None m_InitialEventNameOverriden: Optional[int] = None + m_ReleaseInstanceOnDisable: Optional[int] = None @unitypy_define @@ -1873,6 +1901,7 @@ class LODGroup(Component): m_Size: float m_AnimateCrossFading: Optional[bool] = None m_FadeMode: Optional[int] = None + m_GlobalIlluminationLOD: Optional[int] = None m_LastLODIsBillboard: Optional[bool] = None m_ScreenRelativeTransitionHeight: Optional[float] = None @@ -2059,8 +2088,11 @@ class BillboardRenderer(Renderer): m_SortingOrder: int m_StaticBatchRoot: PPtr[Transform] m_DynamicOccludee: Optional[int] = None + m_ForceMeshLod: Optional[int] = None m_LightProbeUsage: Optional[int] = None m_LightProbeVolumeOverride: Optional[PPtr[GameObject]] = None + m_MaskInteraction: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None m_MotionVectors: Optional[int] = None m_RayTraceProcedural: Optional[int] = None m_RayTracingAccelStructBuildFlags: Optional[int] = None @@ -2111,6 +2143,7 @@ class LineRenderer(Renderer): m_UseWorldSpace: bool m_ApplyActiveColorSpace: Optional[bool] = None m_DynamicOccludee: Optional[int] = None + m_ForceMeshLod: Optional[int] = None m_LightProbeAnchor: Optional[PPtr[Transform]] = None m_LightProbeUsage: Optional[int] = None m_LightProbeVolumeOverride: Optional[PPtr[GameObject]] = None @@ -2118,6 +2151,7 @@ class LineRenderer(Renderer): m_LightmapTilingOffsetDynamic: Optional[Vector4f] = None m_Loop: Optional[bool] = None m_MaskInteraction: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None m_MotionVectors: Optional[int] = None m_ProbeAnchor: Optional[PPtr[Transform]] = None m_RayTraceProcedural: Optional[int] = None @@ -2182,11 +2216,14 @@ class MeshRenderer(Renderer): m_AdditionalVertexStreams: Optional[PPtr[Mesh]] = None m_DynamicOccludee: Optional[int] = None m_EnlightenVertexStream: Optional[PPtr[Mesh]] = None + m_ForceMeshLod: Optional[int] = None m_LightProbeAnchor: Optional[PPtr[Transform]] = None m_LightProbeUsage: Optional[int] = None m_LightProbeVolumeOverride: Optional[PPtr[GameObject]] = None m_LightmapIndexDynamic: Optional[int] = None m_LightmapTilingOffsetDynamic: Optional[Vector4f] = None + m_MaskInteraction: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None m_MotionVectors: Optional[int] = None m_ProbeAnchor: Optional[PPtr[Transform]] = None m_RayTraceProcedural: Optional[int] = None @@ -2206,6 +2243,50 @@ class MeshRenderer(Renderer): m_UseLightProbes: Optional[bool] = None +@unitypy_define +class PanelRenderer(Renderer): + m_CastShadows: int + m_DynamicOccludee: int + m_Enabled: bool + m_ForceMeshLod: int + m_GameObject: PPtr[GameObject] + m_LightProbeUsage: int + m_LightProbeVolumeOverride: PPtr[GameObject] + m_LightmapIndex: int + m_LightmapIndexDynamic: int + m_LightmapTilingOffset: Vector4f + m_LightmapTilingOffsetDynamic: Vector4f + m_MaskInteraction: int + m_Materials: List[PPtr[Material]] + m_MeshLodSelectionBias: float + m_MotionVectors: int + m_PanelSettings: PPtr[MonoBehaviour] + m_ParentUI: PPtr[PanelRenderer] + m_Pivot: int + m_PivotReferenceSize: int + m_Position: int + m_ProbeAnchor: PPtr[Transform] + m_RayTraceProcedural: int + m_RayTracingAccelStructBuildFlags: int + m_RayTracingAccelStructBuildFlagsOverride: int + m_RayTracingMode: int + m_ReceiveShadows: int + m_ReflectionProbeUsage: int + m_RendererPriority: int + m_RenderingLayerMask: int + m_SmallMeshCulling: int + m_SortingLayer: int + m_SortingLayerID: int + m_SortingOrder: int + m_StaticBatchInfo: StaticBatchInfo + m_StaticBatchRoot: PPtr[Transform] + m_StaticShadowCaster: int + m_WorldSpaceHeight: float + m_WorldSpaceSizeMode: int + m_WorldSpaceWidth: float + sourceAsset: PPtr[MonoBehaviour] + + @unitypy_define class ParticleRenderer(Renderer): UV_Animation: UVAnimation @@ -2263,6 +2344,7 @@ class ParticleSystemRenderer(Renderer): m_DynamicOccludee: Optional[int] = None m_EnableGPUInstancing: Optional[bool] = None m_Flip: Optional[Vector3f] = None + m_ForceMeshLod: Optional[int] = None m_FreeformStretching: Optional[bool] = None m_LightProbeAnchor: Optional[PPtr[Transform]] = None m_LightProbeUsage: Optional[int] = None @@ -2274,6 +2356,7 @@ class ParticleSystemRenderer(Renderer): m_Mesh2: Optional[PPtr[Mesh]] = None m_Mesh3: Optional[PPtr[Mesh]] = None m_MeshDistribution: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None m_MeshWeighting: Optional[float] = None m_MeshWeighting1: Optional[float] = None m_MeshWeighting2: Optional[float] = None @@ -2308,6 +2391,43 @@ class ParticleSystemRenderer(Renderer): m_VertexStreams: Optional[List[int]] = None +@unitypy_define +class RenderAs2D(Renderer): + m_CastShadows: int + m_DynamicOccludee: int + m_Enabled: bool + m_ForceMeshLod: int + m_GameObject: PPtr[GameObject] + m_LightProbeUsage: int + m_LightProbeVolumeOverride: PPtr[GameObject] + m_LightmapIndex: int + m_LightmapIndexDynamic: int + m_LightmapTilingOffset: Vector4f + m_LightmapTilingOffsetDynamic: Vector4f + m_MaskInteraction: int + m_Materials: List[PPtr[Material]] + m_MeshLodSelectionBias: float + m_MotionVectors: int + m_ProbeAnchor: PPtr[Transform] + m_RayTraceProcedural: int + m_RayTracingAccelStructBuildFlags: int + m_RayTracingAccelStructBuildFlagsOverride: int + m_RayTracingMode: int + m_ReceiveShadows: int + m_ReflectionProbeUsage: int + m_RendererPriority: int + m_RenderingLayerMask: int + m_SmallMeshCulling: int + m_SortingLayer: int + m_SortingLayerID: int + m_SortingOrder: int + m_StaticBatchInfo: StaticBatchInfo + m_StaticBatchRoot: PPtr[Transform] + m_StaticShadowCaster: int + m_Owner: Optional[PPtr[Component]] = None + m_OwningSortingGroup: Optional[PPtr[SortingGroup]] = None + + @unitypy_define class SkinnedMeshRenderer(Renderer): m_AABB: AABB @@ -2326,11 +2446,14 @@ class SkinnedMeshRenderer(Renderer): m_UpdateWhenOffscreen: bool m_BlendShapeWeights: Optional[List[float]] = None m_DynamicOccludee: Optional[int] = None + m_ForceMeshLod: Optional[int] = None m_LightProbeAnchor: Optional[PPtr[Transform]] = None m_LightProbeUsage: Optional[int] = None m_LightProbeVolumeOverride: Optional[PPtr[GameObject]] = None m_LightmapIndexDynamic: Optional[int] = None m_LightmapTilingOffsetDynamic: Optional[Vector4f] = None + m_MaskInteraction: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None m_MotionVectors: Optional[int] = None m_ProbeAnchor: Optional[PPtr[Transform]] = None m_RayTraceProcedural: Optional[int] = None @@ -2382,8 +2505,11 @@ class SpriteMask(Renderer): m_StaticBatchRoot: PPtr[Transform] m_BackSortingLayerID: Optional[int] = None m_DynamicOccludee: Optional[int] = None + m_ForceMeshLod: Optional[int] = None m_FrontSortingLayerID: Optional[int] = None + m_MaskInteraction: Optional[int] = None m_MaskSource: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None m_RayTraceProcedural: Optional[int] = None m_RayTracingAccelStructBuildFlags: Optional[int] = None m_RayTracingAccelStructBuildFlagsOverride: Optional[int] = None @@ -2413,12 +2539,14 @@ class SpriteRenderer(Renderer): m_DynamicOccludee: Optional[int] = None m_FlipX: Optional[bool] = None m_FlipY: Optional[bool] = None + m_ForceMeshLod: Optional[int] = None m_LightProbeAnchor: Optional[PPtr[Transform]] = None m_LightProbeUsage: Optional[int] = None m_LightProbeVolumeOverride: Optional[PPtr[GameObject]] = None m_LightmapIndexDynamic: Optional[int] = None m_LightmapTilingOffsetDynamic: Optional[Vector4f] = None m_MaskInteraction: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None m_MotionVectors: Optional[int] = None m_ProbeAnchor: Optional[PPtr[Transform]] = None m_RayTraceProcedural: Optional[int] = None @@ -2469,6 +2597,8 @@ class SpriteShapeRenderer(Renderer): m_Sprites: List[PPtr[Sprite]] m_StaticBatchInfo: StaticBatchInfo m_StaticBatchRoot: PPtr[Transform] + m_ForceMeshLod: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None m_RayTraceProcedural: Optional[int] = None m_RayTracingAccelStructBuildFlags: Optional[int] = None m_RayTracingAccelStructBuildFlagsOverride: Optional[int] = None @@ -2508,6 +2638,8 @@ class TilemapRenderer(Renderer): m_StaticBatchRoot: PPtr[Transform] m_ChunkCullingBounds: Optional[Vector3f] = None m_DetectChunkCullingBounds: Optional[int] = None + m_ForceMeshLod: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None m_Mode: Optional[int] = None m_RayTraceProcedural: Optional[int] = None m_RayTracingAccelStructBuildFlags: Optional[int] = None @@ -2537,12 +2669,14 @@ class TrailRenderer(Renderer): m_DynamicOccludee: Optional[int] = None m_Emitting: Optional[bool] = None m_EndWidth: Optional[float] = None + m_ForceMeshLod: Optional[int] = None m_LightProbeAnchor: Optional[PPtr[Transform]] = None m_LightProbeUsage: Optional[int] = None m_LightProbeVolumeOverride: Optional[PPtr[GameObject]] = None m_LightmapIndexDynamic: Optional[int] = None m_LightmapTilingOffsetDynamic: Optional[Vector4f] = None m_MaskInteraction: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None m_MotionVectors: Optional[int] = None m_Parameters: Optional[LineParameters] = None m_ProbeAnchor: Optional[PPtr[Transform]] = None @@ -2570,13 +2704,16 @@ class UIRenderer(Renderer): m_CastShadows: Optional[int] = None m_DynamicOccludee: Optional[int] = None m_Enabled: Optional[bool] = None + m_ForceMeshLod: Optional[int] = None m_LightProbeUsage: Optional[int] = None m_LightProbeVolumeOverride: Optional[PPtr[GameObject]] = None m_LightmapIndex: Optional[int] = None m_LightmapIndexDynamic: Optional[int] = None m_LightmapTilingOffset: Optional[Vector4f] = None m_LightmapTilingOffsetDynamic: Optional[Vector4f] = None + m_MaskInteraction: Optional[int] = None m_Materials: Optional[List[PPtr[Material]]] = None + m_MeshLodSelectionBias: Optional[float] = None m_MotionVectors: Optional[int] = None m_ProbeAnchor: Optional[PPtr[Transform]] = None m_RayTraceProcedural: Optional[int] = None @@ -2619,7 +2756,10 @@ class VFXRenderer(Renderer): m_SortingOrder: int m_StaticBatchInfo: StaticBatchInfo m_StaticBatchRoot: PPtr[Transform] + m_ForceMeshLod: Optional[int] = None + m_MaskInteraction: Optional[int] = None m_Materials: Optional[List[PPtr[Material]]] = None + m_MeshLodSelectionBias: Optional[float] = None m_RayTraceProcedural: Optional[int] = None m_RayTracingAccelStructBuildFlags: Optional[int] = None m_RayTracingAccelStructBuildFlagsOverride: Optional[int] = None @@ -2630,22 +2770,24 @@ class VFXRenderer(Renderer): @unitypy_define class Rigidbody(Component): - m_AngularDrag: float m_CollisionDetection: int m_Constraints: int - m_Drag: float m_GameObject: PPtr[GameObject] m_Interpolate: int m_IsKinematic: bool m_Mass: float m_UseGravity: bool + m_AngularDamping: Optional[float] = None + m_AngularDrag: Optional[float] = None m_CenterOfMass: Optional[Vector3f] = None + m_Drag: Optional[float] = None m_ExcludeLayers: Optional[BitField] = None m_ImplicitCom: Optional[bool] = None m_ImplicitTensor: Optional[bool] = None m_IncludeLayers: Optional[BitField] = None m_InertiaRotation: Optional[Quaternionf] = None m_InertiaTensor: Optional[Vector3f] = None + m_LinearDamping: Optional[float] = None @unitypy_define @@ -2786,9 +2928,7 @@ class AnimatorStateMachine(NamedObject): m_Name: str m_ParentStateMachinePosition: Vector3f m_StateMachineBehaviours: List[PPtr[MonoBehaviour]] - m_StateMachineTransitions: List[ - Tuple[PPtr[AnimatorStateMachine], List[PPtr[AnimatorTransition]]] - ] + m_StateMachineTransitions: List[Tuple[PPtr[AnimatorStateMachine], List[PPtr[AnimatorTransition]]]] @unitypy_define @@ -2925,9 +3065,7 @@ class AudioImporter(AssetImporter): m_Normalize: Optional[bool] = None m_OldHashIdentity: Optional[MdFour] = None m_Output: Optional[Union[AudioImporterOutput, Output]] = None - m_PlatformSettingOverrides: Optional[ - Union[List[Tuple[str, SampleSettings]], List[Tuple[int, SampleSettings]]] - ] = None + m_PlatformSettingOverrides: Optional[Union[List[Tuple[int, SampleSettings]], List[Tuple[str, SampleSettings]]]] = None m_PreloadAudioData: Optional[bool] = None m_PreviewData: Optional[PreviewData] = None m_PreviewDataLength: Optional[int] = None @@ -2938,6 +3076,17 @@ class AudioImporter(AssetImporter): m_UserData: Optional[str] = None +@unitypy_define +class BlockShaderImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_EmbedBlocksInGeneratedShader: bool + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UsedFileIDs: List[int] + m_UserData: str + + @unitypy_define class BuildArchiveImporter(AssetImporter): m_AssetBundleName: str @@ -3088,12 +3237,12 @@ class FBXImporter(ModelImporter): normalSmoothAngle: float bakeAxisConversion: Optional[bool] = None blendShapeNormalImportMode: Optional[int] = None + calculateBlendshapeNormalsDeltaFromImportedNormals: Optional[bool] = None + generateMeshLods: Optional[bool] = None generateSecondaryUV: Optional[bool] = None indexFormat: Optional[int] = None keepQuads: Optional[bool] = None - legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: Optional[bool] = ( - None - ) + legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: Optional[bool] = None m_AddHumanoidExtraRootOnlyWhenUsingAvatar: Optional[bool] = None m_AdditionalBone: Optional[bool] = None m_AnimationDoRetargetingWarnings: Optional[bool] = None @@ -3175,6 +3324,8 @@ class FBXImporter(ModelImporter): m_UsedFileIDs: Optional[List[int]] = None m_UserData: Optional[str] = None maxBonesPerVertex: Optional[int] = None + maximumMeshLod: Optional[int] = None + meshLodGenerationFlags: Optional[int] = None meshOptimizationFlags: Optional[int] = None minBoneWeight: Optional[float] = None normalCalculationMode: Optional[int] = None @@ -3216,12 +3367,12 @@ class Mesh3DSImporter(ModelImporter): normalSmoothAngle: float bakeAxisConversion: Optional[bool] = None blendShapeNormalImportMode: Optional[int] = None + calculateBlendshapeNormalsDeltaFromImportedNormals: Optional[bool] = None + generateMeshLods: Optional[bool] = None generateSecondaryUV: Optional[bool] = None indexFormat: Optional[int] = None keepQuads: Optional[bool] = None - legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: Optional[bool] = ( - None - ) + legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: Optional[bool] = None m_AddHumanoidExtraRootOnlyWhenUsingAvatar: Optional[bool] = None m_AdditionalBone: Optional[bool] = None m_AnimationDoRetargetingWarnings: Optional[bool] = None @@ -3303,6 +3454,8 @@ class Mesh3DSImporter(ModelImporter): m_UsedFileIDs: Optional[List[int]] = None m_UserData: Optional[str] = None maxBonesPerVertex: Optional[int] = None + maximumMeshLod: Optional[int] = None + meshLodGenerationFlags: Optional[int] = None meshOptimizationFlags: Optional[int] = None minBoneWeight: Optional[float] = None normalCalculationMode: Optional[int] = None @@ -3388,10 +3541,10 @@ class SketchUpImporter(ModelImporter): weldVertices: bool bakeAxisConversion: Optional[bool] = None blendShapeNormalImportMode: Optional[int] = None + calculateBlendshapeNormalsDeltaFromImportedNormals: Optional[bool] = None + generateMeshLods: Optional[bool] = None indexFormat: Optional[int] = None - legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: Optional[bool] = ( - None - ) + legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: Optional[bool] = None m_AddHumanoidExtraRootOnlyWhenUsingAvatar: Optional[bool] = None m_AutoGenerateAvatarMappingIfUnspecified: Optional[bool] = None m_AutoMapExternalMaterials: Optional[bool] = None @@ -3434,6 +3587,8 @@ class SketchUpImporter(ModelImporter): m_UseSRGBMaterialColor: Optional[bool] = None m_UsedFileIDs: Optional[List[int]] = None maxBonesPerVertex: Optional[int] = None + maximumMeshLod: Optional[int] = None + meshLodGenerationFlags: Optional[int] = None meshOptimizationFlags: Optional[int] = None minBoneWeight: Optional[float] = None normalCalculationMode: Optional[int] = None @@ -3532,10 +3687,7 @@ class PluginImporter(AssetImporter): m_IsPreloaded: bool m_Name: str m_Output: PluginImportOutput - m_PlatformData: Union[ - List[Tuple[str, PlatformSettingsData]], - List[Tuple[Tuple[str, str], PlatformSettingsData]], - ] + m_PlatformData: Union[List[Tuple[Tuple[str, str], PlatformSettingsData]], List[Tuple[str, PlatformSettingsData]]] m_UserData: str m_DefineConstraints: Optional[List[str]] = None m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None @@ -3710,9 +3862,7 @@ class SpriteAtlasImporter(AssetImporter): m_BindAsDefault: Optional[bool] = None m_PackingSettings: Optional[PackingSettings] = None m_PlatformSettings: Optional[List[TextureImporterPlatformSettings]] = None - m_SecondaryTextureSettings: Optional[List[Tuple[str, SecondaryTextureSettings]]] = ( - None - ) + m_SecondaryTextureSettings: Optional[List[Tuple[str, SecondaryTextureSettings]]] = None m_TextureSettings: Optional[TextureSettings] = None m_VariantMultiplier: Optional[float] = None @@ -3812,9 +3962,7 @@ class TextureImporter(AssetImporter): m_Output: Optional[TextureImportOutput] = None m_PSDRemoveMatte: Optional[bool] = None m_PSDShowRemoveMatteOption: Optional[bool] = None - m_PlatformSettings: Optional[ - Union[List[TextureImporterPlatformSettings], List[PlatformSettings]] - ] = None + m_PlatformSettings: Optional[Union[List[PlatformSettings], List[TextureImporterPlatformSettings]]] = None m_PushPullDilation: Optional[int] = None m_RGBM: Optional[int] = None m_RecommendedTextureFormat: Optional[int] = None @@ -3887,10 +4035,7 @@ class VideoClipImporter(AssetImporter): m_Name: str m_Output: VideoClipImporterOutput m_StartFrame: int - m_TargetSettings: Union[ - List[Tuple[int, VideoClipImporterTargetSettings]], - List[Tuple[str, VideoClipImporterTargetSettings]], - ] + m_TargetSettings: Union[List[Tuple[int, VideoClipImporterTargetSettings]], List[Tuple[str, VideoClipImporterTargetSettings]]] m_UserData: str m_AudioImportMode: Optional[int] = None m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None @@ -3919,6 +4064,7 @@ class VisualEffectImporter(AssetImporter): m_Name: str m_UserData: str m_Template: Optional[VFXTemplate] = None + m_UseAsTemplate: Optional[bool] = None m_UsedFileIDs: Optional[List[int]] = None @@ -4102,6 +4248,39 @@ class BillboardAsset(NamedObject): rotated: Optional[List[int]] = None +@unitypy_define +class BlobObject(NamedObject): + m_BlobData: List[int] + m_BlobTypeHash: int + m_Name: str + m_NestedBlobObjectReferenceOffsets: List[int] + m_NestedBlobObjectReferences: List[PPtr[BlobObject]] + + +@unitypy_define +class BlockShaderContainer(NamedObject): + blob: List[int] + dependencies: List[PPtr[BlockShaderContainer]] + generatedPaths: List[str] + guid: GUID + m_Name: str + state: int + + +@unitypy_define +class BlockShaderErrors(NamedObject): + errors: List[Error] + filePath: str + m_Name: str + + +@unitypy_define +class BlockShaderSyntaxTree(NamedObject): + filePath: str + m_Name: str + source: str + + @unitypy_define class BuildReport(NamedObject): m_Appendices: List[PPtr[Object]] @@ -4109,6 +4288,7 @@ class BuildReport(NamedObject): m_Files: List[BuildReportFile] m_Name: str m_Summary: BuildSummary + m_RootAssetPaths: Optional[List[str]] = None @unitypy_define @@ -4131,14 +4311,21 @@ class ComputeShader(NamedObject): m_Name: str constantBuffers: Optional[List[ComputeShaderCB]] = None kernels: Optional[List[ComputeShaderKernel]] = None - variants: Optional[ - Union[List[ComputeShaderVariant], List[ComputeShaderPlatformVariant]] - ] = None + variants: Optional[Union[List[ComputeShaderPlatformVariant], List[ComputeShaderVariant]]] = None + + +@unitypy_define +class D3D12DeviceFilterLists(NamedObject): + m_AllowFilterList: List[D3D12DeviceFilterData] + m_DenyFilterList: List[D3D12DeviceFilterData] + m_GraphicsJobsFilterList: List[D3D12GraphicsJobsDeviceFilterData] + m_Name: str @unitypy_define class DefaultAsset(NamedObject): m_Name: str + m_ErrorCode: Optional[int] = None m_IsWarning: Optional[bool] = None m_Message: Optional[str] = None @@ -4151,11 +4338,13 @@ class BrokenPrefabAsset(DefaultAsset): m_IsWarning: bool m_Message: str m_Name: str + m_ErrorCode: Optional[int] = None @unitypy_define class SceneAsset(DefaultAsset): m_Name: str + m_ErrorCode: Optional[int] = None m_IsWarning: Optional[bool] = None m_Message: Optional[str] = None @@ -4182,7 +4371,7 @@ class Font(NamedObject): m_ConvertCase: int m_DefaultMaterial: PPtr[Material] m_DefaultStyle: int - m_FontData: List[str] + m_FontData: List[int] m_FontNames: List[str] m_FontSize: float m_KerningValues: List[Tuple[Tuple[int, int], float]] @@ -4377,7 +4566,7 @@ class Material(NamedObject): m_EnableInstancingVariants: Optional[bool] = None m_InvalidKeywords: Optional[List[str]] = None m_LightmapFlags: Optional[int] = None - m_ShaderKeywords: Optional[Union[str, List[str]]] = None + m_ShaderKeywords: Optional[Union[List[str], str]] = None m_ValidKeywords: Optional[List[str]] = None stringTagMap: Optional[List[Tuple[str, str]]] = None @@ -4404,7 +4593,7 @@ class ProceduralMaterial(Material): m_LoadingBehavior: Optional[int] = None m_MaximumSize: Optional[int] = None m_PrototypeName: Optional[str] = None - m_ShaderKeywords: Optional[Union[str, List[str]]] = None + m_ShaderKeywords: Optional[Union[List[str], str]] = None m_SubstancePackage: Optional[PPtr[SubstanceArchive]] = None m_Textures: Optional[List[PPtr[ProceduralTexture]]] = None m_ValidKeywords: Optional[List[str]] = None @@ -4434,6 +4623,7 @@ class Mesh(NamedObject): m_IsReadable: Optional[bool] = None m_KeepIndices: Optional[bool] = None m_KeepVertices: Optional[bool] = None + m_MeshLodInfo: Optional[MeshLodInfo] = None m_MeshMetrics_0_: Optional[float] = None m_MeshMetrics_1_: Optional[float] = None m_Normals: Optional[List[Vector3f]] = None @@ -4508,7 +4698,7 @@ class PreviewAnimationClip(AnimationClip): @unitypy_define class BlendTree(Motion): - m_Childs: Union[List[Child], List[ChildMotion]] + m_Childs: Union[List[ChildMotion], List[Child]] m_MaxThreshold: float m_MinThreshold: float m_Name: str @@ -4594,7 +4784,7 @@ class Preset(NamedObject): class RayTracingShader(NamedObject): m_MaxRecursionDepth: int m_Name: str - variants: List[RayTracingShaderVariant] + variants: Union[List[RayTracingShaderPlatformVariant], List[RayTracingShaderVariant]] m_EnableRayPayloadSizeChecks: Optional[bool] = None @@ -4624,10 +4814,9 @@ class AnimatorController(RuntimeAnimatorController): m_ControllerSize: int m_Name: str m_TOS: List[Tuple[int, str]] + m_EvaluateTransitionsOnStart: Optional[bool] = None m_MultiThreadedStateMachine: Optional[bool] = None - m_StateMachineBehaviourVectorDescription: Optional[ - StateMachineBehaviourVectorDescription - ] = None + m_StateMachineBehaviourVectorDescription: Optional[StateMachineBehaviourVectorDescription] = None m_StateMachineBehaviours: Optional[List[PPtr[MonoBehaviour]]] = None @@ -4642,10 +4831,11 @@ class AnimatorOverrideController(RuntimeAnimatorController): 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 + compressedLengths: Optional[Union[List[List[int]], List[int]]] = None + decompressedLengths: Optional[Union[List[List[int]], List[int]]] = None decompressedSize: Optional[int] = None m_AssetGUID: Optional[GUID] = None + m_AssetLocalIdentifierInFile: Optional[int] = None m_Dependencies: Optional[List[PPtr[Shader]]] = None m_NonModifiableTextures: Optional[List[Tuple[str, PPtr[Texture]]]] = None m_ParsedForm: Optional[SerializedShader] = None @@ -4653,7 +4843,7 @@ class Shader(NamedObject): 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 + offsets: Optional[Union[List[List[int]], List[int]]] = None platforms: Optional[List[int]] = None stageCounts: Optional[List[int]] = None @@ -4700,11 +4890,12 @@ class SpriteAtlas(NamedObject): m_PackedSprites: List[PPtr[Sprite]] m_RenderDataMap: List[Tuple[Tuple[GUID, int], SpriteAtlasData]] m_Tag: str + m_Guid: Optional[GUID] = None @unitypy_define class SpriteAtlasAsset(NamedObject): - m_ImporterData: Union[SpriteAtlasEditorData, SpriteAtlasAssetData] + m_ImporterData: Union[SpriteAtlasAssetData, SpriteAtlasEditorData] m_IsVariant: bool m_MasterAtlas: PPtr[SpriteAtlas] m_Name: str @@ -5123,12 +5314,20 @@ class VisualEffectSubgraphOperator(VisualEffectSubgraph): @unitypy_define class VisualEffectResource(NamedObject): m_Graph: PPtr[MonoBehaviour] - m_Infos: Union[VisualEffectSettings, VisualEffectInfo] + m_Infos: Union[VisualEffectInfo, VisualEffectSettings] m_Name: str m_ShaderSources: Optional[List[VFXShaderSourceDesc]] = None m_Systems: Optional[List[VFXEditorSystemDesc]] = None +@unitypy_define +class VulkanDeviceFilterLists(NamedObject): + m_GfxJobFilterList: List[VulkanGraphicsJobsDeviceFilterData] + m_Name: str + m_VulkanAllowFilterList: List[AndroidDeviceFilterData] + m_VulkanDenyFilterList: List[AndroidDeviceFilterData] + + @unitypy_define class EditorExtensionImpl(Object): gFlattenedTypeTree: Optional[List[int]] = None @@ -5144,6 +5343,7 @@ class EditorSettings(Object): m_AssetPipelineMode: Optional[int] = None m_AsyncShaderCompilation: Optional[bool] = None m_Bc7TextureCompressor: Optional[int] = None + m_BlockShaders: Optional[bool] = None m_CacheServerDownloadBatchSize: Optional[int] = None m_CacheServerEnableAuth: Optional[bool] = None m_CacheServerEnableDownload: Optional[bool] = None @@ -5168,9 +5368,11 @@ class EditorSettings(Object): m_EtcTextureCompressorBehavior: Optional[int] = None m_EtcTextureFastCompressor: Optional[int] = None m_EtcTextureNormalCompressor: Optional[int] = None - m_ExternalVersionControlSupport: Optional[Union[str, int]] = None + m_ExternalVersionControlSupport: Optional[Union[int, str]] = None + m_ForceAssetUnloadAndGCOnSceneLoad: Optional[bool] = None m_GameObjectNamingDigits: Optional[int] = None m_GameObjectNamingScheme: Optional[int] = None + m_HideBuildProfileClassicPlatforms: Optional[bool] = None m_InspectorUseIMGUIDefaultInspector: Optional[bool] = None m_LineEndingsForNewScripts: Optional[int] = None m_PrefabModeAllowAutoSave: Optional[bool] = None @@ -5183,10 +5385,12 @@ class EditorSettings(Object): m_RefreshImportMode: Optional[int] = None m_SerializationMode: Optional[int] = None m_SerializeInlineMappingsOnOneLine: Optional[bool] = None + m_ShadowmaskStitching: Optional[bool] = None m_ShowLightmapResolutionOverlay: Optional[bool] = None m_SpritePackerCacheSize: Optional[int] = None m_SpritePackerMode: Optional[int] = None m_SpritePackerPaddingPower: Optional[int] = None + m_UnlockBlockShaders: Optional[bool] = None m_UseLegacyProbeSampleCount: Optional[bool] = None m_UserGeneratedProjectSuffix: Optional[str] = None m_WebSecurityEmulationEnabled: Optional[int] = None @@ -5206,6 +5410,7 @@ class EditorUserBuildSettings(Object): m_SelectedBuildTargetGroup: int m_SelectedStandaloneTarget: int m_ActiveBuildPlatformGroupName: Optional[str] = None + m_ActiveBuildProfile: Optional[PPtr[MonoBehaviour]] = None m_ActiveBuildTargetGroup: Optional[int] = None m_ActivePlatformGuid: Optional[GUID] = None m_ActiveProfilePath: Optional[str] = None @@ -5220,7 +5425,9 @@ class EditorUserBuildSettings(Object): m_AndroidReleaseMinification: Optional[int] = None m_AndroidUseLegacySdkTools: Optional[bool] = None m_BuildAppBundle: Optional[bool] = None + m_BuildOutputToBuildMetadataMap: Optional[List[Tuple[str, str]]] = None m_BuildScriptsOnly: Optional[bool] = None + m_BuildWithCodeCoverage: Optional[bool] = None m_BuildWithDeepProfilingSupport: Optional[bool] = None m_CompressFilesInPackage: Optional[bool] = None m_CompressWithPsArc: Optional[bool] = None @@ -5233,6 +5440,7 @@ class EditorUserBuildSettings(Object): m_EnableHostIOForSwitch: Optional[bool] = None m_EnableMemoryTrackerForSwitch: Optional[bool] = None m_EnableRomCompressionForSwitch: Optional[bool] = None + m_EnableUnpublishableErrorsForSwitch: Optional[bool] = None m_ExplicitArrayBoundsChecks: Optional[bool] = None m_ExplicitDivideByZeroChecks: Optional[bool] = None m_ExplicitNullChecks: Optional[bool] = None @@ -5248,6 +5456,7 @@ class EditorUserBuildSettings(Object): m_Il2CppCodeGeneration: Optional[int] = None m_MovePackageToDiscOuterEdge: Optional[bool] = None m_NVNAftermath: Optional[bool] = None + m_NVNAftermathLevel: Optional[int] = None m_NVNDrawValidation: Optional[bool] = None m_NVNDrawValidationHeavy: Optional[bool] = None m_NVNDrawValidationLight: Optional[bool] = None @@ -5276,6 +5485,7 @@ class EditorUserBuildSettings(Object): m_SelectedBuildPlatformGroupName: Optional[str] = None m_SelectedBuildTarget: Optional[int] = None m_SelectedCompressionType: Optional[List[Tuple[str, int]]] = None + m_SelectedDiagnosticSetting: Optional[List[Tuple[str, int]]] = None m_SelectedEmbeddedLinuxArchitecture: Optional[int] = None m_SelectedFacebookTarget: Optional[int] = None m_SelectedIOSBuildType: Optional[int] = None @@ -5405,7 +5615,10 @@ class AudioManager(GlobalGameManager): m_DSPBufferSize: int m_Volume: float m_AmbisonicDecoderPlugin: Optional[str] = None + m_AudioFoundation: Optional[int] = None m_DisableAudio: Optional[bool] = None + m_OutputChannelLayout: Optional[int] = None + m_OutputSamplingRate: Optional[int] = None m_RealVoiceCount: Optional[int] = None m_RequestedDSPBufferSize: Optional[int] = None m_SampleRate: Optional[int] = None @@ -5424,7 +5637,7 @@ class BuildSettings(GlobalGameManager): hasShadows: bool isEducationalBuild: bool m_Version: str - buildGUID: Optional[Union[str, GUID]] = None + buildGUID: Optional[Union[GUID, str]] = None buildTags: Optional[List[str]] = None enableMultipleDisplays: Optional[bool] = None enabledVRDevices: Optional[List[str]] = None @@ -5443,9 +5656,7 @@ class BuildSettings(GlobalGameManager): m_AuthToken: Optional[str] = None m_GraphicsAPIs: Optional[List[int]] = None preloadedPlugins: Optional[List[str]] = None - runtimeClassHashes: Optional[ - Union[List[Tuple[int, int]], List[Tuple[int, Hash128]]] - ] = None + runtimeClassHashes: Optional[Union[List[Tuple[int, Hash128]], List[Tuple[int, int]]]] = None scenes: Optional[List[str]] = None scriptHashes: Optional[List[Tuple[Hash128, Hash128]]] = None usesOnMouseEvents: Optional[bool] = None @@ -5588,6 +5799,7 @@ class Physics2DSettings(GlobalGameManager): m_MaxTranslationSpeed: Optional[float] = None m_MinPenetrationForPenalty: Optional[float] = None m_MinSubStepFPS: Optional[float] = None + m_PhysicsLowLevelSettings: Optional[PPtr[Object]] = None m_QueriesHitTriggers: Optional[bool] = None m_QueriesStartInColliders: Optional[bool] = None m_RaycastsHitTriggers: Optional[bool] = None @@ -5629,14 +5841,19 @@ class PhysicsManager(GlobalGameManager): m_EnableUnifiedHeightmaps: Optional[bool] = None m_FastMotionThreshold: Optional[float] = None m_FrictionType: Optional[int] = None + m_GenerateOnTriggerStayEvents: Optional[bool] = None m_ImprovedPatchFriction: Optional[bool] = None + m_IncrementalStaticBroadphase: Optional[bool] = None m_InvokeCollisionCallbacks: Optional[bool] = None + m_LogVerbosity: Optional[int] = None m_MaxAngularVelocity: Optional[float] = None m_MinPenetrationForPenalty: Optional[float] = None m_QueriesHitBackfaces: Optional[bool] = None m_QueriesHitTriggers: Optional[bool] = None m_RaycastsHitTriggers: Optional[bool] = None + m_ReleaseSceneBuffers: Optional[bool] = None m_ReuseCollisionCallbacks: Optional[bool] = None + m_SceneBuffersReleaseInterval: Optional[int] = None m_ScratchBufferChunkCount: Optional[int] = None m_SimulationMode: Optional[int] = None m_SleepAngularVelocity: Optional[float] = None @@ -5679,13 +5896,16 @@ class PlayerSettings(GlobalGameManager): Prepare_IOS_For_Recording: Optional[bool] = None accelerometerFrequency: Optional[int] = None activeInputHandler: Optional[int] = None + adjustIOSFPSUsingThermalState: Optional[bool] = None allowFullscreenSwitch: Optional[bool] = None allowHDRDisplaySupport: Optional[bool] = None + allowedHttpConnections: Optional[int] = None androidApplicationEntry: Optional[int] = None androidAutoRotationBehavior: Optional[int] = None androidBlitType: Optional[int] = None androidDefaultWindowHeight: Optional[int] = None androidDefaultWindowWidth: Optional[int] = None + androidDisplayOptions: Optional[int] = None androidFullscreenMode: Optional[int] = None androidMaxAspectRatio: Optional[float] = None androidMinAspectRatio: Optional[float] = None @@ -5693,14 +5913,18 @@ class PlayerSettings(GlobalGameManager): androidMinimumWindowWidth: Optional[int] = None androidPredictiveBackSupport: Optional[bool] = None androidRenderOutsideSafeArea: Optional[bool] = None + androidRequestedVisibleInsets: Optional[int] = None androidResizableWindow: Optional[bool] = None androidResizeableActivity: Optional[bool] = None androidShowActivityIndicatorOnLoading: Optional[int] = None androidStartInFullscreen: Optional[bool] = None androidSupportedAspectRatio: Optional[int] = None + androidSystemBarsBehavior: Optional[int] = None androidUseSwappy: Optional[bool] = None androidVulkanAllowFilterList: Optional[List[AndroidDeviceFilterData]] = None androidVulkanDenyFilterList: Optional[List[AndroidDeviceFilterData]] = None + androidVulkanDeviceFilterListAsset: Optional[PPtr[VulkanDeviceFilterLists]] = None + audioSpatialExperience: Optional[int] = None bakeCollisionMeshes: Optional[bool] = None bundleIdentifier: Optional[str] = None bundleVersion: Optional[str] = None @@ -5711,6 +5935,7 @@ class PlayerSettings(GlobalGameManager): cursorHotspot: Optional[Vector2f] = None d3d11ForceExclusiveMode: Optional[bool] = None d3d11FullscreenMode: Optional[int] = None + d3d12DeviceFilterListAsset: Optional[PPtr[D3D12DeviceFilterLists]] = None d3d9FullscreenMode: Optional[int] = None debugUnloadMode: Optional[int] = None dedicatedServerOptimizations: Optional[bool] = None @@ -5773,6 +5998,7 @@ class PlayerSettings(GlobalGameManager): m_SplashScreenLogos: Optional[List[SplashScreenLogo]] = None m_SplashScreenOverlayOpacity: Optional[float] = None m_SplashScreenStyle: Optional[int] = None + m_SpriteBatchMaxVertexCount: Optional[int] = None m_SpriteBatchVertexThreshold: Optional[int] = None m_StackTraceTypes: Optional[List[int]] = None m_StereoRenderingPath: Optional[int] = None @@ -5821,6 +6047,7 @@ class PlayerSettings(GlobalGameManager): submitAnalytics: Optional[bool] = None switchAllowGpuScratchShrinking: Optional[bool] = None switchGpuScratchPoolGranularity: Optional[int] = None + switchGraphicsJobsSyncAfterKick: Optional[bool] = None switchMaxWorkerMultiple: Optional[int] = None switchNVNDefaultPoolsGranularity: Optional[int] = None switchNVNGraphicsFirmwareMemory: Optional[int] = None @@ -5836,6 +6063,8 @@ class PlayerSettings(GlobalGameManager): targetPixelDensity: Optional[int] = None targetPlatform: Optional[int] = None targetResolution: Optional[int] = None + thermalStateCriticalIOSFPS: Optional[int] = None + thermalStateSeriousIOSFPS: Optional[int] = None tizenShowActivityIndicatorOnLoading: Optional[int] = None tvOSBundleVersion: Optional[str] = None uiUse16BitDepthBuffer: Optional[bool] = None @@ -6021,6 +6250,7 @@ class UnityConnectSettings(GlobalGameManager): UnityAnalyticsSettings: UnityAnalyticsSettings UnityPurchasingSettings: UnityPurchasingSettings CrashReportingSettings: Optional[CrashReportingSettings] = None + InsightsSettings: Optional[InsightsSettings] = None PerformanceReportingSettings: Optional[PerformanceReportingSettings] = None UnityAdsSettings: Optional[UnityAdsSettings] = None m_ConfigUrl: Optional[str] = None @@ -6047,6 +6277,7 @@ class VFXManager(GlobalGameManager): m_EmptyShader: Optional[PPtr[Shader]] = None m_MaxCapacity: Optional[int] = None m_MaxScrubTime: Optional[float] = None + m_PrefixSumShader: Optional[PPtr[ComputeShader]] = None m_RuntimeResources: Optional[PPtr[MonoBehaviour]] = None m_RuntimeVersion: Optional[int] = None m_StripUpdateShader: Optional[PPtr[ComputeShader]] = None @@ -6066,6 +6297,7 @@ class HaloManager(LevelGameManager): class LightmapSettings(LevelGameManager): m_Lightmaps: List[LightmapData] m_LightmapsMode: int + m_BakeOnSceneLoad: Optional[int] = None m_BakedColorSpace: Optional[int] = None m_EnlightenSceneMapping: Optional[EnlightenSceneMapping] = None m_GISettings: Optional[GISettings] = None @@ -6453,9 +6685,7 @@ class Asset: type: int assetBundleIndex: Optional[int] = None digest: Optional[MdFour] = None - guidOfPathLocationDependencies: Optional[ - Union[List[Tuple[str, GUID]], List[Tuple[GUID, str]]] - ] = None + guidOfPathLocationDependencies: Optional[Union[List[Tuple[GUID, str]], List[Tuple[str, GUID]]]] = None hash: Optional[Union[Hash128, MdFour]] = None hashOfImportedAssetDependencies: Optional[List[GUID]] = None hashOfSourceAssetDependencies: Optional[List[GUID]] = None @@ -6538,6 +6768,15 @@ class AssetLabels: m_Labels: List[str] +@unitypy_define +class AssetStats: + objectCount: int + resourceCount: int + size: int + sourceAssetGUID: GUID + sourceAssetPath: str + + @unitypy_define class AssetTimeStamp: metaModificationDate_0_: int @@ -6556,6 +6795,10 @@ class AttachmentIndexArray: class AttachmentInfo: format: int needsResolve: bool + canMultiview: Optional[bool] = None + loadAction: Optional[int] = None + sampleCount: Optional[int] = None + storeAction: Optional[int] = None @unitypy_define @@ -6574,12 +6817,12 @@ class AudioMixerConstant: exposedParameterIndices: List[int] exposedParameterNames: List[int] groupGUIDs: List[GUID] - groupNameBuffer: List[str] + groupNameBuffer: List[int] groups: List[GroupConstant] numSideChainBuffers: int - pluginEffectNameBuffer: List[str] + pluginEffectNameBuffer: List[int] snapshotGUIDs: List[GUID] - snapshotNameBuffer: List[str] + snapshotNameBuffer: List[int] snapshots: List[SnapshotConstant] groupConnections: Optional[List[GroupConnection]] = None @@ -6851,6 +7094,7 @@ class BuildReportFile: path: str role: str totalSize: int + flags: Optional[int] = None @unitypy_define @@ -6894,10 +7138,13 @@ class BuildSummary: totalErrors: int totalSize: int totalWarnings: int + buildContentOptions: Optional[int] = None buildGUID: Optional[GUID] = None + buildManifestHash: Optional[Hash128] = None buildResult: Optional[int] = None buildStartTime: Optional[DateTime] = None buildType: Optional[int] = None + dataPath: Optional[str] = None multiProcessEnabled: Optional[bool] = None name: Optional[str] = None platformGroupName: Optional[str] = None @@ -7057,8 +7304,8 @@ class Clip: @unitypy_define class ClipAnimationInfo: - firstFrame: Union[int, float] - lastFrame: Union[int, float] + firstFrame: Union[float, int] + lastFrame: Union[float, int] loop: bool name: str wrapMode: int @@ -7324,6 +7571,7 @@ class ComputeShaderResource: name: Union[FastPropertyName, str] counter: Optional[ComputeBufferCounter] = None generatedName: Optional[Union[FastPropertyName, str]] = None + resType: Optional[int] = None samplerBindPoint: Optional[int] = None secondaryBindPoint: Optional[int] = None texDimension: Optional[int] = None @@ -7392,8 +7640,9 @@ class ControllerConstant: @unitypy_define class CrashReportingSettings: - m_Enabled: bool m_EventUrl: str + m_EnableCloudDiagnosticsReporting: Optional[bool] = None + m_Enabled: Optional[bool] = None m_LogBufferSize: Optional[int] = None m_NativeEventUrl: Optional[str] = None @@ -7417,6 +7666,27 @@ class CustomDataModule: vectorComponentCount1: int +@unitypy_define +class D3D12DeviceFilterData: + deviceName: str + deviceType: int + driverVersion: str + driverVersionComparator: int + featureLevel: str + featureLevelComparator: int + graphicsMemory: str + graphicsMemoryComparator: int + processorCount: str + processorCountComparator: int + vendorName: str + + +@unitypy_define +class D3D12GraphicsJobsDeviceFilterData: + filter: D3D12DeviceFilterData + preferredMode: int + + @unitypy_define class DataTemplate(NamedObject): m_Father: PPtr[DataTemplate] @@ -7618,6 +7888,20 @@ class EnlightenTerrainChunksInformation: numChunksInY: int +@unitypy_define +class EntityId: + pass + + +@unitypy_define +class Error: + filePath: str + message: str + severity: int + startChar: int + startLine: int + + @unitypy_define class ExpandedData: m_ClassID: int @@ -7897,6 +8181,11 @@ class GraphicsStateInfo: userBackface: bool vertexLayout: int wireframe: bool + baseShadingRate: Optional[int] = None + nonfilteringSamplerBindings: Optional[int] = None + shadingRateCombinerFragment: Optional[int] = None + shadingRateCombinerPrimitive: Optional[int] = None + unfilterableTextureBindings: Optional[int] = None @unitypy_define @@ -8107,7 +8396,7 @@ class HumanPose: m_LookAtWeight: float4 m_RightHandPose: HandPose m_RootX: xform - m_TDoFArray: Optional[Union[List[float4], List[float3]]] = None + m_TDoFArray: Optional[Union[List[float3], List[float4]]] = None @unitypy_define @@ -8193,6 +8482,14 @@ class InputImportSettings: wrapMode: Optional[int] = None +@unitypy_define +class InsightsSettings: + m_Enabled: bool + m_EngineDiagnosticsEnabled: bool + m_EventUrl: str + m_StackTraceUrl: str + + @unitypy_define class IntPoint: X: int @@ -8283,12 +8580,12 @@ class JointTranslationLimits2D: @unitypy_define class Keyframe: - inSlope: Union[Quaternionf, float, Vector3f] - outSlope: Union[Quaternionf, float, Vector3f] + inSlope: Union[Quaternionf, Vector3f, float] + outSlope: Union[Quaternionf, Vector3f, float] time: float - value: Union[Quaternionf, float, Vector3f] - inWeight: Optional[Union[Quaternionf, float, Vector3f]] = None - outWeight: Optional[Union[Quaternionf, float, Vector3f]] = None + value: Union[Quaternionf, Vector3f, float] + inWeight: Optional[Union[Quaternionf, Vector3f, float]] = None + outWeight: Optional[Union[Quaternionf, Vector3f, float]] = None weightedMode: Optional[int] = None @@ -8488,6 +8785,12 @@ class LineParameters: widthMultiplier: Optional[float] = None +@unitypy_define +class LodSelectionCurve: + m_LodBias: float + m_LodSlope: float + + @unitypy_define class Lumin: depthFormat: int @@ -8561,6 +8864,24 @@ class MeshBlendShapeVertex: vertex: Vector3f +@unitypy_define +class MeshLodInfo: + m_LodSelectionCurve: LodSelectionCurve + m_NumLevels: int + m_SubMeshes: List[MeshLodSubMesh] + + +@unitypy_define +class MeshLodRange: + m_IndexCount: int + m_IndexStart: int + + +@unitypy_define +class MeshLodSubMesh: + m_Levels: List[MeshLodRange] + + @unitypy_define class MinMaxAABB: m_Max: Vector3f @@ -8579,9 +8900,9 @@ class MinMaxCurve: @unitypy_define class MinMaxGradient: maxColor: ColorRGBA - maxGradient: Union[GradientNEW, Gradient] + maxGradient: Union[Gradient, GradientNEW] minColor: ColorRGBA - minGradient: Union[GradientNEW, Gradient] + minGradient: Union[Gradient, GradientNEW] minMaxState: int @@ -8809,29 +9130,7 @@ class Oculus: @unitypy_define class OffsetPtr: - data: Union[ - SkeletonMask, - SkeletonPose, - Blend2dDataConstant, - Blend1dDataConstant, - BlendTreeConstant, - Skeleton, - Clip, - SelectorTransitionConstant, - LayerConstant, - ValueArray, - SelectorStateConstant, - ConditionConstant, - Human, - HumanLayerConstant, - Hand, - StateConstant, - StateMachineConstant, - BlendTreeNodeConstant, - BlendDirectDataConstant, - ValueArrayConstant, - TransitionConstant, - ] + data: Union[Blend1dDataConstant, Blend2dDataConstant, BlendDirectDataConstant, BlendTreeConstant, BlendTreeNodeConstant, Clip, ConditionConstant, Hand, Human, HumanLayerConstant, LayerConstant, SelectorStateConstant, SelectorTransitionConstant, Skeleton, SkeletonMask, SkeletonPose, StateConstant, StateMachineConstant, TransitionConstant, ValueArray, ValueArrayConstant] @unitypy_define @@ -9042,7 +9341,7 @@ class Polygon2D(ABC): @unitypy_define class PrefabModification: m_Modifications: List[PropertyModification] - m_RemovedComponents: Union[List[PPtr[Object]], List[PPtr[Component]]] + m_RemovedComponents: Union[List[PPtr[Component]], List[PPtr[Object]]] m_TransformParent: PPtr[Transform] m_AddedComponents: Optional[List[AddedComponent]] = None m_AddedGameObjects: Optional[List[AddedGameObject]] = None @@ -9133,6 +9432,7 @@ class QualitySetting: globalTextureMipmapLimit: Optional[int] = None lodBias: Optional[float] = None maximumLODLevel: Optional[int] = None + meshLodThreshold: Optional[float] = None name: Optional[str] = None particleRaycastBudget: Optional[int] = None realtimeGICPUUsage: Optional[int] = None @@ -9215,6 +9515,17 @@ class RayTracingShaderParam: type: Optional[int] = None +@unitypy_define +class RayTracingShaderPlatformVariant: + dynamicKeywords: List[str] + editorOnlyVariant: bool + globalKeywords: List[str] + localKeywords: List[str] + targetRenderer: int + uniqueVariants: List[RayTracingShaderReflectionData] + variantIndices: List[Tuple[str, int]] + + @unitypy_define class RayTracingShaderReflectionData: code: List[int] @@ -9222,6 +9533,7 @@ class RayTracingShaderReflectionData: globalResources: RayTracingShaderResources hasErrors: bool localResources: RayTracingShaderResources + codeHash: Optional[int] = None precompiled: Optional[List[int]] = None requirements: Optional[int] = None @@ -9251,6 +9563,7 @@ class RayTracingShaderResources: class RayTracingShaderVariant: resourceReflectionData: RayTracingShaderReflectionData targetRenderer: int + editorOnlyVariant: Optional[bool] = None @unitypy_define @@ -9276,6 +9589,8 @@ class RenderPassInfo: shadingRateIndex: int subPassCount: int subPasses: List[SubPassDescriptor] + foveationImageIndex: Optional[int] = None + hasEyeTexture: Optional[bool] = None @unitypy_define @@ -9717,7 +10032,7 @@ class SceneDataContainer: @unitypy_define class SceneIdentifier: guid: GUID - handle: int + handle: Union[UnitySceneHandle, int] @unitypy_define @@ -9858,9 +10173,7 @@ class SerializedShader: m_Name: str m_PropInfo: SerializedProperties m_SubShaders: List[SerializedSubShader] - m_CustomEditorForRenderPipelines: Optional[ - List[SerializedCustomEditorForRenderPipeline] - ] = None + m_CustomEditorForRenderPipelines: Optional[List[SerializedCustomEditorForRenderPipeline]] = None m_KeywordFlags: Optional[List[int]] = None m_KeywordNames: Optional[List[str]] = None @@ -10534,9 +10847,7 @@ class SpriteAtlasEditorData: platformSettings: List[TextureImporterPlatformSettings] textureSettings: TextureSettings variantMultiplier: float - secondaryTextureSettings: Optional[List[Tuple[str, SecondaryTextureSettings]]] = ( - None - ) + secondaryTextureSettings: Optional[List[Tuple[str, SecondaryTextureSettings]]] = None storedHash: Optional[Hash128] = None totalSpriteSurfaceArea: Optional[int] = None @@ -10696,9 +11007,7 @@ class StateMachine(NamedObject): m_OrderedTransitions: List[Tuple[PPtr[State], List[PPtr[Transition]]]] m_ParentStateMachinePosition: Vector3f m_States: List[PPtr[State]] - m_LocalTransitions: Optional[List[Tuple[PPtr[State], List[PPtr[Transition]]]]] = ( - None - ) + m_LocalTransitions: Optional[List[Tuple[PPtr[State], List[PPtr[Transition]]]]] = None @unitypy_define @@ -10712,6 +11021,7 @@ class StateMachineConstant: m_AnyStateTransitionConstantArray: List[OffsetPtr] m_DefaultState: int m_StateConstantArray: List[OffsetPtr] + m_EvaluateTransitionsOnStart: Optional[bool] = None m_MotionSetCount: Optional[int] = None m_SelectorStateConstantArray: Optional[List[OffsetPtr]] = None m_SynchronizedLayerCount: Optional[int] = None @@ -10993,7 +11303,7 @@ class TileAnimationData: @unitypy_define class TilemapRefCountedData: - m_Data: Union[PPtr[GameObject], ColorRGBA, PPtr[Object], Matrix4x4f, PPtr[Sprite]] + m_Data: Union[ColorRGBA, Matrix4x4f, PPtr[GameObject], PPtr[Object], PPtr[Sprite]] m_RefCount: int @@ -11095,6 +11405,14 @@ class TriggerModule: primitives: Optional[List[PPtr[Component]]] = None +@unitypy_define +class TypeStats: + classID: int + objectCount: int + resourceCount: int + size: int + + @unitypy_define class UAVParameter: m_Index: int @@ -11145,8 +11463,8 @@ class UnityAdsSettings(GlobalGameManager): @unitypy_define class UnityAnalyticsSettings: m_Enabled: bool - m_InitializeOnStartup: bool m_TestMode: bool + m_InitializeOnStartup: Optional[bool] = None m_PackageRequiringCoreStatsPresent: Optional[bool] = None m_TestConfigUrl: Optional[str] = None m_TestEventUrl: Optional[str] = None @@ -11154,13 +11472,9 @@ class UnityAnalyticsSettings: @unitypy_define class UnityPropertySheet: - m_Colors: Union[ - List[Tuple[FastPropertyName, ColorRGBA]], List[Tuple[str, ColorRGBA]] - ] + m_Colors: Union[List[Tuple[FastPropertyName, ColorRGBA]], List[Tuple[str, ColorRGBA]]] m_Floats: Union[List[Tuple[FastPropertyName, float]], List[Tuple[str, float]]] - m_TexEnvs: Union[ - List[Tuple[FastPropertyName, UnityTexEnv]], List[Tuple[str, UnityTexEnv]] - ] + m_TexEnvs: Union[List[Tuple[FastPropertyName, UnityTexEnv]], List[Tuple[str, UnityTexEnv]]] m_Ints: Optional[List[Tuple[str, int]]] = None @@ -11170,6 +11484,11 @@ class UnityPurchasingSettings: m_TestMode: bool +@unitypy_define +class UnitySceneHandle: + value: EntityId + + @unitypy_define class UnityTexEnv: m_Offset: Vector2f @@ -11226,37 +11545,13 @@ class VFXEditorTaskDesc: class VFXEntryExposed: m_Name: str m_Overridden: bool - m_Value: Union[ - Gradient, - Vector3f, - float, - PPtr[NamedObject], - Vector4f, - AnimationCurve, - PPtr[Object], - int, - Vector2f, - bool, - Matrix4x4f, - ] + m_Value: Union[AnimationCurve, Gradient, Matrix4x4f, PPtr[NamedObject], PPtr[Object], Vector2f, Vector3f, Vector4f, bool, float, int] @unitypy_define class VFXEntryExpressionValue: m_ExpressionIndex: int - m_Value: Union[ - Gradient, - Vector3f, - float, - PPtr[NamedObject], - Vector4f, - AnimationCurve, - PPtr[Object], - int, - Vector2f, - bool, - Matrix4x4f, - ] + m_Value: Union[AnimationCurve, Gradient, Matrix4x4f, PPtr[NamedObject], PPtr[Object], Vector2f, Vector3f, Vector4f, bool, float, int] @unitypy_define @@ -11289,7 +11584,7 @@ class VFXExpressionContainer: @unitypy_define class VFXField: - m_Array: Union[List[VFXEntryExpressionValue], List[VFXEntryExposed]] + m_Array: Union[List[VFXEntryExposed], List[VFXEntryExpressionValue]] @unitypy_define @@ -11352,12 +11647,13 @@ class VFXPropertySheetSerializedBase: @unitypy_define class VFXRendererSettings: - lightProbeUsage: int motionVectorGenerationMode: int - receiveShadows: bool - reflectionProbeUsage: int shadowCastingMode: int + lightProbeUsage: Optional[int] = None rayTracingMode: Optional[int] = None + receiveShadows: Optional[bool] = None + reflectionProbeUsage: Optional[int] = None + transparencyPriority: Optional[int] = None @unitypy_define @@ -11398,6 +11694,7 @@ class VFXTemplate: icon: PPtr[Texture2D] name: str thumbnail: PPtr[Texture2D] + order: Optional[int] = None @unitypy_define @@ -11422,9 +11719,10 @@ class ValueArray: m_BoolValues: List[bool] m_FloatValues: List[float] m_IntValues: List[int] - m_PositionValues: Optional[Union[List[float4], List[float3]]] = None + m_EntityIdValues: Optional[List[EntityId]] = None + m_PositionValues: Optional[Union[List[float3], List[float4]]] = None m_QuaternionValues: Optional[List[float4]] = None - m_ScaleValues: Optional[Union[List[float4], List[float3]]] = None + m_ScaleValues: Optional[Union[List[float3], List[float4]]] = None m_VectorValues: Optional[List[float4]] = None @@ -11461,6 +11759,7 @@ class VariantInfo: passType: Optional[int] = None shader: Optional[PPtr[Shader]] = None shaderAssetGUID: Optional[str] = None + shaderAssetLocalIdentifierInFile: Optional[int] = None shaderName: Optional[str] = None subShaderIndex: Optional[int] = None @@ -11558,7 +11857,7 @@ class VisualEffectInfo: m_CPUBuffers: List[VFXCPUBufferDesc] m_CullingFlags: int m_Events: List[VFXEventDesc] - m_ExposedExpressions: Union[List[VFXMapping], List[VFXExposedMapping]] + m_ExposedExpressions: Union[List[VFXExposedMapping], List[VFXMapping]] m_Expressions: VFXExpressionContainer m_PropertySheet: VFXPropertySheetSerializedBase m_RendererSettings: VFXRendererSettings @@ -11587,6 +11886,12 @@ class VisualEffectSettings: m_InstancingMode: Optional[int] = None +@unitypy_define +class VulkanGraphicsJobsDeviceFilterData: + filter: AndroidDeviceFilterData + preferredMode: int + + @unitypy_define class WheelFrictionCurve: asymptoteSlip: Optional[float] = None diff --git a/UnityPy/cli/update_tpk.py b/UnityPy/cli/update_tpk.py index a63fef61..6cb776c9 100644 --- a/UnityPy/cli/update_tpk.py +++ b/UnityPy/cli/update_tpk.py @@ -3,7 +3,9 @@ from urllib.request import urlopen from zipfile import ZipFile -URL = "https://nightly.link/AssetRipper/Tpk/workflows/type_tree_tpk/master/uncompressed_file.zip" +from UnityPy.tools.TpkClassGenerator import generate_classes + +URL = "https://nightly.link/AssetRipper/Tpk/workflows/type_tree_tpk/master/lzma_file.zip" RESOURCE_PATH = os.path.join(os.path.dirname(__file__), "..", "resources") @@ -11,10 +13,14 @@ def update_tpk(): print("Updating TPK file...") print("\tDownloading...") with urlopen(URL) as response: + if response.status != 200: + raise Exception(f"Failed to download TPK file: {response.status} {response.reason}") zip_data = response.read() print("\tExtracting...") with ZipFile(BytesIO(zip_data)) as zip_file: - zip_file.extract("uncompressed.tpk", path=RESOURCE_PATH) + zip_file.extract("lzma.tpk", path=RESOURCE_PATH) + print("\tGenerating classes...") + generate_classes() print("\tDone.") diff --git a/UnityPy/environment.py b/UnityPy/environment.py index b287248a..a44d228f 100644 --- a/UnityPy/environment.py +++ b/UnityPy/environment.py @@ -56,7 +56,7 @@ def __init__(self, *args: FileSourceType, fs: Optional[AbstractFileSystem] = Non if ntpath.splitext(arg)[-1] in [".apk", ".zip"]: self.load_zip_file(arg) else: - self.path = ntpath.dirname(arg) + self.path = ntpath.dirname(arg) or ntpath.curdir if reSplit.match(arg): self.load_files([arg]) else: @@ -291,7 +291,7 @@ def load_assets(self, assets: List[str], open_f: Callable[[str], BinaryIO]): data = self._load_split_file(basepath) path = basepath else: - data = open_f(path).read() + data = open_f(path) self.load_file(data, name=path) def find_file(self, name: str, is_dependency: bool = True) -> Union[File, None]: diff --git a/UnityPy/export/AudioClipConverter.py b/UnityPy/export/AudioClipConverter.py index 8cb68e0b..c381d994 100644 --- a/UnityPy/export/AudioClipConverter.py +++ b/UnityPy/export/AudioClipConverter.py @@ -1,83 +1,14 @@ from __future__ import annotations -import ctypes -import os -import platform -from threading import Lock from typing import TYPE_CHECKING, Dict -from UnityPy.streams import EndianBinaryWriter +import fmod_toolkit from ..helpers.ResourceReader import get_resource_data -try: - import numpy as np -except ImportError: - np = None - import struct - if TYPE_CHECKING: from ..classes import AudioClip -# pyfmodex loads the dll/so/dylib on import -# so we have to adjust the environment vars -# before importing it -# This is done in import_pyfmodex() -# which will replace the global pyfmodex var -pyfmodex = None - - -def get_fmod_path( - system: str, # "Windows", "Linux", "Darwin" - arch: str, # "x64", "x86", "arm", "arm64" -) -> str: - if system == "Darwin": - # universal dylib - return "lib/FMOD/Darwin/libfmod.dylib" - if system == "Windows": - return f"lib/FMOD/Windows/{arch}/fmod.dll" - if system == "Linux": - if arch == "x64": - arch = "x86_64" - return f"lib/FMOD/Linux/{arch}/libfmod.so" - - raise NotImplementedError(f"Unsupported system: {system}") - - -def import_pyfmodex(): - global pyfmodex - if pyfmodex is not None: - return - - # determine system - Windows, Darwin, Linux, Android - system = platform.system() - arch = platform.architecture()[0] - machine = platform.machine() - - if "arm" in machine: - arch = "arm" - elif "aarch64" in machine: - if system == "Linux": - arch = "arm64" - else: - arch = "arm" - elif arch == "32bit": - arch = "x86" - elif arch == "64bit": - arch = "x64" - - fmod_rel_path = get_fmod_path(system, arch) - fmod_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), fmod_rel_path) - os.environ["PYFMODEX_DLL_PATH"] = fmod_path - - # build path and load library - # prepare the environment for pyfmodex - if system != "Windows": - # hotfix ctypes for pyfmodex for non windows systems - ctypes.windll = None - - import pyfmodex - def extract_audioclip_samples(audio: AudioClip, convert_pcm_float: bool = True) -> Dict[str, bytes]: """extracts all the samples from an AudioClip @@ -86,9 +17,11 @@ def extract_audioclip_samples(audio: AudioClip, convert_pcm_float: bool = True) :return: {filename : sample(bytes)} :rtype: dict """ + audio_data: bytes if audio.m_AudioData: - audio_data = audio.m_AudioData - else: + audio_data = bytes(audio.m_AudioData) + elif audio.m_Resource: + assert audio.object_reader is not None, "AudioClip uses an external resource but object_reader is not set" resource = audio.m_Resource audio_data = get_resource_data( resource.m_Source, @@ -96,6 +29,8 @@ def extract_audioclip_samples(audio: AudioClip, convert_pcm_float: bool = True) resource.m_Offset, resource.m_Size, ) + else: + raise ValueError("AudioClip with neither m_AudioData nor m_Resource") magic = memoryview(audio_data)[:8] if magic[:4] == b"OggS": @@ -104,127 +39,11 @@ def extract_audioclip_samples(audio: AudioClip, convert_pcm_float: bool = True) return {f"{audio.m_Name}.wav": audio_data} elif magic[4:8] == b"ftyp": return {f"{audio.m_Name}.m4a": audio_data} - return dump_samples(audio, audio_data, convert_pcm_float) - - -SYSTEM_INSTANCES = {} # (channels, flags) -> (pyfmodex_system_instance, lock) -SYSTEM_GLOBAL_LOCK = Lock() - - -def get_pyfmodex_system_instance(channels: int, flags: int): - global pyfmodex, SYSTEM_INSTANCES, SYSTEM_GLOBAL_LOCK - with SYSTEM_GLOBAL_LOCK: - instance_key = (channels, flags) - if instance_key in SYSTEM_INSTANCES: - return SYSTEM_INSTANCES[instance_key] - - system = pyfmodex.System() - system.init(channels, flags, None) - lock = Lock() - SYSTEM_INSTANCES[instance_key] = (system, lock) - return system, lock - - -def dump_samples(clip: AudioClip, audio_data: bytes, convert_pcm_float: bool = True) -> Dict[str, bytes]: - global pyfmodex - if pyfmodex is None: - import_pyfmodex() - if not pyfmodex: - return {} - - if clip.m_Channels is None: - raise NotImplementedError("AudioClip.m_Channels is None, no solution implemented yet.") - - system, lock = get_pyfmodex_system_instance(clip.m_Channels, pyfmodex.flags.INIT_FLAGS.NORMAL) - with lock: - sound = system.create_sound( - bytes(audio_data), - pyfmodex.flags.MODE.OPENMEMORY, - exinfo=pyfmodex.structure_declarations.CREATESOUNDEXINFO( - length=len(audio_data), - numchannels=clip.m_Channels, - defaultfrequency=clip.m_Frequency, - ), - ) - - # iterate over subsounds - samples = {} - for i in range(sound.num_subsounds): - if i > 0: - filename = "%s-%i.wav" % (clip.m_Name, i) - else: - filename = "%s.wav" % clip.m_Name - subsound = sound.get_subsound(i) - samples[filename] = subsound_to_wav(subsound, convert_pcm_float) - subsound.release() - - sound.release() - return samples - - -def subsound_to_wav(subsound, convert_pcm_float: bool = True) -> bytes: - # get sound settings - sound_format = subsound.format.format - sound_data_length = subsound.get_length(pyfmodex.enums.TIMEUNIT.PCMBYTES) - channels = subsound.format.channels - bits = subsound.format.bits - sample_rate = int(subsound.default_frequency) - - if sound_format in [ - pyfmodex.enums.SOUND_FORMAT.PCM8, - pyfmodex.enums.SOUND_FORMAT.PCM16, - pyfmodex.enums.SOUND_FORMAT.PCM24, - pyfmodex.enums.SOUND_FORMAT.PCM32, - ]: - # format is PCM integer - audio_format = 1 - wav_data_length = sound_data_length - convert_pcm_float = False - elif sound_format == pyfmodex.enums.SOUND_FORMAT.PCMFLOAT: - # format is IEEE 754 float - if convert_pcm_float: - audio_format = 1 - bits = 16 - wav_data_length = sound_data_length // 2 - else: - audio_format = 3 - wav_data_length = sound_data_length - else: - raise NotImplementedError("Sound format " + sound_format + " is not supported.") - - w = EndianBinaryWriter(endian="<") - - # RIFF header - w.write(b"RIFF") # chunk id - w.write_int(wav_data_length + 36) # chunk size - 4 + (8 + 16 (sub chunk 1 size)) + (8 + length (sub chunk 2 size)) - w.write(b"WAVE") # format - - # fmt chunk - sub chunk 1 - w.write(b"fmt ") # sub chunk 1 id - w.write_int(16) # sub chunk 1 size, 16 for PCM - w.write_short(audio_format) # audio format, 1: PCM integer, 3: IEEE 754 float - w.write_short(channels) # number of channels - w.write_int(sample_rate) # sample rate - w.write_int(sample_rate * channels * bits // 8) # byte rate - w.write_short(channels * bits // 8) # block align - w.write_short(bits) # bits per sample - - # data chunk - sub chunk 2 - w.write(b"data") # sub chunk 2 id - w.write_int(wav_data_length) # sub chunk 2 size - # sub chunk 2 data - lock = subsound.lock(0, sound_data_length) - for ptr, sound_data_length in lock: - ptr_data = ctypes.string_at(ptr, sound_data_length.value) - if convert_pcm_float: - if np is not None: - ptr_data = np.frombuffer(ptr_data, dtype=np.float32) - ptr_data = (ptr_data * (1 << 15)).astype(np.int16).tobytes() - else: - ptr_data = struct.unpack("<%df" % (len(ptr_data) // 4), ptr_data) - ptr_data = struct.pack("<%dh" % len(ptr_data), *[int(f * (1 << 15)) for f in ptr_data]) - - w.write(ptr_data) - subsound.unlock(*lock) - return w.bytes + return fmod_toolkit.raw_to_wav( + audio_data, + audio.m_Name, + audio.m_Channels or 2, + audio.m_Frequency or 44100, + convert_pcm_float=convert_pcm_float, + ) diff --git a/UnityPy/export/MeshRendererExporter.py b/UnityPy/export/MeshRendererExporter.py index d880d04f..138d9cf2 100644 --- a/UnityPy/export/MeshRendererExporter.py +++ b/UnityPy/export/MeshRendererExporter.py @@ -25,9 +25,9 @@ class Renderer(Renderer): def get_mesh(meshR: Renderer) -> Optional[Mesh]: if isinstance(meshR, SkinnedMeshRenderer): if meshR.m_Mesh: - return meshR.m_Mesh.read() + return meshR.m_Mesh.deref_parse_as_object() else: - m_GameObject = meshR.m_GameObject.read() + m_GameObject = meshR.m_GameObject.deref_parse_as_object() for comp in m_GameObject.m_Component: if isinstance(comp, tuple): pptr = comp[1] @@ -39,9 +39,9 @@ def get_mesh(meshR: Renderer) -> Optional[Mesh]: if not obj: continue if obj.type.name == "MeshFilter": - filter: MeshFilter = pptr.read() + filter: MeshFilter = pptr.deref_parse_as_object() if filter.m_Mesh: - return filter.m_Mesh.read() + return filter.m_Mesh.deref_parse_as_object() return None @@ -66,7 +66,7 @@ def export_mesh_renderer(renderer: Renderer, export_dir: str) -> None: continue matPtr: Optional[PPtr[Material]] = renderer.m_Materials[i - firstSubMesh] if matPtr: - mat: Material = matPtr.read() + mat: Material = matPtr.deref_parse_as_object() else: material_names.append(None) continue @@ -79,10 +79,10 @@ def export_mesh_renderer(renderer: Renderer, export_dir: str) -> None: if not isinstance(key, str): # FastPropertyName key = key.name - tex: Texture2D = texEnv.m_Texture.read() + tex: Texture2D = texEnv.m_Texture.deref_parse_as_object() texName = f"{tex.m_Name if tex.m_Name else key}.png" with env.fs.open(env.fs.sep.join([export_dir, texName]), "wb") as f: - tex.read().image.save(f) + tex.image.save(f) # save .obj with env.fs.open( @@ -172,7 +172,7 @@ def properties_to_dict(properties): # FastPropertyName key = key.name - tex: Texture2D = texEnv.m_Texture.read() + tex: Texture2D = texEnv.m_Texture.deref_parse_as_object() texName = f"{tex.m_Name if tex.m_Name else key}.png" if key == "_MainTex": sb.append(f"map_Kd {texName}") diff --git a/UnityPy/export/SpriteHelper.py b/UnityPy/export/SpriteHelper.py index 06349cc6..9ab5c8e4 100644 --- a/UnityPy/export/SpriteHelper.py +++ b/UnityPy/export/SpriteHelper.py @@ -47,14 +47,14 @@ def get_image(sprite: Sprite, texture: PPtr[Texture2D], alpha_texture: Optional[ if alpha_texture: cache_id = (texture.path_id, alpha_texture.path_id) if cache_id not in cache: - original_image = get_image_from_texture2d(texture.read(), False) - alpha_image = get_image_from_texture2d(alpha_texture.read(), False) + original_image = get_image_from_texture2d(texture.deref_parse_as_object(), False) + alpha_image = get_image_from_texture2d(alpha_texture.deref_parse_as_object(), False) original_image = Image.merge("RGBA", (*original_image.split()[:3], alpha_image.split()[0])) cache[cache_id] = original_image else: cache_id = texture.path_id if cache_id not in cache: - original_image = get_image_from_texture2d(texture.read(), False) + original_image = get_image_from_texture2d(texture.deref_parse_as_object(), False) cache[cache_id] = original_image return cache[cache_id] @@ -62,14 +62,15 @@ def get_image(sprite: Sprite, texture: PPtr[Texture2D], alpha_texture: Optional[ def get_image_from_sprite(m_Sprite: Sprite) -> Image.Image: atlas = None if m_Sprite.m_SpriteAtlas: - atlas = m_Sprite.m_SpriteAtlas.read() + atlas = m_Sprite.m_SpriteAtlas.deref_parse_as_object() elif m_Sprite.m_AtlasTags: # looks like the direct pointer is empty, let's try to find the Atlas via its name assert m_Sprite.assets_file, "Sprite assets file is not set!" for obj in m_Sprite.assets_file.objects.values(): if obj.type == ClassIDType.SpriteAtlas: - atlas = obj.read() - if atlas.m_Name == m_Sprite.m_AtlasTags[0]: + name = obj.peek_name() + if name == m_Sprite.m_AtlasTags[0]: + atlas = obj.parse_as_object() break atlas = None diff --git a/UnityPy/export/Texture2DConverter.py b/UnityPy/export/Texture2DConverter.py index 960d5bf1..f3462a41 100644 --- a/UnityPy/export/Texture2DConverter.py +++ b/UnityPy/export/Texture2DConverter.py @@ -1,8 +1,9 @@ from __future__ import annotations import struct +from functools import lru_cache from io import BytesIO -from threading import Lock +from threading import get_ident from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union import astc_encoder @@ -108,9 +109,8 @@ def compress_astc(data: bytes, width: int, height: int, target_texture_format: T assert block_size is not None, f"failed to get block size for {target_texture_format.name}" swizzle = astc_encoder.ASTCSwizzle.from_str("RGBA") - context, lock = get_astc_context(block_size) - with lock: - enc_img = context.compress(astc_image, swizzle) + context = get_astc_context(block_size) + enc_img = context.compress(astc_image, swizzle) return enc_img @@ -359,31 +359,32 @@ def astc(image_data: bytes, width: int, height: int, block_size: tuple) -> Image if len(image_data) < texture_size: raise ValueError(f"Invalid ASTC data size: {len(image_data)} < {texture_size}") - context, lock = get_astc_context(block_size) - with lock: - context.decompress(image_data[:texture_size], image, astc_encoder.ASTCSwizzle.from_str("RGBA")) + context = get_astc_context(block_size) + context.decompress(image_data[:texture_size], image, astc_encoder.ASTCSwizzle.from_str("RGBA")) assert image.data is not None, "Decompression failed, image data is None" return Image.frombytes("RGBA", (width, height), image.data, "raw", "RGBA") -ASTC_CONTEXTS: Dict[Tuple[int, int], Tuple[astc_encoder.ASTCContext, Lock]] = {} +@lru_cache(maxsize=128) +def _get_astc_context(ident: int, block_size: tuple): + config = astc_encoder.ASTCConfig( + astc_encoder.ASTCProfile.LDR, + *block_size, + block_z=1, + quality=100, + flags=astc_encoder.ASTCConfigFlags.USE_DECODE_UNORM8, + ) + context = astc_encoder.ASTCContext(config) + return context def get_astc_context(block_size: tuple): - """Get the ASTC context and its lock using the given `block_size`.""" - if block_size not in ASTC_CONTEXTS: - config = astc_encoder.ASTCConfig( - astc_encoder.ASTCProfile.LDR, - *block_size, - block_z=1, - quality=100, - flags=astc_encoder.ASTCConfigFlags.USE_DECODE_UNORM8, - ) - context = astc_encoder.ASTCContext(config) - lock = Lock() - ASTC_CONTEXTS[block_size] = (context, lock) - return ASTC_CONTEXTS[block_size] + """Get the ASTC context for the current thread using the given `block_size`. + Created contexts belong to and only to the calling thread, and may be cached. + This function is thread safe. + """ + return _get_astc_context(get_ident(), block_size) def calculate_astc_compressed_size(width: int, height: int, block_size: tuple) -> int: diff --git a/UnityPy/files/BundleFile.py b/UnityPy/files/BundleFile.py index ed014c92..3e17d7de 100644 --- a/UnityPy/files/BundleFile.py +++ b/UnityPy/files/BundleFile.py @@ -41,7 +41,10 @@ def __init__( if signature == "UnityArchive": raise NotImplementedError("BundleFile - UnityArchive") elif signature in ["UnityWeb", "UnityRaw"]: - m_DirectoryInfo, blocksReader = self.read_web_raw(reader) + if self.version == 6: + m_DirectoryInfo, blocksReader = self.read_fs(reader) + else: + m_DirectoryInfo, blocksReader = self.read_web_raw(reader) elif signature == "UnityFS": m_DirectoryInfo, blocksReader = self.read_fs(reader) else: @@ -98,6 +101,10 @@ def read_fs(self, reader: EndianBinaryReader): uncompressedSize = reader.read_u_int() dataflagsValue = reader.read_u_int() + # UnityWeb version 6 + if self.signature != "UnityFS": + reader.read_byte() + version = self.parse_version() # https://issuetracker.unity3d.com/issues/files-within-assetbundles-do-not-start-on-aligned-boundaries-breaking-patching-on-nintendo-switch # Unity CN introduced encryption before the alignment fix was introduced. @@ -116,19 +123,12 @@ def read_fs(self, reader: EndianBinaryReader): if self.dataflags & self.dataflags.UsesAssetBundleEncryption: self.decryptor = ArchiveStorageManager.ArchiveStorageDecryptor(reader) - # check if we need to align the reader - # - align to 16 bytes and check if all are 0 - # - if not, reset the reader to the previous position - if self.version >= 7: + # if header version is 7 or later we need to align the reader + # for 2019.4.15 and later, version should be 7 and aligned + # but some games in these versions somehow has version 6 while aligned + if self.version >= 7 or (version[0] == 2019 and version >= (2019, 4, 15)): reader.align_stream(16) self._uses_block_alignment = True - elif version >= (2019, 4): - pre_align = reader.Position - align_data = reader.read((16 - pre_align % 16) % 16) - if any(align_data): - reader.Position = pre_align - else: - self._uses_block_alignment = True start = reader.Position if self.dataflags & ArchiveFlags.BlocksInfoAtTheEnd: # kArchiveBlocksInfoAtTheEnd @@ -212,7 +212,10 @@ def save(self, packer=None): if self.signature == "UnityArchive": raise NotImplementedError("BundleFile - UnityArchive") elif self.signature in ["UnityWeb", "UnityRaw"]: - self.save_web_raw(writer) + if self.version == 6: + self.save_fs(writer, 64, 64) + else: + self.save_web_raw(writer) elif self.signature == "UnityFS": if not packer or packer == "none": self.save_fs(writer, 64, 64) @@ -360,6 +363,10 @@ def save_fs(self, writer: EndianBinaryWriter, data_flag: int, block_info_flag: i # compression and file layout flag writer.write_u_int(data_flag) + # UnityWeb version 6 + if self.signature != "UnityFS": + writer.write_byte(0) + if self._uses_block_alignment: # UnityFS\x00 - 8 # size 8 @@ -502,7 +509,7 @@ def decompress_data( The decompressed data.""" comp_flag = CompressionFlags(flags & ArchiveFlags.CompressionTypeMask) - if self.decryptor is not None and flags & 0x100: + if self.decryptor is not None and flags & 0x100 and comp_flag != CompressionFlags.NONE: compressed_data = self.decryptor.decrypt_block(compressed_data, index) if comp_flag in CompressionHelper.DECOMPRESSION_MAP: diff --git a/UnityPy/files/ObjectReader.py b/UnityPy/files/ObjectReader.py index 3b01c841..fc03b874 100644 --- a/UnityPy/files/ObjectReader.py +++ b/UnityPy/files/ObjectReader.py @@ -7,21 +7,21 @@ Generic, List, Optional, - Tuple, Type, TypeVar, Union, cast, ) +from attrs import define + from ..classes import MonoBehaviour from ..classes.ClassIDTypeToClassMap import ClassIDTypeToClassMap -from ..enums import BuildTarget, ClassIDType +from ..enums import ClassIDType from ..exceptions import TypeTreeError from ..helpers import TypeTreeHelper from ..helpers.Tpk import get_typetree_node from ..helpers.TypeTreeNode import TypeTreeNode -from ..helpers.UnityVersion import UnityVersion from ..streams import EndianBinaryReader, EndianBinaryWriter if TYPE_CHECKING: @@ -31,89 +31,103 @@ NodeInput = Union[TypeTreeNode, List[Dict[str, Union[str, int]]]] +@define( + slots=True, +) class ObjectReader(Generic[T]): assets_file: SerializedFile reader: EndianBinaryReader - data: bytes - version: UnityVersion - version2: int - platform: BuildTarget path_id: int - byte_start_offset: Tuple[int, int] - byte_start: int - byte_size_offset: Tuple[int, int] - byte_size: int type_id: int serialized_type: Optional[SerializedType] class_id: int type: ClassIDType + byte_start: int + byte_size: int is_destroyed: Optional[int] is_stripped: Optional[int] + data: Optional[bytes] = None + _read_until: Optional[int] = None + + @property + def version(self): + return self.assets_file.version + + @property + def version2(self): + return self.assets_file.header.version + + @property + def platform(self): + return self.assets_file.target_platform # saves where the parser stopped # in case that not all data is read # and the obj.data is changed, the unknown data can be added again - - def __init__(self, assets_file: SerializedFile, reader: EndianBinaryReader): - self.assets_file = assets_file - self.reader = reader - self.data = b"" - self.version = assets_file.version - self.version2 = assets_file.header.version - self.platform = assets_file.target_platform - + @classmethod + def from_reader(cls, assets_file: SerializedFile, reader: EndianBinaryReader) -> ObjectReader[Any]: header = assets_file.header types = assets_file.types # AssetStudio ObjectInfo init if assets_file.big_id_enabled: - self.path_id = reader.read_long() + path_id = reader.read_long() elif header.version < 14: - self.path_id = reader.read_int() + path_id = reader.read_int() else: reader.align_stream() - self.path_id = reader.read_long() + path_id = reader.read_long() if header.version >= 22: - self.byte_start_offset = (self.reader.real_offset(), 8) - self.byte_start = reader.read_long() + byte_start = reader.read_long() else: - self.byte_start_offset = (self.reader.real_offset(), 4) - self.byte_start = reader.read_u_int() - - self.byte_start += header.data_offset - self.byte_header_offset = header.data_offset - self.byte_base_offset = self.reader.BaseOffset + byte_start = reader.read_u_int() - self.byte_size_offset = (self.reader.real_offset(), 4) - self.byte_size = reader.read_u_int() + byte_start += header.data_offset + byte_size = reader.read_u_int() - self.type_id = reader.read_int() + type_id = reader.read_int() + serialized_type = None if header.version < 16: - self.class_id = reader.read_u_short() - self.serialized_type = None + class_id = reader.read_u_short() for typ in types: - if typ.class_id == self.type_id: - self.serialized_type = typ + if typ.class_id == type_id: + serialized_type = typ break else: - typ = types[self.type_id] - self.serialized_type = typ - self.class_id = typ.class_id + typ = types[type_id] + serialized_type = typ + class_id = typ.class_id - self.type = ClassIDType(self.class_id) + clz_type = ClassIDType(class_id) + is_destroyed = None if header.version < 11: - self.is_destroyed = reader.read_u_short() + is_destroyed = reader.read_u_short() if 11 <= header.version < 17: script_type_index = reader.read_short() - if self.serialized_type: - self.serialized_type.script_type_index = script_type_index + if serialized_type: + serialized_type.script_type_index = script_type_index + is_stripped = None if header.version == 15 or header.version == 16: - self.stripped = reader.read_byte() + is_stripped = reader.read_byte() + + return cls( + assets_file, + reader, + path_id, + type_id, + serialized_type, + class_id, + clz_type, + byte_start, + byte_size, + is_destroyed, + is_stripped, + ) def write(self, header, writer: EndianBinaryWriter, data_writer: EndianBinaryWriter): if self.assets_file.big_id_enabled: @@ -124,7 +138,7 @@ def write(self, header, writer: EndianBinaryWriter, data_writer: EndianBinaryWri writer.align_stream() writer.write_long(self.path_id) - if self.data: + if self.data is not None: data = self.data # in some cases the parser doesn't read all of the object data # games might still require the missing data @@ -161,9 +175,10 @@ def write(self, header, writer: EndianBinaryWriter, data_writer: EndianBinaryWri writer.write_short(self.serialized_type.script_type_index) if header.version == 15 or header.version == 16: - writer.write_byte(self.stripped) + assert self.is_stripped is not None + writer.write_byte(self.is_stripped) - def set_raw_data(self, data): + def set_raw_data(self, data: bytes): self.data = data if self.assets_file: self.assets_file.mark_changed() @@ -197,9 +212,7 @@ def reset(self): self.reader.Position = self.byte_start def read(self, check_read: bool = True) -> T: - obj = self.read_typetree(wrap=True, check_read=check_read) - self._read_until = self.reader.Position - return obj # type: ignore + return self.read_typetree(wrap=True, check_read=check_read) # type: ignore def get(self, key, default=None): return getattr(self, key, default) @@ -283,8 +296,8 @@ def _get_typetree_node( node = get_typetree_node(self.class_id, self.version) if node.m_Type == "MonoBehaviour": try: - node = self._try_monobehaviour_node(node) - except Exception: + node = self.generate_monobehaviour_node(node) + except ValueError: pass if not node: raise TypeTreeError("There are no TypeTree nodes for this object.") @@ -297,12 +310,29 @@ def parse_as_object(self, node: Optional[NodeInput] = None, check_read: bool = T def parse_as_dict(self, node: Optional[NodeInput] = None, check_read: bool = True) -> dict[str, Any]: return self.read_typetree(nodes=node, wrap=False, check_read=check_read) # type: ignore - def _try_monobehaviour_node(self, base_node: TypeTreeNode) -> TypeTreeNode: + def patch( + self, + obj: Union[dict, T], + nodes: Optional[NodeInput] = None, + writer: Optional[EndianBinaryWriter] = None, + ): + return self.save_typetree(obj, nodes=nodes, writer=writer) + + # MonoBehaviour specific methods + def parse_monobehaviour_head(self, mb_node: Optional[TypeTreeNode] = None) -> MonoBehaviour: + if mb_node is None: + mb_node = get_typetree_node(ClassIDType.MonoBehaviour, self.version) + + mb = self.read_typetree(nodes=mb_node, wrap=True, check_read=False) + return cast(MonoBehaviour, mb) + + def generate_monobehaviour_node(self, mb_node: Optional[TypeTreeNode] = None) -> TypeTreeNode: env = self.assets_file.environment generator = env.typetree_generator if generator is None: - raise ValueError("No typetree generator set!") - monobehaviour = cast(MonoBehaviour, self.parse_as_object(base_node, check_read=False)) + raise ValueError("MonoBehaviour detected, but no typetree_generator set to the environment!") + + monobehaviour = self.parse_monobehaviour_head(mb_node) script = monobehaviour.m_Script.deref_parse_as_object() if script.m_Namespace != "": @@ -310,8 +340,13 @@ def _try_monobehaviour_node(self, base_node: TypeTreeNode) -> TypeTreeNode: else: fullname = script.m_ClassName - node = generator.get_nodes_up(base_node, script.m_AssemblyName, fullname) + node = generator.get_nodes_up(script.m_AssemblyName, fullname) if node: return node else: - raise ValueError("Failed to get custom MonoBehaviour node!") + raise ValueError(f"Failed to generate MonoBehaviour node for {fullname} of {script.m_AssemblyName}!") + + +__all__ = [ + "ObjectReader", +] diff --git a/UnityPy/files/SerializedFile.py b/UnityPy/files/SerializedFile.py index 3326b515..1ca608a6 100644 --- a/UnityPy/files/SerializedFile.py +++ b/UnityPy/files/SerializedFile.py @@ -11,7 +11,8 @@ from ..helpers.TypeTreeHelper import TypeTreeNode from ..helpers.UnityVersion import UnityVersion from ..streams import EndianBinaryWriter -from . import BundleFile, File, ObjectReader +from . import BundleFile, File +from .ObjectReader import ObjectReader if TYPE_CHECKING: from ..classes import AssetBundle, Object @@ -290,7 +291,7 @@ def __init__(self, reader: EndianBinaryReader, parent=None, name=None, **kwargs) object_count = reader.read_int() self.objects = {} for _ in range(object_count): - obj = ObjectReader.ObjectReader(self, reader) + obj = ObjectReader.from_reader(self, reader) self.objects[obj.path_id] = obj # Read Scripts @@ -315,7 +316,7 @@ def __init__(self, reader: EndianBinaryReader, parent=None, name=None, **kwargs) # read the asset_bundles to get the containers for obj in self.objects.values(): if obj.type == ClassIDType.AssetBundle: - self.assetbundle = obj.read() + self.assetbundle = obj.parse_as_object() self._container = ContainerHelper(self.assetbundle) break else: @@ -413,7 +414,7 @@ def save(self, packer: Optional[str] = None) -> bytes: # ReadObjects meta_writer.write_int(len(self.objects)) - for obj in self.objects.values(): + for obj in sorted(self.objects.values(), key=lambda x: x.path_id): obj.write(header, meta_writer, data_writer) data_writer.align_stream(8) diff --git a/UnityPy/helpers/ImportHelper.py b/UnityPy/helpers/ImportHelper.py index fcee540e..2717c6f6 100644 --- a/UnityPy/helpers/ImportHelper.py +++ b/UnityPy/helpers/ImportHelper.py @@ -2,14 +2,15 @@ import io import os -from typing import List, Optional, Tuple, Union +from gzip import GzipFile +from typing import BinaryIO, List, Optional, Tuple, Union from .. import files from ..enums import FileType from ..streams import EndianBinaryReader from .CompressionHelper import BROTLI_MAGIC, GZIP_MAGIC -FileSourceType = Union[str, bytes, bytearray, io.IOBase, EndianBinaryReader] +FileSourceType = Union[str, bytes, bytearray, io.IOBase, EndianBinaryReader, BinaryIO] def file_name_without_extension(file_name: str) -> str: @@ -74,7 +75,12 @@ def check_file_type( magic = bytes(reader.read_bytes(2)) reader.Position = 0 if GZIP_MAGIC == magic: - return FileType.WebFile, reader + g_stream = GzipFile(fileobj=reader) + g_reader = EndianBinaryReader(g_stream, endian="<") + signature = g_reader.read_string_to_null(20) + g_stream.close() + if signature.startswith(("UnityWebData", "TuanjieWebData")): + return FileType.WebFile, reader reader.Position = 0x20 magic = bytes(reader.read_bytes(6)) reader.Position = 0 @@ -127,22 +133,26 @@ def parse_file( ) -> Union[files.File, EndianBinaryReader]: if typ is None: typ, _ = check_file_type(reader) - if typ == FileType.AssetsFile and not name.endswith( - ( - ".resS", - ".resource", - ".config", - ".xml", - ".dat", - ) - ): - f = files.SerializedFile(reader, parent, name=name, is_dependency=is_dependency) - elif typ == FileType.BundleFile: - f = files.BundleFile(reader, parent, name=name, is_dependency=is_dependency) - elif typ == FileType.WebFile: - f = files.WebFile(reader, parent, name=name, is_dependency=is_dependency) - else: - f = reader + f = reader + try: + if typ == FileType.AssetsFile and not name.endswith( + ( + ".resS", + ".resource", + ".config", + ".xml", + ".dat", + ) + ): + f = files.SerializedFile(reader, parent, name=name, is_dependency=is_dependency) + elif typ == FileType.BundleFile: + f = files.BundleFile(reader, parent, name=name, is_dependency=is_dependency) + elif typ == FileType.WebFile: + f = files.WebFile(reader, parent, name=name, is_dependency=is_dependency) + except Exception as e: + reader.seek(0) + print(f"Error parsing file {name!r} as {typ}: {e}") + raise e return f diff --git a/UnityPy/helpers/MeshHelper.py b/UnityPy/helpers/MeshHelper.py index 848f9a99..bbcde5dd 100644 --- a/UnityPy/helpers/MeshHelper.py +++ b/UnityPy/helpers/MeshHelper.py @@ -159,11 +159,10 @@ def process(self): if mesh.m_Use16BitIndices is not None: self.m_Use16BitIndices = bool(mesh.m_Use16BitIndices) elif ( - self.version >= (2017, 4) + (self.version >= (2017, 4)) or # version == (2017, 3, 1) & patched - px string - self.version[:2] == (2017, 3) - and mesh.m_MeshCompression == 0 + (self.version[:2] == (2017, 3) and mesh.m_MeshCompression == 0) ): self.m_Use16BitIndices = mesh.m_IndexFormat == 0 self.copy_from_mesh() @@ -292,10 +291,10 @@ def get_channels(self, m_Streams: list[StreamInfo]) -> list[ChannelInfo]: for _ in range(6) ] for s, m_Stream in enumerate(m_Streams): - channelMask = bytearray(m_Stream.channelMask) # BitArray + channelMask = m_Stream.channelMask # uint offset = 0 for i in range(6): - if channelMask[i]: + if channelMask & (1 << i): m_Channel = m_Channels[i] m_Channel.stream = s m_Channel.offset = offset @@ -326,6 +325,12 @@ def read_vertex_data(self, m_Channels: list[ChannelInfo], m_Streams: list[Stream if m_VertexData is None: return + # could be empty for fully compressed meshes + # in that case data will be read from CompressedMesh via decompress_compressed_mesh + # also avoids a crash in UnityPyBoost.unpack_vertexdata with empty data + if m_VertexData.m_VertexCount == 0 or not m_VertexData.m_DataSize: + return + self.m_VertexCount = m_VertexCount = m_VertexData.m_VertexCount # m_VertexDataRaw = m_VertexData.m_DataSize diff --git a/UnityPy/helpers/Tpk.py b/UnityPy/helpers/Tpk.py index a113102b..2f2f8965 100644 --- a/UnityPy/helpers/Tpk.py +++ b/UnityPy/helpers/Tpk.py @@ -6,6 +6,7 @@ from struct import Struct from typing import Any, Dict, List, Optional, Tuple, TypeVar +from .CompressionHelper import decompress_lzma from .TypeTreeHelper import TypeTreeNode from .UnityVersion import UnityVersion @@ -17,7 +18,7 @@ def init(): - with open_binary("UnityPy.resources", "uncompressed.tpk") as f: + with open_binary("UnityPy.resources", "lzma.tpk") as f: data = f.read() global TPKTYPETREE @@ -52,7 +53,7 @@ def generate_node(class_info: TpkUnityClass) -> TypeTreeNode: assert class_info.ReleaseRootNode is not None, "Class {} has no ReleaseRootNode".format(class_info) nodes = [] - NODES = TPKTYPETREE.NodeBuffer.Nodes + NODES = TPKTYPETREE.NodeBuffer stack = [(class_info.ReleaseRootNode, 0)] index = 0 while stack: @@ -65,8 +66,8 @@ def generate_node(class_info: TpkUnityClass) -> TypeTreeNode: m_Version=node.Version, m_MetaFlag=node.MetaFlag, m_Level=level, - m_Type=TPKTYPETREE.StringBuffer.Strings[node.TypeName], - m_Name=TPKTYPETREE.StringBuffer.Strings[node.Name], + m_Type=TPKTYPETREE.StringBuffer[node.TypeName], + m_Name=TPKTYPETREE.StringBuffer[node.Name], ) ) stack = [(node_id, level + 1) for node_id in node.SubNodes] + stack @@ -172,9 +173,7 @@ def GetDataBlob(self) -> TpkDataBlob: decompressed = lz4.block.decompress(self.CompressedBytes, self.UncompressedSize) elif self.CompressionType == TpkCompressionType.Lzma: - import lzma - - decompressed = lzma.decompress(self.CompressedBytes) + decompressed = decompress_lzma(self.CompressedBytes) elif self.CompressionType == TpkCompressionType.Brotli: import brotli @@ -222,7 +221,7 @@ class TpkTypeTreeBlob(TpkDataBlob): def __init__(self, stream: BytesIO) -> None: (self.CreationTime,) = INT64.unpack(stream.read(INT64.size)) (versionCount,) = INT32.unpack(stream.read(INT32.size)) - self.Versions = [read_version(stream) for _ in range(versionCount)] + self.Versions = read_versions(stream, versionCount) (classCount,) = INT32.unpack(stream.read(INT32.size)) self.ClassInformation = {x.ID: x for x in (TpkClassInformation(stream) for _ in range(classCount))} self.CommonString = TpkCommonString(stream) @@ -303,7 +302,9 @@ def to_dict(self) -> Dict[str, Any]: "ReleaseRootNode": self.ReleaseRootNode, } - def __eq__(self, other: TpkUnityClass) -> bool: + def __eq__(self, other: object) -> bool: + if not isinstance(other, TpkUnityClass): + return False return self.to_dict() == other.to_dict() def __hash__(self) -> int: @@ -318,36 +319,22 @@ def __hash__(self) -> int: ) -class TpkClassInformation: - __slots__ = ("ID", "Classes") +class TpkClassInformation(List[Tuple[UnityVersion, Optional[TpkUnityClass]]]): ID: int - # TODO - might want to use dict - Classes: List[Tuple[UnityVersion, Optional[TpkUnityClass]]] def __init__(self, stream: BytesIO) -> None: (self.ID,) = INT32.unpack(stream.read(INT32.size)) (count,) = INT32.unpack(stream.read(INT32.size)) - self.Classes = [ + self.extend( ( read_version(stream), TpkUnityClass(stream) if stream.read(1)[0] else None, ) for _ in range(count) - ] + ) def getVersionedClass(self, version: UnityVersion) -> Optional[TpkUnityClass]: - return get_item_for_version(version, self.Classes) - - -class TpkUnityNodeBuffer: - Nodes: List[TpkUnityNode] - - def __init__(self, stream: BytesIO) -> None: - (count,) = INT32.unpack(stream.read(INT32.size)) - self.Nodes = [TpkUnityNode(stream) for _ in range(count)] - - def __getitem__(self, index: int) -> TpkUnityNode: - return self.Nodes[index] + return get_item_for_version(version, self) class TpkUnityNode: @@ -383,7 +370,9 @@ def __init__(self, stream: BytesIO) -> None: SubNodeStruct = Struct(f"<{count}H") self.SubNodes = list(SubNodeStruct.unpack(stream.read(SubNodeStruct.size))) - def __eq__(self, other: TpkUnityNode) -> bool: + def __eq__(self, other: object) -> bool: + if not isinstance(other, TpkUnityNode): + return False return self.__dict__ == other.__dict__ def __hash__(self) -> int: @@ -391,6 +380,12 @@ def __hash__(self) -> int: return hash(self.__dict__) +class TpkUnityNodeBuffer(List[TpkUnityNode]): + def __init__(self, stream: BytesIO) -> None: + (count,) = INT32.unpack(stream.read(INT32.size)) + self.extend(TpkUnityNode(stream) for _ in range(count)) + + ###################################################################################### # # Strings @@ -398,16 +393,10 @@ def __hash__(self) -> int: ###################################################################################### -class TpkStringBuffer: - __slots__ = "Strings" - Strings: List[str] - +class TpkStringBuffer(List[str]): def __init__(self, stream: BytesIO) -> None: - self.Strings = [read_string(stream) for _ in range(INT32.unpack(stream.read(INT32.size))[0])] - - @property - def Count(self) -> int: - return len(self.Strings) + count = INT32.unpack(stream.read(INT32.size))[0] + self.extend(read_string(stream) for _ in range(count)) class TpkCommonString: @@ -423,7 +412,7 @@ def __init__(self, stream: BytesIO) -> None: self.StringBufferIndices = indicesStruct.unpack(stream.read(indicesStruct.size)) def GetStrings(self, buffer: TpkStringBuffer) -> List[str]: - return [buffer.Strings[i] for i in self.StringBufferIndices] + return [buffer[i] for i in self.StringBufferIndices] def GetCount(self, exactVersion: UnityVersion) -> int: return get_item_for_version(exactVersion, self.VersionInformation) @@ -464,6 +453,11 @@ def read_version(stream: BytesIO) -> UnityVersion: return UnityVersion(UINT64.unpack(stream.read(UINT64.size))[0]) +def read_versions(stream: BytesIO, count: int) -> List[UnityVersion]: + struct = Struct(f"<{count}Q") + return [UnityVersion(x) for x in struct.unpack(stream.read(struct.size))] + + def get_item_for_version(exactVersion: UnityVersion, items: List[Tuple[UnityVersion, T]]) -> T: ret = None for version, item in items: diff --git a/UnityPy/helpers/TypeTreeGenerator.py b/UnityPy/helpers/TypeTreeGenerator.py index 7fa84392..4f313784 100644 --- a/UnityPy/helpers/TypeTreeGenerator.py +++ b/UnityPy/helpers/TypeTreeGenerator.py @@ -4,7 +4,7 @@ from .TypeTreeNode import TypeTreeNode try: - from TypeTreeGeneratorAPI import TypeTreeGenerator as TypeTreeGeneratorBase + from TypeTreeGeneratorAPI import TypeTreeGenerator as TypeTreeGeneratorBase # pyright: ignore[reportAssignmentType] except ImportError: class TypeTreeGeneratorBase: @@ -30,8 +30,10 @@ def load_local_game(self, root_dir: str): if "GameAssembly.dll" in root_files: ga_fp = os.path.join(root_dir, "GameAssembly.dll") gm_fp = os.path.join(data_dir, "il2cpp_data", "Metadata", "global-metadata.dat") - ga_raw = open(ga_fp, "rb").read() - gm_raw = open(gm_fp, "rb").read() + with open(ga_fp, "rb") as f: + ga_raw = f.read() + with open(gm_fp, "rb") as f: + gm_raw = f.read() self.load_il2cpp(ga_raw, gm_raw) else: self.load_local_dll_folder(os.path.join(data_dir, "Managed")) @@ -45,50 +47,32 @@ def load_local_dll_folder(self, dll_dir: str): data = f.read() self.load_dll(data) - def get_nodes_up(self, base_node: TypeTreeNode, assembly: str, fullname: str) -> TypeTreeNode: - root = self.cache.get((assembly, fullname)) - if root is not None: - return root + def get_nodes_up(self, assembly: str, fullname: str) -> TypeTreeNode: + key = (assembly, fullname) + if key in self.cache: + return self.cache[key] if not assembly.endswith(".dll"): assembly = f"{assembly}.dll" + base_nodes = self.get_nodes(assembly, fullname) - base_root = base_nodes[0] - root = TypeTreeNode( - base_root.m_Level, - base_root.m_Type, - base_root.m_Name, - 0, - 0, - m_MetaFlag=base_root.m_MetaFlag, - m_Children=base_node.m_Children[:], + node = TypeTreeNode.from_list( + [ + TypeTreeNode( + base_node.m_Level, + base_node.m_Type, + base_node.m_Name, + 0, + 0, + m_MetaFlag=base_node.m_MetaFlag, + ) + for base_node in base_nodes + ] ) - stack: List[TypeTreeNode] = [] - parent = root - prev = root - - for base_node in base_nodes[1:]: - node = TypeTreeNode( - base_node.m_Level, - base_node.m_Type, - base_node.m_Name, - 0, - 0, - m_MetaFlag=base_node.m_MetaFlag, - ) - if node.m_Level > prev.m_Level: - stack.append(parent) - parent = prev - elif node.m_Level < prev.m_Level: - while node.m_Level <= parent.m_Level: - parent = stack.pop() - - parent.m_Children.append(node) - prev = node - - self.cache[(assembly, fullname)] = root - return root + + self.cache[key] = node + return node __all__ = ("TypeTreeGenerator",) diff --git a/UnityPy/helpers/TypeTreeHelper.py b/UnityPy/helpers/TypeTreeHelper.py index 6a20731c..ee562b24 100644 --- a/UnityPy/helpers/TypeTreeHelper.py +++ b/UnityPy/helpers/TypeTreeHelper.py @@ -1,6 +1,7 @@ from __future__ import annotations import re +from sys import version_info as py_version_info from typing import TYPE_CHECKING, Any, Optional, Union from attrs import define @@ -108,6 +109,17 @@ def get_ref_type_node(ref_object: dict, assetfile: SerializedFile) -> Optional[T raise ValueError(f"Referenced type not found: {cls} {ns} {asm}") +if py_version_info >= (3, 14): + from annotationlib import get_annotations as annotationlib_get_annotations + + def get_annotation_keys(clz) -> set[str]: + return set(annotationlib_get_annotations(clz).keys()) +else: + + def get_annotation_keys(clz) -> set[str]: + return set(clz.__annotations__) + + def read_typetree( root_node: TypeTreeNode, reader: EndianBinaryReader, @@ -231,7 +243,7 @@ def read_value( value = clz(**value) except TypeError: keys = set(value.keys()) - annotation_keys = set(clz.__annotations__) + annotation_keys = get_annotation_keys(clz) missing_keys = annotation_keys - keys if clz is UnknownObject or missing_keys: value = UnknownObject(node, **value) @@ -319,7 +331,7 @@ def read_value_array( UnknownObject, ) keys = set(child._clean_name for child in node.m_Children) - annotation_keys = set(clz.__annotations__) + annotation_keys = get_annotation_keys(clz) missing_keys = annotation_keys - keys extra_keys = keys - annotation_keys if missing_keys or clz is UnknownObject: diff --git a/UnityPy/helpers/UnityVersion.py b/UnityPy/helpers/UnityVersion.py index 53046fc9..3f001f6f 100644 --- a/UnityPy/helpers/UnityVersion.py +++ b/UnityPy/helpers/UnityVersion.py @@ -4,7 +4,10 @@ from enum import IntEnum from typing import Optional, Tuple, Union -VersionPattern = re.compile(r"^(?P\d+)\.(?P\d+)\.(?P\d+)(?P.+?)?(?P\d+)?$") +VersionPattern = re.compile( + r"^(?P\d+)\.(?P\d+)\.(?P\d+)(?P.+?)?(?P\d+)?(?P.*)$", + flags=re.DOTALL, +) class UnityVersionType(IntEnum): @@ -20,6 +23,7 @@ class UnityVersionType(IntEnum): class UnityVersion(int): # https://github.com/AssetRipper/VersionUtilities/blob/master/VersionUtilities/UnityVersion.cs _type_str: Optional[str] + _postfix: Optional[str] @property def major(self): @@ -41,6 +45,10 @@ def type(self): def type_str(self): return getattr(self, "_type_str", self.type.name) + @property + def postfix(self): + return getattr(self, "_postfix", "") + @property def type_number(self): return self & 0xFF @@ -56,6 +64,7 @@ def from_str(cls, version: str): # formats: # old: 5.0.0, .. # new: 2018.1.1f2 .. + # the new format string can be followed by a custom postfix match = VersionPattern.match(version) if not match: raise ValueError(f"Invalid version string: {version}") @@ -64,6 +73,7 @@ def from_str(cls, version: str): build = int(match.group("build")) type_str = match.group("type_str") type_number = int(match.group("type_number") or 0) + postfix = match.group("postfix") if type_str is None: return cls.from_list(major, minor, build) @@ -72,23 +82,29 @@ def from_str(cls, version: str): obj = cls.from_list(major, minor, build, type, type_number) if type is UnityVersionType.u: obj._type_str = type_str + if postfix: + obj._postfix = postfix + return obj + def __str__(self) -> str: + if self.major <= 5: + return f"{self.major}.{self.minor}.{self.build}" + else: + return f"{self.major}.{self.minor}{self.type_str}{self.type_number}{self.postfix}" + def __repr__(self) -> str: - return f"UnityVersion {self.major}.{self.minor}{self.type_str}{self.type_number}" - - def __getitem__(self, idx: int) -> int: - if idx == 0: - return self.major - elif idx == 1: - return self.minor - elif idx == 2: - return self.build - elif idx == 3: - return self.type.value - elif idx == 4: - return self.type_number - raise IndexError("Invalid UnityVersion index") + return f"UnityVersion {self.__str__()}" + + def __getitem__(self, idx: Union[int, slice]) -> Union[int, Tuple[int, ...]]: + values = ( + self.major, + self.minor, + self.build, + self.type.value, + self.type_number, + ) + return values[idx] def as_tuple(self) -> Tuple[int, int, int, int, int]: return (self.major, self.minor, self.build, self.type.value, self.type_number) diff --git a/UnityPy/lib/FMOD/Darwin/__init__.py b/UnityPy/lib/FMOD/Darwin/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/UnityPy/lib/FMOD/Darwin/libfmod.dylib b/UnityPy/lib/FMOD/Darwin/libfmod.dylib deleted file mode 100644 index f94ced2d..00000000 Binary files a/UnityPy/lib/FMOD/Darwin/libfmod.dylib and /dev/null differ diff --git a/UnityPy/lib/FMOD/Linux/__init__.py b/UnityPy/lib/FMOD/Linux/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/UnityPy/lib/FMOD/Linux/arm/__init__.py b/UnityPy/lib/FMOD/Linux/arm/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/UnityPy/lib/FMOD/Linux/arm/libfmod.so b/UnityPy/lib/FMOD/Linux/arm/libfmod.so deleted file mode 100644 index b886b3cb..00000000 Binary files a/UnityPy/lib/FMOD/Linux/arm/libfmod.so and /dev/null differ diff --git a/UnityPy/lib/FMOD/Linux/arm64/__init__.py b/UnityPy/lib/FMOD/Linux/arm64/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/UnityPy/lib/FMOD/Linux/arm64/libfmod.so b/UnityPy/lib/FMOD/Linux/arm64/libfmod.so deleted file mode 100644 index 0bf089b6..00000000 Binary files a/UnityPy/lib/FMOD/Linux/arm64/libfmod.so and /dev/null differ diff --git a/UnityPy/lib/FMOD/Linux/x86/__init__.py b/UnityPy/lib/FMOD/Linux/x86/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/UnityPy/lib/FMOD/Linux/x86/libfmod.so b/UnityPy/lib/FMOD/Linux/x86/libfmod.so deleted file mode 100644 index 7fa4f5f5..00000000 Binary files a/UnityPy/lib/FMOD/Linux/x86/libfmod.so and /dev/null differ diff --git a/UnityPy/lib/FMOD/Linux/x86_64/__init__.py b/UnityPy/lib/FMOD/Linux/x86_64/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/UnityPy/lib/FMOD/Linux/x86_64/libfmod.so b/UnityPy/lib/FMOD/Linux/x86_64/libfmod.so deleted file mode 100644 index cb55a9d5..00000000 Binary files a/UnityPy/lib/FMOD/Linux/x86_64/libfmod.so and /dev/null differ diff --git a/UnityPy/lib/FMOD/Windows/__init__.py b/UnityPy/lib/FMOD/Windows/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/UnityPy/lib/FMOD/Windows/arm/__init__.py b/UnityPy/lib/FMOD/Windows/arm/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/UnityPy/lib/FMOD/Windows/arm/fmod.dll b/UnityPy/lib/FMOD/Windows/arm/fmod.dll deleted file mode 100644 index eba99c03..00000000 Binary files a/UnityPy/lib/FMOD/Windows/arm/fmod.dll and /dev/null differ diff --git a/UnityPy/lib/FMOD/Windows/x64/__init__.py b/UnityPy/lib/FMOD/Windows/x64/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/UnityPy/lib/FMOD/Windows/x64/fmod.dll b/UnityPy/lib/FMOD/Windows/x64/fmod.dll deleted file mode 100644 index 22d5d7bf..00000000 Binary files a/UnityPy/lib/FMOD/Windows/x64/fmod.dll and /dev/null differ diff --git a/UnityPy/lib/FMOD/Windows/x86/__init__.py b/UnityPy/lib/FMOD/Windows/x86/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/UnityPy/lib/FMOD/Windows/x86/fmod.dll b/UnityPy/lib/FMOD/Windows/x86/fmod.dll deleted file mode 100644 index b19e554d..00000000 Binary files a/UnityPy/lib/FMOD/Windows/x86/fmod.dll and /dev/null differ diff --git a/UnityPy/lib/FMOD/__init__.py b/UnityPy/lib/FMOD/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/UnityPy/lib/README.MD b/UnityPy/lib/README.MD deleted file mode 100644 index 544b62aa..00000000 --- a/UnityPy/lib/README.MD +++ /dev/null @@ -1,4 +0,0 @@ -## FMOD -Source: [FMOD Engine 2.00.10](https://fmod.com/download) -Modifications: - - Linux: clear executable stack flag (``execstack -c libfmod.so``) \ No newline at end of file diff --git a/UnityPy/lib/__init__.py b/UnityPy/lib/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/UnityPy/resources/lzma.tpk b/UnityPy/resources/lzma.tpk new file mode 100644 index 00000000..c518569a --- /dev/null +++ b/UnityPy/resources/lzma.tpk @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc67b99c06dd7575431c70207a2f78624501b17d5150092b511783bfe6db2127 +size 187318 diff --git a/UnityPy/resources/uncompressed.tpk b/UnityPy/resources/uncompressed.tpk deleted file mode 100644 index b95eef7b..00000000 Binary files a/UnityPy/resources/uncompressed.tpk and /dev/null differ diff --git a/UnityPy/streams/EndianBinaryReader.py b/UnityPy/streams/EndianBinaryReader.py index 796a3d71..968b32d8 100644 --- a/UnityPy/streams/EndianBinaryReader.py +++ b/UnityPy/streams/EndianBinaryReader.py @@ -210,6 +210,23 @@ def read_the_rest(self, obj_start: int, obj_size: int) -> builtins.bytes: """Returns the rest of the current reader bytes.""" return self.read_bytes(obj_size - (self.Position - obj_start)) + def seek(self, offset: int, whence: int = 0) -> int: + if whence == 0: + new_pos = offset + elif whence == 1: + new_pos = self.Position + offset + elif whence == 2: + new_pos = self.Length + offset + else: + raise ValueError("Invalid whence value") + if new_pos < 0: + raise ValueError("New position is before the start of the stream") + self.Position = new_pos + return self.Position + + def tell(self) -> int: + return self.Position + class EndianBinaryReader_Memoryview(EndianBinaryReader): __slots__ = ("view", "_endian", "BaseOffset", "Position", "Length") diff --git a/generators/ClassesGenerator.py b/UnityPy/tools/TpkClassGenerator.py similarity index 86% rename from generators/ClassesGenerator.py rename to UnityPy/tools/TpkClassGenerator.py index 5e617c6c..ebef4272 100644 --- a/generators/ClassesGenerator.py +++ b/UnityPy/tools/TpkClassGenerator.py @@ -13,11 +13,11 @@ from UnityPy.helpers.Tpk import TPKTYPETREE, TpkUnityNode # noqa: E402 from UnityPy.helpers.TypeTreeNode import clean_name # noqa: E402 -NODES = TPKTYPETREE.NodeBuffer.Nodes -STRINGS = TPKTYPETREE.StringBuffer.Strings +NODES = TPKTYPETREE.NodeBuffer +STRINGS = TPKTYPETREE.StringBuffer BASE_TYPE_MAP = { - "char": "str", + "char": "int", # used for byte data "short": "int", "int": "int", "long long": "int", @@ -51,15 +51,15 @@ from attrs import define as attrs_define from .math import ( - ColorRGBA, - Matrix3x4f, - Matrix4x4f, - Quaternionf, - Vector2f, - Vector3f, - Vector4f, - float3, - float4, + ColorRGBA, + Matrix3x4f, + Matrix4x4f, + Quaternionf, + Vector2f, + Vector3f, + Vector4f, + float3, + float4, ) from .Object import Object from .PPtr import PPtr @@ -68,25 +68,25 @@ def unitypy_define(cls: T) -> T: - \"\"\" - A hacky solution to bypass multiple problems related to attrs and inheritance. - - The class inheritance is very lax and based on the typetrees. - Some of the child classes might not have the same attributes as the parent class, - which would make type-hinting more tricky, and breaks attrs.define. - - Therefore this function bypasses the issue - by redefining the bases for problematic classes for the attrs.define call. - \"\"\" - bases = cls.__bases__ - if bases[0] in (object, Object, ABC): - cls = attrs_define(cls, slots=True, unsafe_hash=True) - else: - cls.__bases__ = (Object,) - cls = attrs_define(cls, slots=False, unsafe_hash=True) - cls.__bases__ = bases - return cls -"""[0:] + \"\"\" + A hacky solution to bypass multiple problems related to attrs and inheritance. + + The class inheritance is very lax and based on the typetrees. + Some of the child classes might not have the same attributes as the parent class, + which would make type-hinting more tricky, and breaks attrs.define. + + Therefore this function bypasses the issue + by redefining the bases for problematic classes for the attrs.define call. + \"\"\" + bases = cls.__bases__ + if bases[0] in (object, Object, ABC): + cls = attrs_define(cls, slots=True, unsafe_hash=True) + else: + cls.__bases__ = (Object,) + cls = attrs_define(cls, slots=False, unsafe_hash=True) + cls.__bases__ = bases + return cls +"""[1:] # LIST_BASE_TYPE_MAP = { # "short": "np.int16", @@ -140,12 +140,12 @@ def generate_str(self) -> str: if len(self.types) == 1: typ = next(iter(self.types)) else: - typ = f"Union[{', '.join(self.types)}]" + typ = f"Union[{', '.join(sorted(self.types))}]" if self.optional: - return f" {self.clean_name}: Optional[{typ}] = None" + return f" {self.clean_name}: Optional[{typ}] = None" else: - return f" {self.clean_name}: {typ}" + return f" {self.clean_name}: {typ}" @property def clean_name(self) -> str: @@ -180,7 +180,7 @@ def generate_str(self) -> str: parentsString = f"({', '.join(parents)})" if parents else "" if len(self.fields) == 0: - field_strings = [" pass"] + field_strings = [" pass"] else: field_strings = map( NodeClassField.generate_str, @@ -306,7 +306,7 @@ def generate_field_type(node_id: int, node: Optional[TpkUnityNode] = None) -> st return res -def main(): +def generate_classes(): main_classes: Set[str] = set() deps: Dict[str, List[str]] = {} @@ -315,7 +315,7 @@ def main(): base = None cls_name: Optional[str] = None - for _version, unity_class in class_info.Classes: + for _version, unity_class in class_info: if unity_class is None: continue cls_name = STRINGS[unity_class.Name] @@ -358,12 +358,12 @@ def main(): names.add(name) i += 1 - fp = os.path.join(ROOT, "UnityPy", "classes", "generated.py") + fp = os.path.join(ROOT, "classes", "generated.py") with open(fp, "w", encoding="utf8") as f: f.write(GENERATED_HEADER) f.write("\n\n") - f.write("\n\n".join(cls.generate_str() for cls in map(CLASS_CACHE_NAME.__getitem__, sorted_classes))) + f.write("\n\n\n".join(cls.generate_str() for cls in map(CLASS_CACHE_NAME.__getitem__, sorted_classes))) f.write("\n") @@ -371,6 +371,6 @@ def main(): import time t1 = time.time_ns() - main() + generate_classes() t2 = time.time_ns() print(t2 - t1 / 10**9) diff --git a/UnityPy/tools/extractor.py b/UnityPy/tools/extractor.py index c4823919..b1f0c8e6 100644 --- a/UnityPy/tools/extractor.py +++ b/UnityPy/tools/extractor.py @@ -19,12 +19,12 @@ Texture2D, ) from UnityPy.enums.ClassIDType import ClassIDType -from UnityPy.files import SerializedFile +from UnityPy.files import ObjectReader, SerializedFile def export_obj( - obj: Union[Object, PPtr], - fp: Path, + obj: Union[ObjectReader, PPtr], + fp: str, append_name: bool = False, append_path_id: bool = False, export_unknown_as_typetree: bool = False, @@ -53,25 +53,29 @@ def export_obj( return [] # set filepath - obj = obj.read() + if isinstance(obj, PPtr): + obj = obj.deref() + + instance = obj.parse_as_object() # a filter that returned True during an earlier extract_assets check can return False now with more info from read() - if asset_filter and not asset_filter(obj): + if asset_filter and not asset_filter(instance): return [] if append_name: + name = getattr(instance, "m_Name", obj.type.name) fp = os.path.join( fp, - obj.m_Name if getattr(obj, "m_Name") else obj.object_reader.type.name, # noqa: B009 + name, ) fp, extension = os.path.splitext(fp) if append_path_id: - fp = f"{fp}_{obj.object_reader.path_id}" + fp = f"{fp}_{obj.m_PathID}" # export - return export_func(obj, fp, extension) + return export_func(instance, fp, extension) def extract_assets( @@ -158,7 +162,11 @@ def defaulted_export_index(type: ClassIDType): ############################################################################### -def exportTextAsset(obj: TextAsset, fp: str, extension: str = ".txt") -> List[Tuple[SerializedFile, int]]: +def exportTextAsset( + obj: Union[TextAsset, ObjectReader], fp: str, extension: str = ".txt" +) -> List[Tuple[SerializedFile, int]]: + if isinstance(obj, ObjectReader): + obj = obj.parse_as_object() if not extension: extension = ".txt" with open(f"{fp}{extension}", "wb") as f: @@ -166,7 +174,9 @@ def exportTextAsset(obj: TextAsset, fp: str, extension: str = ".txt") -> List[Tu return [(obj.assets_file, obj.object_reader.path_id)] -def exportFont(obj: Font, fp: str, extension: str = "") -> List[Tuple[SerializedFile, int]]: +def exportFont(obj: Union[Font, ObjectReader], fp: str, extension: str = "") -> List[Tuple[SerializedFile, int]]: + if isinstance(obj, ObjectReader): + obj = obj.parse_as_object() # TODO - export glyphs if obj.m_FontData: extension = ".ttf" @@ -177,7 +187,9 @@ def exportFont(obj: Font, fp: str, extension: str = "") -> List[Tuple[Serialized return [(obj.assets_file, obj.object_reader.path_id)] -def exportMesh(obj: Mesh, fp: str, extension=".obj") -> List[Tuple[SerializedFile, int]]: +def exportMesh(obj: Union[Mesh, ObjectReader], fp: str, extension=".obj") -> List[Tuple[SerializedFile, int]]: + if isinstance(obj, ObjectReader): + obj = obj.parse_as_object() if not extension: extension = ".obj" with open(f"{fp}{extension}", "wt", encoding="utf8", newline="") as f: @@ -185,7 +197,9 @@ def exportMesh(obj: Mesh, fp: str, extension=".obj") -> List[Tuple[SerializedFil return [(obj.assets_file, obj.object_reader.path_id)] -def exportShader(obj: Shader, fp: str, extension=".txt") -> List[Tuple[SerializedFile, int]]: +def exportShader(obj: Union[Shader, ObjectReader], fp: str, extension=".txt") -> List[Tuple[SerializedFile, int]]: + if isinstance(obj, ObjectReader): + obj = obj.parse_as_object() if not extension: extension = ".txt" with open(f"{fp}{extension}", "wt", encoding="utf8", newline="") as f: @@ -194,39 +208,32 @@ def exportShader(obj: Shader, fp: str, extension=".txt") -> List[Tuple[Serialize def exportMonoBehaviour( - obj: Union[MonoBehaviour, Object], fp: str, extension: str = "" + obj: Union[MonoBehaviour, ObjectReader], fp: str, extension: str = "" ) -> List[Tuple[SerializedFile, int]]: - export = None - - if obj.object_reader.serialized_type.node: - # a typetree is available from the SerializedFile for this object - export = obj.object_reader.read_typetree() - elif isinstance(obj, MonoBehaviour): - # try to get the typetree from the MonoBehavior script - script_ptr = obj.m_Script - if script_ptr: - # looks like we have a script - script = script_ptr.read() - # check if there is a locally stored typetree for it - nodes = MONOBEHAVIOUR_TYPETREES.get(script.m_AssemblyName, {}).get(script.m_ClassName, None) - if nodes: - export = obj.object_reader.read_typetree(nodes) - else: - export = obj.object_reader.read_typetree() + reader = obj.object_reader if isinstance(obj, MonoBehaviour) else obj - if not export: - extension = ".bin" - export = obj.object_reader.raw_data - else: + try: + export = reader.parse_as_dict() extension = ".json" export = json.dumps(export, indent=4, ensure_ascii=False).encode("utf8", errors="surrogateescape") + except Exception: + extension = ".bin" + export = reader.get_raw_data() with open(f"{fp}{extension}", "wb") as f: f.write(export) - return [(obj.assets_file, obj.object_reader.path_id)] + return [(obj.assets_file, obj.path_id)] -def exportAudioClip(obj: AudioClip, fp: str, extension: str = "") -> List[Tuple[SerializedFile, int]]: - samples = obj.samples +def exportAudioClip( + obj: Union[AudioClip, ObjectReader], fp: str, extension: str = "" +) -> List[Tuple[SerializedFile, int]]: + if isinstance(obj, ObjectReader): + clip = obj.parse_as_object() + else: + clip = obj + obj = clip.object_reader + + samples = clip.samples if len(samples) == 0: pass elif len(samples) == 1: @@ -237,25 +244,35 @@ def exportAudioClip(obj: AudioClip, fp: str, extension: str = "") -> List[Tuple[ for name, clip_data in samples.items(): with open(os.path.join(fp, f"{name}.wav"), "wb") as f: f.write(clip_data) - return [(obj.assets_file, obj.object_reader.path_id)] + return [(obj.assets_file, obj.path_id)] -def exportSprite(obj: Sprite, fp: str, extension: str = ".png") -> List[Tuple[SerializedFile, int]]: - if not extension: - extension = ".png" - obj.image.save(f"{fp}{extension}") +def exportSprite( + obj: Union[Sprite, ObjectReader], fp: str, extension: str = ".png" +) -> List[Tuple[SerializedFile, int]]: + if isinstance(obj, ObjectReader): + sprite = obj.parse_as_object() + else: + sprite = obj + obj = sprite.object_reader + + sprite.image.save(f"{fp}{extension}") exported = [ - (obj.assets_file, obj.object_reader.path_id), - (obj.m_RD.texture.assetsfile, obj.m_RD.texture.path_id), + (obj.assets_file, obj.path_id), + (sprite.m_RD.texture.assetsfile, sprite.m_RD.texture.path_id), ] - alpha_assets_file = getattr(obj.m_RD.alphaTexture, "assets_file", None) - alpha_path_id = getattr(obj.m_RD.alphaTexture, "path_id", None) + alpha_assets_file = getattr(sprite.m_RD.alphaTexture, "assets_file", None) + alpha_path_id = getattr(sprite.m_RD.alphaTexture, "path_id", None) if alpha_path_id and alpha_assets_file: exported.append((alpha_assets_file, alpha_path_id)) return exported -def exportTexture2D(obj: Texture2D, fp: str, extension: str = ".png") -> List[Tuple[SerializedFile, int]]: +def exportTexture2D( + obj: Union[Texture2D, ObjectReader], fp: str, extension: str = ".png" +) -> List[Tuple[SerializedFile, int]]: + if isinstance(obj, ObjectReader): + obj = obj.parse_as_object() if not extension: extension = ".png" if obj.m_Width: @@ -264,7 +281,11 @@ def exportTexture2D(obj: Texture2D, fp: str, extension: str = ".png") -> List[Tu return [(obj.assets_file, obj.object_reader.path_id)] -def exportGameObject(obj: GameObject, fp: str, extension: str = "") -> List[Tuple[SerializedFile, int]]: +def exportGameObject( + obj: Union[GameObject, ObjectReader], fp: str, extension: str = "" +) -> List[Tuple[SerializedFile, int]]: + if isinstance(obj, ObjectReader): + obj = obj.parse_as_object() exported = [(obj.assets_file, obj.object_reader.path_id)] refs = crawl_obj(obj) if refs: @@ -303,34 +324,34 @@ def exportGameObject(obj: GameObject, fp: str, extension: str = "") -> List[Tupl MONOBEHAVIOUR_TYPETREES: Dict[ASSEMBLY_NAME_DLL, Dict[CLASS_NAME, List[Dict]]] = {} -def crawl_obj(obj: Object, ret: Optional[dict] = None) -> Dict[int, Union[Object, PPtr]]: +def crawl_obj(obj: Union[Object, ObjectReader, PPtr], 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 = {} + values: Sequence if isinstance(obj, PPtr): - if obj.path_id == 0 and obj.file_id == 0 and obj.index == -2: + if obj.m_PathID == 0 and obj.m_FileID == 0 and obj.m_Index == -2: return ret try: - obj = obj.read() + instance = obj.deref_parse_as_dict() + values = instance.values() except AttributeError: return ret + elif isinstance(obj, Object): + values = obj.__dict__.values() + elif isinstance(obj, ObjectReader): + values = obj.parse_as_dict().values() else: return ret - ret[obj.path_id] = obj - # MonoBehaviour really on their typetree - # while Object denotes that the class of the object isn't implemented yet - if isinstance(obj, (MonoBehaviour, Object)): - data = obj.read_typetree().__dict__.values() - else: - data = obj.__dict__.values() + ret[obj.m_PathID] = obj - for value in flatten(data): + for value in flatten(values): if isinstance(value, (Object, PPtr)): - if value.path_id in ret: + if value.m_PathID in ret: continue crawl_obj(value, ret) diff --git a/UnityPyBoost/TypeTreeHelper.cpp b/UnityPyBoost/TypeTreeHelper.cpp index eedd30ca..65e4e304 100644 --- a/UnityPyBoost/TypeTreeHelper.cpp +++ b/UnityPyBoost/TypeTreeHelper.cpp @@ -432,22 +432,12 @@ inline PyObject *read_class(ReaderT *reader, TypeTreeNodeObject *node, TypeTreeR return value; } -#if PY_VERSION_HEX >= 0x030e0000 -PyObject *get_annotations = nullptr; -#endif +static PyObject *_get_annotations = nullptr; inline PyObject *get_annotations(PyObject *clz) { #if PY_VERSION_HEX >= 0x030e0000 - if (get_annotations == nullptr) - { - // from annotationlib import get_annotations - PyObject *annotationlib = PyImport_ImportModule("annotationlib"); - get_annotations = PyObject_GetAttrString(annotationlib, "get_annotations"); - Py_INCREF(get_annotations); - Py_DECREF(annotationlib); - } - return PyObject_CallFunctionObjArgs(get_annotations, clz, NULL); + return PyObject_CallFunctionObjArgs(_get_annotations, clz, NULL); #else return PyObject_GetAttrString(clz, "__annotations__"); #endif @@ -1266,5 +1256,12 @@ int add_typetreenode_to_module(PyObject *m) return -1; Py_INCREF(&TypeTreeNodeType); PyModule_AddObject(m, "TypeTreeNode", (PyObject *)&TypeTreeNodeType); +#if PY_VERSION_HEX >= 0x030e0000 + PyObject *annotationlib = PyImport_ImportModule("annotationlib"); + _get_annotations = PyObject_GetAttrString(annotationlib, "get_annotations"); + PyModule_AddObject(m, "_get_annotations", _get_annotations); // steals ref + Py_INCREF(_get_annotations); + Py_DECREF(annotationlib); +#endif return 0; } diff --git a/pyproject.toml b/pyproject.toml index 2fe2e517..3ab9e435 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Games/Entertainment", "Topic :: Multimedia :: Graphics", @@ -44,9 +45,9 @@ dependencies = [ "Pillow", "texture2ddecoder >= 1.0.5", # texture decompression "etcpak", # ETC & DXT compression - "astc-encoder-py >= 0.1.8", # ASTC compression + "astc-encoder-py >= 0.1.12", # ASTC compression # audio extraction - "pyfmodex >= 0.7.1", + "fmod_toolkit", # filesystem handling "fsspec", # better classes @@ -59,7 +60,7 @@ UnityPy = "UnityPy.cli:main" [project.optional-dependencies] # optional dependencies must be lowercase/normalized -ttgen = ["typetreegeneratorapi>=0.0.7"] +ttgen = ["typetreegeneratorapi>=0.0.10"] full = ["unitypy[ttgen]"] tests = ["pytest", "pillow", "psutil", "unitypy[full]"] dev = ["ruff", "unitypy[tests]"] diff --git a/setup.py b/setup.py index 5d81c25f..5ca47e17 100644 --- a/setup.py +++ b/setup.py @@ -110,7 +110,7 @@ def run(self): setup( name="UnityPy", packages=find_packages(), - package_data={"UnityPy": ["resources/uncompressed.tpk"]}, + package_data={"UnityPy": ["resources/lzma.tpk"]}, ext_modules=[ Extension( "UnityPy.UnityPyBoost", diff --git a/tests/samples/atlas_test b/tests/samples/atlas_test index faa7e996..97970158 100644 Binary files a/tests/samples/atlas_test and b/tests/samples/atlas_test differ diff --git a/tests/samples/banner_1 b/tests/samples/banner_1 index 132d0e3b..de0f8dff 100644 Binary files a/tests/samples/banner_1 and b/tests/samples/banner_1 differ diff --git a/tests/samples/char_118_yuki.ab b/tests/samples/char_118_yuki.ab index dda9c41a..9c8d2167 100644 Binary files a/tests/samples/char_118_yuki.ab and b/tests/samples/char_118_yuki.ab differ diff --git a/tests/samples/xinzexi_2_n_tex b/tests/samples/xinzexi_2_n_tex index e80f3c81..4c21cba5 100644 Binary files a/tests/samples/xinzexi_2_n_tex and b/tests/samples/xinzexi_2_n_tex differ diff --git a/tests/samples/xinzexi_2_n_tex_mesh b/tests/samples/xinzexi_2_n_tex_mesh index b0231623..785dbd86 100644 --- a/tests/samples/xinzexi_2_n_tex_mesh +++ b/tests/samples/xinzexi_2_n_tex_mesh @@ -1,1812 +1,3 @@ -g xinzexi_2_n-mesh -v -1152 671 0 -v -1152 1343 0 -v -1440 1343 0 -v -1440 671 0 -v -1152 31 0 -v -1152 671 0 -v -1440 671 0 -v -1440 31 0 -v -832 607 0 -v -832 927 0 -v -1152 927 0 -v -1152 607 0 -v -512 607 0 -v -512 927 0 -v -832 927 0 -v -832 607 0 -v -64 319 0 -v -64 607 0 -v -384 607 0 -v -384 319 0 -v -384 319 0 -v -384 607 0 -v -672 607 0 -v -672 319 0 -v -960 927 0 -v -960 1215 0 -v -1152 1215 0 -v -1152 927 0 -v -960 1215 0 -v -960 1503 0 -v -1152 1503 0 -v -1152 1215 0 -v -1440 735 0 -v -1440 991 0 -v -1632 991 0 -v -1632 735 0 -v -1440 991 0 -v -1440 1247 0 -v -1632 1247 0 -v -1632 991 0 -v -704 223 0 -v -704 479 0 -v -832 479 0 -v -832 223 0 -v -1440 1247 0 -v -1440 1407 0 -v -1632 1407 0 -v -1632 1247 0 -v -1152 1343 0 -v -1152 1503 0 -v -1312 1503 0 -v -1312 1343 0 -v -1632 1663 0 -v -1632 2047 0 -v -1696 2047 0 -v -1696 1663 0 -v -1632 1311 0 -v -1632 1663 0 -v -1696 1663 0 -v -1696 1311 0 -v -992 1503 0 -v -992 1599 0 -v -1216 1599 0 -v -1216 1503 0 -v -1440 223 0 -v -1440 447 0 -v -1536 447 0 -v -1536 223 0 -v -800 479 0 -v -800 607 0 -v -960 607 0 -v -960 479 0 -v -800 927 0 -v -800 1055 0 -v -960 1055 0 -v -960 927 0 -v -1024 95 0 -v -1024 255 0 -v -1152 255 0 -v -1152 95 0 -v -1440 31 0 -v -1440 223 0 -v -1536 223 0 -v -1536 31 0 -v -192 191 0 -v -192 255 0 -v -448 255 0 -v -448 191 0 -v -448 191 0 -v -448 255 0 -v -704 255 0 -v -704 191 0 -v -832 1311 0 -v -832 1439 0 -v -928 1439 0 -v -928 1311 0 -v -800 1599 0 -v -800 1727 0 -v -896 1727 0 -v -896 1599 0 -v -1632 1055 0 -v -1632 1183 0 -v -1728 1183 0 -v -1728 1055 0 -v -256 607 0 -v -256 703 0 -v -384 703 0 -v -384 607 0 -v -1632 927 0 -v -1632 1055 0 -v -1728 1055 0 -v -1728 927 0 -v -384 607 0 -v -384 703 0 -v -512 703 0 -v -512 607 0 -v -1600 31 0 -v -1600 159 0 -v -1696 159 0 -v -1696 31 0 -v -1600 159 0 -v -1600 287 0 -v -1696 287 0 -v -1696 159 0 -v -1888 735 0 -v -1888 895 0 -v -1937 895 0 -v -1937 735 0 -v -1760 959 0 -v -1760 1055 0 -v -1856 1055 0 -v -1856 959 0 -v -896 1535 0 -v -896 1631 0 -v -992 1631 0 -v -992 1535 0 -v -128 287 0 -v -128 319 0 -v -416 319 0 -v -416 287 0 -v -416 287 0 -v -416 319 0 -v -704 319 0 -v -704 287 0 -v -1888 607 0 -v -1888 735 0 -v -1937 735 0 -v -1937 607 0 -v -1408 1503 0 -v -1408 1567 0 -v -1536 1567 0 -v -1536 1503 0 -v -1344 1407 0 -v -1344 1535 0 -v -1408 1535 0 -v -1408 1407 0 -v -0 415 0 -v -0 543 0 -v -64 543 0 -v -64 415 0 -v -1088 479 0 -v -1088 607 0 -v -1152 607 0 -v -1152 479 0 -v -1536 31 0 -v -1536 223 0 -v -1568 223 0 -v -1568 31 0 -v -1536 1503 0 -v -1536 1567 0 -v -1632 1567 0 -v -1632 1503 0 -v -1536 223 0 -v -1536 415 0 -v -1568 415 0 -v -1568 223 0 -v -1696 159 0 -v -1696 255 0 -v -1760 255 0 -v -1760 159 0 -v -768 1343 0 -v -768 1407 0 -v -832 1407 0 -v -832 1343 0 -v -448 831 0 -v -448 895 0 -v -512 895 0 -v -512 831 0 -v -1280 0 0 -v -1280 31 0 -v -1408 31 0 -v -1408 0 0 -v -1440 1439 0 -v -1440 1503 0 -v -1504 1503 0 -v -1504 1439 0 -v -1472 671 0 -v -1472 735 0 -v -1536 735 0 -v -1536 671 0 -v -1792 479 0 -v -1792 543 0 -v -1856 543 0 -v -1856 479 0 -v -320 703 0 -v -320 767 0 -v -384 767 0 -v -384 703 0 -v -448 703 0 -v -448 767 0 -v -512 767 0 -v -512 703 0 -v -1568 319 0 -v -1568 447 0 -v -1600 447 0 -v -1600 319 0 -v -1856 863 0 -v -1856 991 0 -v -1888 991 0 -v -1888 863 0 -v -1696 95 0 -v -1696 159 0 -v -1760 159 0 -v -1760 95 0 -v -1568 191 0 -v -1568 319 0 -v -1600 319 0 -v -1600 191 0 -v -160 255 0 -v -160 287 0 -v -288 287 0 -v -288 255 0 -v -832 351 0 -v -832 479 0 -v -864 479 0 -v -864 351 0 -v -288 255 0 -v -288 287 0 -v -416 287 0 -v -416 255 0 -v -672 383 0 -v -672 479 0 -v -704 479 0 -v -704 383 0 -v -1440 639 0 -v -1440 735 0 -v -1472 735 0 -v -1472 639 0 -v -928 1119 0 -v -928 1215 0 -v -960 1215 0 -v -960 1119 0 -v -1856 767 0 -v -1856 863 0 -v -1888 863 0 -v -1888 767 0 -v -1568 63 0 -v -1568 159 0 -v -1600 159 0 -v -1600 63 0 -v -832 255 0 -v -832 351 0 -v -864 351 0 -v -864 255 0 -v -1696 415 0 -v -1696 447 0 -v -1792 447 0 -v -1792 415 0 -v -512 255 0 -v -512 287 0 -v -608 287 0 -v -608 255 0 -v -704 927 0 -v -704 959 0 -v -800 959 0 -v -800 927 0 -v -1600 415 0 -v -1600 447 0 -v -1696 447 0 -v -1696 415 0 -v -608 927 0 -v -608 959 0 -v -704 959 0 -v -704 927 0 -v -1408 0 0 -v -1408 31 0 -v -1504 31 0 -v -1504 0 0 -v -1696 1791 0 -v -1696 1887 0 -v -1728 1887 0 -v -1728 1791 0 -v -1408 1407 0 -v -1408 1503 0 -v -1440 1503 0 -v -1440 1407 0 -v -608 255 0 -v -608 287 0 -v -704 287 0 -v -704 255 0 -v -1696 1887 0 -v -1696 1983 0 -v -1728 1983 0 -v -1728 1887 0 -v -1600 1599 0 -v -1600 1663 0 -v -1632 1663 0 -v -1632 1599 0 -v -768 511 0 -v -768 575 0 -v -800 575 0 -v -800 511 0 -v -1504 1471 0 -v -1504 1503 0 -v -1568 1503 0 -v -1568 1471 0 -v -1216 1567 0 -v -1216 1599 0 -v -1280 1599 0 -v -1280 1567 0 -v -832 1567 0 -v -832 1599 0 -v -896 1599 0 -v -896 1567 0 -v -672 543 0 -v -672 607 0 -v -704 607 0 -v -704 543 0 -v -1440 479 0 -v -1440 543 0 -v -1472 543 0 -v -1472 479 0 -v -416 799 0 -v -416 863 0 -v -448 863 0 -v -448 799 0 -v -384 767 0 -v -384 831 0 -v -416 831 0 -v -416 767 0 -v -1632 383 0 -v -1632 415 0 -v -1696 415 0 -v -1696 383 0 -v -768 1663 0 -v -768 1727 0 -v -800 1727 0 -v -800 1663 0 -v -896 1631 0 -v -896 1695 0 -v -928 1695 0 -v -928 1631 0 -v -1760 223 0 -v -1760 287 0 -v -1792 287 0 -v -1792 223 0 -v -1728 31 0 -v -1728 95 0 -v -1760 95 0 -v -1760 31 0 -v -1120 255 0 -v -1120 319 0 -v -1152 319 0 -v -1152 255 0 -v -1792 223 0 -v -1792 287 0 -v -1824 287 0 -v -1824 223 0 -v -864 415 0 -v -864 479 0 -v -896 479 0 -v -896 415 0 -v -32 351 0 -v -32 415 0 -v -64 415 0 -v -64 351 0 -v -1088 63 0 -v -1088 95 0 -v -1152 95 0 -v -1152 63 0 -v -1600 0 0 -v -1600 31 0 -v -1664 31 0 -v -1664 0 0 -v -960 191 0 -v -960 255 0 -v -992 255 0 -v -992 191 0 -v -480 159 0 -v -480 191 0 -v -544 191 0 -v -544 159 0 -v -1888 543 0 -v -1888 607 0 -v -1920 607 0 -v -1920 543 0 -v -512 1087 0 -v -512 1151 0 -v -544 1151 0 -v -544 1087 0 -v -896 1055 0 -v -896 1119 0 -v -928 1119 0 -v -928 1055 0 -v -1600 1439 0 -v -1600 1503 0 -v -1632 1503 0 -v -1632 1439 0 -v -1632 1247 0 -v -1632 1311 0 -v -1664 1311 0 -v -1664 1247 0 -v -1888 895 0 -v -1888 959 0 -v -1920 959 0 -v -1920 895 0 -v -960 543 0 -v -960 607 0 -v -992 607 0 -v -992 543 0 -v -1760 1055 0 -v -1760 1119 0 -v -1792 1119 0 -v -1792 1055 0 -v -768 959 0 -v -768 1023 0 -v -800 1023 0 -v -800 959 0 -v -1824 895 0 -v -1824 959 0 -v -1856 959 0 -v -1856 895 0 -v -928 1343 0 -v -928 1407 0 -v -960 1407 0 -v -960 1343 0 -v -1504 1407 0 -v -1504 1439 0 -v -1568 1439 0 -v -1568 1407 0 -v -1312 1375 0 -v -1312 1439 0 -v -1344 1439 0 -v -1344 1375 0 -v -928 1407 0 -v -928 1471 0 -v -960 1471 0 -v -960 1407 0 -v -1728 1087 0 -v -1728 1151 0 -v -1760 1151 0 -v -1760 1087 0 -v -192 607 0 -v -192 639 0 -v -256 639 0 -v -256 607 0 -v -1856 575 0 -v -1856 639 0 -v -1888 639 0 -v -1888 575 0 -v -1728 1023 0 -v -1728 1087 0 -v -1760 1087 0 -v -1760 1023 0 -v -128 607 0 -v -128 639 0 -v -192 639 0 -v -192 607 0 -v -1312 1439 0 -v -1312 1503 0 -v -1344 1503 0 -v -1344 1439 0 -v -1440 575 0 -v -1440 639 0 -v -1472 639 0 -v -1472 575 0 -v -672 319 0 -v -672 383 0 -v -704 383 0 -v -704 319 0 -v -1408 1343 0 -v -1408 1407 0 -v -1440 1407 0 -v -1440 1343 0 -v -928 1055 0 -v -928 1119 0 -v -960 1119 0 -v -960 1055 0 -v -1568 0 0 -v -1568 63 0 -v -1600 63 0 -v -1600 0 0 -v -1504 1567 0 -v -1504 1599 0 -v -1568 1599 0 -v -1568 1567 0 -v -1568 1567 0 -v -1568 1599 0 -v -1632 1599 0 -v -1632 1567 0 -v -1696 1631 0 -v -1696 1695 0 -v -1728 1695 0 -v -1728 1631 0 -v -1696 1567 0 -v -1696 1631 0 -v -1728 1631 0 -v -1728 1567 0 -v -1856 511 0 -v -1856 575 0 -v -1888 575 0 -v -1888 511 0 -v -1600 287 0 -v -1600 351 0 -v -1632 351 0 -v -1632 287 0 -v -1568 1407 0 -v -1568 1439 0 -v -1632 1439 0 -v -1632 1407 0 -v -1600 351 0 -v -1600 415 0 -v -1632 415 0 -v -1632 351 0 -v -1152 1599 0 -v -1152 1631 0 -v -1216 1631 0 -v -1216 1599 0 -v -992 159 0 -v -992 223 0 -v -1024 223 0 -v -1024 159 0 -v -992 223 0 -v -992 287 0 -v -1024 287 0 -v -1024 223 0 -v -1760 447 0 -v -1760 479 0 -v -1824 479 0 -v -1824 447 0 -v -1120 351 0 -v -1120 415 0 -v -1152 415 0 -v -1152 351 0 -v -1120 415 0 -v -1120 479 0 -v -1152 479 0 -v -1152 415 0 -v -1696 447 0 -v -1696 479 0 -v -1760 479 0 -v -1760 447 0 -v -448 799 0 -v -448 831 0 -v -480 831 0 -v -480 799 0 -v -288 703 0 -v -288 735 0 -v -320 735 0 -v -320 703 0 -v -736 959 0 -v -736 991 0 -v -768 991 0 -v -768 959 0 -v -1792 927 0 -v -1792 959 0 -v -1824 959 0 -v -1824 927 0 -v -1536 703 0 -v -1536 735 0 -v -1568 735 0 -v -1568 703 0 -v -704 575 0 -v -704 607 0 -v -736 607 0 -v -736 575 0 -v -416 703 0 -v -416 735 0 -v -448 735 0 -v -448 703 0 -v -352 767 0 -v -352 799 0 -v -384 799 0 -v -384 767 0 -v -224 639 0 -v -224 671 0 -v -256 671 0 -v -256 639 0 -v -480 767 0 -v -480 799 0 -v -512 799 0 -v -512 767 0 -v -32 543 0 -v -32 575 0 -v -64 575 0 -v -64 543 0 -v -896 351 0 -v -896 383 0 -v -928 383 0 -v -928 351 0 -v -1472 447 0 -v -1472 479 0 -v -1504 479 0 -v -1504 447 0 -v -1632 287 0 -v -1632 319 0 -v -1664 319 0 -v -1664 287 0 -v -1760 0 0 -v -1760 31 0 -v -1792 31 0 -v -1792 0 0 -v -1024 255 0 -v -1024 287 0 -v -1056 287 0 -v -1056 255 0 -v -1824 543 0 -v -1824 575 0 -v -1856 575 0 -v -1856 543 0 -v -1824 255 0 -v -1824 287 0 -v -1856 287 0 -v -1856 255 0 -v -736 479 0 -v -736 511 0 -v -768 511 0 -v -768 479 0 -v -896 447 0 -v -896 479 0 -v -928 479 0 -v -928 447 0 -v -1760 479 0 -v -1760 511 0 -v -1792 511 0 -v -1792 479 0 -v -1792 1055 0 -v -1792 1087 0 -v -1824 1087 0 -v -1824 1055 0 -v -1440 447 0 -v -1440 479 0 -v -1472 479 0 -v -1472 447 0 -v -768 479 0 -v -768 511 0 -v -800 511 0 -v -800 479 0 -v -1696 383 0 -v -1696 415 0 -v -1728 415 0 -v -1728 383 0 -v -1728 0 0 -v -1728 31 0 -v -1760 31 0 -v -1760 0 0 -v -1760 191 0 -v -1760 223 0 -v -1792 223 0 -v -1792 191 0 -v -1824 863 0 -v -1824 895 0 -v -1856 895 0 -v -1856 863 0 -v -1216 1599 0 -v -1216 1631 0 -v -1248 1631 0 -v -1248 1599 0 -v -416 767 0 -v -416 799 0 -v -448 799 0 -v -448 767 0 -v -672 511 0 -v -672 543 0 -v -704 543 0 -v -704 511 0 -v -384 735 0 -v -384 767 0 -v -416 767 0 -v -416 735 0 -v -928 1631 0 -v -928 1663 0 -v -960 1663 0 -v -960 1631 0 -v -800 1311 0 -v -800 1343 0 -v -832 1343 0 -v -832 1311 0 -v -1376 1343 0 -v -1376 1375 0 -v -1408 1375 0 -v -1408 1343 0 -v -1632 1183 0 -v -1632 1215 0 -v -1664 1215 0 -v -1664 1183 0 -v -864 1055 0 -v -864 1087 0 -v -896 1087 0 -v -896 1055 0 -v -640 1087 0 -v -640 1119 0 -v -672 1119 0 -v -672 1087 0 -v -1248 1535 0 -v -1248 1567 0 -v -1280 1567 0 -v -1280 1535 0 -v -992 1599 0 -v -992 1631 0 -v -1024 1631 0 -v -1024 1599 0 -v -1216 1503 0 -v -1216 1535 0 -v -1248 1535 0 -v -1248 1503 0 -v -1344 1375 0 -v -1344 1407 0 -v -1376 1407 0 -v -1376 1375 0 -v -896 1439 0 -v -896 1471 0 -v -928 1471 0 -v -928 1439 0 -vt 0.00048828125 0.00102460384 -vt 0.00048828125 0.689549208 -vt 0.141113281 0.689549208 -vt 0.141113281 0.00102460384 -vt 0.142089844 0.00102460384 -vt 0.142089844 0.656762302 -vt 0.282714844 0.656762302 -vt 0.282714844 0.00102460384 -vt 0.283691406 0.00102460384 -vt 0.283691406 0.328893423 -vt 0.439941406 0.328893423 -vt 0.439941406 0.00102460384 -vt 0.440917969 0.00102460384 -vt 0.440917969 0.328893423 -vt 0.597167969 0.328893423 -vt 0.597167969 0.00102460384 -vt 0.598144531 0.00102460384 -vt 0.598144531 0.296106577 -vt 0.754394531 0.296106577 -vt 0.754394531 0.00102460384 -vt 0.755371094 0.00102460384 -vt 0.755371094 0.296106577 -vt 0.895996094 0.296106577 -vt 0.895996094 0.00102460384 -vt 0.896972656 0.00102460384 -vt 0.896972656 0.296106577 -vt 0.990722656 0.296106577 -vt 0.990722656 0.00102460384 -vt 0.598144531 0.298155725 -vt 0.598144531 0.593237698 -vt 0.691894531 0.593237698 -vt 0.691894531 0.298155725 -vt 0.692871094 0.298155725 -vt 0.692871094 0.560450792 -vt 0.786621094 0.560450792 -vt 0.786621094 0.298155725 -vt 0.787597656 0.298155725 -vt 0.787597656 0.560450792 -vt 0.881347656 0.560450792 -vt 0.881347656 0.298155725 -vt 0.882324219 0.298155725 -vt 0.882324219 0.560450792 -vt 0.944824219 0.560450792 -vt 0.944824219 0.298155725 -vt 0.283691406 0.330942631 -vt 0.283691406 0.49487704 -vt 0.377441406 0.49487704 -vt 0.377441406 0.330942631 -vt 0.378417969 0.330942631 -vt 0.378417969 0.49487704 -vt 0.456542969 0.49487704 -vt 0.456542969 0.330942631 -vt 0.945800781 0.298155725 -vt 0.945800781 0.691598356 -vt 0.977050781 0.691598356 -vt 0.977050781 0.298155725 -vt 0.457519531 0.330942631 -vt 0.457519531 0.691598356 -vt 0.488769531 0.691598356 -vt 0.488769531 0.330942631 -vt 0.283691406 0.496926248 -vt 0.283691406 0.595286846 -vt 0.393066406 0.595286846 -vt 0.393066406 0.496926248 -vt 0.489746094 0.330942631 -vt 0.489746094 0.560450792 -vt 0.536621094 0.560450792 -vt 0.536621094 0.330942631 -vt 0.489746094 0.5625 -vt 0.489746094 0.693647504 -vt 0.567871094 0.693647504 -vt 0.567871094 0.5625 -vt 0.692871094 0.5625 -vt 0.692871094 0.693647504 -vt 0.770996094 0.693647504 -vt 0.770996094 0.5625 -vt 0.394042969 0.496926248 -vt 0.394042969 0.660860658 -vt 0.456542969 0.660860658 -vt 0.456542969 0.496926248 -vt 0.771972656 0.5625 -vt 0.771972656 0.759221315 -vt 0.818847656 0.759221315 -vt 0.818847656 0.5625 -vt 0.819824219 0.5625 -vt 0.819824219 0.628073812 -vt 0.944824219 0.628073812 -vt 0.944824219 0.5625 -vt 0.819824219 0.63012296 -vt 0.819824219 0.695696712 -vt 0.944824219 0.695696712 -vt 0.944824219 0.63012296 -vt 0.598144531 0.595286846 -vt 0.598144531 0.726434469 -vt 0.645019531 0.726434469 -vt 0.645019531 0.595286846 -vt 0.283691406 0.597336054 -vt 0.283691406 0.728483617 -vt 0.330566406 0.728483617 -vt 0.330566406 0.597336054 -vt 0.331542969 0.597336054 -vt 0.331542969 0.728483617 -vt 0.378417969 0.728483617 -vt 0.378417969 0.597336054 -vt 0.142089844 0.65881145 -vt 0.142089844 0.757172108 -vt 0.204589844 0.757172108 -vt 0.204589844 0.65881145 -vt 0.205566406 0.65881145 -vt 0.205566406 0.789959013 -vt 0.252441406 0.789959013 -vt 0.252441406 0.65881145 -vt 0.394042969 0.662909865 -vt 0.394042969 0.761270523 -vt 0.456542969 0.761270523 -vt 0.456542969 0.662909865 -vt 0.00048828125 0.691598356 -vt 0.00048828125 0.822745919 -vt 0.0473632812 0.822745919 -vt 0.0473632812 0.691598356 -vt 0.0483398438 0.691598356 -vt 0.0483398438 0.822745919 -vt 0.0952148438 0.822745919 -vt 0.0952148438 0.691598356 -vt 0.568847656 0.330942631 -vt 0.568847656 0.49487704 -vt 0.593261719 0.49487704 -vt 0.593261719 0.330942631 -vt 0.945800781 0.693647504 -vt 0.945800781 0.792008162 -vt 0.992675781 0.792008162 -vt 0.992675781 0.693647504 -vt 0.489746094 0.695696712 -vt 0.489746094 0.794057369 -vt 0.536621094 0.794057369 -vt 0.536621094 0.695696712 -vt 0.598144531 0.728483617 -vt 0.598144531 0.761270523 -vt 0.738769531 0.761270523 -vt 0.738769531 0.728483617 -vt 0.771972656 0.761270523 -vt 0.771972656 0.794057369 -vt 0.912597656 0.794057369 -vt 0.912597656 0.761270523 -vt 0.568847656 0.496926248 -vt 0.568847656 0.628073812 -vt 0.593261719 0.628073812 -vt 0.593261719 0.496926248 -vt 0.283691406 0.730532765 -vt 0.283691406 0.796106577 -vt 0.346191406 0.796106577 -vt 0.346191406 0.730532765 -vt 0.0961914062 0.691598356 -vt 0.0961914062 0.822745919 -vt 0.127441406 0.822745919 -vt 0.127441406 0.691598356 -vt 0.457519531 0.693647504 -vt 0.457519531 0.824795067 -vt 0.488769531 0.824795067 -vt 0.488769531 0.693647504 -vt 0.537597656 0.695696712 -vt 0.537597656 0.826844275 -vt 0.568847656 0.826844275 -vt 0.568847656 0.695696712 -vt 0.569824219 0.63012296 -vt 0.569824219 0.826844275 -vt 0.585449219 0.826844275 -vt 0.585449219 0.63012296 -vt 0.142089844 0.759221315 -vt 0.142089844 0.824795067 -vt 0.188964844 0.824795067 -vt 0.188964844 0.759221315 -vt 0.253417969 0.65881145 -vt 0.253417969 0.855532765 -vt 0.269042969 0.855532765 -vt 0.269042969 0.65881145 -vt 0.739746094 0.695696712 -vt 0.739746094 0.794057369 -vt 0.770996094 0.794057369 -vt 0.770996094 0.695696712 -vt 0.913574219 0.697745919 -vt 0.913574219 0.763319671 -vt 0.944824219 0.763319671 -vt 0.944824219 0.697745919 -vt 0.347167969 0.730532765 -vt 0.347167969 0.796106577 -vt 0.378417969 0.796106577 -vt 0.378417969 0.730532765 -vt 0.394042969 0.763319671 -vt 0.394042969 0.796106577 -vt 0.456542969 0.796106577 -vt 0.456542969 0.763319671 -vt 0.598144531 0.763319671 -vt 0.598144531 0.828893423 -vt 0.629394531 0.828893423 -vt 0.629394531 0.763319671 -vt 0.630371094 0.763319671 -vt 0.630371094 0.828893423 -vt 0.661621094 0.828893423 -vt 0.661621094 0.763319671 -vt 0.662597656 0.763319671 -vt 0.662597656 0.828893423 -vt 0.693847656 0.828893423 -vt 0.693847656 0.763319671 -vt 0.694824219 0.763319671 -vt 0.694824219 0.828893423 -vt 0.726074219 0.828893423 -vt 0.726074219 0.763319671 -vt 0.913574219 0.765368819 -vt 0.913574219 0.830942631 -vt 0.944824219 0.830942631 -vt 0.944824219 0.765368819 -vt 0.205566406 0.792008162 -vt 0.205566406 0.923155725 -vt 0.221191406 0.923155725 -vt 0.221191406 0.792008162 -vt 0.222167969 0.792008162 -vt 0.222167969 0.923155725 -vt 0.237792969 0.923155725 -vt 0.237792969 0.792008162 -vt 0.945800781 0.794057369 -vt 0.945800781 0.859631181 -vt 0.977050781 0.859631181 -vt 0.977050781 0.794057369 -vt 0.978027344 0.794057369 -vt 0.978027344 0.925204933 -vt 0.993652344 0.925204933 -vt 0.993652344 0.794057369 -vt 0.739746094 0.796106577 -vt 0.739746094 0.828893423 -vt 0.802246094 0.828893423 -vt 0.802246094 0.796106577 -vt 0.489746094 0.796106577 -vt 0.489746094 0.927254081 -vt 0.505371094 0.927254081 -vt 0.505371094 0.796106577 -vt 0.803222656 0.796106577 -vt 0.803222656 0.828893423 -vt 0.865722656 0.828893423 -vt 0.865722656 0.796106577 -vt 0.506347656 0.796106577 -vt 0.506347656 0.894467235 -vt 0.521972656 0.894467235 -vt 0.521972656 0.796106577 -vt 0.866699219 0.796106577 -vt 0.866699219 0.894467235 -vt 0.882324219 0.894467235 -vt 0.882324219 0.796106577 -vt 0.883300781 0.796106577 -vt 0.883300781 0.894467235 -vt 0.898925781 0.894467235 -vt 0.898925781 0.796106577 -vt 0.283691406 0.798155725 -vt 0.283691406 0.896516383 -vt 0.299316406 0.896516383 -vt 0.299316406 0.798155725 -vt 0.300292969 0.798155725 -vt 0.300292969 0.896516383 -vt 0.315917969 0.896516383 -vt 0.315917969 0.798155725 -vt 0.316894531 0.798155725 -vt 0.316894531 0.896516383 -vt 0.332519531 0.896516383 -vt 0.332519531 0.798155725 -vt 0.333496094 0.798155725 -vt 0.333496094 0.830942631 -vt 0.380371094 0.830942631 -vt 0.380371094 0.798155725 -vt 0.394042969 0.798155725 -vt 0.394042969 0.830942631 -vt 0.440917969 0.830942631 -vt 0.440917969 0.798155725 -vt 0.00048828125 0.824795067 -vt 0.00048828125 0.857581973 -vt 0.0473632812 0.857581973 -vt 0.0473632812 0.824795067 -vt 0.0483398438 0.824795067 -vt 0.0483398438 0.857581973 -vt 0.0952148438 0.857581973 -vt 0.0952148438 0.824795067 -vt 0.142089844 0.826844275 -vt 0.142089844 0.859631181 -vt 0.188964844 0.859631181 -vt 0.188964844 0.826844275 -vt 0.537597656 0.828893423 -vt 0.537597656 0.861680329 -vt 0.584472656 0.861680329 -vt 0.584472656 0.828893423 -vt 0.0961914062 0.824795067 -vt 0.0961914062 0.923155725 -vt 0.111816406 0.923155725 -vt 0.111816406 0.824795067 -vt 0.112792969 0.824795067 -vt 0.112792969 0.923155725 -vt 0.128417969 0.923155725 -vt 0.128417969 0.824795067 -vt 0.598144531 0.830942631 -vt 0.598144531 0.863729477 -vt 0.645019531 0.863729477 -vt 0.645019531 0.830942631 -vt 0.457519531 0.826844275 -vt 0.457519531 0.925204933 -vt 0.473144531 0.925204933 -vt 0.473144531 0.826844275 -vt 0.645996094 0.830942631 -vt 0.645996094 0.896516383 -vt 0.661621094 0.896516383 -vt 0.661621094 0.830942631 -vt 0.662597656 0.830942631 -vt 0.662597656 0.896516383 -vt 0.678222656 0.896516383 -vt 0.678222656 0.830942631 -vt 0.679199219 0.830942631 -vt 0.679199219 0.863729477 -vt 0.710449219 0.863729477 -vt 0.710449219 0.830942631 -vt 0.711425781 0.830942631 -vt 0.711425781 0.863729477 -vt 0.742675781 0.863729477 -vt 0.742675781 0.830942631 -vt 0.743652344 0.830942631 -vt 0.743652344 0.863729477 -vt 0.774902344 0.863729477 -vt 0.774902344 0.830942631 -vt 0.775878906 0.830942631 -vt 0.775878906 0.896516383 -vt 0.791503906 0.896516383 -vt 0.791503906 0.830942631 -vt 0.792480469 0.830942631 -vt 0.792480469 0.896516383 -vt 0.808105469 0.896516383 -vt 0.808105469 0.830942631 -vt 0.809082031 0.830942631 -vt 0.809082031 0.896516383 -vt 0.824707031 0.896516383 -vt 0.824707031 0.830942631 -vt 0.825683594 0.830942631 -vt 0.825683594 0.896516383 -vt 0.841308594 0.896516383 -vt 0.841308594 0.830942631 -vt 0.333496094 0.832991838 -vt 0.333496094 0.865778685 -vt 0.364746094 0.865778685 -vt 0.364746094 0.832991838 -vt 0.842285156 0.830942631 -vt 0.842285156 0.896516383 -vt 0.857910156 0.896516383 -vt 0.857910156 0.830942631 -vt 0.365722656 0.832991838 -vt 0.365722656 0.89856559 -vt 0.381347656 0.89856559 -vt 0.381347656 0.832991838 -vt 0.394042969 0.832991838 -vt 0.394042969 0.89856559 -vt 0.409667969 0.89856559 -vt 0.409667969 0.832991838 -vt 0.410644531 0.832991838 -vt 0.410644531 0.89856559 -vt 0.426269531 0.89856559 -vt 0.426269531 0.832991838 -vt 0.427246094 0.832991838 -vt 0.427246094 0.89856559 -vt 0.442871094 0.89856559 -vt 0.442871094 0.832991838 -vt 0.913574219 0.832991838 -vt 0.913574219 0.89856559 -vt 0.929199219 0.89856559 -vt 0.929199219 0.832991838 -vt 0.253417969 0.857581973 -vt 0.253417969 0.923155725 -vt 0.269042969 0.923155725 -vt 0.269042969 0.857581973 -vt 0.00048828125 0.859631181 -vt 0.00048828125 0.925204933 -vt 0.0161132812 0.925204933 -vt 0.0161132812 0.859631181 -vt 0.0170898438 0.859631181 -vt 0.0170898438 0.892418027 -vt 0.0483398438 0.892418027 -vt 0.0483398438 0.859631181 -vt 0.0493164062 0.859631181 -vt 0.0493164062 0.892418027 -vt 0.0805664062 0.892418027 -vt 0.0805664062 0.859631181 -vt 0.142089844 0.861680329 -vt 0.142089844 0.927254081 -vt 0.157714844 0.927254081 -vt 0.157714844 0.861680329 -vt 0.158691406 0.861680329 -vt 0.158691406 0.894467235 -vt 0.189941406 0.894467235 -vt 0.189941406 0.861680329 -vt 0.945800781 0.861680329 -vt 0.945800781 0.927254081 -vt 0.961425781 0.927254081 -vt 0.961425781 0.861680329 -vt 0.537597656 0.863729477 -vt 0.537597656 0.929303288 -vt 0.553222656 0.929303288 -vt 0.553222656 0.863729477 -vt 0.554199219 0.863729477 -vt 0.554199219 0.929303288 -vt 0.569824219 0.929303288 -vt 0.569824219 0.863729477 -vt 0.570800781 0.863729477 -vt 0.570800781 0.929303288 -vt 0.586425781 0.929303288 -vt 0.586425781 0.863729477 -vt 0.598144531 0.865778685 -vt 0.598144531 0.931352437 -vt 0.613769531 0.931352437 -vt 0.613769531 0.865778685 -vt 0.614746094 0.865778685 -vt 0.614746094 0.931352437 -vt 0.630371094 0.931352437 -vt 0.630371094 0.865778685 -vt 0.679199219 0.865778685 -vt 0.679199219 0.931352437 -vt 0.694824219 0.931352437 -vt 0.694824219 0.865778685 -vt 0.695800781 0.865778685 -vt 0.695800781 0.931352437 -vt 0.711425781 0.931352437 -vt 0.711425781 0.865778685 -vt 0.712402344 0.865778685 -vt 0.712402344 0.931352437 -vt 0.728027344 0.931352437 -vt 0.728027344 0.865778685 -vt 0.729003906 0.865778685 -vt 0.729003906 0.931352437 -vt 0.744628906 0.931352437 -vt 0.744628906 0.865778685 -vt 0.745605469 0.865778685 -vt 0.745605469 0.931352437 -vt 0.761230469 0.931352437 -vt 0.761230469 0.865778685 -vt 0.333496094 0.867827892 -vt 0.333496094 0.900614738 -vt 0.364746094 0.900614738 -vt 0.364746094 0.867827892 -vt 0.0170898438 0.894467235 -vt 0.0170898438 0.960040987 -vt 0.0327148438 0.960040987 -vt 0.0327148438 0.894467235 -vt 0.0336914062 0.894467235 -vt 0.0336914062 0.960040987 -vt 0.0493164062 0.960040987 -vt 0.0493164062 0.894467235 -vt 0.0502929688 0.894467235 -vt 0.0502929688 0.960040987 -vt 0.0659179688 0.960040987 -vt 0.0659179688 0.894467235 -vt 0.158691406 0.896516383 -vt 0.158691406 0.929303288 -vt 0.189941406 0.929303288 -vt 0.189941406 0.896516383 -vt 0.0668945312 0.894467235 -vt 0.0668945312 0.960040987 -vt 0.0825195312 0.960040987 -vt 0.0825195312 0.894467235 -vt 0.506347656 0.896516383 -vt 0.506347656 0.962090135 -vt 0.521972656 0.962090135 -vt 0.521972656 0.896516383 -vt 0.866699219 0.896516383 -vt 0.866699219 0.929303288 -vt 0.897949219 0.929303288 -vt 0.897949219 0.896516383 -vt 0.283691406 0.89856559 -vt 0.283691406 0.964139342 -vt 0.299316406 0.964139342 -vt 0.299316406 0.89856559 -vt 0.300292969 0.89856559 -vt 0.300292969 0.964139342 -vt 0.315917969 0.964139342 -vt 0.315917969 0.89856559 -vt 0.316894531 0.89856559 -vt 0.316894531 0.964139342 -vt 0.332519531 0.964139342 -vt 0.332519531 0.89856559 -vt 0.645996094 0.89856559 -vt 0.645996094 0.964139342 -vt 0.661621094 0.964139342 -vt 0.661621094 0.89856559 -vt 0.662597656 0.89856559 -vt 0.662597656 0.964139342 -vt 0.678222656 0.964139342 -vt 0.678222656 0.89856559 -vt 0.775878906 0.89856559 -vt 0.775878906 0.964139342 -vt 0.791503906 0.964139342 -vt 0.791503906 0.89856559 -vt 0.792480469 0.89856559 -vt 0.792480469 0.931352437 -vt 0.823730469 0.931352437 -vt 0.823730469 0.89856559 -vt 0.824707031 0.89856559 -vt 0.824707031 0.931352437 -vt 0.855957031 0.931352437 -vt 0.855957031 0.89856559 -vt 0.365722656 0.900614738 -vt 0.365722656 0.96618855 -vt 0.381347656 0.96618855 -vt 0.381347656 0.900614738 -vt 0.394042969 0.900614738 -vt 0.394042969 0.96618855 -vt 0.409667969 0.96618855 -vt 0.409667969 0.900614738 -vt 0.410644531 0.900614738 -vt 0.410644531 0.96618855 -vt 0.426269531 0.96618855 -vt 0.426269531 0.900614738 -vt 0.427246094 0.900614738 -vt 0.427246094 0.96618855 -vt 0.442871094 0.96618855 -vt 0.442871094 0.900614738 -vt 0.913574219 0.900614738 -vt 0.913574219 0.933401644 -vt 0.944824219 0.933401644 -vt 0.944824219 0.900614738 -vt 0.333496094 0.902663946 -vt 0.333496094 0.968237698 -vt 0.349121094 0.968237698 -vt 0.349121094 0.902663946 -vt 0.0961914062 0.925204933 -vt 0.0961914062 0.957991779 -vt 0.127441406 0.957991779 -vt 0.127441406 0.925204933 -vt 0.205566406 0.925204933 -vt 0.205566406 0.990778685 -vt 0.221191406 0.990778685 -vt 0.221191406 0.925204933 -vt 0.222167969 0.925204933 -vt 0.222167969 0.990778685 -vt 0.237792969 0.990778685 -vt 0.237792969 0.925204933 -vt 0.457519531 0.927254081 -vt 0.457519531 0.960040987 -vt 0.488769531 0.960040987 -vt 0.488769531 0.927254081 -vt 0.253417969 0.925204933 -vt 0.253417969 0.990778685 -vt 0.269042969 0.990778685 -vt 0.269042969 0.925204933 -vt 0.00048828125 0.927254081 -vt 0.00048828125 0.992827892 -vt 0.0161132812 0.992827892 -vt 0.0161132812 0.927254081 -vt 0.945800781 0.929303288 -vt 0.945800781 0.962090135 -vt 0.977050781 0.962090135 -vt 0.977050781 0.929303288 -vt 0.978027344 0.927254081 -vt 0.978027344 0.960040987 -vt 0.993652344 0.960040987 -vt 0.993652344 0.927254081 -vt 0.142089844 0.929303288 -vt 0.142089844 0.962090135 -vt 0.157714844 0.962090135 -vt 0.157714844 0.929303288 -vt 0.489746094 0.929303288 -vt 0.489746094 0.962090135 -vt 0.505371094 0.962090135 -vt 0.505371094 0.929303288 -vt 0.158691406 0.931352437 -vt 0.158691406 0.964139342 -vt 0.174316406 0.964139342 -vt 0.174316406 0.931352437 -vt 0.175292969 0.931352437 -vt 0.175292969 0.964139342 -vt 0.190917969 0.964139342 -vt 0.190917969 0.931352437 -vt 0.537597656 0.931352437 -vt 0.537597656 0.964139342 -vt 0.553222656 0.964139342 -vt 0.553222656 0.931352437 -vt 0.554199219 0.931352437 -vt 0.554199219 0.964139342 -vt 0.569824219 0.964139342 -vt 0.569824219 0.931352437 -vt 0.570800781 0.931352437 -vt 0.570800781 0.964139342 -vt 0.586425781 0.964139342 -vt 0.586425781 0.931352437 -vt 0.866699219 0.931352437 -vt 0.866699219 0.964139342 -vt 0.882324219 0.964139342 -vt 0.882324219 0.931352437 -vt 0.883300781 0.931352437 -vt 0.883300781 0.964139342 -vt 0.898925781 0.964139342 -vt 0.898925781 0.931352437 -vt 0.598144531 0.933401644 -vt 0.598144531 0.96618855 -vt 0.613769531 0.96618855 -vt 0.613769531 0.933401644 -vt 0.614746094 0.933401644 -vt 0.614746094 0.96618855 -vt 0.630371094 0.96618855 -vt 0.630371094 0.933401644 -vt 0.679199219 0.933401644 -vt 0.679199219 0.96618855 -vt 0.694824219 0.96618855 -vt 0.694824219 0.933401644 -vt 0.695800781 0.933401644 -vt 0.695800781 0.96618855 -vt 0.711425781 0.96618855 -vt 0.711425781 0.933401644 -vt 0.712402344 0.933401644 -vt 0.712402344 0.96618855 -vt 0.728027344 0.96618855 -vt 0.728027344 0.933401644 -vt 0.729003906 0.933401644 -vt 0.729003906 0.96618855 -vt 0.744628906 0.96618855 -vt 0.744628906 0.933401644 -vt 0.745605469 0.933401644 -vt 0.745605469 0.96618855 -vt 0.761230469 0.96618855 -vt 0.761230469 0.933401644 -vt 0.792480469 0.933401644 -vt 0.792480469 0.96618855 -vt 0.808105469 0.96618855 -vt 0.808105469 0.933401644 -vt 0.809082031 0.933401644 -vt 0.809082031 0.96618855 -vt 0.824707031 0.96618855 -vt 0.824707031 0.933401644 -vt 0.825683594 0.933401644 -vt 0.825683594 0.96618855 -vt 0.841308594 0.96618855 -vt 0.841308594 0.933401644 -vt 0.842285156 0.933401644 -vt 0.842285156 0.96618855 -vt 0.857910156 0.96618855 -vt 0.857910156 0.933401644 -vt 0.913574219 0.935450792 -vt 0.913574219 0.968237698 -vt 0.929199219 0.968237698 -vt 0.929199219 0.935450792 -vt 0.0961914062 0.960040987 -vt 0.0961914062 0.992827892 -vt 0.111816406 0.992827892 -vt 0.111816406 0.960040987 -vt 0.112792969 0.960040987 -vt 0.112792969 0.992827892 -vt 0.128417969 0.992827892 -vt 0.128417969 0.960040987 -vt 0.0170898438 0.962090135 -vt 0.0170898438 0.99487704 -vt 0.0327148438 0.99487704 -vt 0.0327148438 0.962090135 -vt 0.0336914062 0.962090135 -vt 0.0336914062 0.99487704 -vt 0.0493164062 0.99487704 -vt 0.0493164062 0.962090135 -vt 0.0502929688 0.962090135 -vt 0.0502929688 0.99487704 -vt 0.0659179688 0.99487704 -vt 0.0659179688 0.962090135 -vt 0.0668945312 0.962090135 -vt 0.0668945312 0.99487704 -vt 0.0825195312 0.99487704 -vt 0.0825195312 0.962090135 -vt 0.457519531 0.962090135 -vt 0.457519531 0.99487704 -vt 0.473144531 0.99487704 -vt 0.473144531 0.962090135 -vt 0.978027344 0.962090135 -vt 0.978027344 0.99487704 -vt 0.993652344 0.99487704 -vt 0.993652344 0.962090135 -vt 0.142089844 0.964139342 -vt 0.142089844 0.996926248 -vt 0.157714844 0.996926248 -vt 0.157714844 0.964139342 -vt 0.489746094 0.964139342 -vt 0.489746094 0.996926248 -vt 0.505371094 0.996926248 -vt 0.505371094 0.964139342 -vt 0.506347656 0.964139342 -vt 0.506347656 0.996926248 -vt 0.521972656 0.996926248 -vt 0.521972656 0.964139342 -vt 0.945800781 0.964139342 -vt 0.945800781 0.996926248 -vt 0.961425781 0.996926248 -vt 0.961425781 0.964139342 -vt 0.158691406 0.96618855 -vt 0.158691406 0.998975396 -vt 0.174316406 0.998975396 -vt 0.174316406 0.96618855 -vt 0.175292969 0.96618855 -vt 0.175292969 0.998975396 -vt 0.190917969 0.998975396 -vt 0.190917969 0.96618855 -vt 0.283691406 0.96618855 -vt 0.283691406 0.998975396 -vt 0.299316406 0.998975396 -vt 0.299316406 0.96618855 -vt 0.300292969 0.96618855 -vt 0.300292969 0.998975396 -vt 0.315917969 0.998975396 -vt 0.315917969 0.96618855 -vt 0.316894531 0.96618855 -vt 0.316894531 0.998975396 -vt 0.332519531 0.998975396 -vt 0.332519531 0.96618855 -vt 0.537597656 0.96618855 -vt 0.537597656 0.998975396 -vt 0.553222656 0.998975396 -vt 0.553222656 0.96618855 -vt 0.554199219 0.96618855 -vt 0.554199219 0.998975396 -vt 0.569824219 0.998975396 -vt 0.569824219 0.96618855 -vt 0.570800781 0.96618855 -vt 0.570800781 0.998975396 -vt 0.586425781 0.998975396 -vt 0.586425781 0.96618855 -vt 0.645996094 0.96618855 -vt 0.645996094 0.998975396 -vt 0.661621094 0.998975396 -vt 0.661621094 0.96618855 -g xinzexi_2_n-mesh_0 -f 3/3/3 2/2/2 1/1/1 -f 1/1/1 4/4/4 3/3/3 -f 7/7/7 6/6/6 5/5/5 -f 5/5/5 8/8/8 7/7/7 -f 11/11/11 10/10/10 9/9/9 -f 9/9/9 12/12/12 11/11/11 -f 15/15/15 14/14/14 13/13/13 -f 13/13/13 16/16/16 15/15/15 -f 19/19/19 18/18/18 17/17/17 -f 17/17/17 20/20/20 19/19/19 -f 23/23/23 22/22/22 21/21/21 -f 21/21/21 24/24/24 23/23/23 -f 27/27/27 26/26/26 25/25/25 -f 25/25/25 28/28/28 27/27/27 -f 31/31/31 30/30/30 29/29/29 -f 29/29/29 32/32/32 31/31/31 -f 35/35/35 34/34/34 33/33/33 -f 33/33/33 36/36/36 35/35/35 -f 39/39/39 38/38/38 37/37/37 -f 37/37/37 40/40/40 39/39/39 -f 43/43/43 42/42/42 41/41/41 -f 41/41/41 44/44/44 43/43/43 -f 47/47/47 46/46/46 45/45/45 -f 45/45/45 48/48/48 47/47/47 -f 51/51/51 50/50/50 49/49/49 -f 49/49/49 52/52/52 51/51/51 -f 55/55/55 54/54/54 53/53/53 -f 53/53/53 56/56/56 55/55/55 -f 59/59/59 58/58/58 57/57/57 -f 57/57/57 60/60/60 59/59/59 -f 63/63/63 62/62/62 61/61/61 -f 61/61/61 64/64/64 63/63/63 -f 67/67/67 66/66/66 65/65/65 -f 65/65/65 68/68/68 67/67/67 -f 71/71/71 70/70/70 69/69/69 -f 69/69/69 72/72/72 71/71/71 -f 75/75/75 74/74/74 73/73/73 -f 73/73/73 76/76/76 75/75/75 -f 79/79/79 78/78/78 77/77/77 -f 77/77/77 80/80/80 79/79/79 -f 83/83/83 82/82/82 81/81/81 -f 81/81/81 84/84/84 83/83/83 -f 87/87/87 86/86/86 85/85/85 -f 85/85/85 88/88/88 87/87/87 -f 91/91/91 90/90/90 89/89/89 -f 89/89/89 92/92/92 91/91/91 -f 95/95/95 94/94/94 93/93/93 -f 93/93/93 96/96/96 95/95/95 -f 99/99/99 98/98/98 97/97/97 -f 97/97/97 100/100/100 99/99/99 -f 103/103/103 102/102/102 101/101/101 -f 101/101/101 104/104/104 103/103/103 -f 107/107/107 106/106/106 105/105/105 -f 105/105/105 108/108/108 107/107/107 -f 111/111/111 110/110/110 109/109/109 -f 109/109/109 112/112/112 111/111/111 -f 115/115/115 114/114/114 113/113/113 -f 113/113/113 116/116/116 115/115/115 -f 119/119/119 118/118/118 117/117/117 -f 117/117/117 120/120/120 119/119/119 -f 123/123/123 122/122/122 121/121/121 -f 121/121/121 124/124/124 123/123/123 -f 127/127/127 126/126/126 125/125/125 -f 125/125/125 128/128/128 127/127/127 -f 131/131/131 130/130/130 129/129/129 -f 129/129/129 132/132/132 131/131/131 -f 135/135/135 134/134/134 133/133/133 -f 133/133/133 136/136/136 135/135/135 -f 139/139/139 138/138/138 137/137/137 -f 137/137/137 140/140/140 139/139/139 -f 143/143/143 142/142/142 141/141/141 -f 141/141/141 144/144/144 143/143/143 -f 147/147/147 146/146/146 145/145/145 -f 145/145/145 148/148/148 147/147/147 -f 151/151/151 150/150/150 149/149/149 -f 149/149/149 152/152/152 151/151/151 -f 155/155/155 154/154/154 153/153/153 -f 153/153/153 156/156/156 155/155/155 -f 159/159/159 158/158/158 157/157/157 -f 157/157/157 160/160/160 159/159/159 -f 163/163/163 162/162/162 161/161/161 -f 161/161/161 164/164/164 163/163/163 -f 167/167/167 166/166/166 165/165/165 -f 165/165/165 168/168/168 167/167/167 -f 171/171/171 170/170/170 169/169/169 -f 169/169/169 172/172/172 171/171/171 -f 175/175/175 174/174/174 173/173/173 -f 173/173/173 176/176/176 175/175/175 -f 179/179/179 178/178/178 177/177/177 -f 177/177/177 180/180/180 179/179/179 -f 183/183/183 182/182/182 181/181/181 -f 181/181/181 184/184/184 183/183/183 -f 187/187/187 186/186/186 185/185/185 -f 185/185/185 188/188/188 187/187/187 -f 191/191/191 190/190/190 189/189/189 -f 189/189/189 192/192/192 191/191/191 -f 195/195/195 194/194/194 193/193/193 -f 193/193/193 196/196/196 195/195/195 -f 199/199/199 198/198/198 197/197/197 -f 197/197/197 200/200/200 199/199/199 -f 203/203/203 202/202/202 201/201/201 -f 201/201/201 204/204/204 203/203/203 -f 207/207/207 206/206/206 205/205/205 -f 205/205/205 208/208/208 207/207/207 -f 211/211/211 210/210/210 209/209/209 -f 209/209/209 212/212/212 211/211/211 -f 215/215/215 214/214/214 213/213/213 -f 213/213/213 216/216/216 215/215/215 -f 219/219/219 218/218/218 217/217/217 -f 217/217/217 220/220/220 219/219/219 -f 223/223/223 222/222/222 221/221/221 -f 221/221/221 224/224/224 223/223/223 -f 227/227/227 226/226/226 225/225/225 -f 225/225/225 228/228/228 227/227/227 -f 231/231/231 230/230/230 229/229/229 -f 229/229/229 232/232/232 231/231/231 -f 235/235/235 234/234/234 233/233/233 -f 233/233/233 236/236/236 235/235/235 -f 239/239/239 238/238/238 237/237/237 -f 237/237/237 240/240/240 239/239/239 -f 243/243/243 242/242/242 241/241/241 -f 241/241/241 244/244/244 243/243/243 -f 247/247/247 246/246/246 245/245/245 -f 245/245/245 248/248/248 247/247/247 -f 251/251/251 250/250/250 249/249/249 -f 249/249/249 252/252/252 251/251/251 -f 255/255/255 254/254/254 253/253/253 -f 253/253/253 256/256/256 255/255/255 -f 259/259/259 258/258/258 257/257/257 -f 257/257/257 260/260/260 259/259/259 -f 263/263/263 262/262/262 261/261/261 -f 261/261/261 264/264/264 263/263/263 -f 267/267/267 266/266/266 265/265/265 -f 265/265/265 268/268/268 267/267/267 -f 271/271/271 270/270/270 269/269/269 -f 269/269/269 272/272/272 271/271/271 -f 275/275/275 274/274/274 273/273/273 -f 273/273/273 276/276/276 275/275/275 -f 279/279/279 278/278/278 277/277/277 -f 277/277/277 280/280/280 279/279/279 -f 283/283/283 282/282/282 281/281/281 -f 281/281/281 284/284/284 283/283/283 -f 287/287/287 286/286/286 285/285/285 -f 285/285/285 288/288/288 287/287/287 -f 291/291/291 290/290/290 289/289/289 -f 289/289/289 292/292/292 291/291/291 -f 295/295/295 294/294/294 293/293/293 -f 293/293/293 296/296/296 295/295/295 -f 299/299/299 298/298/298 297/297/297 -f 297/297/297 300/300/300 299/299/299 -f 303/303/303 302/302/302 301/301/301 -f 301/301/301 304/304/304 303/303/303 -f 307/307/307 306/306/306 305/305/305 -f 305/305/305 308/308/308 307/307/307 -f 311/311/311 310/310/310 309/309/309 -f 309/309/309 312/312/312 311/311/311 -f 315/315/315 314/314/314 313/313/313 -f 313/313/313 316/316/316 315/315/315 -f 319/319/319 318/318/318 317/317/317 -f 317/317/317 320/320/320 319/319/319 -f 323/323/323 322/322/322 321/321/321 -f 321/321/321 324/324/324 323/323/323 -f 327/327/327 326/326/326 325/325/325 -f 325/325/325 328/328/328 327/327/327 -f 331/331/331 330/330/330 329/329/329 -f 329/329/329 332/332/332 331/331/331 -f 335/335/335 334/334/334 333/333/333 -f 333/333/333 336/336/336 335/335/335 -f 339/339/339 338/338/338 337/337/337 -f 337/337/337 340/340/340 339/339/339 -f 343/343/343 342/342/342 341/341/341 -f 341/341/341 344/344/344 343/343/343 -f 347/347/347 346/346/346 345/345/345 -f 345/345/345 348/348/348 347/347/347 -f 351/351/351 350/350/350 349/349/349 -f 349/349/349 352/352/352 351/351/351 -f 355/355/355 354/354/354 353/353/353 -f 353/353/353 356/356/356 355/355/355 -f 359/359/359 358/358/358 357/357/357 -f 357/357/357 360/360/360 359/359/359 -f 363/363/363 362/362/362 361/361/361 -f 361/361/361 364/364/364 363/363/363 -f 367/367/367 366/366/366 365/365/365 -f 365/365/365 368/368/368 367/367/367 -f 371/371/371 370/370/370 369/369/369 -f 369/369/369 372/372/372 371/371/371 -f 375/375/375 374/374/374 373/373/373 -f 373/373/373 376/376/376 375/375/375 -f 379/379/379 378/378/378 377/377/377 -f 377/377/377 380/380/380 379/379/379 -f 383/383/383 382/382/382 381/381/381 -f 381/381/381 384/384/384 383/383/383 -f 387/387/387 386/386/386 385/385/385 -f 385/385/385 388/388/388 387/387/387 -f 391/391/391 390/390/390 389/389/389 -f 389/389/389 392/392/392 391/391/391 -f 395/395/395 394/394/394 393/393/393 -f 393/393/393 396/396/396 395/395/395 -f 399/399/399 398/398/398 397/397/397 -f 397/397/397 400/400/400 399/399/399 -f 403/403/403 402/402/402 401/401/401 -f 401/401/401 404/404/404 403/403/403 -f 407/407/407 406/406/406 405/405/405 -f 405/405/405 408/408/408 407/407/407 -f 411/411/411 410/410/410 409/409/409 -f 409/409/409 412/412/412 411/411/411 -f 415/415/415 414/414/414 413/413/413 -f 413/413/413 416/416/416 415/415/415 -f 419/419/419 418/418/418 417/417/417 -f 417/417/417 420/420/420 419/419/419 -f 423/423/423 422/422/422 421/421/421 -f 421/421/421 424/424/424 423/423/423 -f 427/427/427 426/426/426 425/425/425 -f 425/425/425 428/428/428 427/427/427 -f 431/431/431 430/430/430 429/429/429 -f 429/429/429 432/432/432 431/431/431 -f 435/435/435 434/434/434 433/433/433 -f 433/433/433 436/436/436 435/435/435 -f 439/439/439 438/438/438 437/437/437 -f 437/437/437 440/440/440 439/439/439 -f 443/443/443 442/442/442 441/441/441 -f 441/441/441 444/444/444 443/443/443 -f 447/447/447 446/446/446 445/445/445 -f 445/445/445 448/448/448 447/447/447 -f 451/451/451 450/450/450 449/449/449 -f 449/449/449 452/452/452 451/451/451 -f 455/455/455 454/454/454 453/453/453 -f 453/453/453 456/456/456 455/455/455 -f 459/459/459 458/458/458 457/457/457 -f 457/457/457 460/460/460 459/459/459 -f 463/463/463 462/462/462 461/461/461 -f 461/461/461 464/464/464 463/463/463 -f 467/467/467 466/466/466 465/465/465 -f 465/465/465 468/468/468 467/467/467 -f 471/471/471 470/470/470 469/469/469 -f 469/469/469 472/472/472 471/471/471 -f 475/475/475 474/474/474 473/473/473 -f 473/473/473 476/476/476 475/475/475 -f 479/479/479 478/478/478 477/477/477 -f 477/477/477 480/480/480 479/479/479 -f 483/483/483 482/482/482 481/481/481 -f 481/481/481 484/484/484 483/483/483 -f 487/487/487 486/486/486 485/485/485 -f 485/485/485 488/488/488 487/487/487 -f 491/491/491 490/490/490 489/489/489 -f 489/489/489 492/492/492 491/491/491 -f 495/495/495 494/494/494 493/493/493 -f 493/493/493 496/496/496 495/495/495 -f 499/499/499 498/498/498 497/497/497 -f 497/497/497 500/500/500 499/499/499 -f 503/503/503 502/502/502 501/501/501 -f 501/501/501 504/504/504 503/503/503 -f 507/507/507 506/506/506 505/505/505 -f 505/505/505 508/508/508 507/507/507 -f 511/511/511 510/510/510 509/509/509 -f 509/509/509 512/512/512 511/511/511 -f 515/515/515 514/514/514 513/513/513 -f 513/513/513 516/516/516 515/515/515 -f 519/519/519 518/518/518 517/517/517 -f 517/517/517 520/520/520 519/519/519 -f 523/523/523 522/522/522 521/521/521 -f 521/521/521 524/524/524 523/523/523 -f 527/527/527 526/526/526 525/525/525 -f 525/525/525 528/528/528 527/527/527 -f 531/531/531 530/530/530 529/529/529 -f 529/529/529 532/532/532 531/531/531 -f 535/535/535 534/534/534 533/533/533 -f 533/533/533 536/536/536 535/535/535 -f 539/539/539 538/538/538 537/537/537 -f 537/537/537 540/540/540 539/539/539 -f 543/543/543 542/542/542 541/541/541 -f 541/541/541 544/544/544 543/543/543 -f 547/547/547 546/546/546 545/545/545 -f 545/545/545 548/548/548 547/547/547 -f 551/551/551 550/550/550 549/549/549 -f 549/549/549 552/552/552 551/551/551 -f 555/555/555 554/554/554 553/553/553 -f 553/553/553 556/556/556 555/555/555 -f 559/559/559 558/558/558 557/557/557 -f 557/557/557 560/560/560 559/559/559 -f 563/563/563 562/562/562 561/561/561 -f 561/561/561 564/564/564 563/563/563 -f 567/567/567 566/566/566 565/565/565 -f 565/565/565 568/568/568 567/567/567 -f 571/571/571 570/570/570 569/569/569 -f 569/569/569 572/572/572 571/571/571 -f 575/575/575 574/574/574 573/573/573 -f 573/573/573 576/576/576 575/575/575 -f 579/579/579 578/578/578 577/577/577 -f 577/577/577 580/580/580 579/579/579 -f 583/583/583 582/582/582 581/581/581 -f 581/581/581 584/584/584 583/583/583 -f 587/587/587 586/586/586 585/585/585 -f 585/585/585 588/588/588 587/587/587 -f 591/591/591 590/590/590 589/589/589 -f 589/589/589 592/592/592 591/591/591 -f 595/595/595 594/594/594 593/593/593 -f 593/593/593 596/596/596 595/595/595 -f 599/599/599 598/598/598 597/597/597 -f 597/597/597 600/600/600 599/599/599 -f 603/603/603 602/602/602 601/601/601 -f 601/601/601 604/604/604 603/603/603 -f 607/607/607 606/606/606 605/605/605 -f 605/605/605 608/608/608 607/607/607 -f 611/611/611 610/610/610 609/609/609 -f 609/609/609 612/612/612 611/611/611 -f 615/615/615 614/614/614 613/613/613 -f 613/613/613 616/616/616 615/615/615 -f 619/619/619 618/618/618 617/617/617 -f 617/617/617 620/620/620 619/619/619 -f 623/623/623 622/622/622 621/621/621 -f 621/621/621 624/624/624 623/623/623 -f 627/627/627 626/626/626 625/625/625 -f 625/625/625 628/628/628 627/627/627 -f 631/631/631 630/630/630 629/629/629 -f 629/629/629 632/632/632 631/631/631 -f 635/635/635 634/634/634 633/633/633 -f 633/633/633 636/636/636 635/635/635 -f 639/639/639 638/638/638 637/637/637 -f 637/637/637 640/640/640 639/639/639 -f 643/643/643 642/642/642 641/641/641 -f 641/641/641 644/644/644 643/643/643 -f 647/647/647 646/646/646 645/645/645 -f 645/645/645 648/648/648 647/647/647 -f 651/651/651 650/650/650 649/649/649 -f 649/649/649 652/652/652 651/651/651 -f 655/655/655 654/654/654 653/653/653 -f 653/653/653 656/656/656 655/655/655 -f 659/659/659 658/658/658 657/657/657 -f 657/657/657 660/660/660 659/659/659 -f 663/663/663 662/662/662 661/661/661 -f 661/661/661 664/664/664 663/663/663 -f 667/667/667 666/666/666 665/665/665 -f 665/665/665 668/668/668 667/667/667 -f 671/671/671 670/670/670 669/669/669 -f 669/669/669 672/672/672 671/671/671 -f 675/675/675 674/674/674 673/673/673 -f 673/673/673 676/676/676 675/675/675 -f 679/679/679 678/678/678 677/677/677 -f 677/677/677 680/680/680 679/679/679 -f 683/683/683 682/682/682 681/681/681 -f 681/681/681 684/684/684 683/683/683 -f 687/687/687 686/686/686 685/685/685 -f 685/685/685 688/688/688 687/687/687 -f 691/691/691 690/690/690 689/689/689 -f 689/689/689 692/692/692 691/691/691 -f 695/695/695 694/694/694 693/693/693 -f 693/693/693 696/696/696 695/695/695 -f 699/699/699 698/698/698 697/697/697 -f 697/697/697 700/700/700 699/699/699 -f 703/703/703 702/702/702 701/701/701 -f 701/701/701 704/704/704 703/703/703 -f 707/707/707 706/706/706 705/705/705 -f 705/705/705 708/708/708 707/707/707 -f 711/711/711 710/710/710 709/709/709 -f 709/709/709 712/712/712 711/711/711 -f 715/715/715 714/714/714 713/713/713 -f 713/713/713 716/716/716 715/715/715 -f 719/719/719 718/718/718 717/717/717 -f 717/717/717 720/720/720 719/719/719 -f 723/723/723 722/722/722 721/721/721 -f 721/721/721 724/724/724 723/723/723 +version https://git-lfs.github.com/spec/v1 +oid sha256:8bb2082b9586b3f8f3bad2d22d33346ec0492053f2a76323aa8b4b4047368ca8 +size 44643 diff --git a/tests/test_UnityVersion.py b/tests/test_UnityVersion.py index 67581071..a0125563 100644 --- a/tests/test_UnityVersion.py +++ b/tests/test_UnityVersion.py @@ -14,6 +14,7 @@ ("2021.1.0c1", (2021, 1, 0, UnityVersionType.c.value, 1)), ("2022.2.0x1", (2022, 2, 0, UnityVersionType.x.value, 1)), ("2018.1.1z2", (2018, 1, 1, UnityVersionType.u.value, 2)), # unknown type + ("2022.3.62f2\n2", (2022, 3, 62, UnityVersionType.f.value, 2)), ], ) def test_parse_unity_version(version_str, expected_tuple): @@ -24,6 +25,7 @@ def test_parse_unity_version(version_str, expected_tuple): assert v.build == expected_tuple[2] assert v.type.value == expected_tuple[3] assert v.type_number == expected_tuple[4] + assert UnityVersion.from_list(*expected_tuple) == v @pytest.mark.parametrize( @@ -58,6 +60,7 @@ def test_comparison_with_tuple(version_str, compare_tuple): ("2018.1.1f2", "2018.1.1f1"), ("2018.1.1f2", "2018.1.2f2"), ("2018.1.1f2", "2018.2.1f2"), + ("2022.3.62f2\n2", "2022.3.62f2"), ], ) def test_comparison_with_unityversion(version_str, other_str): @@ -69,12 +72,3 @@ def test_comparison_with_unityversion(version_str, other_str): assert (v1 <= v2) == (v1.as_tuple() <= v2.as_tuple()) assert (v1 > v2) == (v1.as_tuple() > v2.as_tuple()) assert (v1 >= v2) == (v1.as_tuple() >= v2.as_tuple()) - - -def test_repr_and_str(): - v = UnityVersion.from_str("2018.1.1f2") - assert "UnityVersion" in repr(v) - assert str(v.major) in repr(v) - assert str(v.minor) in repr(v) - assert v.type_str in repr(v) - assert str(v.type_number) in repr(v) diff --git a/tests/test_main.py b/tests/test_main.py index cb22d3b2..792e3626 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -14,22 +14,24 @@ def test_read_single(): for f in os.listdir(SAMPLES): env = UnityPy.load(os.path.join(SAMPLES, f)) for obj in env.objects: - obj.read() + obj.parse_as_object() + obj.parse_as_dict() def test_read_batch(): env = UnityPy.load(SAMPLES) for obj in env.objects: - obj.read() + obj.parse_as_object() + obj.parse_as_dict() def test_save_dict(): env = UnityPy.load(SAMPLES) for obj in env.objects: data = obj.get_raw_data() - item = obj.read_typetree(wrap=False) + item = obj.parse_as_dict() assert isinstance(item, dict) - re_data = obj.save_typetree(item) + re_data = obj.patch(item) assert data == re_data @@ -37,9 +39,9 @@ def test_save_wrap(): env = UnityPy.load(SAMPLES) for obj in env.objects: data = obj.get_raw_data() - item = obj.read_typetree(wrap=True) + item = obj.parse_as_object() assert not isinstance(item, dict) - re_data = obj.save_typetree(item) + re_data = obj.patch(item) assert data == re_data @@ -48,7 +50,7 @@ def test_texture2d(): env = UnityPy.load(os.path.join(SAMPLES, f)) for obj in env.objects: if obj.type.name == "Texture2D": - data = obj.read() + data = obj.parse_as_object() data.image.save(io.BytesIO(), format="PNG") data.image = data.image.transpose(Image.ROTATE_90) data.save() @@ -59,7 +61,8 @@ def test_sprite(): env = UnityPy.load(os.path.join(SAMPLES, f)) for obj in env.objects: if obj.type.name == "Sprite": - obj.read().image.save(io.BytesIO(), format="PNG") + sprite = obj.parse_as_object() + sprite.image.save(io.BytesIO(), format="PNG") if platform.system() == "Darwin": @@ -69,26 +72,18 @@ def test_sprite(): def test_audioclip(): - # as not platforms are supported by FMOD - # we have to check if the platform is supported first - try: - from UnityPy.export import AudioClipConverter + from fmod_toolkit.importer import import_pyfmodex - AudioClipConverter.import_pyfmodex() - except NotImplementedError: - return - except OSError: - # cibuildwheel doesn't copy the .so files - # so we have to skip the test on it - print("Failed to load the fmod lib for your system.") - print("Skipping the audioclip test.") - return - if AudioClipConverter.pyfmodex is False: + try: + import_pyfmodex() + except ValueError: + print("FMOD toolkit not available, skipping AudioClip tests") return + env = UnityPy.load(os.path.join(SAMPLES, "char_118_yuki.ab")) for obj in env.objects: if obj.type.name == "AudioClip": - clip = obj.read() + clip = obj.parse_as_object() assert len(clip.samples) == 1 @@ -98,7 +93,7 @@ def test_mesh(): wanted = f.read().replace(b"\r", b"") for obj in env.objects: if obj.type.name == "Mesh": - mesh = obj.read() + mesh = obj.parse_as_object() data = mesh.export() if isinstance(data, str): data = data.encode("utf8").replace(b"\r", b"")