diff --git a/.gitignore b/.gitignore index b1405525..85f41872 100644 --- a/.gitignore +++ b/.gitignore @@ -2,18 +2,15 @@ test.py AssetStudio/ .vscode/ - +uv.lock # Byte-compiled / optimized / DLL files __pycache__/ -.dump -.idea/ -*.py[cod] +*.py[codz] *$py.class -# precompiled C extensions -UnityPy/*.so -UnityPy/*.pyd +# C extensions +*.so # Distribution / packaging .Python @@ -23,18 +20,21 @@ dist/ downloads/ eggs/ .eggs/ +lib/ +lib64/ parts/ sdist/ var/ wheels/ +share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec @@ -45,14 +45,18 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ +.nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover +*.py.cover +*.lcov .hypothesis/ .pytest_cache/ +cover/ # Translations *.mo @@ -62,6 +66,7 @@ coverage.xml *.log local_settings.py db.sqlite3 +db.sqlite3-journal # Flask stuff: instance/ @@ -74,22 +79,85 @@ instance/ docs/_build/ # PyBuilder +.pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints -# pyenv -.python-version +# IPython +profile_default/ +ipython_config.py -# celery beat schedule file -celerybeat-schedule +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +# Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +# poetry.lock +# poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +# pdm.lock +# pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +# pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi/* +!.pixi/config.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule* +celerybeat.pid + +# Redis +*.rdb +*.aof +*.pid + +# RabbitMQ +mnesia/ +rabbitmq/ +rabbitmq-data/ + +# ActiveMQ +activemq-data/ # SageMath parsed files *.sage.py # Environments .env +.envrc .venv env/ venv/ @@ -109,3 +177,50 @@ venv.bak/ # mypy .mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +# .idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ +# Temporary file for partial code execution +tempCodeRunnerFile.py + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml diff --git a/LICENSE b/LICENSE index 6bc7be1f..bf224f94 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2021 K0lb3 +Copyright (c) 2019-2026 K0lb3 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/UnityPy/__init__.py b/UnityPy/__init__.py index 96f2b0ea..44828896 100644 --- a/UnityPy/__init__.py +++ b/UnityPy/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.25.1" +__version__ = "1.25.3" from .environment import Environment as Environment from .helpers.ArchiveStorageManager import ( diff --git a/UnityPy/classes/generated.py b/UnityPy/classes/generated.py index 70a336bb..2e269735 100644 --- a/UnityPy/classes/generated.py +++ b/UnityPy/classes/generated.py @@ -117,16 +117,18 @@ class BuiltAssetBundleInfoSet(Object): @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] + m_generatedFileCount: Optional[int] = None + m_generatedFileSize: Optional[int] = None + m_reusedSerializedFileCount: Optional[int] = None + m_reusedSerializedFileSize: Optional[int] = None + m_sizeReusedContentInOutputDirectory: Optional[int] = None @unitypy_define @@ -1521,6 +1523,7 @@ class Terrain(Behaviour): m_CastShadows: Optional[bool] = None m_DefaultSmoothness: Optional[float] = None m_DrawInstanced: Optional[bool] = None + m_EnableHeightmapLODFrustumCulling: Optional[bool] = None m_EnableHeightmapRayTracing: Optional[bool] = None m_EnableTreesAndDetailsRayTracing: Optional[bool] = None m_ExplicitProbeSetHash: Optional[Hash128] = None @@ -2535,6 +2538,7 @@ class SpriteRenderer(Renderer): m_Sprite: PPtr[Sprite] m_StaticBatchRoot: PPtr[Transform] m_AdaptiveModeThreshold: Optional[float] = None + m_BlendShapeWeights: Optional[List[float]] = None m_DrawMode: Optional[int] = None m_DynamicOccludee: Optional[int] = None m_FlipX: Optional[bool] = None @@ -3240,6 +3244,8 @@ class FBXImporter(ModelImporter): calculateBlendshapeNormalsDeltaFromImportedNormals: Optional[bool] = None generateMeshLods: Optional[bool] = None generateSecondaryUV: Optional[bool] = None + importUVs: Optional[int] = None + importVertexColors: Optional[bool] = None indexFormat: Optional[int] = None keepQuads: Optional[bool] = None legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: Optional[bool] = None @@ -3306,6 +3312,8 @@ class FBXImporter(ModelImporter): m_NodeNameCollisionStrategy: Optional[int] = None m_OldHashIdentity: Optional[MdFour] = None m_OptimizeGameObjects: Optional[bool] = None + m_PreBakeConvexCollisionMesh: Optional[bool] = None + m_PreBakeTriangleCollisionMesh: Optional[bool] = None m_PreserveHierarchy: Optional[bool] = None m_PreviousCalculatedGlobalScale: Optional[float] = None m_ReferencedClips: Optional[List[GUID]] = None @@ -3315,6 +3323,7 @@ class FBXImporter(ModelImporter): m_ResampleRotations: Optional[bool] = None m_RigImportErrors: Optional[str] = None m_RigImportWarnings: Optional[str] = None + m_SearchTexturesGlobally: Optional[bool] = None m_SortHierarchyByName: Optional[bool] = None m_SplitAnimations: Optional[bool] = None m_StrictVertexDataChecks: Optional[bool] = None @@ -3370,6 +3379,8 @@ class Mesh3DSImporter(ModelImporter): calculateBlendshapeNormalsDeltaFromImportedNormals: Optional[bool] = None generateMeshLods: Optional[bool] = None generateSecondaryUV: Optional[bool] = None + importUVs: Optional[int] = None + importVertexColors: Optional[bool] = None indexFormat: Optional[int] = None keepQuads: Optional[bool] = None legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: Optional[bool] = None @@ -3436,6 +3447,8 @@ class Mesh3DSImporter(ModelImporter): m_NodeNameCollisionStrategy: Optional[int] = None m_OldHashIdentity: Optional[MdFour] = None m_OptimizeGameObjects: Optional[bool] = None + m_PreBakeConvexCollisionMesh: Optional[bool] = None + m_PreBakeTriangleCollisionMesh: Optional[bool] = None m_PreserveHierarchy: Optional[bool] = None m_PreviousCalculatedGlobalScale: Optional[float] = None m_ReferencedClips: Optional[List[GUID]] = None @@ -3445,6 +3458,7 @@ class Mesh3DSImporter(ModelImporter): m_ResampleRotations: Optional[bool] = None m_RigImportErrors: Optional[str] = None m_RigImportWarnings: Optional[str] = None + m_SearchTexturesGlobally: Optional[bool] = None m_SortHierarchyByName: Optional[bool] = None m_SplitAnimations: Optional[bool] = None m_StrictVertexDataChecks: Optional[bool] = None @@ -3543,6 +3557,8 @@ class SketchUpImporter(ModelImporter): blendShapeNormalImportMode: Optional[int] = None calculateBlendshapeNormalsDeltaFromImportedNormals: Optional[bool] = None generateMeshLods: Optional[bool] = None + importUVs: Optional[int] = None + importVertexColors: Optional[bool] = None indexFormat: Optional[int] = None legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: Optional[bool] = None m_AddHumanoidExtraRootOnlyWhenUsingAvatar: Optional[bool] = None @@ -3573,6 +3589,8 @@ class SketchUpImporter(ModelImporter): m_MaterialLocation: Optional[int] = None m_Materials: Optional[List[SourceAssetIdentifier]] = None m_NodeNameCollisionStrategy: Optional[int] = None + m_PreBakeConvexCollisionMesh: Optional[bool] = None + m_PreBakeTriangleCollisionMesh: Optional[bool] = None m_PreserveHierarchy: Optional[bool] = None m_PreviousCalculatedGlobalScale: Optional[float] = None m_RemapMaterialsIfMaterialImportModeIsNone: Optional[bool] = None @@ -3581,6 +3599,7 @@ class SketchUpImporter(ModelImporter): m_ResampleRotations: Optional[bool] = None m_RigImportErrors: Optional[str] = None m_RigImportWarnings: Optional[str] = None + m_SearchTexturesGlobally: Optional[bool] = None m_SortHierarchyByName: Optional[bool] = None m_StrictVertexDataChecks: Optional[bool] = None m_SupportsEmbeddedMaterials: Optional[bool] = None @@ -3972,6 +3991,7 @@ class TextureImporter(AssetImporter): m_SpriteBorder: Optional[Vector4f] = None m_SpriteExtrude: Optional[int] = None m_SpriteGenerateFallbackPhysicsShape: Optional[int] = None + m_SpriteGeometrySubdivision: Optional[float] = None m_SpriteMeshType: Optional[int] = None m_SpriteMode: Optional[int] = None m_SpritePackingTag: Optional[str] = None @@ -3979,6 +3999,7 @@ class TextureImporter(AssetImporter): m_SpritePixelsToUnits: Optional[float] = None m_SpriteSheet: Optional[SpriteSheetMetaData] = None m_SpriteTessellationDetail: Optional[float] = None + m_SpriteTessellationMethod: Optional[int] = None m_StreamingMipmaps: Optional[int] = None m_StreamingMipmapsPriority: Optional[int] = None m_Swizzle: Optional[int] = None @@ -4281,6 +4302,193 @@ class BlockShaderSyntaxTree(NamedObject): source: str +@unitypy_define +class BuildProfilePlayerSettings(NamedObject): + AID: Hash128 + AndroidEnableSustainedPerformanceMode: bool + AndroidFilterTouchesWhenObscured: bool + AndroidProfiler: bool + Force_IOS_Speakers_When_Recording: bool + Prepare_IOS_For_Recording: bool + accelerometerFrequency: int + activeInputHandler: int + adjustIOSFPSUsingThermalState: bool + allowFullscreenSwitch: bool + allowHDRDisplaySupport: bool + allowedAutorotateToLandscapeLeft: bool + allowedAutorotateToLandscapeRight: bool + allowedAutorotateToPortrait: bool + allowedAutorotateToPortraitUpsideDown: bool + allowedHttpConnections: int + androidApplicationEntry: int + androidAutoRotationBehavior: int + androidBlitType: int + androidDefaultWindowHeight: int + androidDefaultWindowWidth: int + androidDisplayOptions: int + androidFullscreenMode: int + androidMaxAspectRatio: float + androidMinAspectRatio: float + androidMinimumWindowHeight: int + androidMinimumWindowWidth: int + androidPredictiveBackSupport: bool + androidRenderOutsideSafeArea: bool + androidRequestedVisibleInsets: int + androidResizeableActivity: bool + androidShowActivityIndicatorOnLoading: int + androidStartInFullscreen: bool + androidSupportedAspectRatio: int + androidSystemBarsBehavior: int + androidUseSwappy: bool + androidVulkanAllowFilterList: List[AndroidDeviceFilterData] + androidVulkanDenyFilterList: List[AndroidDeviceFilterData] + androidVulkanDeviceFilterListAsset: PPtr[VulkanDeviceFilterLists] + audioSpatialExperience: int + bakeCollisionMeshes: bool + bundleVersion: str + callOnDisableOnAssetBundleUnload: bool + cloudEnabled: bool + cloudProjectId: str + companyName: str + cursorHotspot: Vector2f + d3d12DeviceFilterListAsset: PPtr[D3D12DeviceFilterLists] + dedicatedServerOptimizations: bool + defaultCursor: PPtr[Texture2D] + defaultIsNativeResolution: bool + defaultScreenHeight: int + defaultScreenHeightWeb: int + defaultScreenOrientation: int + defaultScreenWidth: int + defaultScreenWidthWeb: int + deferSystemGesturesMode: int + disableDepthAndStencilBuffers: bool + enableDirectStorage: bool + enableFrameTimingStats: bool + enableOpenGLProfilerGPURecorders: bool + forceSingleInstance: bool + framebufferDepthMemorylessMode: int + fullscreenMode: int + gpuSkinning: bool + hdrBitDepth: int + hideHomeButton: bool + hmiLoadingImage: PPtr[Texture2D] + insecureHttpOption: int + invalidatedPatternTexture: PPtr[Texture2D] + iosShowActivityIndicatorOnLoading: int + iosUseCustomAppBackgroundBehavior: bool + legacyClampBlendShapeWeights: bool + loadStoreDebugModeEnabled: bool + m_ActiveColorSpace: int + m_ColorGamuts: List[int] + m_MTRendering: bool + m_Name: str + m_ShowUnitySplashLogo: bool + m_ShowUnitySplashScreen: bool + m_SplashScreenAnimation: int + m_SplashScreenBackgroundAnimationZoom: float + m_SplashScreenBackgroundColor: ColorRGBA + m_SplashScreenBackgroundLandscape: PPtr[Texture2D] + m_SplashScreenBackgroundLandscapeAspect: float + m_SplashScreenBackgroundLandscapeUvs: Rectf + m_SplashScreenBackgroundPortrait: PPtr[Texture2D] + m_SplashScreenBackgroundPortraitAspect: float + m_SplashScreenBackgroundPortraitUvs: Rectf + m_SplashScreenDrawMode: int + m_SplashScreenLogoAnimationZoom: float + m_SplashScreenLogoStyle: int + m_SplashScreenLogos: List[SplashScreenLogo] + m_SplashScreenOverlayOpacity: float + m_SpriteBatchMaxVertexCount: int + m_SpriteBatchVertexThreshold: int + m_StackTraceTypes: List[int] + m_StereoRenderingPath: int + m_UnitySplashLogo: PPtr[Sprite] + m_VirtualRealitySplashScreen: PPtr[Texture2D] + macAppStoreCategory: str + macRetinaSupport: bool + meshDeformation: int + metalFramebufferOnly: bool + metalUseMetalDisplayLink: bool + metroInputSource: int + mipStripping: bool + mobileMTRenderingBaked: bool + muteOtherAudioSources: bool + numberOfMipsStripped: int + numberOfMipsStrippedPerMipmapLimitGroup: List[Tuple[str, int]] + organizationId: str + platformRequiresReadableAssets: bool + playerMinOpenGLESVersion: int + preloadedAssets: List[PPtr[Object]] + preserveFramebufferAlpha: bool + productGUID: GUID + productName: str + projectName: str + qualitySettingsNames: List[str] + resetResolutionOnWindowResize: bool + resizableWindow: bool + resolutionScalingMode: int + runInBackground: bool + submitAnalytics: bool + switchAllowGpuScratchShrinking: bool + switchGpuScratchPoolGranularity: int + switchGraphicsJobsSyncAfterKick: bool + switchMaxWorkerMultiple: int + switchNVNDefaultPoolsGranularity: int + switchNVNGraphicsFirmwareMemory: int + switchNVNMaxPublicSamplerIDCount: int + switchNVNMaxPublicTextureIDCount: int + switchNVNOtherPoolsGranularity: int + switchNVNShaderPoolsGranularity: int + switchQueueCommandMemory: int + switchQueueComputeMemory: int + switchQueueControlMemory: int + targetDevice: int + targetPixelDensity: int + thermalStateCriticalIOSFPS: int + thermalStateSeriousIOSFPS: int + tvOSBundleVersion: str + unsupportedMSAAFallback: int + use32BitDisplayBuffer: bool + useFlipModelSwapchain: bool + useHDRDisplay: bool + useMacAppStoreValidation: bool + useOSAutorotation: bool + useOnDemandResources: bool + usePlayerLog: bool + virtualTexturingSupportEnabled: bool + visibleInBackground: bool + visionOSBundleVersion: str + vrSettings: VRSettings + vulkanEnableCommandBufferRecycling: bool + vulkanEnableLateAcquireNextImage: bool + vulkanEnablePreTransform: bool + vulkanEnableSetSRGBWrite: bool + vulkanNumSwapchainBuffers: int + webGPUDeviceFilterListAsset: PPtr[WebGPUDeviceFilterLists] + windowsGamepadBackendHint: int + wsaTransparentSwapchain: bool + xboxEnableAvatar: bool + xboxEnableFitness: bool + xboxEnableGuest: bool + xboxEnableHeadOrientation: bool + xboxEnableKinect: bool + xboxEnableKinectAutoTracking: bool + xboxEnablePIXSampling: bool + xboxOneDisableEsram: bool + xboxOneDisableKinectGpuReservation: bool + xboxOneEnable7thCore: bool + xboxOneEnableTypeOptimization: bool + xboxOneLoggingLevel: int + xboxOneMonoLoggingLevel: int + xboxOnePresentImmediateThreshold: int + xboxOneResolution: int + xboxOneSResolution: int + xboxOneXResolution: int + xboxPIXTextureCapture: bool + xboxSpeechDB: int + webProgressiveAssetLoading: Optional[bool] = None + + @unitypy_define class BuildReport(NamedObject): m_Appendices: List[PPtr[Object]] @@ -4608,7 +4816,6 @@ class Mesh(NamedObject): m_IndexBuffer: List[int] m_LocalAABB: AABB m_MeshCompression: int - m_MeshUsageFlags: int m_Name: str m_SubMeshes: List[SubMesh] m_BakedConvexCollisionMesh: Optional[List[int]] = None @@ -4626,7 +4833,10 @@ class Mesh(NamedObject): m_MeshLodInfo: Optional[MeshLodInfo] = None m_MeshMetrics_0_: Optional[float] = None m_MeshMetrics_1_: Optional[float] = None + m_MeshUsageFlags: Optional[int] = None m_Normals: Optional[List[Vector3f]] = None + m_PreBakeConvexCollisionMesh: Optional[bool] = None + m_PreBakeTriangleCollisionMesh: Optional[bool] = None m_RootBoneNameHash: Optional[int] = None m_ShapeVertices: Optional[List[MeshBlendShapeVertex]] = None m_Shapes: Optional[Union[BlendShapeData, List[MeshBlendShape]]] = None @@ -4886,11 +5096,11 @@ class Sprite(NamedObject): class SpriteAtlas(NamedObject): m_IsVariant: bool m_Name: str - m_PackedSpriteNamesToIndex: List[str] - m_PackedSprites: List[PPtr[Sprite]] m_RenderDataMap: List[Tuple[Tuple[GUID, int], SpriteAtlasData]] m_Tag: str m_Guid: Optional[GUID] = None + m_PackedSpriteNamesToIndex: Optional[List[str]] = None + m_PackedSprites: Optional[List[PPtr[Sprite]]] = None @unitypy_define @@ -5262,6 +5472,12 @@ class Texture3D(Texture): m_UsageMode: Optional[int] = None +@unitypy_define +class UIAnimationClip(NamedObject): + m_AnimationClip: PPtr[AnimationClip] + m_Name: str + + @unitypy_define class VideoClip(NamedObject): Height: int @@ -5328,6 +5544,13 @@ class VulkanDeviceFilterLists(NamedObject): m_VulkanDenyFilterList: List[AndroidDeviceFilterData] +@unitypy_define +class WebGPUDeviceFilterLists(NamedObject): + m_AllowFilterList: List[WebGPUDeviceFilterData] + m_DenyFilterList: List[WebGPUDeviceFilterData] + m_Name: str + + @unitypy_define class EditorExtensionImpl(Object): gFlattenedTypeTree: Optional[List[int]] = None @@ -5350,6 +5573,7 @@ class EditorSettings(Object): m_CacheServerEnableTls: Optional[bool] = None m_CacheServerEnableUpload: Optional[bool] = None m_CacheServerEndpoint: Optional[str] = None + m_CacheServerImportResultCachingEnabled: Optional[bool] = None m_CacheServerMode: Optional[int] = None m_CacheServerNamespacePrefix: Optional[str] = None m_CacheServerValidationMode: Optional[int] = None @@ -5359,6 +5583,7 @@ class EditorSettings(Object): m_DisableCookiesInLightmapper: Optional[bool] = None m_EnableEditorAsyncCPUTextureLoading: Optional[bool] = None m_EnableEnlightenBakedGI: Optional[bool] = None + m_EnableMSBuildCompilationPipeline: Optional[bool] = None m_EnableRoslynAnalyzers: Optional[bool] = None m_EnableTextureStreamingInEditMode: Optional[bool] = None m_EnableTextureStreamingInPlayMode: Optional[bool] = None @@ -5391,6 +5616,7 @@ class EditorSettings(Object): m_SpritePackerMode: Optional[int] = None m_SpritePackerPaddingPower: Optional[int] = None m_UnlockBlockShaders: Optional[bool] = None + m_UseLegacyHierarchy: Optional[bool] = None m_UseLegacyProbeSampleCount: Optional[bool] = None m_UserGeneratedProjectSuffix: Optional[str] = None m_WebSecurityEmulationEnabled: Optional[int] = None @@ -5569,6 +5795,7 @@ class EditorUserSettings(Object): m_SemanticMergeMode: Optional[int] = None m_StandbyImportWorkerCount: Optional[int] = None m_VCAllowAsyncUpdate: Optional[bool] = None + m_VCAutoRevertUnchangedFiles: Optional[bool] = None m_VCHierarchyOverlayIcons: Optional[bool] = None m_VCOtherOverlayIcons: Optional[bool] = None m_VCOverlayIcons: Optional[bool] = None @@ -5630,7 +5857,6 @@ class AudioManager(GlobalGameManager): @unitypy_define class BuildSettings(GlobalGameManager): - enableDynamicBatching: bool hasAdvancedVersion: bool hasPROVersion: bool hasPublishingRights: bool @@ -5639,6 +5865,7 @@ class BuildSettings(GlobalGameManager): m_Version: str buildGUID: Optional[Union[GUID, str]] = None buildTags: Optional[List[str]] = None + enableDynamicBatching: Optional[bool] = None enableMultipleDisplays: Optional[bool] = None enabledVRDevices: Optional[List[str]] = None hasClusterRendering: Optional[bool] = None @@ -5685,15 +5912,20 @@ class DelayedCallManager(GlobalGameManager): @unitypy_define class GraphicsSettings(GlobalGameManager): m_AlwaysIncludedShaders: List[PPtr[Shader]] + m_AdditionalWarmupCollections: Optional[List[PPtr[GraphicsStateCollection]]] = None m_AllowEnlightenSupportForUpgradedProject: Optional[bool] = None + m_CacheMissCollectionPath: Optional[str] = None m_CameraRelativeLightCulling: Optional[bool] = None m_CameraRelativeShadowCulling: Optional[bool] = None + m_CollectionStartupAction: Optional[int] = None m_CurrentRenderPipelineGlobalSettings: Optional[PPtr[Object]] = None m_CustomRenderPipeline: Optional[PPtr[MonoBehaviour]] = None m_DefaultRenderingLayerMask: Optional[int] = None m_Deferred: Optional[BuiltinShaderSettings] = None m_DeferredReflections: Optional[BuiltinShaderSettings] = None m_DepthNormals: Optional[BuiltinShaderSettings] = None + m_EnableCacheMissTracing: Optional[bool] = None + m_GraphicsStateCollection: Optional[PPtr[GraphicsStateCollection]] = None m_LegacyDeferred: Optional[BuiltinShaderSettings] = None m_LensFlare: Optional[BuiltinShaderSettings] = None m_LightHalo: Optional[BuiltinShaderSettings] = None @@ -5716,9 +5948,13 @@ class GraphicsSettings(GlobalGameManager): m_TierSettings_Tier1: Optional[TierGraphicsSettings] = None m_TierSettings_Tier2: Optional[TierGraphicsSettings] = None m_TierSettings_Tier3: Optional[TierGraphicsSettings] = None + m_TraceSavePath: Optional[str] = None + m_TraceSendToEditor: Optional[bool] = None m_TransparencySortAxis: Optional[Vector3f] = None m_TransparencySortMode: Optional[int] = None m_VideoShadersIncludeMode: Optional[int] = None + m_WarmupAsync: Optional[bool] = None + m_WarmupProgressivelyLimit: Optional[int] = None @unitypy_define @@ -5813,6 +6049,11 @@ class Physics2DSettings(GlobalGameManager): m_VelocityThreshold: Optional[float] = None +@unitypy_define +class PhysicsCoreProjectSettings2D(GlobalGameManager): + m_PhysicsCoreSettings: PPtr[Object] + + @unitypy_define class PhysicsManager(GlobalGameManager): m_BounceThreshold: float @@ -5862,6 +6103,7 @@ class PhysicsManager(GlobalGameManager): m_SolverIterationCount: Optional[int] = None m_SolverType: Optional[int] = None m_SolverVelocityIterations: Optional[int] = None + m_ThreadingMode: Optional[int] = None m_WorldBounds: Optional[AABB] = None m_WorldSubdivisions: Optional[int] = None @@ -5928,6 +6170,7 @@ class PlayerSettings(GlobalGameManager): bakeCollisionMeshes: Optional[bool] = None bundleIdentifier: Optional[str] = None bundleVersion: Optional[str] = None + callOnDisableOnAssetBundleUnload: Optional[bool] = None captureSingleScreen: Optional[bool] = None cloudEnabled: Optional[bool] = None cloudProjectId: Optional[str] = None @@ -5946,6 +6189,7 @@ class PlayerSettings(GlobalGameManager): disableDepthAndStencilBuffers: Optional[bool] = None disableOldInputManagerSupport: Optional[bool] = None displayResolutionDialog: Optional[int] = None + enableDirectStorage: Optional[bool] = None enableFrameTimingStats: Optional[bool] = None enableGamepadInput: Optional[bool] = None enableHWStatistics: Optional[bool] = None @@ -6012,6 +6256,7 @@ class PlayerSettings(GlobalGameManager): macRetinaSupport: Optional[bool] = None meshDeformation: Optional[int] = None metalFramebufferOnly: Optional[bool] = None + metalUseMetalDisplayLink: Optional[bool] = None metroEnableIndependentInputSource: Optional[bool] = None metroEnableLowLatencyPresentationAPI: Optional[bool] = None metroInputSource: Optional[int] = None @@ -6087,6 +6332,8 @@ class PlayerSettings(GlobalGameManager): vulkanEnableSetSRGBWrite: Optional[bool] = None vulkanNumSwapchainBuffers: Optional[int] = None vulkanUseSWCommandBuffers: Optional[bool] = None + webGPUDeviceFilterListAsset: Optional[PPtr[WebGPUDeviceFilterLists]] = None + webProgressiveAssetLoading: Optional[bool] = None wiiHio2Usage: Optional[int] = None wiiLoadingScreenBackground: Optional[ColorRGBA] = None wiiLoadingScreenFileName: Optional[str] = None @@ -6490,6 +6737,12 @@ class ShaderContainer(Object): pass +@unitypy_define +class ShaderIncludeReflection(Object): + m_Functions: List[ReflectedFunction] + m_ReflectionLog: ErrorLog + + @unitypy_define class SiblingDerived(Object): pass @@ -6547,6 +6800,11 @@ class TilemapEditorUserSettings(Object): m_LastUsedPalette: PPtr[GameObject] +@unitypy_define +class UIAnimationBinder(Object): + pass + + @unitypy_define class VersionControlSettings(Object): m_Mode: str @@ -6887,6 +7145,13 @@ class Axes: m_Type: int +@unitypy_define +class Binding: + m_Slot: int + m_EncodedData: Optional[int] = None + m_Set: Optional[int] = None + + @unitypy_define class BitField: m_Bits: int @@ -7088,6 +7353,15 @@ class BufferBinding: m_ArraySize: Optional[int] = None +@unitypy_define +class BufferBindingParameter: + m_ArraySize: int + m_NameIndex: int + m_Binding: Optional[Binding] = None + m_Index: Optional[int] = None + m_ResourceType: Optional[int] = None + + @unitypy_define class BuildReportFile: id: int @@ -7141,7 +7415,11 @@ class BuildSummary: buildContentOptions: Optional[int] = None buildGUID: Optional[GUID] = None buildManifestHash: Optional[Hash128] = None + buildName: Optional[str] = None + buildProfileGuid: Optional[GUID] = None + buildProfilePath: Optional[str] = None buildResult: Optional[int] = None + buildSessionGUID: Optional[GUID] = None buildStartTime: Optional[DateTime] = None buildType: Optional[int] = None dataPath: Optional[str] = None @@ -7522,7 +7800,7 @@ class ComputeShaderCB: @unitypy_define class ComputeShaderKernel: - builtinSamplers: List[ComputeShaderBuiltinSampler] + builtinSamplers: Union[List[ComputeShaderBuiltinSampler], List[SamplerParameter]] cbs: List[ComputeShaderResource] code: List[int] inBuffers: List[ComputeShaderResource] @@ -7567,10 +7845,12 @@ class ComputeShaderPlatformVariant: @unitypy_define class ComputeShaderResource: - bindPoint: int name: Union[FastPropertyName, str] + bindPoint: Optional[int] = None counter: Optional[ComputeBufferCounter] = None generatedName: Optional[Union[FastPropertyName, str]] = None + m_Binding: Optional[Binding] = None + m_SamplerBinding: Optional[Binding] = None resType: Optional[int] = None samplerBindPoint: Optional[int] = None secondaryBindPoint: Optional[int] = None @@ -7618,6 +7898,16 @@ class ConstantBuffer: m_StructParams: Optional[List[StructParameter]] = None +@unitypy_define +class ConstantBufferParameter: + m_IsPartialCB: bool + m_MatrixParams: List[MatrixParameter] + m_NameIndex: int + m_Size: int + m_StructParams: List[StructParameter] + m_VectorParams: List[VectorParameter] + + @unitypy_define class ConstantClip: data: List[float] @@ -7846,6 +8136,11 @@ class EmissionModule: time3: Optional[float] = None +@unitypy_define +class EnlightenCAHMap: + m_Map: List[Tuple[LookupKey, Hash128]] + + @unitypy_define class EnlightenRendererInformation: dynamicLightmapSTInSystem: Vector4f @@ -7860,6 +8155,7 @@ class EnlightenSceneMapping: m_SystemAtlases: List[EnlightenSystemAtlasInformation] m_Systems: List[EnlightenSystemInformation] m_TerrainChunks: List[EnlightenTerrainChunksInformation] + m_CAHMap: Optional[EnlightenCAHMap] = None m_Probesets: Optional[List[Hash128]] = None @@ -7902,6 +8198,12 @@ class Error: startLine: int +@unitypy_define +class ErrorLog: + m_HasErrors: bool + m_Messages: List[Message] + + @unitypy_define class ExpandedData: m_ClassID: int @@ -7923,6 +8225,10 @@ class Expression: data_3_: int op: int valueIndex: int + dataSize: Optional[int] = None + dataType: Optional[int] = None + jmpCode: Optional[int] = None + scalarSwitchCase: Optional[int] = None @unitypy_define @@ -8037,6 +8343,7 @@ class GenericBinding: classID: Optional[int] = None isIntCurve: Optional[int] = None isSerializeReferenceCurve: Optional[int] = None + metaData: Optional[int] = None typeID: Optional[int] = None @@ -8297,6 +8604,12 @@ class HierarchicalSceneData: m_SceneGUID: GUID +@unitypy_define +class Hint: + m_Key: str + m_Value: str + + @unitypy_define class HoloLens: depthFormat: int @@ -8791,6 +9104,12 @@ class LodSelectionCurve: m_LodSlope: float +@unitypy_define +class LookupKey: + extension: str + hash: Hash128 + + @unitypy_define class Lumin: depthFormat: int @@ -8826,10 +9145,11 @@ class MaterialInstanceSettings: @unitypy_define class MatrixParameter: m_ArraySize: int - m_Index: int m_NameIndex: int m_RowCount: int m_Type: int + m_Index: Optional[int] = None + m_OffsetInConstantBuffer: Optional[int] = None @unitypy_define @@ -8882,6 +9202,14 @@ class MeshLodSubMesh: m_Levels: List[MeshLodRange] +@unitypy_define +class Message: + m_Code: int + m_Location: SourceLocation + m_Severity: int + m_Text: Optional[str] = None + + @unitypy_define class MinMaxAABB: m_Max: Vector3f @@ -9392,6 +9720,19 @@ class ProceduralTextureAssignment: shaderProp: Union[FastPropertyName, str] +@unitypy_define +class ProgramParameters: + m_BufferParams: List[BufferBindingParameter] + m_ConstantBufferBindings: List[BufferBindingParameter] + m_ConstantBuffers: List[ConstantBufferParameter] + m_Samplers: List[SamplerParameter] + m_TextureParams: List[TextureParameter] + m_UAVParams: List[UAVParameter] + m_MatrixParams: Optional[List[MatrixParameter]] = None + m_SpecializationConstantParams: Optional[List[SpecializationConstantParameter]] = None + m_VectorParams: Optional[List[VectorParameter]] = None + + @unitypy_define class PropertyModification: objectReference: PPtr[Object] @@ -9547,6 +9888,7 @@ class RayTracingShaderResource: texDimension: int arraySize: Optional[int] = None multisampled: Optional[bool] = None + resType: Optional[int] = None @unitypy_define @@ -9574,6 +9916,24 @@ class Rectf: y: float +@unitypy_define +class ReflectedFunction: + m_Hints: List[Hint] + m_Name: str + m_Parameters: List[ReflectedParameter] + m_ReturnTypeName: str + m_Body: Optional[str] = None + m_Namespace: Optional[List[str]] = None + + +@unitypy_define +class ReflectedParameter: + m_Direction: int + m_Hints: List[Hint] + m_Name: str + m_TypeName: str + + @unitypy_define class RenderManager(GlobalGameManager): pass @@ -10009,8 +10369,9 @@ class SampleSettings: @unitypy_define class SamplerParameter: - bindPoint: int sampler: int + bindPoint: Optional[int] = None + m_Binding: Optional[Binding] = None @unitypy_define @@ -10112,6 +10473,7 @@ class SerializedPass: m_HasProceduralInstancingVariant: Optional[bool] = None m_LocalKeywordMask: Optional[List[int]] = None m_Platforms: Optional[List[int]] = None + m_SerializedDynamicBranchKeywordMask: Optional[List[int]] = None m_SerializedKeywordStateMask: Optional[List[int]] = None progRayTracing: Optional[SerializedProgram] = None @@ -10127,7 +10489,7 @@ class SerializedPlayerSubProgram: @unitypy_define class SerializedProgram: m_SubPrograms: List[SerializedSubProgram] - m_CommonParameters: Optional[SerializedProgramParameters] = None + m_CommonParameters: Optional[Union[ProgramParameters, SerializedProgramParameters]] = None m_ParameterBlobIndices: Optional[List[List[int]]] = None m_PlayerSubPrograms: Optional[List[List[SerializedPlayerSubProgram]]] = None m_SerializedKeywordStateMask: Optional[List[int]] = None @@ -10268,7 +10630,7 @@ class SerializedSubProgram: m_KeywordIndices: Optional[List[int]] = None m_LocalKeywordIndices: Optional[List[int]] = None m_MatrixParams: Optional[List[MatrixParameter]] = None - m_Parameters: Optional[SerializedProgramParameters] = None + m_Parameters: Optional[Union[ProgramParameters, SerializedProgramParameters]] = None m_Samplers: Optional[List[SamplerParameter]] = None m_ShaderRequirements: Optional[int] = None m_TextureParams: Optional[List[TextureParameter]] = None @@ -10489,6 +10851,12 @@ class SourceAssetIdentifier: type: str +@unitypy_define +class SourceLocation: + m_File: str + m_Position: int + + @unitypy_define class SourceTextureInformation: doesTextureContainAlpha: bool @@ -10498,6 +10866,12 @@ class SourceTextureInformation: sourceWasHDR: Optional[bool] = None +@unitypy_define +class SpecializationConstantParameter: + m_Binding: Binding + m_NameIndex: int + + @unitypy_define class SpeedTreeWind: BRANCH_DIRECTIONAL_1: bool @@ -10835,6 +11209,7 @@ class SpriteAtlasData: uvTransform: Vector4f atlasRectOffset: Optional[Vector2f] = None secondaryTextures: Optional[List[SecondarySpriteTexture]] = None + spriteInstanceData: Optional[SpriteInstanceData] = None @unitypy_define @@ -10879,6 +11254,23 @@ class SpriteData: sprite: PPtr[Object] +@unitypy_define +class SpriteInstanceData: + border: Vector4f + m_Bindpose: List[Matrix4x4f] + m_BlendShapes: BlendShapeData + m_IndexBuffer: List[int] + m_IndexFormat: int + m_SubMeshes: List[SubMesh] + m_VertexData: VertexData + physicsShape: List[List[Vector2f]] + pivot: Vector2f + pixelsToUnits: float + rect: Rectf + spriteBones: List[SpriteBone] + spriteName: str + + @unitypy_define class SpriteMetaData: m_Alignment: int @@ -10910,6 +11302,7 @@ class SpriteRenderData: downscaleMultiplier: Optional[float] = None indices: Optional[List[int]] = None m_Bindpose: Optional[List[Matrix4x4f]] = None + m_BlendShapes: Optional[BlendShapeData] = None m_IndexBuffer: Optional[List[int]] = None m_SourceSkin: Optional[List[BoneWeights4]] = None m_SubMeshes: Optional[List[SubMesh]] = None @@ -11073,11 +11466,12 @@ class StreamingInfo: @unitypy_define class StructParameter: m_ArraySize: int - m_Index: int m_MatrixMembers: List[MatrixParameter] m_NameIndex: int m_StructSize: int m_VectorMembers: List[VectorParameter] + m_Index: Optional[int] = None + m_OffsetInConstantBuffer: Optional[int] = None @unitypy_define @@ -11234,10 +11628,12 @@ class TextureImporterPlatformSettings: @unitypy_define class TextureParameter: m_Dim: int - m_Index: int m_NameIndex: int - m_SamplerIndex: int + m_Binding: Optional[Binding] = None + m_Index: Optional[int] = None m_MultiSampled: Optional[bool] = None + m_SamplerBinding: Optional[Binding] = None + m_SamplerIndex: Optional[int] = None @unitypy_define @@ -11415,9 +11811,11 @@ class TypeStats: @unitypy_define class UAVParameter: - m_Index: int m_NameIndex: int - m_OriginalIndex: int + m_Binding: Optional[Binding] = None + m_Index: Optional[int] = None + m_OriginalBinding: Optional[Binding] = None + m_OriginalIndex: Optional[int] = None @unitypy_define @@ -11516,6 +11914,7 @@ class VFXCPUBufferDesc: initialData: VFXCPUBufferData layout: List[VFXLayoutElementDesc] stride: int + debugName: Optional[str] = None @unitypy_define @@ -11593,6 +11992,7 @@ class VFXGPUBufferDesc: layout: List[VFXLayoutElementDesc] size: int stride: int + debugName: Optional[str] = None mode: Optional[int] = None target: Optional[int] = None type: Optional[int] = None @@ -11680,7 +12080,7 @@ class VFXSystemDesc: class VFXTaskDesc: buffers: List[VFXMapping] params: List[VFXMapping] - processor: PPtr[NamedObject] + processor: Union[PPtr[NamedObject], PPtr[Object]] type: int values: List[VFXMapping] instanceSplitIndex: Optional[int] = None @@ -11774,9 +12174,10 @@ class Vector3Curve: class VectorParameter: m_ArraySize: int m_Dim: int - m_Index: int m_NameIndex: int m_Type: int + m_Index: Optional[int] = None + m_OffsetInConstantBuffer: Optional[int] = None @unitypy_define @@ -11892,6 +12293,23 @@ class VulkanGraphicsJobsDeviceFilterData: preferredMode: int +@unitypy_define +class WebGPUDeviceFilterData: + browserName: str + browserVersion: str + browserVersionComparator: int + deviceType: int + features: List[int] + limits: List[WebGPUDeviceFilterLimit] + + +@unitypy_define +class WebGPUDeviceFilterLimit: + comparator: int + limit: int + value: int + + @unitypy_define class WheelFrictionCurve: asymptoteSlip: Optional[float] = None diff --git a/UnityPy/cli/update_tpk.py b/UnityPy/cli/update_tpk.py index 6cb776c9..f2a51273 100644 --- a/UnityPy/cli/update_tpk.py +++ b/UnityPy/cli/update_tpk.py @@ -1,27 +1,29 @@ import os -from io import BytesIO -from urllib.request import urlopen -from zipfile import ZipFile -from UnityPy.tools.TpkClassGenerator import generate_classes +from tpk_ar.utils import download_tpk -URL = "https://nightly.link/AssetRipper/Tpk/workflows/type_tree_tpk/master/lzma_file.zip" RESOURCE_PATH = os.path.join(os.path.dirname(__file__), "..", "resources") 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("lzma.tpk", path=RESOURCE_PATH) + tpk_data = download_tpk() + + print("\tSaving...") + with open(os.path.join(RESOURCE_PATH, "lzma.tpk"), "wb") as f: + f.write(tpk_data) + print("\tGenerating classes...") + # import here to avoid loading a potentially broken or missing tpk file + + from UnityPy.tools.TpkClassGenerator import generate_classes + generate_classes() print("\tDone.") __all__ = ["update_tpk"] + +if __name__ == "__main__": + update_tpk() diff --git a/UnityPy/enums/CommonString.py b/UnityPy/enums/CommonString.py deleted file mode 100644 index 0491b660..00000000 --- a/UnityPy/enums/CommonString.py +++ /dev/null @@ -1,110 +0,0 @@ -CommonString = { - 0: "AABB", - 5: "AnimationClip", - 19: "AnimationCurve", - 34: "AnimationState", - 49: "Array", - 55: "Base", - 60: "BitField", - 69: "bitset", - 76: "bool", - 81: "char", - 86: "ColorRGBA", - 96: "Component", - 106: "data", - 111: "deque", - 117: "double", - 124: "dynamic_array", - 138: "FastPropertyName", - 155: "first", - 161: "float", - 167: "Font", - 172: "GameObject", - 183: "Generic Mono", - 196: "GradientNEW", - 208: "GUID", - 213: "GUIStyle", - 222: "int", - 226: "list", - 231: "long long", - 241: "map", - 245: "Matrix4x4f", - 256: "MdFour", - 263: "MonoBehaviour", - 277: "MonoScript", - 288: "m_ByteSize", - 299: "m_Curve", - 307: "m_EditorClassIdentifier", - 331: "m_EditorHideFlags", - 349: "m_Enabled", - 359: "m_ExtensionPtr", - 374: "m_GameObject", - 387: "m_Index", - 395: "m_IsArray", - 405: "m_IsStatic", - 416: "m_MetaFlag", - 427: "m_Name", - 434: "m_ObjectHideFlags", - 452: "m_PrefabInternal", - 469: "m_PrefabParentObject", - 490: "m_Script", - 499: "m_StaticEditorFlags", - 519: "m_Type", - 526: "m_Version", - 536: "Object", - 543: "pair", - 548: "PPtr", - 564: "PPtr", - 581: "PPtr", - 596: "PPtr", - 616: "PPtr", - 633: "PPtr", - 646: "PPtr", - 659: "PPtr", - 672: "PPtr", - 688: "PPtr", - 702: "PPtr", - 718: "PPtr", - 734: "Prefab", - 741: "Quaternionf", - 753: "Rectf", - 759: "RectInt", - 767: "RectOffset", - 778: "second", - 785: "set", - 789: "short", - 795: "size", - 800: "SInt16", - 807: "SInt32", - 814: "SInt64", - 821: "SInt8", - 827: "staticvector", - 840: "string", - 847: "TextAsset", - 857: "TextMesh", - 866: "Texture", - 874: "Texture2D", - 884: "Transform", - 894: "TypelessData", - 907: "UInt16", - 914: "UInt32", - 921: "UInt64", - 928: "UInt8", - 934: "unsigned int", - 947: "unsigned long long", - 966: "unsigned short", - 981: "vector", - 988: "Vector2f", - 997: "Vector3f", - 1006: "Vector4f", - 1015: "m_ScriptingClassIdentifier", - 1042: "Gradient", - 1051: "Type*", - 1057: "int2_storage", - 1070: "int3_storage", - 1083: "BoundsInt", - 1093: "m_CorrespondingSourceObject", - 1121: "m_PrefabInstance", - 1138: "m_PrefabAsset", - 1152: "FileSize", -} diff --git a/UnityPy/enums/__init__.py b/UnityPy/enums/__init__.py index fe4db176..c9a99d83 100644 --- a/UnityPy/enums/__init__.py +++ b/UnityPy/enums/__init__.py @@ -2,7 +2,6 @@ from .BuildTarget import BuildTarget from .BundleFile import ArchiveFlags, ArchiveFlagsOld, CompressionFlags from .ClassIDType import ClassIDType -from .CommonString import CommonString from .FileType import FileType from .GfxPrimitiveType import GfxPrimitiveType from .GraphicsFormat import GraphicsFormat @@ -25,7 +24,6 @@ "ArchiveFlagsOld", "CompressionFlags", "ClassIDType", - "CommonString", "FileType", "GfxPrimitiveType", "GraphicsFormat", diff --git a/UnityPy/environment.py b/UnityPy/environment.py index a44d228f..c4a60aea 100644 --- a/UnityPy/environment.py +++ b/UnityPy/environment.py @@ -32,6 +32,7 @@ class Environment: local_files: List[str] local_files_simple: List[str] typetree_generator: Optional["TypeTreeGenerator"] = None + _container_index_built: bool = False def __init__(self, *args: FileSourceType, fs: Optional[AbstractFileSystem] = None, path: Optional[str] = None): self.files = {} @@ -39,6 +40,7 @@ def __init__(self, *args: FileSourceType, fs: Optional[AbstractFileSystem] = Non self.fs = fs or LocalFileSystem() self.local_files = [] self.local_files_simple = [] + self._container_index_built = False if path is None: # if no path is given, use the current working directory @@ -203,9 +205,19 @@ def search(item): return search(self) + def _build_container_index(self) -> None: + if self._container_index_built: + return + + self._container_index_built = True + for f in self.cabs.values(): + if isinstance(f, SerializedFile): + f.container.parse_preload_table() + @property def container(self) -> ContainerHelper: """Returns a dictionary of all objects in the Environment.""" + self._build_container_index() container = [] for f in self.cabs.values(): if isinstance(f, SerializedFile) and not f.is_dependency: @@ -249,6 +261,7 @@ def register_cab(self, name: str, item: Union[SerializedFile, EndianBinaryReader The file to register. """ self.cabs[simplify_name(name)] = item + self._container_index_built = False def get_cab(self, name: str) -> Union[SerializedFile, EndianBinaryReader, None]: """ diff --git a/UnityPy/export/Texture2DConverter.py b/UnityPy/export/Texture2DConverter.py index f3462a41..cdb743ee 100644 --- a/UnityPy/export/Texture2DConverter.py +++ b/UnityPy/export/Texture2DConverter.py @@ -512,7 +512,7 @@ def rgb9e5float(image_data: bytes, width: int, height: int) -> Image.Image: TF.RGB9e5Float: (rgb9e5float, ()), TF.BC4: (pillow, ("L", "bcn", 4)), TF.BC5: (pillow, ("RGB", "bcn", 5)), - TF.BC6H: (pillow, ("RGBA", "bcn", 6)), + TF.BC6H: (pillow, ("RGB", "bcn", 6)), TF.BC7: (pillow, ("RGBA", "bcn", 7)), TF.DXT1Crunched: (pillow, ("RGBA", "bcn", 1)), TF.DXT5Crunched: (pillow, ("RGBA", "bcn", 3)), diff --git a/UnityPy/files/ObjectReader.py b/UnityPy/files/ObjectReader.py index fc03b874..b5fe1dbd 100644 --- a/UnityPy/files/ObjectReader.py +++ b/UnityPy/files/ObjectReader.py @@ -198,6 +198,9 @@ def peek_name(self) -> Union[str, None]: @property def container(self): + env = self.assets_file.environment + if env is not None: + env._build_container_index() return self.assets_file._container.path_dict.get(self.path_id) @property diff --git a/UnityPy/files/SerializedFile.py b/UnityPy/files/SerializedFile.py index 1ca608a6..d8853378 100644 --- a/UnityPy/files/SerializedFile.py +++ b/UnityPy/files/SerializedFile.py @@ -6,8 +6,9 @@ from attrs import define from .. import config -from ..enums import BuildTarget, ClassIDType, CommonString +from ..enums import BuildTarget, ClassIDType from ..helpers.ContainerHelper import ContainerHelper +from ..helpers.Tpk import get_common_strings from ..helpers.TypeTreeHelper import TypeTreeNode from ..helpers.UnityVersion import UnityVersion from ..streams import EndianBinaryWriter @@ -500,4 +501,4 @@ def read_string(string_buffer_reader: EndianBinaryReader, value: int) -> str: return string_buffer_reader.read_string_to_null() offset = value & 0x7FFFFFFF - return CommonString.get(offset, str(offset)) + return get_common_strings().get(offset, str(offset)) diff --git a/UnityPy/helpers/ContainerHelper.py b/UnityPy/helpers/ContainerHelper.py index 95c3d30e..f2ad977c 100644 --- a/UnityPy/helpers/ContainerHelper.py +++ b/UnityPy/helpers/ContainerHelper.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Dict, Generator, Iterator, List, Tuple, Union +from typing import TYPE_CHECKING, Dict, Generator, Iterator, List, Optional, Tuple, Union from attrs import define @@ -16,13 +16,37 @@ class ContainerHelper: container: List[Tuple[str, AssetInfo]] container_dict: Dict[str, PPtr[Object]] path_dict: Dict[int, str] + _preload_table: Optional[List[PPtr[Object]]] = None def __init__(self, container: Union[List[Tuple[str, AssetInfo]], AssetBundle]) -> None: + preload_table: Optional[List[PPtr[Object]]] = None if not isinstance(container, (list)): + preload_table = container.m_PreloadTable container = container.m_Container self.container = container self.container_dict = {key: value.asset for key, value in container} self.path_dict = {value.asset.path_id: key for key, value in container} + self._preload_table = preload_table + + def parse_preload_table(self) -> None: + if self._preload_table is None: + return + + for path, info in self.container: + start = info.preloadIndex + size = info.preloadSize + if start < 0 or size <= 0 or start + size > len(self._preload_table): + continue + for pptr in self._preload_table[start : start + size]: + if not pptr: + continue + try: + target = pptr.deref() + except (FileNotFoundError, KeyError): + continue + target.assets_file._container.path_dict.setdefault(pptr.path_id, path) + + self._preload_table = None def items(self) -> Generator[Tuple[str, PPtr[Object]], None, None]: return ((key, value.asset) for key, value in self.container) diff --git a/UnityPy/helpers/MeshHelper.py b/UnityPy/helpers/MeshHelper.py index bbcde5dd..371e8baf 100644 --- a/UnityPy/helpers/MeshHelper.py +++ b/UnityPy/helpers/MeshHelper.py @@ -375,19 +375,26 @@ def read_vertex_data(self, m_Channels: list[ChannelInfo], m_Streams: list[Stream swap, ) else: - componentBytes = bytearray(m_VertexCount * channel_dimension * component_byte_size) + channelSize = channel_dimension * component_byte_size - vertexBaseOffset = m_Stream.offset + m_Channel.offset - for v in range(m_VertexCount): - vertexOffset = vertexBaseOffset + m_Stream.stride * v - for d in range(channel_dimension): - componentOffset = vertexOffset + component_byte_size * d - vertexDataSrc = componentOffset - componentDataSrc = component_byte_size * (v * channel_dimension + d) - buff = m_VertexData.m_DataSize[vertexDataSrc : vertexDataSrc + component_byte_size] - if swap: # swap bytes - buff = buff[::-1] - componentBytes[componentDataSrc : componentDataSrc + component_byte_size] = buff + componentBytes = bytearray(m_VertexCount * channel_dimension * component_byte_size) + vertexData = m_VertexData.m_DataSize + + componentOffset = 0 + vertexOffset = m_Stream.offset + m_Channel.offset + + for _ in range(m_VertexCount): + componentBytes[componentOffset : componentOffset + channelSize] = vertexData[ + vertexOffset : vertexOffset + channelSize + ] + componentOffset += channelSize + vertexOffset += m_Stream.stride + + if swap: + for offset in range(0, len(componentBytes), component_byte_size): + item = componentBytes[offset : offset + component_byte_size] + item.reverse() + componentBytes[offset : offset + component_byte_size] = item component_data = list(struct.iter_unpack(f">{channel_dimension}{component_dtype}", componentBytes)) self.assign_channel_vertex_data(chn, component_data) diff --git a/UnityPy/helpers/Tpk.py b/UnityPy/helpers/Tpk.py index 2f2f8965..3ccdfbef 100644 --- a/UnityPy/helpers/Tpk.py +++ b/UnityPy/helpers/Tpk.py @@ -1,59 +1,62 @@ from __future__ import annotations -from enum import IntEnum, IntFlag -from importlib.resources import open_binary +import sys +from functools import cache from io import BytesIO -from struct import Struct -from typing import Any, Dict, List, Optional, Tuple, TypeVar +from typing import TYPE_CHECKING, Dict, Optional, cast -from .CompressionHelper import decompress_lzma -from .TypeTreeHelper import TypeTreeNode +from tpk_ar import TpkFile, TpkTypeTreeBlob, TpkUnityClass, TpkUnityNode +from tpk_ar import UnityVersion as TpkUnityVersion + +from . import TypeTreeHelper from .UnityVersion import UnityVersion -T = TypeVar("T") +if TYPE_CHECKING: + from .TypeTreeHelper import TypeTreeNode -TPKTYPETREE: TpkTypeTreeBlob = None # pyright: ignore[reportAssignmentType] -CLASSES_CACHE: Dict[Tuple[int, UnityVersion], TypeTreeNode] = {} -NODES_CACHE: Dict[TpkUnityClass, TypeTreeNode] = {} +@cache +def get_typetree() -> TpkTypeTreeBlob: + package = "UnityPy.resources" + resource = "lzma.tpk" -def init(): - with open_binary("UnityPy.resources", "lzma.tpk") as f: - data = f.read() + tpk_data: bytes + if sys.version_info >= (3, 9): + from importlib.resources import files - global TPKTYPETREE - with BytesIO(data) as stream: - blob = TpkFile(stream).GetDataBlob() - assert isinstance(blob, TpkTypeTreeBlob) - TPKTYPETREE = blob + tpk_data = files(package).joinpath(resource).read_bytes() + else: + from importlib.resources import open_binary -def get_typetree_node(class_id: int, version: UnityVersion): - global CLASSES_CACHE - key = (class_id, version) - cached = CLASSES_CACHE.get(key) - if cached: - return cached + tpk_data = open_binary(package, resource).read() + + with BytesIO(tpk_data) as stream: + tree = TpkFile.parse(stream).GetDataBlob() + assert isinstance(tree, TpkTypeTreeBlob) + return tree - class_info = TPKTYPETREE.ClassInformation[class_id].getVersionedClass(version) + +@cache +def get_typetree_node(class_id: int, version: UnityVersion): + tpk_version = cast(TpkUnityVersion, version) + class_info = get_typetree().ClassInformation[class_id].getVersionedClass(tpk_version) if class_info is None: raise ValueError("Could not find class info for class id {}".format(class_id)) node = generate_node(class_info) - CLASSES_CACHE[key] = node return node -def generate_node(class_info: TpkUnityClass) -> TypeTreeNode: - global NODES_CACHE - cached = NODES_CACHE.get(class_info) - if cached: - return cached - +@cache +def generate_node(class_info: TpkUnityClass) -> "TypeTreeNode": assert class_info.ReleaseRootNode is not None, "Class {} has no ReleaseRootNode".format(class_info) + TypeTreeNode = TypeTreeHelper.TypeTreeNode + nodes = [] - NODES = TPKTYPETREE.NodeBuffer + NODES = get_typetree().NodeBuffer + STRINGBUFFER = get_typetree().StringBuffer stack = [(class_info.ReleaseRootNode, 0)] index = 0 while stack: @@ -66,408 +69,16 @@ def generate_node(class_info: TpkUnityClass) -> TypeTreeNode: m_Version=node.Version, m_MetaFlag=node.MetaFlag, m_Level=level, - m_Type=TPKTYPETREE.StringBuffer[node.TypeName], - m_Name=TPKTYPETREE.StringBuffer[node.Name], + m_Type=STRINGBUFFER[node.TypeName], + m_Name=STRINGBUFFER[node.Name], ) ) stack = [(node_id, level + 1) for node_id in node.SubNodes] + stack index += 1 - result = TypeTreeNode.from_list(nodes) - NODES_CACHE[class_info] = result - return result - - -###################################################################################### -# -# Enums -# -###################################################################################### - - -class TpkCompressionType(IntEnum): - NONE = 0 - Lz4 = 1 - Lzma = 2 - Brotli = 3 - - -class TpkDataType(IntEnum): - TypeTreeInformation = 0 - Collection = 1 - FileSystem = 2 - Json = 3 - ReferenceAssemblies = 4 - EngineAssets = 5 - - def ToBlob(self, stream): - if self.value == TpkDataType.TypeTreeInformation: - return TpkTypeTreeBlob(stream) - elif self.value == TpkDataType.Collection: - return TpkCollectionBlob(stream) - elif self.value == TpkDataType.FileSystem: - return TpkFileSystemBlob(stream) - elif self.value == TpkDataType.Json: - return TpkJsonBlob(stream) - else: - raise Exception("Unimplemented TpkDataType -> Blob conversion") - - -class TpkUnityClassFlags(IntFlag): - NONE = 0 - IsAbstract = 1 - IsSealed = 2 - IsEditorOnly = 4 - IsReleaseOnly = 8 - IsStripped = 16 - Reserved = 32 - HasEditorRootNode = 64 - HasReleaseRootNode = 128 - - -###################################################################################### -# -# Main Class -# -###################################################################################### - - -class TpkFile: - Struct = Struct(" TpkDataBlob: - decompressed: bytes - if self.CompressionType == TpkCompressionType.NONE: - decompressed = self.CompressedBytes - - elif self.CompressionType == TpkCompressionType.Lz4: - import lz4.block - - decompressed = lz4.block.decompress(self.CompressedBytes, self.UncompressedSize) - - elif self.CompressionType == TpkCompressionType.Lzma: - decompressed = decompress_lzma(self.CompressedBytes) - - elif self.CompressionType == TpkCompressionType.Brotli: - import brotli - - decompressed = brotli.decompress(self.CompressedBytes) - - else: - raise Exception("Invalid compression type") - - return self.DataType.ToBlob(BytesIO(decompressed)) - - -###################################################################################### -# -# Blobs -# -###################################################################################### - - -class TpkDataBlob: - __slots__ = ("DataType",) - DataType: TpkDataType - - def __init__(self, stream: BytesIO) -> None: - raise NotImplementedError("TpkDataBlob is an abstract class") - - -class TpkTypeTreeBlob(TpkDataBlob): - __slots__ = ( - "CreationTime", - "Versions", - "ClassInformation", - "CommonString", - "NodeBuffer", - "StringBuffer", - ) - CreationTime: int - Versions: List[UnityVersion] - ClassInformation: Dict[int, TpkClassInformation] # List[TpkClassInformation] - CommonString: TpkCommonString - NodeBuffer: TpkUnityNodeBuffer - StringBuffer: TpkStringBuffer - DataType: TpkDataType = TpkDataType.TypeTreeInformation - - def __init__(self, stream: BytesIO) -> None: - (self.CreationTime,) = INT64.unpack(stream.read(INT64.size)) - (versionCount,) = INT32.unpack(stream.read(INT32.size)) - 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) - self.NodeBuffer = TpkUnityNodeBuffer(stream) - self.StringBuffer = TpkStringBuffer(stream) - - -class TpkCollectionBlob(TpkDataBlob): - __slots__ = "Blobs" - Blobs: List[Tuple[str, TpkDataBlob]] - - def __init__(self, stream: BytesIO) -> None: - (count,) = INT32.unpack(stream.read(INT32.size)) - self.Blobs = [ - # relativePath, data - ( - read_string(stream), - TpkDataType(BYTE.unpack(stream.read(1))[0]).ToBlob(stream), - ) - for _ in range(count) - ] - - -class TpkFileSystemBlob(TpkDataBlob): - __slots__ = ("Files",) - # TODO: check if dict might be better - Files: List[Tuple[str, bytes]] - - def __init__(self, stream: BytesIO) -> None: - (count,) = INT32.unpack(stream.read(INT32.size)) - self.Files = [ - # relativePath, data - (read_string(stream), read_data(stream)) - for _ in range(count) - ] - - -class TpkJsonBlob(TpkDataBlob): - __slots__ = "Text" - Text: str - DataType = TpkDataType.Json - - def __init__(self, stream: BytesIO) -> None: - self.Text = read_string(stream) - - -###################################################################################### -# -# Unity -# -###################################################################################### - - -class TpkUnityClass: - __slots__ = ("Name", "Base", "Flags", "EditorRootNode", "ReleaseRootNode") - Struct = Struct(" None: - self.Name, self.Base, Flags = TpkUnityClass.Struct.unpack(stream.read(TpkUnityClass.Struct.size)) - self.Flags = TpkUnityClassFlags(Flags) - self.EditorRootNode = self.ReleaseRootNode = None - if self.Flags & TpkUnityClassFlags.HasEditorRootNode: - (self.EditorRootNode,) = UINT16.unpack(stream.read(UINT16.size)) - if self.Flags & TpkUnityClassFlags.HasReleaseRootNode: - (self.ReleaseRootNode,) = UINT16.unpack(stream.read(UINT16.size)) - - def to_dict(self) -> Dict[str, Any]: - return { - "Name": self.Name, - "Base": self.Base, - "Flags": self.Flags, - "EditorRootNode": self.EditorRootNode, - "ReleaseRootNode": self.ReleaseRootNode, - } - - def __eq__(self, other: object) -> bool: - if not isinstance(other, TpkUnityClass): - return False - return self.to_dict() == other.to_dict() - - def __hash__(self) -> int: - return hash( - ( - self.Name, - self.Base, - self.Flags, - self.EditorRootNode, - self.ReleaseRootNode, - ) - ) - - -class TpkClassInformation(List[Tuple[UnityVersion, Optional[TpkUnityClass]]]): - ID: int - - def __init__(self, stream: BytesIO) -> None: - (self.ID,) = INT32.unpack(stream.read(INT32.size)) - (count,) = INT32.unpack(stream.read(INT32.size)) - 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) - - -class TpkUnityNode: - __slots__ = ( - "TypeName", - "Name", - "ByteSize", - "Version", - "TypeFlags", - "MetaFlag", - "SubNodes", - ) - Struct = Struct(" None: - ( - self.TypeName, - self.Name, - self.ByteSize, - self.Version, - self.TypeFlags, - self.MetaFlag, - count, - ) = TpkUnityNode.Struct.unpack(stream.read(TpkUnityNode.Struct.size)) - - SubNodeStruct = Struct(f"<{count}H") - self.SubNodes = list(SubNodeStruct.unpack(stream.read(SubNodeStruct.size))) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, TpkUnityNode): - return False - return self.__dict__ == other.__dict__ - - def __hash__(self) -> int: - # TODO - 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 -# -###################################################################################### - - -class TpkStringBuffer(List[str]): - def __init__(self, stream: BytesIO) -> None: - count = INT32.unpack(stream.read(INT32.size))[0] - self.extend(read_string(stream) for _ in range(count)) - - -class TpkCommonString: - __slots__ = ("VersionInformation", "StringBufferIndices") - VersionInformation: List[Tuple[UnityVersion, int]] - StringBufferIndices: Tuple[int, ...] - - def __init__(self, stream: BytesIO) -> None: - (versionCount,) = INT32.unpack(stream.read(INT32.size)) - self.VersionInformation = [(read_version(stream), stream.read(1)[0]) for _ in range(versionCount)] - (indicesCount,) = INT32.unpack(stream.read(INT32.size)) - indicesStruct = Struct(f"<{indicesCount}H") - self.StringBufferIndices = indicesStruct.unpack(stream.read(indicesStruct.size)) - - def GetStrings(self, buffer: TpkStringBuffer) -> List[str]: - return [buffer[i] for i in self.StringBufferIndices] - - def GetCount(self, exactVersion: UnityVersion) -> int: - return get_item_for_version(exactVersion, self.VersionInformation) - - -###################################################################################### -# -# helper functions -# -###################################################################################### - -BYTE = Struct("b") -UINT16 = Struct(" str: - # varint - shift = 0 - length = 0 - while True: - (i,) = stream.read(1) - length |= (i & 0x7F) << shift - shift += 7 - if not (i & 0x80): - break - # string - return stream.read(length).decode("utf-8") - - -def read_data(stream: BytesIO) -> bytes: - return stream.read(INT32.unpack(stream.read(INT32.size))[0]) - - -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: - if exactVersion >= version: - ret = item - else: - break - if ret: - return ret - raise ValueError("Could not find exact version") + return TypeTreeNode.from_list(nodes) -init() +@cache +def get_common_strings(version: Optional[UnityVersion] = None) -> Dict[int, str]: + tpk_version: TpkUnityVersion | None = cast(TpkUnityVersion, version) if version is not None else None + return get_typetree().CommonString.BuildMap(get_typetree().StringBuffer, tpk_version) diff --git a/UnityPy/helpers/TypeTreeHelper.py b/UnityPy/helpers/TypeTreeHelper.py index ee562b24..7eb916eb 100644 --- a/UnityPy/helpers/TypeTreeHelper.py +++ b/UnityPy/helpers/TypeTreeHelper.py @@ -1,6 +1,5 @@ from __future__ import annotations -import re from sys import version_info as py_version_info from typing import TYPE_CHECKING, Any, Optional, Union @@ -277,7 +276,7 @@ def read_value_array( elif node.m_Type == "string": value = [reader.read_aligned_string() for _ in range(size)] elif node.m_Type == "TypelessData": - value = [reader.read_bytes() for _ in range(size)] + value = [reader.read_byte_array() for _ in range(size)] elif node.m_Type == "pair": key_node = node.m_Children[0] value_node = node.m_Children[1] @@ -365,19 +364,6 @@ def metaflag_is_aligned(meta_flag: int | None) -> bool: return ((meta_flag or 0) & kAlignBytes) != 0 -def clean_name(name: str) -> str: - if name.startswith("(int&)"): - name = name[6:] - if name.endswith("?"): - name = name[:-1] - name = re.sub(r"[ \.:\-\[\]]", "_", name) - if name in ["pass", "from"]: - name += "_" - if name[0].isdigit(): - name = f"x{name}" - return name - - FUNCTION_WRITE_MAP = { "SInt8": EndianBinaryWriter.write_byte, "UInt8": EndianBinaryWriter.write_u_byte, diff --git a/UnityPy/helpers/TypeTreeNode.py b/UnityPy/helpers/TypeTreeNode.py index 31aacc91..a0a09571 100644 --- a/UnityPy/helpers/TypeTreeNode.py +++ b/UnityPy/helpers/TypeTreeNode.py @@ -16,12 +16,10 @@ from attrs import define, field +from ..helpers.Tpk import get_common_strings from ..streams.EndianBinaryReader import EndianBinaryReader from ..streams.EndianBinaryWriter import EndianBinaryWriter -if TYPE_CHECKING: - from .Tpk import UnityVersion - try: from ..UnityPyBoost import TypeTreeNode as TypeTreeNodeC # type: ignore except ImportError: @@ -302,32 +300,6 @@ def __eq__(self, other: TypeTreeNode) -> bool: # type: ignore return self.to_dict() == other.to_dict() and self.m_Children == other.m_Children -COMMONSTRING_CACHE: Dict[Optional[UnityVersion], Dict[int, str]] = {} - - -def get_common_strings(version: Optional[UnityVersion] = None) -> Dict[int, str]: - if version in COMMONSTRING_CACHE: - return COMMONSTRING_CACHE[version] - - from .Tpk import TPKTYPETREE - - tree = TPKTYPETREE - common_string = tree.CommonString - strings = common_string.GetStrings(tree.StringBuffer) - if version: - count = common_string.GetCount(version) - strings = strings[:count] - - ret: Dict[int, str] = {} - offset = 0 - for string in strings: - ret[offset] = string - offset += len(string) + 1 - - COMMONSTRING_CACHE[version] = ret - return ret - - def _get_blob_node_struct(endian: str, version: int) -> tuple[Struct, list[str]]: struct_type = f"{endian}hBBIIiii" keys = [ @@ -347,15 +319,18 @@ def _get_blob_node_struct(endian: str, version: int) -> tuple[Struct, list[str]] return Struct(struct_type), keys +CLEAN_NAME_REMOVE_RE = re.compile(r"[\?\*]") +CLEAN_NAME_REPLACE_RE = re.compile(r"[ \.:\-\[\]]") + + def clean_name(name: str) -> str: # keep in sync with TypeTreeHelper.cpp if len(name) == 0: return name if name.startswith("(int&)"): name = name[6:] - if name.endswith("?"): - name = name[:-1] - name = re.sub(r"[ \.:\-\[\]]", "_", name) + name = CLEAN_NAME_REMOVE_RE.sub("", name) + name = CLEAN_NAME_REPLACE_RE.sub("_", name) if name in ["pass", "from"]: name += "_" if name[0].isdigit(): diff --git a/UnityPy/resources/lzma.tpk b/UnityPy/resources/lzma.tpk index c518569a..26e07449 100644 --- a/UnityPy/resources/lzma.tpk +++ b/UnityPy/resources/lzma.tpk @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fc67b99c06dd7575431c70207a2f78624501b17d5150092b511783bfe6db2127 -size 187318 +oid sha256:0b2277765f0f7a6253df04426abd83d2bf37f8d1ad30542cddd2d81ae484741b +size 207798 diff --git a/UnityPy/tools/TpkClassGenerator.py b/UnityPy/tools/TpkClassGenerator.py index ebef4272..2536644e 100644 --- a/UnityPy/tools/TpkClassGenerator.py +++ b/UnityPy/tools/TpkClassGenerator.py @@ -10,11 +10,11 @@ # import UnityPy from the parent directory instead of the installed package ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(ROOT) -from UnityPy.helpers.Tpk import TPKTYPETREE, TpkUnityNode # noqa: E402 +from UnityPy.helpers.Tpk import TpkUnityNode, get_typetree # noqa: E402 from UnityPy.helpers.TypeTreeNode import clean_name # noqa: E402 -NODES = TPKTYPETREE.NodeBuffer -STRINGS = TPKTYPETREE.StringBuffer +NODES = get_typetree().NodeBuffer +STRINGS = get_typetree().StringBuffer BASE_TYPE_MAP = { "char": "int", # used for byte data @@ -310,7 +310,7 @@ def generate_classes(): main_classes: Set[str] = set() deps: Dict[str, List[str]] = {} - for _class_id, class_info in TPKTYPETREE.ClassInformation.items(): + for _class_id, class_info in get_typetree().ClassInformation.items(): abstract = True base = None cls_name: Optional[str] = None diff --git a/UnityPyBoost/ArchiveStorageDecryptor.cpp b/UnityPyBoost/ArchiveStorageDecryptor.cpp index 65713267..b26534ff 100644 --- a/UnityPyBoost/ArchiveStorageDecryptor.cpp +++ b/UnityPyBoost/ArchiveStorageDecryptor.cpp @@ -62,15 +62,15 @@ PyObject *decrypt_block(PyObject *self, PyObject *args) { if (index_data.buf) PyBuffer_Release(&index_data); if (substitute_data.buf) PyBuffer_Release(&substitute_data); if (data.buf) PyBuffer_Release(&data); - return NULL; + return nullptr; } - PyObject *result = PyBytes_FromStringAndSize(NULL, data.len); - if (result == NULL) { + PyObject *result = PyBytes_FromStringAndSize(nullptr, data.len); + if (result == nullptr) { PyBuffer_Release(&index_data); PyBuffer_Release(&substitute_data); PyBuffer_Release(&data); - return NULL; + return nullptr; } unsigned char *result_raw = (unsigned char *)PyBytes_AS_STRING(result); diff --git a/UnityPyBoost/Mesh.cpp b/UnityPyBoost/Mesh.cpp index beb66629..e1baf036 100644 --- a/UnityPyBoost/Mesh.cpp +++ b/UnityPyBoost/Mesh.cpp @@ -1,5 +1,6 @@ #include "Mesh.hpp" #include +#include #include #define MAX(x, y) (((x) > (y)) ? (x) : (y)) @@ -20,6 +21,64 @@ enum VertexFormat kVertexFormatSInt32 }; +template +void unpack_vertexdata_template(uint8_t *componentBytes, uint8_t *vertexData, uint32_t m_VertexCount, uint32_t m_StreamOffset, uint32_t m_StreamStride, uint32_t m_ChannelOffset, uint32_t m_ChannelDimension) +{ + const auto channelSize = componentByteSize * m_ChannelDimension; + + uint8_t *componentCur = componentBytes; + uint8_t *vertexCur = vertexData; + + // move vertexCur to the first vertex + vertexCur += m_StreamOffset + m_ChannelOffset; + + for (uint32_t v = 0; v < m_VertexCount; v++) + { + memcpy(componentCur, vertexCur, channelSize); + componentCur += channelSize; + vertexCur += m_StreamStride; + } +} + +template +void swap_vertexdata(uint8_t *componentBytes, uint32_t m_VertexCount, uint32_t m_ChannelDimension) +{ + if constexpr (componentByteSize == 1) + { + // do nothing + } + else if constexpr (componentByteSize == 2) + { + uint16_t *componentUints = (uint16_t *)componentBytes; + for (uint32_t i = 0; i < m_VertexCount * m_ChannelDimension; i++) + { + swap_any_inplace(componentUints++); + } + } + else if constexpr (componentByteSize == 4) + { + uint32_t *componentUints = (uint32_t *)componentBytes; + for (uint32_t i = 0; i < m_VertexCount * m_ChannelDimension; i++) + { + swap_any_inplace(componentUints++); + } + } + else if constexpr (componentByteSize == 8) + { + uint64_t *componentUints = (uint64_t *)componentBytes; + for (uint32_t i = 0; i < m_VertexCount * m_ChannelDimension; i++) + { + swap_any_inplace(componentUints++); + } + } + else + { + const auto compoentByteSizeStr = std::to_string(componentByteSize); + const auto error_message = "Swap not implemented for this size: " + compoentByteSizeStr; + PyErr_SetString(PyExc_ValueError, error_message.c_str()); + } +} + PyObject *unpack_vertexdata(PyObject *self, PyObject *args) { // define vars @@ -39,7 +98,7 @@ PyObject *unpack_vertexdata(PyObject *self, PyObject *args) { PyBuffer_Release(&vertexDataView); } - return NULL; + return nullptr; } uint8_t *vertexData = (uint8_t *)vertexDataView.buf; @@ -52,159 +111,49 @@ PyObject *unpack_vertexdata(PyObject *self, PyObject *args) { PyBuffer_Release(&vertexDataView); PyErr_SetString(PyExc_ValueError, "Vertex data access out of bounds"); - return NULL; + return nullptr; } PyObject *res = PyBytes_FromStringAndSize(nullptr, componentBytesLength); if (!res) { PyBuffer_Release(&vertexDataView); - return NULL; + return nullptr; } uint8_t *componentBytes = (uint8_t *)PyBytes_AS_STRING(res); - for (uint32_t v = 0; v < m_VertexCount; v++) + switch (componentByteSize) { - uint32_t vertexOffset = m_StreamOffset + m_ChannelOffset + m_StreamStride * v; - for (uint32_t d = 0; d < m_ChannelDimension; d++) + case 1: + unpack_vertexdata_template<1>(componentBytes, vertexData, m_VertexCount, m_StreamOffset, m_StreamStride, m_ChannelOffset, m_ChannelDimension); + break; + case 2: + unpack_vertexdata_template<2>(componentBytes, vertexData, m_VertexCount, m_StreamOffset, m_StreamStride, m_ChannelOffset, m_ChannelDimension); + if (swap) { - uint32_t vertexDataOffset = vertexOffset + componentByteSize * d; - uint32_t componentOffset = componentByteSize * (v * m_ChannelDimension + d); - memcpy(componentBytes + componentOffset, vertexData + vertexDataOffset, componentByteSize); + swap_vertexdata<2>(componentBytes, m_VertexCount, m_ChannelDimension); } - } - - if (swap) // swap bytes - { - if (componentByteSize == 2) + break; + case 4: + unpack_vertexdata_template<4>(componentBytes, vertexData, m_VertexCount, m_StreamOffset, m_StreamStride, m_ChannelOffset, m_ChannelDimension); + if (swap) { - uint16_t *componentUints = (uint16_t *)componentBytes; - for (uint32_t i = 0; i < componentBytesLength; i += 2) - { - swap_any_inplace(componentUints++); - } + swap_vertexdata<4>(componentBytes, m_VertexCount, m_ChannelDimension); } - else if (componentByteSize == 4) + break; + case 8: + unpack_vertexdata_template<8>(componentBytes, vertexData, m_VertexCount, m_StreamOffset, m_StreamStride, m_ChannelOffset, m_ChannelDimension); + if (swap) { - - uint32_t *componentUints = (uint32_t *)componentBytes; - for (uint32_t i = 0; i < componentBytesLength; i += 4) - { - swap_any_inplace(componentUints++); - } + swap_vertexdata<8>(componentBytes, m_VertexCount, m_ChannelDimension); } + break; + default: + PyBuffer_Release(&vertexDataView); + PyErr_SetString(PyExc_ValueError, "Unsupported component byte size"); + return nullptr; } PyBuffer_Release(&vertexDataView); return res; - - // fast enough in Python - // uint32_t itemCount = componentBytesLength / componentByteSize; - // PyObject *lst = PyList_New(itemCount); - // if (!lst) - // return NULL; - - // switch (format) - // { - // case kVertexFormatFloat: - // { - // float *items = (float *)componentBytes; - // for (uint32_t i = 0; i < itemCount; i++) - // { - // PyList_SetItem(lst, i, PyFloat_FromDouble((double)*items++)); - // } - // // result[i] = BitConverter.ToSingle(inputBytes, i * 4); - // break; - // } - // case kVertexFormatFloat16: - // { - // uint16_t *items = (uint16_t *)componentBytes; - // for (uint32_t i = 0; i < itemCount; i++) - // { - // double x = _PyFloat_Unpack2(items++, 0); - // if (x == -1.0 && PyErr_Occurred()) - // { - // return NULL; - // } - // PyList_SetItem(lst, i, PyFloat_FromDouble(x)); - // } - // // result[i] = Half.ToHalf(inputBytes, i * 2); - // break; - // } - // case kVertexFormatUNorm8: - // { - // uint8_t *items = componentBytes; - // for (uint32_t i = 0; i < itemCount; i++) - // { - // PyList_SetItem(lst, i, PyFloat_FromDouble((double)(*items++ / 255.0f))); - // } - // // result[i] = inputBytes[i] / 255f; - // break; - // } - // case kVertexFormatSNorm8: - // { - // int8_t *items = (int8_t *)componentBytes; - // for (uint32_t i = 0; i < itemCount; i++) - // { - // PyList_SetItem(lst, i, PyFloat_FromDouble((double)MAX((*items++ / 127.0f), -1.0f))); - // } - // // result[i] = Math.Max((sbyte)inputBytes[i] / 127f, -1f); - // break; - // } - // case kVertexFormatUNorm16: - // { - // uint16_t *items = (uint16_t *)componentBytes; - // for (uint32_t i = 0; i < itemCount; i++) - // { - // PyList_SetItem(lst, i, PyFloat_FromDouble((double)(*items++ / 65535.0f))); - // } - // // result[i] = BitConverter.ToUInt16(inputBytes, i * 2) / 65535f; - // break; - // } - // case kVertexFormatSNorm16: - // { - // int16_t *items = (int16_t *)componentBytes; - // for (uint32_t i = 0; i < itemCount; i++) - // { - // PyList_SetItem(lst, i, PyFloat_FromDouble((double)MAX((*items++ / 32767.0f), -1.0f))); - // } - // // result[i] = Math.Max(BitConverter.ToInt16(inputBytes, i * 2) / 32767f, -1f); - // break; - // } - // case kVertexFormatUInt8: - // case kVertexFormatSInt8: - // { - // uint8_t *items = componentBytes; - // for (uint32_t i = 0; i < itemCount; i++) - // { - // PyList_SetItem(lst, i, PyLong_FromUnsignedLong((uint32_t)*items++)); - // } - // // result[i] = inputBytes[i]; - // break; - // } - // case kVertexFormatUInt16: - // case kVertexFormatSInt16: - // { - // uint16_t *items = (uint16_t *)componentBytes; - // for (uint32_t i = 0; i < itemCount; i++) - // { - // PyList_SetItem(lst, i, PyLong_FromUnsignedLong((uint32_t)*items++)); - // } - // // result[i] = BitConverter.ToInt16(inputBytes, i * 2); - // break; - // } - // case kVertexFormatUInt32: - // case kVertexFormatSInt32: - // { - // uint32_t *items = (uint32_t *)componentBytes; - // for (uint32_t i = 0; i < itemCount; i++) - // { - // PyList_SetItem(lst, i, PyLong_FromUnsignedLong(*items++)); - // } - // // result[i] = BitConverter.ToInt32(inputBytes, i * 4); - // break; - // } - // } - // free(componentBytes); - // return lst; } diff --git a/UnityPyBoost/TypeTreeHelper.cpp b/UnityPyBoost/TypeTreeHelper.cpp index 65e4e304..ec9fa234 100644 --- a/UnityPyBoost/TypeTreeHelper.cpp +++ b/UnityPyBoost/TypeTreeHelper.cpp @@ -40,11 +40,8 @@ std::string clean_name(const std::string &name) cleaned_name = cleaned_name.substr(6); } - // Remove trailing "?" - if (!cleaned_name.empty() && cleaned_name.back() == '?') - { - cleaned_name.pop_back(); - } + // Remove certain characters + cleaned_name = std::regex_replace(cleaned_name, std::regex("[\\?\\*]"), ""); // Replace certain characters with "_" cleaned_name = std::regex_replace(cleaned_name, std::regex("[ \\.:\\-\\[\\]]"), "_"); @@ -76,7 +73,7 @@ inline PyObject *read_bool(ReaderT *reader) if (reader->ptr + 1 > reader->end) { PyErr_SetString(PyExc_ValueError, "read_bool out of bounds"); - return NULL; + return nullptr; } PyObject *value = *reader->ptr++ ? Py_True : Py_False; Py_INCREF(value); @@ -87,8 +84,8 @@ inline PyObject *read_bool_array(ReaderT *reader, int32_t count) { if (reader->ptr + count > reader->end) { - PyErr_SetString(PyExc_ValueError, "read_bool out of bounds"); - return NULL; + PyErr_SetString(PyExc_ValueError, "read_bool_array out of bounds"); + return nullptr; } PyObject *list = PyList_New(count); for (auto i = 0; i < count; i++) @@ -105,7 +102,7 @@ inline PyObject *read_u8(ReaderT *reader) if (reader->ptr + 1 > reader->end) { PyErr_SetString(PyExc_ValueError, "read_u8 out of bounds"); - return NULL; + return nullptr; } return PyLong_FromUnsignedLong(*reader->ptr++); } @@ -114,8 +111,8 @@ inline PyObject *read_u8_array(ReaderT *reader, int32_t count) { if (reader->ptr + count > reader->end) { - PyErr_SetString(PyExc_ValueError, "read_u8 out of bounds"); - return NULL; + PyErr_SetString(PyExc_ValueError, "read_u8_array out of bounds"); + return nullptr; } PyObject *list = PyList_New(count); for (auto i = 0; i < count; i++) @@ -130,7 +127,7 @@ inline PyObject *read_s8(ReaderT *reader) if (reader->ptr + 1 > reader->end) { PyErr_SetString(PyExc_ValueError, "read_s8 out of bounds"); - return NULL; + return nullptr; } return PyLong_FromLong((int8_t)*reader->ptr++); } @@ -139,8 +136,8 @@ inline PyObject *read_s8_array(ReaderT *reader, int32_t count) { if (reader->ptr + count > reader->end) { - PyErr_SetString(PyExc_ValueError, "read_s8 out of bounds"); - return NULL; + PyErr_SetString(PyExc_ValueError, "read_s8_array out of bounds"); + return nullptr; } PyObject *list = PyList_New(count); int8_t *ptr = (int8_t *)reader->ptr; @@ -159,7 +156,9 @@ inline PyObject *read_num(ReaderT *reader) if (reader->ptr + sizeof(T) > reader->end) { - return PyErr_Format(PyExc_ValueError, "read_%s out of bounds", typeid(T).name()); + std::string error_msg = "read_" + std::string(typeid(T).name()) + " out of bounds"; + PyErr_SetString(PyExc_EOFError, error_msg.c_str()); + return nullptr; } T value = *(T *)reader->ptr; if constexpr (swap) @@ -195,7 +194,9 @@ inline PyObject *read_num(ReaderT *reader) } else { - return PyErr_Format(PyExc_TypeError, "Unsupported type for read_num: %s", typeid(T).name()); + std::string error_msg = "Unsupported type for read_num: " + std::string(typeid(T).name()); + PyErr_SetString(PyExc_TypeError, error_msg.c_str()); + return nullptr; } } @@ -206,7 +207,9 @@ inline PyObject *read_num_array(ReaderT *reader, int32_t count) if (reader->ptr + sizeof(T) * count > reader->end) { - return PyErr_Format(PyExc_ValueError, "read_%s_array out of bounds", typeid(T).name()); + std::string error_msg = "read_" + std::string(typeid(T).name()) + "_array out of bounds"; + PyErr_SetString(PyExc_EOFError, error_msg.c_str()); + return nullptr; } PyObject *list = PyList_New(count); T *ptr = (T *)reader->ptr; @@ -247,7 +250,9 @@ inline PyObject *read_num_array(ReaderT *reader, int32_t count) else { Py_DECREF(list); - return PyErr_Format(PyExc_TypeError, "Unsupported type for read_num_array: %s", typeid(T).name()); + std::string error_msg = "Unsupported type for read_num_array: " + std::string(typeid(T).name()); + PyErr_SetString(PyExc_TypeError, error_msg.c_str()); + return nullptr; } PyList_SET_ITEM(list, i, item); } @@ -260,7 +265,7 @@ inline bool _read_length(ReaderT *reader, int32_t *length) { if (reader->ptr + sizeof(int32_t) > reader->end) { - PyErr_SetString(PyExc_ValueError, "read_length out of bounds"); + PyErr_SetString(PyExc_EOFError, "read_length out of bounds"); return false; } *length = *(int32_t *)reader->ptr; @@ -283,12 +288,12 @@ inline PyObject *read_str(ReaderT *reader) int32_t length; if (!_read_length(reader, &length)) { - return NULL; + return nullptr; } if (reader->ptr + length > reader->end) { - PyErr_SetString(PyExc_ValueError, "read_str out of bounds"); - return NULL; + PyErr_SetString(PyExc_EOFError, "read_str out of bounds"); + return nullptr; } PyObject *py_str = PyUnicode_DecodeUTF8((char *)reader->ptr, length, "surrogateescape"); reader->ptr += length; @@ -302,12 +307,12 @@ inline PyObject *read_bytes(ReaderT *reader) int32_t length; if (!_read_length(reader, &length)) { - return NULL; + return nullptr; } if (reader->ptr + length > reader->end) { PyErr_SetString(PyExc_ValueError, "read_bytes out of bounds"); - return NULL; + return nullptr; } PyObject *bytes = PyBytes_FromStringAndSize((char *)reader->ptr, length); reader->ptr += length; @@ -320,19 +325,19 @@ inline PyObject *read_pair(ReaderT *reader, TypeTreeNodeObject *node, TypeTreeRe if (PyList_GET_SIZE(node->m_Children) != 2) { PyErr_SetString(PyExc_ValueError, "Pair node must have 2 children"); - return NULL; + return nullptr; } PyObject *first = read_typetree_value(reader, (TypeTreeNodeObject *)PyList_GET_ITEM(node->m_Children, 0), config); - if (first == NULL) + if (first == nullptr) { - return NULL; + return nullptr; } PyObject *second = read_typetree_value(reader, (TypeTreeNodeObject *)PyList_GET_ITEM(node->m_Children, 1), config); - if (second == NULL) + if (second == nullptr) { Py_DECREF(first); - return NULL; + return nullptr; } // PyTuple_Pack creates two strong references PyObject *pair = PyTuple_Pack(2, first, second); @@ -348,27 +353,31 @@ inline PyObject *read_pair_array(ReaderT *reader, TypeTreeNodeObject *node, Type if (PyList_GET_SIZE(node->m_Children) != 2) { PyErr_SetString(PyExc_ValueError, "Pair node must have 2 children"); - return NULL; + return nullptr; } TypeTreeNodeObject *first_child = (TypeTreeNodeObject *)PyList_GET_ITEM(node->m_Children, 0); TypeTreeNodeObject *second_child = (TypeTreeNodeObject *)PyList_GET_ITEM(node->m_Children, 1); PyObject *list = PyList_New(count); + if (list == nullptr) + { + return nullptr; + } for (auto i = 0; i < count; i++) { PyObject *first = read_typetree_value(reader, first_child, config); - if (first == NULL) + if (first == nullptr) { Py_DECREF(list); - return NULL; + return nullptr; } PyObject *second = read_typetree_value(reader, second_child, config); - if (second == NULL) + if (second == nullptr) { Py_DECREF(first); Py_DECREF(list); - return NULL; + return nullptr; } PyList_SET_ITEM(list, i, PyTuple_Pack(2, first, second)); // pack creates two strong references // so we need to decref both values here to bring their ref count back to 1 @@ -400,10 +409,10 @@ inline PyObject *read_class(ReaderT *reader, TypeTreeNodeObject *node, TypeTreeR } } PyObject *child_value = read_typetree_value(reader, child, config); // child_value: 1 refcount - if (child_value == NULL) + if (child_value == nullptr) { Py_DECREF(value); // value: 0 refcount - return NULL; + return nullptr; } int set_item_result; if constexpr (as_dict == true) @@ -418,7 +427,7 @@ inline PyObject *read_class(ReaderT *reader, TypeTreeNodeObject *node, TypeTreeR { Py_DECREF(value); // value: 0 refcount Py_DECREF(child_value); // child_value: 0 refcount - return NULL; + return nullptr; } // PyDict_SetItem increases ref count, so we need to decref here Py_DECREF(child_value); // child_value: 1 refcount @@ -437,7 +446,7 @@ static PyObject *_get_annotations = nullptr; inline PyObject *get_annotations(PyObject *clz) { #if PY_VERSION_HEX >= 0x030e0000 - return PyObject_CallFunctionObjArgs(_get_annotations, clz, NULL); + return PyObject_CallFunctionObjArgs(_get_annotations, clz, nullptr); #else return PyObject_GetAttrString(clz, "__annotations__"); #endif @@ -456,9 +465,9 @@ inline PyObject *parse_class(PyObject *kwargs, TypeTreeNodeObject *node, TypeTre // slots check PyObject *slots = nullptr; - if (kwargs == NULL) + if (kwargs == nullptr) { - return NULL; + return nullptr; } if (node->_data_type == NodeDataType::PPtr) @@ -575,14 +584,14 @@ TypeTreeNodeObject *get_ref_type_node(PyObject *ref_object, PyObject *assetsfile if (assetsfile == Py_None) { PyErr_SetString(PyExc_ValueError, "Reference Type found but no SerializedFile passed as assetsfile to read_typetree!"); - return NULL; + return nullptr; } PyObject *ref_types = PyObject_GetAttrString(assetsfile, "ref_types"); if (!ref_types || !PyList_Check(ref_types)) { Py_XDECREF(ref_types); PyErr_SetString(PyExc_ValueError, "No SerializedFile.ref_types"); - return NULL; + return nullptr; } PyObject *type = PyDict_GetItemString(ref_object, "type"); @@ -590,12 +599,12 @@ TypeTreeNodeObject *get_ref_type_node(PyObject *ref_object, PyObject *assetsfile { Py_DECREF(ref_types); PyErr_SetString(PyExc_ValueError, "Failed to get 'type'"); - return NULL; + return nullptr; } - PyObject *cls = NULL; - PyObject *ns = NULL; - PyObject *asm_ = NULL; + PyObject *cls = nullptr; + PyObject *ns = nullptr; + PyObject *asm_ = nullptr; if (PyDict_Check(type)) { cls = PyDict_GetItemString(type, "class"); @@ -619,7 +628,7 @@ TypeTreeNodeObject *get_ref_type_node(PyObject *ref_object, PyObject *assetsfile Py_XDECREF(ns); Py_XDECREF(asm_); PyErr_SetString(PyExc_ValueError, "Failed to get 'class', 'ns' or 'asm'"); - return NULL; + return nullptr; } if (PyUnicode_GET_LENGTH(cls) == 0) @@ -632,7 +641,7 @@ TypeTreeNodeObject *get_ref_type_node(PyObject *ref_object, PyObject *assetsfile } Py_ssize_t ref_types_len = PyList_Size(ref_types); - TypeTreeNodeObject *ref_type_node = NULL; + TypeTreeNodeObject *ref_type_node = nullptr; for (Py_ssize_t i = 0; i < ref_types_len; i++) { PyObject *ref_type = PyList_GetItem(ref_types, i); @@ -750,7 +759,7 @@ PyObject *read_typetree_value(ReaderT *reader, TypeTreeNodeObject *node, TypeTre { PyErr_SetString(PyExc_ValueError, "Failed to get ref type node"); Py_DECREF(value); - return NULL; + return nullptr; } else if (ref_node == (TypeTreeNodeObject *)Py_None) { @@ -765,16 +774,16 @@ PyObject *read_typetree_value(ReaderT *reader, TypeTreeNodeObject *node, TypeTre child_value = read_typetree_value(reader, child, config); } - if (child_value == NULL) + if (child_value == nullptr) { Py_DECREF(value); - return NULL; + return nullptr; } if (PyDict_SetItem(value, child->m_Name, child_value)) { Py_DECREF(value); Py_DECREF(child_value); - return NULL; + return nullptr; } // dict increases ref count, so we need to decref here Py_DECREF(child_value); @@ -782,11 +791,11 @@ PyObject *read_typetree_value(ReaderT *reader, TypeTreeNodeObject *node, TypeTre if (!config->as_dict) { PyObject *clz = PyObject_GetAttrString(config->classes, "UnknownObject"); - if (clz == NULL) + if (clz == nullptr) { PyErr_SetString(PyExc_ValueError, "Failed to get class"); Py_DECREF(value); - return NULL; + return nullptr; } PyObject *args = PyTuple_Pack(1, (PyObject *)node); PyObject *instance = PyObject_Call(clz, args, value); @@ -810,7 +819,7 @@ PyObject *read_typetree_value(ReaderT *reader, TypeTreeNodeObject *node, TypeTre if (PyList_GET_SIZE(child->m_Children) != 2) { PyErr_SetString(PyExc_ValueError, "Array node must have 2 children"); - return NULL; + return nullptr; } if (child->_align) @@ -820,20 +829,24 @@ PyObject *read_typetree_value(ReaderT *reader, TypeTreeNodeObject *node, TypeTre int32_t length; if (!_read_length(reader, &length)) { - return NULL; + return nullptr; } child = (TypeTreeNodeObject *)PyList_GET_ITEM(child->m_Children, 1); if (std::find(std::begin(SUPPORTED_VALUE_ARRAY_READ_TYPES), std::end(SUPPORTED_VALUE_ARRAY_READ_TYPES), child->_data_type) == std::end(SUPPORTED_VALUE_ARRAY_READ_TYPES)) { value = PyList_New(length); + if (value == nullptr) + { + return nullptr; + } for (int i = 0; i < length; i++) { PyObject *item = read_typetree_value(reader, child, config); - if (item == NULL) + if (item == nullptr) { Py_DECREF(value); - return NULL; + return nullptr; } PyList_SET_ITEM(value, i, item); } @@ -858,7 +871,7 @@ PyObject *read_typetree_value(ReaderT *reader, TypeTreeNodeObject *node, TypeTre } } - if (align && value != NULL) + if (align && value != nullptr) { align4(reader); } @@ -911,9 +924,11 @@ PyObject *read_typetree_value_array(ReaderT *reader, TypeTreeNodeObject *node, T value = read_pair_array(reader, node, config, count); break; default: - value = PyErr_Format(PyExc_ValueError, "Unsupported type for read_typetree_value_array: %d", node->_data_type); + std::string error_msg = "Unsupported type for read_value_array: " + std::to_string(node->_data_type); + PyErr_SetString(PyExc_TypeError, error_msg.c_str()); + value = nullptr; } - if (align && value != NULL) + if (align && value != nullptr) { align4(reader); } @@ -936,13 +951,14 @@ static bool is_null_none_or_type(PyObject *obj, PyTypeObject *type, const char * { return true; } - PyErr_Format(PyExc_TypeError, "Expected %s or None for %s, got %R", type_name, field_name, obj); + std::string error_msg = "Expected " + std::string(type_name) + " or None for " + std::string(field_name); + PyErr_SetString(PyExc_TypeError, error_msg.c_str()); return false; } PyObject *read_typetree(PyObject *self, PyObject *args, PyObject *kwargs) { - const char *kwlist[] = {"data", "node", "endian", "as_dict", "assetsfile", "classes", NULL}; + const char *kwlist[] = {"data", "node", "endian", "as_dict", "assetsfile", "classes", nullptr}; Py_buffer view; PyObject *node = nullptr; int as_dict = 1; @@ -1008,7 +1024,7 @@ PyObject *read_typetree(PyObject *self, PyObject *args, PyObject *kwargs) Py_DECREF(config.assetfile); Py_DECREF(config.classes); PyErr_SetString(PyExc_ValueError, "Invalid endian"); - return NULL; + return nullptr; } } @@ -1033,7 +1049,7 @@ PyObject *read_typetree(PyObject *self, PyObject *args, PyObject *kwargs) Py_XDECREF(config.assetfile); Py_XDECREF(config.classes); - return (value != NULL) ? Py_BuildValue("(Nn)", value, bytes_read) : NULL; + return (value != nullptr) ? Py_BuildValue("(Nn)", value, bytes_read) : nullptr; } // TypeTreeNode impl @@ -1122,7 +1138,7 @@ static int TypeTreeNode_init(TypeTreeNodeObject *self, PyObject *args, PyObject "m_Index", "m_MetaFlag", "m_RefTypeHash", - NULL}; + nullptr}; // ensure all fields are set to 0 // in case init fails, so that dealloc doesn't segfault @@ -1225,16 +1241,16 @@ static PyMemberDef TypeTreeNode_members[] = { {"m_MetaFlag", T_OBJECT_EX, offsetof(TypeTreeNodeObject, m_MetaFlag), 0, ""}, {"m_RefTypeHash", T_OBJECT_EX, offsetof(TypeTreeNodeObject, m_RefTypeHash), 0, ""}, {"_clean_name", T_OBJECT_EX, offsetof(TypeTreeNodeObject, _clean_name), 0, ""}, - {NULL} /* Sentinel */ + {nullptr} /* Sentinel */ }; static PyTypeObject TypeTreeNodeType = []() -> PyTypeObject { PyTypeObject type = { #if PY_VERSION_HEX >= 0x03080000 - PyVarObject_HEAD_INIT(NULL, 0) + PyVarObject_HEAD_INIT(nullptr, 0) #else - PyObject_HEAD_INIT(NULL) 0 + PyObject_HEAD_INIT(nullptr) 0 #endif }; type.tp_name = "TypeTreeHelper.TypeTreeNode"; diff --git a/UnityPyBoost/UnityPyBoost.cpp b/UnityPyBoost/UnityPyBoost.cpp index 92908bda..4b3ed8fa 100644 --- a/UnityPyBoost/UnityPyBoost.cpp +++ b/UnityPyBoost/UnityPyBoost.cpp @@ -19,10 +19,10 @@ static struct PyMethodDef method_table[] = { (PyCFunction)decrypt_block, METH_VARARGS, "replacement for ArchiveStorageDecryptor.decrypt_block"}, - {NULL, - NULL, + {nullptr, + nullptr, 0, - NULL} // Sentinel value ending the table + nullptr} // Sentinel value ending the table }; // A struct contains the definition of a module @@ -32,10 +32,10 @@ static PyModuleDef UnityPyBoost_module = { "TODO", -1, // Optional size of the module state memory method_table, - NULL, // Optional slot definitions - NULL, // Optional traversal function - NULL, // Optional clear function - NULL // Optional module deallocation function + nullptr, // Optional slot definitions + nullptr, // Optional traversal function + nullptr, // Optional clear function + nullptr // Optional module deallocation function }; // The module init function diff --git a/pyproject.toml b/pyproject.toml index 3ab9e435..cad2658f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,6 +52,8 @@ dependencies = [ "fsspec", # better classes "attrs", + # tpk handling + "tpk_ar" ] dynamic = ["version"]